一.前言
在文章网络编程技术学习笔记之基础模型中,当服务端收到新的客户端的连接的时候都通过开起一个新线程的方式来完成与客户端的连接。但是,开起一个新的线程所耗费的资源是比较大的。因此,需要考虑其他的方法来让服务端可以只用一个线程就完成与所有客户端的通信。
二.I/O复用
实现I/O复用的目的就是想要改变如图用多进程或者多线程来处理客户端连接的服务端。

所以引入了I/O复用模型,它将改变服务端与客户端的连接方式变为如下的单线程的连接方式。

而实现I/O复用的核心是使用select函数,该函数可以监视一组套接字的状态变化,定义如下:
int
WSAAPI
select(
__in int nfds,
__inout_opt fd_set FAR * readfds,
__inout_opt fd_set FAR * writefds,
__inout_opt fd_set FAR * exceptfds,
__in_opt const struct timeval FAR * timeout
);
参数 | 含义 |
nfds | 忽略。包含nfds参数只是为了与Berkeley套接字兼容。 |
readfds | 将所有关注“是否存在待读取数据”的套接字注册到fd_set型变量中,并传递其地址值 |
writefds | 将所有关注“是否可传输无阻塞数据”的套接字注册到fd_set型变量中,并传递其地址值 |
exceptfds | 将所有关注“是否发生异常”的套接字注册到fd_set型变量中,并传递其值 |
timeout | 调用select函数后,为了防止陷入无线阻塞,传递超时(time-out)信息 |
返回值:发生错误时返回SOCKET_ERROR,超时返回0。因关注的事件返回时,返回值大于0,该值是发生事件的套接字
由此可知,select函数的作用是可以用来监视多个套接字(文件描述符),监视的事件如下:
是否存在套接字接收数据
无需阻塞传输数据的套接字有哪些
哪些套接字发生了异常
另外,select函数所监视的变量是fd_set结构体变量,该结构体的定义如下:
#ifndef FD_SETSIZE
#define FD_SETSIZE 64
#endif /* FD_SETSIZE */
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
成员 | 含义 |
fd_count | 要监视的套接字的数量 |
fd_array | 用来保存套接字的数组
|
由此可知,要监视的套接字需要加入到该结构体中才可以调用select函数进行监视。对于fd_set结构体的操作,Winsock库中提供了一组宏来完成,
宏 | 作用 |
FD_ZERO(fd_set *set) | 将set变量初始化为0 |
FD_SET(int fd, fd_set *fdset) | 将set指向的变量中注册套接字fd的信息,也就是将套接字放入监视范围 |
FD_CLD(int fd, fd_set *fdset) | 从参数fdset指向的变量中清除文件描述符fd的信息,也就是将套接字从监视范围里删除 |
FD_ISSET(int fd, fd_set *fdset) | 若参数fdset指向的变量中包含文件描述符fd的信息,则返回"真",也就是判断相应的套接字状态是否发生了改变 |
根据这组宏中的FD_ZERO和FD_SET就可以完成对结构体fd_set变量的初始化,而这个初始化的目的就是将服务器套接字加入到fd_set变量中,这样才可以通过调用select函数来监视套接字的变化。
另外为了防止长时间进入阻塞状态,也需要对select函数的timeout进行初始化,也就是指定超时时间,这个参数是一个timeval结构体变量,该结构体的定义如下:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
所以对于select函数的调用就如下图所示:

