前面一节我们简单的介绍了事件通知的重叠IO模型,有兴趣的可以点击 C++ Windows Socket五种I/O模型之重叠IO模型(一)查看一下,这一章主要是介绍一下完成例程模型。
基本原理 完成例程在投递一个请求之后如(WSARecv)之后,系统完成之后不再以事件通知你,而是在完成之后自动调用你提供的LPWSAOVERLAPPED_COMPLETION_ROUTINE 类型的的回调函数来处理消息。
WSARecv 在重叠模型中,接收数据就要靠它了,它的参数也比recv要多,和事件通知重叠唯一不同的是最后一个参数: lpCompletionRoutine,名称你可以随便起,但是他参数是固定的,下面详细接收
int WSARecv(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
LPWSAOVERLAPPED_COMPLETION_ROUTINE 类型回调函数 这是完成例程重叠模式关键函数,名称你可以随便命名,但是查询Windows文档中可以看到他的参数是固定如下:
LPWSAOVERLAPPED_COMPLETION_ROUTINE LpwsaoverlappedCompletionRoutine;
void LpwsaoverlappedCompletionRoutine(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags
)
{...}
参数: dwError 表示完成状态,0 表示成功,其他代表不同错误码 cbTransferred 表示都到数据长度,如果出现错误,值为 0 lpOverlapped 返回的重叠结构 dwFlags 与呼叫关联的标志
SleepEX 这一个函数是完成回调的一个重要函数,如果不调用这个函数,回调函数不会执行,函数原型:
DWORD WINAPI SleepEx(
DWORD dwMillesconds,
BOOL bAlertable);
参数: dwMillesconds 线程挂起的最短时间,以毫秒为单位。 bAlertable 如果设置为FALSE,这个函数直到设定的最短时间才会返回,即使中间有回调发生,函数也不回返回,并且不会执行回调函数。 如果设置为TRUE,且调用此函数的线程与调用扩展I/O函数(ReadFileEx或WriteFileEx)的线程相同,则当超时时间已过或出现I/O完成回调函数时,该函数将返回。如果发生I/O完成回调,则调用I/O完成函数。如果一个APC被排队到线程(QueueUserAPC),则该函数在计时器超时或调用APC函数时返回。(简而言之,就是如果要调用回调,此处设置为TRUE)
基本函数就介绍到这个地方,是具体的代码(服务端): 编译Vs2019
#include <iostream>
#include <WinSock2.h>
#define MSGSIZE 1024
#include <thread>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
SOCKET g_sNewSocket;
bool g_bNewConnectionArrived = FALSE;
typedef struct
{
WSAOVERLAPPED overlap;
SOCKET socket;
WSABUF buffer;
char szMessage[MSGSIZE];
DWORD NumberOfBytesRecvd;
DWORD Flags;
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
void CALLBACK CompletionROUTINE(DWORD dwError,DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,DWORD dwFlags)
{
LPPER_IO_OPERATION_DATA lap = (LPPER_IO_OPERATION_DATA)lpOverlapped;
if (dwError!=0 || cbTransferred==0)
{
closesocket(lap->socket);
HeapFree(GetProcessHeap(), 0, lap);
}
else
{
cout << lap->szMessage << endl;
ZeroMemory(&lap->overlap, sizeof(WSAOVERLAPPED));
ZeroMemory(lap->szMessage,MSGSIZE);
lap->buffer.buf = lap->szMessage;
WSARecv(lap->socket,
&lap->buffer,
1,
&lap->NumberOfBytesRecvd,
&lap->Flags,
&lap->overlap,
CompletionROUTINE);
}
}
void workThread()
{
while (true)
{
if (g_bNewConnectionArrived)
{
LPPER_IO_OPERATION_DATA lap = (LPPER_IO_OPERATION_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PER_IO_OPERATION_DATA));
lap->buffer.len = MSGSIZE;
lap->buffer.buf = lap->szMessage;
lap->socket = g_sNewSocket;
WSARecv(g_sNewSocket, &lap->buffer, 1, &lap->NumberOfBytesRecvd, &lap->Flags, &lap->overlap, CompletionROUTINE);
g_bNewConnectionArrived = false;
}
SleepEx(1000, TRUE);
}
}
bool initSocket()
{
WSADATA wsdata;
WORD sVer = MAKEWORD(2, 2);
int ret = WSAStartup(sVer, &wsdata);
if (ret != 0)
{
return false;
}
return true;
}
int main()
{
if (!initSocket())
{
return -1;
}
sockaddr_in Sa, recvSa;
int len = sizeof(Sa);
Sa.sin_port = htons(8888);
Sa.sin_addr.S_un.S_addr = INADDR_ANY;
Sa.sin_family = AF_INET;
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
cout << "Create socket fail" << endl;
return -1;
}
if (bind(sock, (sockaddr*)&Sa, len) == SOCKET_ERROR)
{
cout << "bind fail" << endl;
return -1;
}
if (listen(sock, 5) == SOCKET_ERROR)
{
cout << "listen fail" << endl;
return -1;
}
thread t(workThread);
t.detach();
while (1)
{
g_sNewSocket= accept(sock, (sockaddr*)&recvSa, &len);
if (g_sNewSocket!=INVALID_SOCKET)
{
g_bNewConnectionArrived =true;
}
}
return 0;
}
|