Resurgam的文章

  • 使用ChangeScreenWidthHeight实现更改屏幕分辨率

    背景之前,帮别人写过一个小程序,其中,程序有个功能就是要更改电脑的分辨率。后来了解到,可以使用 ChangeDisplaySettings 函数去实现这个操作。现在,我就把实现的原理和过程,写成文档,分享给大家。
    函数介绍ChangeDisplaySettings 函数
    把缺省显示设备的设置改变为由 LPDEVMODE 设定的图形模式。
    函数声明
    LONG ChangeDisplaySettings ( LPDEVMODE lpDevMode, DWORD dwflags );
    参数

    lpDevMode [in]指向描述新图形模式的DEVMODE结构的指针。 如果lpDevMode为NULL,则当前在注册表中的所有值将用于显示设置。 对于lpDevMode参数传递NULL,对于dwFlags参数传递0是在动态模式更改后返回默认模式的最简单方法。DEVMODE的dmSize成员必须初始化为DEVMODE结构的大小(以字节为单位)。 必须初始化DEVMODE的dmDriverExtra成员,以指示DEVMODE结构之后的专用驱动程序数据的字节数。 此外,您可以使用DEVMODE结构中的任何或所有以下成员。



    VALUE
    MEANING




    dmBitsPerPel
    每像素位数


    dmPelsWidth
    像素宽度


    dmPelsHeight
    像素高度


    dmDisplayFlags
    模式标志


    dmDisplayFrequency
    模式频率


    dmPosition
    设备在多显示器配置中的位置



    除了使用一个或多个前面的DEVMODE成员之外,还必须在dmFields成员中设置以下一个或多个值来更改显示设置。



    VALUE
    MEANING




    DM_BITSPERPEL
    使用dmBitsPerPel值


    DM_PELSWIDTH
    使用 dmPelsWidth 值


    DM_PELSHEIGHT
    使用 dmPelsHeight 值


    DM_DISPLAYFLAGS
    使用 dmDisplayFlags 值


    DM_DISPLAYFREQUENCY
    使用 dmDisplayFrequency 值


    DM_POSITION
    使用 dmPosition 值




    dwflags [in]指示如何更改图形模式。 此参数可以是以下值之一。



    VALUE
    MEANING




    0
    当前屏幕的图形模式将被动态更改


    CDS_FULLSCREEN
    这种模式本质上是暂时的。如果您切换到另一台桌面,则此模式将不会重置


    CDS_GLOBAL
    这些设置将保存在全局设置区域,以便它们将影响机器上的所有用户。 否则,仅修改用户的设置。 此标志仅在使用CDS_UPDATEREGISTRY标志指定时有效


    CDS_NORESET
    设置将保存在注册表中,但不会生效。 此标志仅在使用CDS_UPDATEREGISTRY标志指定时有效


    CDS_RESET
    即使请求的设置与当前设置相同,应该更改设置


    CDS_SET_PRIMARY
    该设备将成为主要设备


    CDS_TEST
    系统测试是否可以设置所请求的图形模式


    CDS_UPDATEREGISTRY
    当前屏幕的图形模式将被动态更改,图形模式将在注册表中更新。 模式信息存储在USER配置文件中



    返回值

    返回DISP_CHANGE_SUCCESSFUL表示成功,其它表示失败。

    实现原理实现修改计算机分辨率功能中,关键的是对 ChangeDisplaySettings 函数的第一个参数赋值。第一个参数是一个结构体 DEVMODE,表示计算机显示的模式。关键是其中的两个参数:dmPelsWidth表示屏幕显示的宽度,dmPelsHeight表示屏幕显示的高度。这样,就指定的屏幕的分辨率。
    其中,分辨率的值并不是随意设置的。如果你不知道分辨率该设什么值,你可以打开自己电脑的分辨率,查看都有哪些分辨率的值。
    编码实现BOOL SetDisplay(int iWidth, int iHeight){ // 设置 DEVMODE 参数 DEVMODE stDevMode = { 0 }; stDevMode.dmSize = sizeof(stDevMode); stDevMode.dmBitsPerPel = 32; stDevMode.dmPelsWidth = iWidth; stDevMode.dmPelsHeight = iHeight; stDevMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; // CDS_UPDATEREGISTRY表示修改是持久的,并在相关注册表中写入了数据 LONG lRet = ::ChangeDisplaySettings(&stDevMode, CDS_UPDATEREGISTRY); if (DISP_CHANGE_SUCCESSFUL == lRet) { return TRUE; } return FALSE;}
    程序测试直接运行程序,计算机的分辨率成功更改。
    总结要注意的是,分辨率的值并不是随意设置的。如果你不知道分辨率该设什么值,你可以打开自己电脑的分辨率,查看都有哪些分辨率的值。
    1  留言 2019-01-22 09:34:13
  • Socket通信之UDP文件传输实验小程序

    背景如果你看到这篇文章的标题,会感到奇怪吧。UDP传输是一个无连接传输,数据发送之后,接收端接不接收得到,发送端是不理会的,所以,会发生丢包的情况。但是,我之所以写这个小程序,是因为一位网友本来打算请我帮ta写个小程序,应付作业的,作业要求是:

    基于UDP测试MP3文件(大于10M)的传输,并测试接收到的文件与发送文件是否一致。还可以调整发送端读取数据缓冲区的大小和延迟时间,以及接收端缓冲区的大小来解决。

    最终,因为我出价太高,而没有交易完成。但是,我就在和ta谈价期间,完成了这个小程序。这个小程序要求有界面的,但是,为了方便初学者的学习理解,本文特地重新开发了个新的程序,是基于控制台程序实现的。
    现在,就把实现过程和原理整理成文档,分享给大家。
    实现过程这个程序的实现,是根据我之前写的《Socket通信之UDP通信小程序》这篇文章修改而来的,大家可以参考这篇文章。在此,不仔细讲解UDP Socket如何使用。我只是大概讲下开发这个UDP文件传输小程序的实现原理。
    我们首先开发接收端程序,所谓的接收端程序,就是把接收的数据,不断地写入到文件中。具体实现过程就是:

    首先,从 recvfrom 函数获取到数据
    然后,根据判断生成文件路径是否存在,若不存在,则创建文件;若存在,则打开文件
    将文件指针移到文件的末尾,接着向文件中写入上述接收到的数据
    关闭文件,释放句柄
    重复上面 4 个步骤,直到文件接收完毕。

    // 数据接收void RecvMsg(){ int iSize = 40960; BYTE *lpBuf = new BYTE[iSize]; int iLen = 0; int iRet = 0; while (TRUE) { sockaddr_in addr = { 0 }; // 注意此处, 既是输入参数也是输出参数 int iLen = sizeof(addr); // 接收数据 int iRet = ::recvfrom(g_sock, (char *)lpBuf, iSize, 0, (sockaddr *)(&addr), &iLen); // 存储为文件 if (0 < iRet) { RecvFile(lpBuf, iRet); } } delete[] lpBuf; lpBuf = NULL;}
    接着,我们就可以开发发送端。发送端的开发流程就是:

    首先,我们根据文件路径打开文件,获取文件句柄
    然后,获取文件的大小,并读取文件的所有数据
    接着,根据发送缓冲区的大小,循环读取数据,并调用发送数据的函数,同时根据延迟时间进行发送延迟
    发送完毕,则释放内存,关闭句柄

    // 文件数据传输void SendFileData(char *pszFileName, char *pszDestIp, int iDestPort, int iBufferSize, int iElapseTime){ // 打开文件 HANDLE hFile = ::CreateFile(pszFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL); if (INVALID_HANDLE_VALUE == hFile) { ::MessageBox(NULL, "CreateFile Error!", NULL, MB_OK); return; } // 申请动态内存 BYTE *lpBuf = new BYTE[iBufferSize]; if (NULL == lpBuf) { ::CloseHandle(hFile); ::MessageBox(NULL, "申请动态内存错误!", NULL, MB_OK); return; } // 获取文件大小 DWORD dwFileSize = ::GetFileSize(hFile, NULL); DWORD dwRet = 0; BOOL bStop = FALSE; // 读取文件并发送 do { // 读取 ::RtlZeroMemory(lpBuf, iBufferSize); ::ReadFile(hFile, lpBuf, iBufferSize, &dwRet, NULL); if (dwRet < iBufferSize) { bStop = TRUE; } // 发送 SendMsg((char *)lpBuf, dwRet, pszDestIp, iDestPort); // 时间间隔 Sleep(iElapseTime); } while (FALSE == bStop); // 释放 delete[] lpBuf; lpBuf = NULL; ::CloseHandle(hFile); ::MessageBox(NULL, "发送完毕!", "DONE", MB_OK);}
    程序测试接收端绑定的地址和端口是:127.0.0.1:4321,发送端绑定的地址和端口是:127.0.0.1:12345,发送一个10.3M大小的MP3文件。设置发送缓冲区大小是10240字节,延时是100毫秒;接收端接收缓冲区大小为40960字节。
    我们先运行接收端,然后在运行发送段。等待一段时间后,文件接收完毕,打开接受到的MP3文件,居然可以正常播放。大家可以自己调整参数试试吧,或许就不能播放了哦。


    总结要特别注意一点就是,接收端使用 recvfrom 函数接收程序的时候,如果返回值为 -1,这么这时就要检查是否发送端发送数据的缓冲区比接收端接收数据的缓冲区还要大,若接收端接收数据的缓冲区较小,则应该调整缓冲区大小,使接收端接缓冲区大小比发送区缓冲区大小大才行。因为 recvfrom 函数返回值为 -1 的原因,可能是上面提到的情况,也可能是 recvfrom 函数的参数有误。
    1  留言 2018-12-20 12:26:26
  • Socket通信之UDP通信小程序

    背景之前自己做过关于Socket通信的视频教程,主要是教大家怎么使用Windows提供的Socket函数接口去实现网络通信的。当时讲了两个小程序的实现,一个是TCP通信,另一个是UDP通信。
    如今,我把视频教程讲解的内容,重新整理成文档的形式,并对程序简化,使用使用控制台重新开发,方便初学者的理解。本文先讲解使用Socket实现UDP通信的小程序,先把程序实现过程和原理整理成文档,分享给大家。
    函数介绍socket 函数
    根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源的函数。
    函数声明
    SOCKET WSAAPI socket( _In_ int af, _In_ int type, _In_ int protocol);
    参数

    af [in]地址族规范。 地址系列的可能值在Winsock2.h头文件中定义。当前支持的值为AF_INET或AF_INET6,它们是IPv4和IPv6的Internet地址族格式。 type[in]指定Socket类型,SOCK_STREAM类型指定产生流式套接字,SOCK_DGRAM类型指定产生数据报式套接字,而SOCK_RAW类型指定产生原始套接字(只有管理员权限用户才能创建原始套接字)。protocol[in]与特定的地址家族相关的协议IPPROTO_TCP、IPPROTO_UDP和IPPROTO_IP,如果指定为0,那么系统就会根据地址格式和套接字类别,自动选择一个合适的协议。
    返回值

    如果没有发生错误,套接字返回引用新套接字的描述符。 否则,返回值为INVALID_SOCKET,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    bind 函数
    将本地地址与套接字相关联。
    函数声明
    int bind( _In_ SOCKET s, _In_ const struct sockaddr *name, _In_ int namelen);
    参数

    s [in]标识未绑定套接字的描述符。名称[in]指向本地地址的sockaddr结构的指针,以分配给绑定的套接字。namelen [in]name参数指向的值的长度(以字节为单位)。
    返回值

    如果没有发生错误,则bind返回零。 否则,它返回SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    htons 函数
    将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。
    函数声明
    u_short WSAAPI htons( _In_ u_short hostshort);
    参数

    hostshort [in]
    主机字节顺序为16位。

    返回值

    返回TCP / IP网络字节顺序。

    inet_addr 函数
    将一个点分十进制的IP转换成一个长整数型数。
    函数声明
    unsigned long inet_addr( _In_ const char *cp);
    参数

    cp [in]
    点分十进制的IP字符串,以NULL结尾。

    返回值

    如果没有发生错误,则inet_addr函数将返回一个无符号长整型值,其中包含给定的Internet地址的适当的二进制表示形式。

    sendto 函数
    将数据发送到特定目的地。
    函数声明
    int sendto( _In_ SOCKET s, _In_ const char *buf, _In_ int len, _In_ int flags, _In_ const struct sockaddr *to, _In_ int tolen);
    参数

    s [in]标识(可能连接)套接字的描述符。buf [in]指向包含要发送的数据的缓冲区的指针。len [in]由buf参数指向的数据的长度(以字节为单位)。flags[in]一组指定呼叫方式的标志。to[in]指向包含目标套接字地址的sockaddr结构的可选指针。tolen[in]由to参数指向的地址的大小(以字节为单位)。
    返回值

    如果没有发生错误,sendto返回发送的总字节数,可以小于len指示的数字。 否则,返回值SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    recvfrom 函数
    接收数据报并存储源地址。
    函数声明
    int recvfrom( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags, _Out_ struct sockaddr *from, _Inout_opt_ int *fromlen);
    参数

    s [in]标识绑定套接字的描述符。buf [out]用于传入数据的缓冲区。len [in]由buf参数指向的缓冲区的长度(以字节为单位)。flags[in]一组修改函数调用行为的选项,超出了为关联套接字指定的选项。 有关详细信息,请参阅下面的备注。from[out]指向sockaddr结构中缓冲区的可选指针,它将在返回时保存源地址。fromlen [in,out,optional]指向from参数指向的缓冲区的大小(以字节为单位)的可选指针。
    返回值

    如果没有发生错误,recvfrom返回接收到的字节数。 如果连接已正常关闭,返回值为零。 否则,返回值SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    实现原理如下图所示:

    我们可以看到,UDP通信时,是无连接的,是不区分 Server 与 Client 的,所以,我们在程序实现的时候,只需要一个程序就可以了。
    和TCP通信程序一样,都首先要初始化Winsock服务环境。
    初始化Winsock环境后,便调用 socket 函数创建数据报套接字;然后对sockaddr_in结构体进行设置,设置绑定的IP地址和端口等信息并调用 bind 函数绑定;绑定成功后,就可以使用 recvfrom 函数和 sendto 函数与另一UDP程序进行数据的收发。通信结束后,变关闭套接字,释放资源。
    编码实现导入库文件#include <Winsock2.h>#pragma comment(lib, "Ws2_32.lib")
    UDP通信程序初始化Winsock库环境,创建数据报套接字,绑定IP地址和端口。
    // 绑定IP地址和端口BOOL Bind(char *lpszIp, int iPort){ // 初始化 Winsock 库 WSADATA wsaData = { 0 }; ::WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建数据报套接字 g_sock = socket(AF_INET, SOCK_DGRAM, 0); if (INVALID_SOCKET == g_sock) { return FALSE; } // 设置绑定IP地址和端口信息 sockaddr_in addr = { 0 }; addr.sin_family = AF_INET; addr.sin_port = ::htons(iPort); addr.sin_addr.S_un.S_addr = ::inet_addr(lpszIp); // 绑定IP地址和端口 if (0 != bind(g_sock, (sockaddr *)(&addr), sizeof(addr))) { return FALSE; } // 创建接收信息多线程 ::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvThreadProc, NULL, NULL, NULL); return TRUE;}
    数据发送。
    // 数据发送void SendMsg(char *lpszText, char *lpszIp, int iPort){ // 设置目的主机的IP地址和端口等地址信息 sockaddr_in addr = { 0 }; addr.sin_family = AF_INET; addr.sin_port = ::htons(iPort); addr.sin_addr.S_un.S_addr = ::inet_addr(lpszIp); // 发送数据到目的主机 ::sendto(g_sock, lpszText, (1 + ::lstrlen(lpszText)), 0, (sockaddr *)(&addr), sizeof(addr)); printf("[sendto]%s\n", lpszText);}
    数据接收。
    // 数据接收void RecvMsg(){ char szBuf[MAX_PATH] = { 0 }; while (TRUE) { sockaddr_in addr = { 0 }; // 注意此处, 既是输入参数也是输出参数 int iLen = sizeof(addr); // 接收数据 ::recvfrom(g_sock, szBuf, MAX_PATH, 0, (sockaddr *)(&addr), &iLen); printf("[recvfrom]%s\n", szBuf); }}
    程序测试我们进行本机测试,先开启一个程序输入绑定的地址和端口为:127.0.0.1:12345。
    再启动另一个程序,绑定的地址和端口为:127.0.0.1:4321。
    然后,直接相互发送数据,进行数据通信。

    总结有 2 个地方如果稍不注意的话,便很容易出错:
    一是在使用Socket函数之前,一定要对Winsock服务进行初始化,初始化是由WSAStartup函数实现的。如果不进行初始化操作,而直接使用Socket函数,会报错。
    二是对于接收数据的 recvfrom 函数,最后一个参数一定要格外注意,它是既是输入参数也是输出参数,也就是说,一定要给它一个初值,初值大小就是sockaddr_in结构体的大小。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2018-12-20 12:26:15
  • Socket通信之TCP通信小程序

    背景之前自己做过关于Socket通信的视频教程,主要是教大家怎么使用Windows提供的Socket函数接口去实现网络通信的。当时讲了两个小程序的实现,一个是TCP通信,另一个是UDP通信。
    如今,我把视频教程讲解的内容,重新整理成文档的形式,并对程序简化,使用使用控制台重新开发,方便初学者的理解。本文先讲解使用Socket实现TCP通信的小程序,先把程序实现过程和原理整理成文档,分享给大家。
    函数介绍socket 函数
    根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源的函数。
    函数声明
    SOCKET WSAAPI socket( _In_ int af, _In_ int type, _In_ int protocol);
    参数

    af [in]地址族规范。 地址系列的可能值在Winsock2.h头文件中定义。当前支持的值为AF_INET或AF_INET6,它们是IPv4和IPv6的Internet地址族格式。 type[in]指定Socket类型,SOCK_STREAM类型指定产生流式套接字,SOCK_DGRAM类型指定产生数据报式套接字,而SOCK_RAW类型指定产生原始套接字(只有管理员权限用户才能创建原始套接字)。protocol[in]与特定的地址家族相关的协议IPPROTO_TCP、IPPROTO_UDP和IPPROTO_IP,如果指定为0,那么系统就会根据地址格式和套接字类别,自动选择一个合适的协议。
    返回值

    如果没有发生错误,套接字返回引用新套接字的描述符。 否则,返回值为INVALID_SOCKET,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    bind 函数
    将本地地址与套接字相关联。
    函数声明
    int bind( _In_ SOCKET s, _In_ const struct sockaddr *name, _In_ int namelen);
    参数

    s [in]标识未绑定套接字的描述符。名称[in]指向本地地址的sockaddr结构的指针,以分配给绑定的套接字。namelen [in]name参数指向的值的长度(以字节为单位)。
    返回值

    如果没有发生错误,则bind返回零。 否则,它返回SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    htons 函数
    将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。
    函数声明
    u_short WSAAPI htons( _In_ u_short hostshort);
    参数

    hostshort [in]
    主机字节顺序为16位。

    返回值

    返回TCP / IP网络字节顺序。

    inet_addr 函数
    将一个点分十进制的IP转换成一个长整数型数。
    函数声明
    unsigned long inet_addr( _In_ const char *cp);
    参数

    cp [in]
    点分十进制的IP字符串,以NULL结尾。

    返回值

    如果没有发生错误,则inet_addr函数将返回一个无符号长整型值,其中包含给定的Internet地址的适当的二进制表示形式。

    listen函数
    将一个套接字置于正在监听传入连接的状态。
    函数声明
    int listen( _In_ SOCKET s, _In_ int backlog);
    参数

    s [in]标识绑定的未连接套接字的描述符。backlog[in]待连接队列的最大长度。 如果设置为SOMAXCONN,负责套接字的底层服务提供商将积压设置为最大合理值。 如果设置为SOMAXCONN_HINT(N)(其中N是数字),则积压值将为N,调整为范围(200, 65535)。
    返回值

    如果没有发生错误,listen将返回零。否则,返回值SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    accept 函数
    允许在套接字上进行连接尝试。
    函数声明
    SOCKET accept( _In_ SOCKET s, _Out_ struct sockaddr *addr, _Inout_ int *addrlen);
    参数

    s [in]一个描述符,用于标识使用listen功能处于侦听状态的套接字。 连接实际上是由accept返回的套接字。addr [out]一个可选的指向缓冲区的指针,它接收通信层已知的连接实体的地址。 addr参数的确切格式由创建sockaddr结构的套接字时建立的地址族确定。addrlen [in,out]指向一个整数的可选指针,其中包含addr参数指向的结构长度。
    返回值

    如果没有发生错误,则accept返回一个SOCKET类型的值,该值是新套接字的描述符。 此返回值是实际连接所在的套接字的句柄。否则,返回值为INVALID_SOCKET,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    send 函数
    在建立连接的套接字上发送数据。
    函数声明
    int send( _In_ SOCKET s, _In_ const char *buf, _In_ int len, _In_ int flags);
    参数

    s [in]标识连接的套接字的描述符。buf [in]指向包含要发送的数据的缓冲区的指针。len [in]由buf参数指向的缓冲区中数据的长度(以字节为单位)。标志[in]一组指定呼叫方式的标志。
    返回值

    如果没有发生错误,发送返回发送的总字节数,可以小于len参数中要发送的数量。 否则,返回值SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

    recv 函数
    从连接的套接字或绑定的无连接套接字接收数据。
    函数声明
    int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);
    参数

    s [in]标识连接的套接字的描述符。buf [out]指向缓冲区的指针,用于接收传入的数据。len [in]由buf参数指向的缓冲区的长度(以字节为单位)。标志[in]一组影响此功能的行为的标志。
    返回值

    如果没有发生错误,则recv返回接收的字节数,buf参数指向的缓冲区将包含接收到的数据。 如果连接已正常关闭,返回值为0。

    实现原理如下图所示:

    无论对服务器端来说还是客户端来说,都首先要初始化Winsock服务环境。
    服务器端初始化Winsock环境后,便调用 socket 函数创建流式套接字;然后对sockaddr_in结构体进行设置,设置服务器绑定的IP地址和端口等信息并调用 bind 函数绑定;绑定成功后,便可以调用 listen 函数设置连接数量,并进行监听。直到有来自客户端的连接请求,服务器便调用 accept 函数接受连接请求,建立连接。这时,便可以使用 recv 函数和 send 函数与客户端进行数据的收发。通信结束后,变关闭套接字,释放资源。
    客户端初始化环境后,便调用 socket 函数创建流式套接字;然后对sockaddr_in结构体进行设置,设置服务器的IP地址和端口等信息并调用 connect 函数向服务器发送连接请求,并等待服务器的响应。服务器接受连接请求后,便成功与服务器建立连接,这时,便可以使用 recv 函数和 send 函数与客户端进行数据的收发。通信结束后,变关闭套接字,释放资源。
    编码实现导入库文件#include <Winsock2.h>#pragma comment(lib, "Ws2_32.lib")
    服务器端初始化Winsock库环境,创建流式套接字,绑定服务器IP地址和端口,并进行监听。
    // 绑定端口并监听BOOL SocketBindAndListen(char *lpszIp, int iPort){ // 初始化 Winsock 库 WSADATA wsaData = {0}; ::WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建流式套接字 g_ServerSocket = ::socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == g_ServerSocket) { return FALSE; } // 设置服务端地址和端口信息 sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = ::htons(iPort); addr.sin_addr.S_un.S_addr = ::inet_addr(lpszIp); // 绑定IP和端口 if (0 != ::bind(g_ServerSocket, (sockaddr *)(&addr), sizeof(addr))) { return FALSE; } // 设置监听 if (0 != ::listen(g_ServerSocket, 1)) { return FALSE; } // 创建接收数据多线程 ::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvThreadProc, NULL, NULL, NULL); return TRUE;}
    服务器端数据发送。
    // 发送数据void SendMsg(char *pszSend){ // 发送数据 ::send(g_ClientSocket, pszSend, (1 + ::lstrlen(pszSend)), 0); printf("[send]%s\n", pszSend);}
    服务器端接收连接请求并接收数据。
    // 接受连接请求 并 接收数据void AcceptRecvMsg(){ sockaddr_in addr = { 0 }; // 注意:该变量既是输入也是输出 int iLen = sizeof(addr); // 接受来自客户端的连接请求 g_ClientSocket = ::accept(g_ServerSocket, (sockaddr *)(&addr), &iLen); printf("accept a connection from client!\n"); char szBuf[MAX_PATH] = { 0 }; while (TRUE) { // 接收数据 int iRet = ::recv(g_ClientSocket, szBuf, MAX_PATH, 0); if (0 >= iRet) { continue; } printf("[recv]%s\n", szBuf); }}
    客户端初始化Winsock库环境,创建流式套接字,并连接服务器。
    // 连接到服务器BOOL Connection(char *lpszServerIp, int iServerPort){ // 初始化 Winsock 库 WSADATA wsaData = { 0 }; ::WSAStartup(MAKEWORD(2, 2), &wsaData); // 创建流式套接字 g_ClientSocket = ::socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == g_ClientSocket) { return FALSE; } // 设置服务端地址和端口信息 sockaddr_in addr = { 0 }; addr.sin_family = AF_INET; addr.sin_port = ::htons(iServerPort); addr.sin_addr.S_un.S_addr = ::inet_addr(lpszServerIp); // 连接到服务器 if (0 != ::connect(g_ClientSocket, (sockaddr *)(&addr), sizeof(addr))) { return FALSE; } // 创建接收数据多线程 ::CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)RecvThreadProc, NULL, NULL, NULL); return TRUE;}
    客户端发送数据。
    // 发送数据void SendMsg(char *pszSend){ // 发送数据 ::send(g_ClientSocket, pszSend, (1 + ::lstrlen(pszSend)), 0); printf("[send]%s\n", pszSend);}
    客户端接收数据。
    // 接收数据void RecvMsg(){ char szBuf[MAX_PATH] = { 0 }; while (TRUE) { // 接收数据 int iRet = ::recv(g_ClientSocket, szBuf, MAX_PATH, 0); if (0 >= iRet) { continue; } printf("[recv]%s\n", szBuf); }}
    程序测试我们进行本机测试,服务器地址端口为:127.0.0.1:12345。
    先运行服务器,进行绑定并监听,然后再运行客户端进行连接,连接成功后,就可以相互进行数据通信。

    总结有 3 个地方如果稍不注意的话,便很容易出错:
    一是在使用Socket函数之前,一定要对Winsock服务进行初始化,初始化是由WSAStartup函数实现的。如果不进行初始化操作,而直接使用Socket函数,会报错。
    二是对于服务端中的接受来自客户端连接请求的accept函数,第三个参数一定要格外注意,它是既是输入参数也是输出参数,也就是说,一定要给它一个初值,初值大小就是sockaddr_in结构体的大小。
    三是测试的时候,如果服务端和客户端通信一直不成功,可以试着使用CMD命令的ping指令ping下两台主机是否能ping通,若不能,则检查是否在同一网段内或者防火墙是否关闭;若ping通,则检查自己的代码是否有误,可以单步进行调试。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2018-12-20 12:26:48
  • 使用ChangeScreenWidthHeight实现更改屏幕分辨率

    背景之前,帮别人写过一个小程序,其中,程序有个功能就是要更改电脑的分辨率。后来了解到,可以使用 ChangeDisplaySettings 函数去实现这个操作。现在,我就把实现的原理和过程,写成文档,分享给大家。
    函数介绍ChangeDisplaySettings 函数
    把缺省显示设备的设置改变为由 LPDEVMODE 设定的图形模式。
    函数声明
    LONG ChangeDisplaySettings ( LPDEVMODE lpDevMode, DWORD dwflags );
    参数

    lpDevMode [in]指向描述新图形模式的DEVMODE结构的指针。 如果lpDevMode为NULL,则当前在注册表中的所有值将用于显示设置。 对于lpDevMode参数传递NULL,对于dwFlags参数传递0是在动态模式更改后返回默认模式的最简单方法。DEVMODE的dmSize成员必须初始化为DEVMODE结构的大小(以字节为单位)。 必须初始化DEVMODE的dmDriverExtra成员,以指示DEVMODE结构之后的专用驱动程序数据的字节数。 此外,您可以使用DEVMODE结构中的任何或所有以下成员。



    VALUE
    MEANING




    dmBitsPerPel
    每像素位数


    dmPelsWidth
    像素宽度


    dmPelsHeight
    像素高度


    dmDisplayFlags
    模式标志


    dmDisplayFrequency
    模式频率


    dmPosition
    设备在多显示器配置中的位置



    除了使用一个或多个前面的DEVMODE成员之外,还必须在dmFields成员中设置以下一个或多个值来更改显示设置。



    VALUE
    MEANING




    DM_BITSPERPEL
    使用dmBitsPerPel值


    DM_PELSWIDTH
    使用 dmPelsWidth 值


    DM_PELSHEIGHT
    使用 dmPelsHeight 值


    DM_DISPLAYFLAGS
    使用 dmDisplayFlags 值


    DM_DISPLAYFREQUENCY
    使用 dmDisplayFrequency 值


    DM_POSITION
    使用 dmPosition 值




    dwflags [in]指示如何更改图形模式。 此参数可以是以下值之一。



    VALUE
    MEANING




    0
    当前屏幕的图形模式将被动态更改


    CDS_FULLSCREEN
    这种模式本质上是暂时的。如果您切换到另一台桌面,则此模式将不会重置


    CDS_GLOBAL
    这些设置将保存在全局设置区域,以便它们将影响机器上的所有用户。 否则,仅修改用户的设置。 此标志仅在使用CDS_UPDATEREGISTRY标志指定时有效


    CDS_NORESET
    设置将保存在注册表中,但不会生效。 此标志仅在使用CDS_UPDATEREGISTRY标志指定时有效


    CDS_RESET
    即使请求的设置与当前设置相同,应该更改设置


    CDS_SET_PRIMARY
    该设备将成为主要设备


    CDS_TEST
    系统测试是否可以设置所请求的图形模式


    CDS_UPDATEREGISTRY
    当前屏幕的图形模式将被动态更改,图形模式将在注册表中更新。 模式信息存储在USER配置文件中



    返回值

    返回DISP_CHANGE_SUCCESSFUL表示成功,其它表示失败。

    实现原理实现修改计算机分辨率功能中,关键的是对 ChangeDisplaySettings 函数的第一个参数赋值。第一个参数是一个结构体 DEVMODE,表示计算机显示的模式。关键是其中的两个参数:dmPelsWidth表示屏幕显示的宽度,dmPelsHeight表示屏幕显示的高度。这样,就指定的屏幕的分辨率。
    其中,分辨率的值并不是随意设置的。如果你不知道分辨率该设什么值,你可以打开自己电脑的分辨率,查看都有哪些分辨率的值。
    编码实现BOOL SetDisplay(int iWidth, int iHeight){ // 设置 DEVMODE 参数 DEVMODE stDevMode = { 0 }; stDevMode.dmSize = sizeof(stDevMode); stDevMode.dmBitsPerPel = 32; stDevMode.dmPelsWidth = iWidth; stDevMode.dmPelsHeight = iHeight; stDevMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL; // CDS_UPDATEREGISTRY表示修改是持久的,并在相关注册表中写入了数据 LONG lRet = ::ChangeDisplaySettings(&stDevMode, CDS_UPDATEREGISTRY); if (DISP_CHANGE_SUCCESSFUL == lRet) { return TRUE; } return FALSE;}
    程序测试直接运行程序,计算机的分辨率成功更改。
    总结要注意的是,分辨率的值并不是随意设置的。如果你不知道分辨率该设什么值,你可以打开自己电脑的分辨率,查看都有哪些分辨率的值。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2018-11-07 10:31:48

发送私信

每个人最终和自己越长越像

16
文章数
15
评论数
eject