这里还需要知道的几点是:
连接服务端和与服务端断开连接的变化都在select函数的第二个参数,也就是readfds中
监视到readfds中变化的套接字是服务器套接字的时候,说明客户端发起了对服务端的连接,此时应该要把客户端的套接字加入监视范围
当监视到readfds中变化的是客户端套接字,且接收数据的长度为0的时候,说明发生了与客户端的断开连接,需要将相应的客户端套接字从监视范围内删除,否则就是接收到了数据
完整的代码如下:
服务端:
// Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <cstdio>
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 30
VOID ShowError(PCHAR msg);
int main()
{
WSADATA wsaData;
SOCKET hServerSocket = INVALID_SOCKET, hClientSocket = INVALID_SOCKET;
SOCKADDR_IN servAddr, clientAddr;
int iClientAddrSize = 0, fdNum = 0, i = 0, strLen = 0;
fd_set reads, cpyReads;
TIMEVAL timeout;
CHAR szBuf[BUF_SIZE] = { 0 };
// 初始化Winsock库
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
ShowError("WSAStartup");
goto fail;
}
// 创建套接字
hServerSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (hServerSocket == INVALID_SOCKET)
{
ShowError("socket");
goto exit;
}
// 绑定端口
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(1900);
if (bind(hServerSocket, (SOCKADDR *)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
{
ShowError("bind");
goto exit;
}
// 监听端口
if (listen(hServerSocket, SOMAXCONN) == SOCKET_ERROR)
{
ShowError("listen");
goto exit;
}
printf("服务端已开启...\n");
// 设置文件描述符
FD_ZERO(&reads);
// 指定监视范围
FD_SET(hServerSocket, &reads);
while (TRUE)
{
cpyReads = reads;
// 设置超时
timeout.tv_sec = 5;
timeout.tv_usec = 5000;
fdNum = select(0, &cpyReads, 0, 0, &timeout);
if (fdNum == SOCKET_ERROR)
{
ShowError("select");
goto exit;
}
// 超时,结束本次循环
if (fdNum == 0) continue;
for (i = 0; i < reads.fd_count; i++)
{
// 该套接字是否有变化
if (FD_ISSET(reads.fd_array[i], &cpyReads))
{
// 如果改变状态的是hServerSocket说明有新的连接
if (reads.fd_array[i] == hServerSocket)
{
// 建立连接
iClientAddrSize = sizeof(clientAddr);
hClientSocket = accept(hServerSocket,
(SOCKADDR *)&clientAddr,
&iClientAddrSize);
if (hClientSocket == INVALID_SOCKET)
{
ShowError("accept");
goto exit;
}
printf("新的连接,IP:%s\n", inet_ntoa(clientAddr.sin_addr));
// 在reads中设置新的套接字,以监听它的状态
FD_SET(hClientSocket, &reads);
}
else
{
strLen = recv(reads.fd_array[i], szBuf, BUF_SIZE, 0);
if (strLen == SOCKET_ERROR)
{
ShowError("recv");
goto exit;
}
else if (strLen == 0)
{
// 如果接收的数据的长度为0,说明是断开连接
// 从reads中清除对应的套接字
FD_CLR(reads.fd_array[i], &reads);
closesocket(cpyReads.fd_array[i]);
printf("close client: %d\n", cpyReads.fd_array[i]);
}
else
{
printf("receive message: %s\n", szBuf);
}
}
}
}
}
exit:
if (hServerSocket != INVALID_SOCKET)
{
closesocket(hServerSocket);
hServerSocket = INVALID_SOCKET;
}
WSACleanup();
fail:
system("pause");
return 0;
}
VOID ShowError(PCHAR msg)
{
printf("%s Error %d\n", msg, WSAGetLastError());
}
客户端:
// Client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <cstdio>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 30
VOID ShowError(PCHAR msg);
int main()
{
WSADATA wsaData;
SOCKET hSocket = INVALID_SOCKET;
SOCKADDR_IN servAddr;
// 初始化Winsock库
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
ShowError("WSAStartup");
goto fail;
}
// 创建套接字
hSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (hSocket == INVALID_SOCKET)
{
ShowError("socket");
goto exit;
}
// 连接服务端
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
servAddr.sin_port = htons(1900);
if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
{
ShowError("connect");
goto exit;
}
printf("连接成功\n");
Sleep(5000);
CHAR szBuf[BUF_SIZE] = { "Hello 1900" };
if (send(hSocket, szBuf, sizeof(szBuf) - 1, 0) == SOCKET_ERROR)
{
ShowError("send");
goto exit;
}
printf("发送数据成功\n");
exit:
if (hSocket != INVALID_SOCKET)
{
closesocket(hSocket);
hSocket = INVALID_SOCKET;
}
WSACleanup();
fail:
system("pause");
return 0;
}
VOID ShowError(PCHAR msg)
{
printf("%s Error %d\n", msg, WSAGetLastError());
}
运行结果如下,可以看到此时只用了一个线程就完成了与多个客户端的连接和通信。

三.重叠I/O
重叠I/O的模型如下所示:

由图可知,重叠I/O是同一线程内部向多个目标传输(或从多个目标接收)数据引起I/O重叠现象。为了完成这项任务,调用的I/O函数应立即返回,只有这样才能发送后续的数据,为了完成这项功能,调用的I/O函数应以非阻塞模型工作。
首先要使用WSASocket函数来创建适用于重叠I/O套接字
SOCKET WSASocket(
__in int af,
__in int type,
__in int protocol,
__in LPWSAPROTOCOL_INFO lpProtocolInfo,
__in GROUP g,
__in DWORD dwFlags);
参数 | 含义 |
af | 协议族信息 |
type | 套接字数据传输方式 |
protocol | 2个套接字之间使用的协议信息 |
lpProtocolInfo | 包含创建的套接字信息的WSAPROTOCOL_INFO结构体变量地址值,不需要时传递为NULL |
g | 为扩展函数而预约的参数,可以使用0 |
dwFlags | 套接字属性,当为WSA_FLAG_OVERLAPPED的时候会创建出重叠I/O |
在重叠I/O中,使用WSASend函数来发送数据,该函数定义如下:
int WSASend(
__in SOCKET s,
__in LPWSABUF lpBuffers,
__in DWORD dwBufferCount,
__out LPDWORD lpNumberOfBytesSent,
__in DWORD dwFlags,
__in LPWSAOVERLAPPED lpOverlapped,
__in LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
参数 | 含义 |
s | 套接字句柄,传递具有重叠I/O属性的套接字句柄时,以重叠I/O模型输出
|
lpBuffers | WSABUF结构体变量数组的地址,WSABUF中存有待传输数据 |
dwBufferCount | lpBuffers数组的长度 |
lpNumberOfBytesSent | 用于保存实际发送字节数的变量地址 |
dwFlags | 用于更改数据传输特性,如传递MSG_OOB时发送OOB模式的数据 |
lpOverlapped | WSAOVERLAPPED结构体变量的地址值,使用事件对象,用于确认完成数据传输 |
lpCompletionRoutine | 传入Completion Routine函数的入口地址值,可以通过该函数确认是否完成数据传输 |
其中WSABUF结构体定义如下:
typedef struct _WSABUF {
ULONG len; /* the length of the buffer */
__field_bcount(len) CHAR FAR *buf; /* the pointer to the buffer */
} WSABUF, FAR * LPWSABUF;
该参数保存的就是需要传输的数据信息,而WSAOVERLAPPED结构体的定义如下:
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
} DUMMYSTRUCTNAME;
PVOID Pointer;
} DUMMYUNIONNAME;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
这里最关键的是hEvent句柄,该句柄用来保证数据的完整发送。
为了进行重叠I/O,WSASend函数的lpOverlapped参数中应该传递有效的结构体变量地址值,而不是NULL。如果传递的是NULL,那么WSASend函数的第一个参数中的句柄所指的套接字将以阻塞模式工作
WSASend函数调用以后,如果返回值为SOCKET_ERROR并且WSAGetLastError的值为WSA_IO_PENDING就说明此时数据并没有发送完。而要等待数据发送完几句要用到OVERLAPPED结构体的hEvent,通过调用WSAWaitFirMultipleEvent函数并将其事件传入就可以等待发送数据完成,该函数定义如下
DWORD WSAWaitForMultipleEvents(
__in DWORD cEvents,
__in const WSAEVENT* lphEvents,
__in BOOL fWaitAll,
__in DWORD dwTimeout,
__in BOOL fAlertable);
参数 | 含义 |
cEvents | lphEvents指向的数组中的事件对象句柄数。事件对象句柄的最大数量是WSA_MAXIMUM_WAIT_EVENTS事件。必须指定一个或多个事件。 |
lpEvents | 指向事件对象句柄数组的指针。数组可以包含不同类型对象的句柄。如果fWaitAll参数设置为TRUE,则它可能不包含同一句柄的多个副本。如果其中一个句柄在等待仍处于挂起状态时关闭,则WSAWaitForMultipleEvents的行为未定义。句柄必须具有同步访问权限。有关更多信息,请参阅标准访问权限。 |
fWaitAll | 指定等待类型的值。如果为TRUE,则当lphEvents数组中所有对象的状态都被通知时,函数返回。如果为FALSE,则当任何事件对象发出信号时,函数返回。在后一种情况下,返回值减去WSA_WAIT_EVENT_0表示其状态导致函数返回的事件对象的索引。如果在调用过程中有多个事件对象发出信号,则这是信号事件对象的数组索引,其索引值在所有信号事件对象中最小。 |
dwTimeout | 超时间隔,以毫秒为单位。如果超时时间间隔过期,即使不满足fWaitAll参数指定的条件,WSAWaitForMultipleEvents也会返回。如果dwTimeout参数为零,WSAWaitForMultipleEvents将测试指定事件对象的状态并立即返回。如果dwTimeout是WSA_INFINITE,那么WSAWaitForMultipleEvents将永远等待;也就是说,超时间隔永远不会过期 |
fAlertable | 指定线程是否处于可警报的等待状态,以便系统可以执行I/O完成例程。如果为TRUE,线程将处于可变等待状态,并且当系统执行I/O完成例程时,WSAWaitForMultipleEvents可以返回。在这种情况下,将返回WSA_WAIT_IO_COMPLETION,并且尚未通知正在等待的事件。应用程序必须再次调用WSAWaitForMultipleEvents函数。如果为FALSE,则线程不会处于可变等待状态,并且不会执行I/O完成例程 |
当该函数成功返回,此时代表数据发送完毕,可以使用WSAGetOverlappedResult函数来获取状态,该函数定义如下:
BOOL WSAAPI WSAGetOverlappedResult(
__in SOCKET s,
__in LPWSAOVERLAPPED lpOverlapped,
__out LPDWORD lpcbTransfer,
__in BOOL fWait,
__out LPDWORD lpdwFlags);
参数 | 含义 |
s | 进行重叠I/O的套接字 |
lpOverlapped | 进行重叠I/O时传递的WSAOVERLAPPED结构体变量的地址 |
lpcbTransfer | 用于保存实际传输的字节数的变量地址值 |
fWait | 如果调用该函数时仍在进行I/O,fWait为TRUE时等待I/O完成,fWait为FALSE时将返回FALSE并跳出函数 |
lpdwFlags | 调用WSARecv函数时,用于获取附加信息(例如OOB消息)。如果不需要,可以传递NULL |
在重叠I/O模型中,接收数据使用WSARecv函数,该函数定义如下:
int WSARecv(
__in SOCKET s,
__inout LPWSABUF lpBuffers,
__in DWORD dwBufferCount,
__out LPDWORD lpNumberOfBytesRecvd,
__inout LPDWORD lpFlags,
__in LPWSAOVERLAPPED lpOverlapped,
__in LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
参数 | 含义 |
s
| 赋予重叠I/O属性的套接字句柄
|
lpBuffers | 用于保存接收数据的WSABUF结构体数组地址值 |
dwBufferCount | 第二个参数传递的数组长度 |
lpNumberOfBytesRecvd | 保存接收数据大小信息的变量地址值 |
lpFlags | 用于设置或读取传输特性信息 |
lpOverlapped | WSAOVERLAPPED结构体变量地址值 |
lpCompletionRoutine | Completion Routine函数地址 |
参数含义和WSASend是一样的所以不重复,到这里就可以实现基于事件的重叠I/O模型,具体代码如下:
服务端:
// Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <cstdio>
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 30
VOID ShowError(PCHAR msg);
int main()
{
WSADATA wsaData;
SOCKET hServerSocket = INVALID_SOCKET, hClientSocket = INVALID_SOCKET;
SOCKADDR_IN servAddr, clientAddr;
int iClientAddrSize = 0;
WSABUF dataBuf;
WSAEVENT evObj;
WSAOVERLAPPED overlapped;
CHAR szBuf[BUF_SIZE] = { 0 };
DWORD recvBytes = 0, flags = 0;
// 初始化Winsock库
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
ShowError("WSAStartup");
goto fail;
}
// 创建套接字
hServerSocket = WSASocket(PF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (hServerSocket == INVALID_SOCKET)
{
ShowError("socket");
goto exit;
}
// 绑定端口
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(1900);
if (bind(hServerSocket, (SOCKADDR *)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
{
ShowError("bind");
goto exit;
}
// 监听端口
if (listen(hServerSocket, SOMAXCONN) == SOCKET_ERROR)
{
ShowError("listen");
goto exit;
}
printf("服务端已开启...\n");
iClientAddrSize = sizeof(clientAddr);
hClientSocket = accept(hServerSocket, (SOCKADDR *)&clientAddr, &iClientAddrSize);
printf("新的连接 IP:%s\n", inet_ntoa(clientAddr.sin_addr));
// 创建等待事件
evObj = WSACreateEvent();
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = evObj;
// 初始化数据接收
dataBuf.len = BUF_SIZE;
dataBuf.buf = szBuf;
if (WSARecv(hClientSocket, &dataBuf, 1, &recvBytes, &flags, &overlapped, NULL) == SOCKET_ERROR)
{
// 说明此时正在接收数据
if (WSAGetLastError() == WSA_IO_PENDING)
{
// 等待数据接收完毕
WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, FALSE);
// 获取接收状态
WSAGetOverlappedResult(hClientSocket, &overlapped, &recvBytes, FALSE, NULL);
}
else
{
ShowError("WSARecv");
}
}
printf("receive message: %s\n", szBuf);
WSACloseEvent(evObj);
exit:
if (hServerSocket != INVALID_SOCKET)
{
closesocket(hServerSocket);
hServerSocket = INVALID_SOCKET;
}
if (hClientSocket != INVALID_SOCKET)
{
closesocket(hClientSocket);
hClientSocket = INVALID_SOCKET;
}
WSACleanup();
fail:
system("pause");
return 0;
}
VOID ShowError(PCHAR msg)
{
printf("%s Error %d\n", msg, WSAGetLastError());
}
客户端:
// Client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <cstdio>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 30
VOID ShowError(PCHAR msg);
int main()
{
WSADATA wsaData;
SOCKET hSocket = INVALID_SOCKET;
SOCKADDR_IN servAddr;
CHAR szBuf[BUF_SIZE] = { "Hello 1900" };
WSAEVENT evObj;
WSAOVERLAPPED overlapped;
WSABUF dataBuf;
DWORD sendBytes = 0;
// 初始化Winsock库
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
ShowError("WSAStartup");
goto fail;
}
// 创建套接字
hSocket = WSASocket(PF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (hSocket == INVALID_SOCKET)
{
ShowError("socket");
goto exit;
}
// 连接服务端
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
servAddr.sin_port = htons(1900);
if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
{
ShowError("connect");
goto exit;
}
printf("连接成功\n");
// 创建等待事件
evObj = WSACreateEvent();
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = evObj;
// 初始化发送数据信息
dataBuf.buf = szBuf;
dataBuf.len = BUF_SIZE;
if (WSASend(hSocket, &dataBuf, 1, &sendBytes, 0, &overlapped, NULL))
{
// 说明此时正在发送数据
if (WSAGetLastError() == WSA_IO_PENDING)
{
// 等到数据发送完毕
WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, FALSE);
WSAGetOverlappedResult(hSocket, &overlapped, &sendBytes, FALSE, NULL);
}
else ShowError("WSASend");
}
exit:
if (hSocket != INVALID_SOCKET)
{
closesocket(hSocket);
hSocket = INVALID_SOCKET;
}
WSACleanup();
fail:
system("pause");
return 0;
}
VOID ShowError(PCHAR msg)
{
printf("%s Error %d\n", msg, WSAGetLastError());
}
可以看到成功完成通信

WSARecv和WSASend函数的最后一个参数指定的Completion Routine函数验证I/O完成情况,注册该函数具体含义就是I/O完成时调用注册过的函数进行事后处理。
该函数定义如下:
typedef
void
(CALLBACK * LPWSAOVERLAPPED_COMPLETION_ROUTINE)(
IN DWORD dwError,
IN DWORD cbTransferred,
IN LPWSAOVERLAPPED lpOverlapped,
IN DWORD dwFlags
);
当dwError不等于0的时候,cbTransferred保存了接收或者发送数据的长度,要通过Completion函数来完成数据的接收还需要在调用WSAWaitForMultipleEvents函数的时候,第三个参数指定为FALSE,第五个参数指定为TRUE,此时函数返回值为WAIT_IO_COMPLETION的时候,则说明数据接收完毕,具体代码如下:
服务端代码:
// Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <cstdio>
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 30
VOID ShowError(PCHAR msg);
void CALLBACK CompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags);
CHAR szBuf[BUF_SIZE] = { 0 };
DWORD recvBytes = 0;
int main()
{
WSADATA wsaData;
SOCKET hServerSocket = INVALID_SOCKET, hClientSocket = INVALID_SOCKET;
SOCKADDR_IN servAddr, clientAddr;
int iClientAddrSize = 0;
WSABUF dataBuf;
WSAEVENT evObj;
WSAOVERLAPPED overlapped;
DWORD flags = 0;
// 初始化Winsock库
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
ShowError("WSAStartup");
goto fail;
}
// 创建套接字
hServerSocket = WSASocket(PF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (hServerSocket == INVALID_SOCKET)
{
ShowError("socket");
goto exit;
}
// 绑定端口
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(1900);
if (bind(hServerSocket, (SOCKADDR *)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
{
ShowError("bind");
goto exit;
}
// 监听端口
if (listen(hServerSocket, SOMAXCONN) == SOCKET_ERROR)
{
ShowError("listen");
goto exit;
}
printf("服务端已开启...\n");
iClientAddrSize = sizeof(clientAddr);
hClientSocket = accept(hServerSocket, (SOCKADDR *)&clientAddr, &iClientAddrSize);
printf("新的连接 IP:%s\n", inet_ntoa(clientAddr.sin_addr));
// 创建等待事件
evObj = WSACreateEvent();
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = evObj;
// 初始化数据接收
dataBuf.len = BUF_SIZE;
dataBuf.buf = szBuf;
if (WSARecv(hClientSocket, &dataBuf, 1, &recvBytes, &flags, &overlapped, CompRoutine) == SOCKET_ERROR)
{
// 说明此时正在接收数据
if (WSAGetLastError() == WSA_IO_PENDING)
{
// 等待数据接收完毕
if (WSAWaitForMultipleEvents(1, &evObj, FALSE, WSA_INFINITE, TRUE) != WAIT_IO_COMPLETION)
{
ShowError("WSAWaitForMultipleEvents");
}
}
else
{
ShowError("WSARecv");
}
}
exit:
if (hServerSocket != INVALID_SOCKET)
{
closesocket(hServerSocket);
hServerSocket = INVALID_SOCKET;
}
if (hClientSocket != INVALID_SOCKET)
{
closesocket(hClientSocket);
hClientSocket = INVALID_SOCKET;
}
if (evObj)
{
WSACloseEvent(evObj);
}
WSACleanup();
fail:
system("pause");
return 0;
}
void CALLBACK CompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
if (dwError != 0) ShowError("Computine");
else
{
recvBytes = szRecvBytes;
printf("receive messge:%s\n", szBuf);
}
}
VOID ShowError(PCHAR msg)
{
printf("%s Error %d\n", msg, WSAGetLastError());
}
客户端代码:
// Client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <cstdio>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 30
VOID ShowError(PCHAR msg);
void CALLBACK CompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags);
int main()
{
WSADATA wsaData;
SOCKET hSocket = INVALID_SOCKET;
SOCKADDR_IN servAddr;
CHAR szBuf[BUF_SIZE] = { "Hello 1900" };
WSAEVENT evObj;
WSAOVERLAPPED overlapped;
WSABUF dataBuf;
DWORD sendBytes = 0;
// 初始化Winsock库
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
ShowError("WSAStartup");
goto fail;
}
// 创建套接字
hSocket = WSASocket(PF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (hSocket == INVALID_SOCKET)
{
ShowError("socket");
goto exit;
}
// 连接服务端
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
servAddr.sin_port = htons(1900);
if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
{
ShowError("connect");
goto exit;
}
printf("连接成功\n");
// 创建等待事件
evObj = WSACreateEvent();
memset(&overlapped, 0, sizeof(overlapped));
// 初始化发送数据信息
dataBuf.buf = szBuf;
dataBuf.len = BUF_SIZE;
if (WSASend(hSocket, &dataBuf, 1, &sendBytes, 0, &overlapped, CompRoutine))
{
// 说明此时正在发送数据
if (WSAGetLastError() == WSA_IO_PENDING)
{
// 等到数据发送完毕
if (WSAWaitForMultipleEvents(1, &evObj, FALSE, WSA_INFINITE, TRUE) != WAIT_IO_COMPLETION)
{
ShowError("WSAWaitForMultipleEvents");
}
}
else ShowError("WSASend");
}
exit:
if (hSocket != INVALID_SOCKET)
{
closesocket(hSocket);
hSocket = INVALID_SOCKET;
}
WSACleanup();
fail:
system("pause");
return 0;
}
void CALLBACK CompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
if (dwError != 0) ShowError("Computine");
else
{
printf("成功发送数据\n");
}
}
VOID ShowError(PCHAR msg)
{
printf("%s Error %d\n", msg, WSAGetLastError());
}
可以看到程序成功完成通信

实战CVE漏洞分析与防范(第1季)
最后于 2021-11-30 09:50
被1900编辑
,原因: