立即学习:https://edu.csdn.net/course/play/6082/113760?utm_source=blogtoedu
重叠Overlapped IO模型
重叠模型让应用程序使用重叠数据(WSAOVERLAPPED) , OVERLAPPED 是WIN32的一项技术,你可以要求操作系统为你传送数据, 并且在传送完毕时通知你,这项技术使你的程序在IO进行过程中仍然能够继续处理事务,支持重叠IO操作的系统对象处理文件,还有管道,串口,SOCKET
重叠Overlapped IO模型的优点
比起select WSAAsyncSelect以及WSAEventSelect等模型,重叠IO模型使应用程序能达到更加的系统性能,因为使用重叠模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序投递了一个10kb大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接拷贝到投递的缓冲区,而这4中模型需要调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区,差别就体现出来了。
重叠IO请求的通知的方式
1、事件对象通知(event object notification)
2、完成例程(completion routines)
重叠IO基于事件通知的流程
将windows事件对象与WSAOVERLAPPED结构关联在一起,(WSAOVERLAPPED结构中专门有对应的参数),使用重叠结构,我们常用的send,sendto,recv,recvfrom都要被WSASend,WSAWendto,WSARecv,WSARecvFrom替换,它们的参数中都有一个Overlapped参数,我们可以假设把我们的WSARecv这样的操作“绑定”到这个重叠结构上,提交一个请求,其他的事情就交个重叠结构去操作,而其中重叠结构又要与windows的事件对象(最多64个事件)“绑定”在一起,这样我们调用完WSARecv以后,就可以坐享其成,邓傲重叠结构操作完成以后,自然就有与之对应的事件来通知我们操作完成,然后我们就可以根据重叠操作的结构去的我们想要的数据。
WSARecv(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
参数:
第一个参数:投递这个操作的套接字
第二个参数:接收缓冲区,是WSABUF结构数组
第三个参数:数组中WSABUG结构的数量
第四个参数:如果接收操作立即完成,返回函数所收到的字节数
第五个参数:设置为0即可
第六个参数:“绑定重叠结构”
第七个参数:完成例程中的参数,我们这里设置为NULL
返回值:
成功返回0 , 完成例程将在调用线程处于alertable状态时被调用
失败返回SOCKET_ERROR的值,并通过WSAGetLastError错误代码,错误代码WSA_IO_PENDING表示重叠操作已经成功启动,并将在稍后的时间内表示完成,任何其他错误代码表名重叠操作为成功启动。
WSASend(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
参数:
第一个参数:标识一个已经连接套接字的描述字
第二个参数:一个指向WSABUF结构数组的指针
第三个参数:lpBuffers数组中WSABUF结构的数组
第四个参数:如果发送操作立即完成,则为一个指向所发送数据字节的指针
第五个参数:标志位
第六个参数:指向WSAOERLAPPED结构的指针,对于非重叠接口则忽略。
第七个参数:一个指向发送操作完成后调用的完成例程的指针(对于非重叠则忽略)
返回值:
成功返回0 , 完成例程将在调用线程处于alertable状态时被调用
否则返回SOCKET_ERROR的值,并通过调用WSAGetLastError()来检索特定的错误代码,错误代码WSA_IO_PENDING表名重叠操作已经成功启动,并将在稍后的事件内表示完成,任何其他的错误代码表名重叠操作为成功启动,也不会出现指示。
typedef struct _WSABUF {
ULONG len; /* the length of the buffer */
_Field_size_bytes_(len) CHAR FAR *buf; /* the pointer to the buffer */
} WSABUF, FAR * LPWSABUF;
WSAGetOverlappedResult函数:
WSAGetOverlappedResult(
SOCKET s,
LPWSAOVERLAPPED lpOverlapped,
LPDWORD lpcbTransfer,
BOOL fWait,
LPDWORD lpdwFlags
);
参数:
第一个参数:套接字s
第二个参数想要查询结构的重叠结构的指针
第三个参数:本次重叠操作的实际接收(或发送)的字节数
第五个参数:设置为TURE,除非重叠操作完成,否则函数不会返回,设置为Fasle,而且操作仍处于挂起状态,那么函数就会返回false,错误为WSA_IO_INCOMPLETE,应为我们是等待事件来通知我们操作完成。
第六个参数:接收结果标志
范返回值:
成功则返回true,重叠操作已经成功完成,并更新lpcbTransfer所指向的值,失败返回false,通过WSAGetLastError来获取错误代码
基于事件通知的重叠IO编程步骤
1、创建套接字在指定端口监听(socket会默认设置WSA_FLAG_OOVERLAPPED标志),并接受客户端连接请求
2、为接受的套接字为新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄,同时将该事件对象句柄分配给一个事件数组,以便稍后有WSAWaitForMultipleEvent函数使用
3、在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构,让WSAOVERLAPPED结构来替我们请求IO请求,注意函数通常会以失败告终,返回SOCKET_ERROR错误状态WSA_IO_PENDING(IO操作尚未完成)
4、使用步骤2的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入“已传信”状态,根据WSAWaitForMultipleEvents函数的返回值来确定究竟事件数组中的哪一个事件被触发了。
5、WSAWaitForMultipleEvents函数返回后,针对“已传信”状态的事件,调用WSAResetEvent函数,事件已经被触发了之后,它对于我们来讲已经没有利用价值了,所以要将它重置准备下一次使用。
6、使用WSAGetOverlappedResult函数取的重叠的返回状态,需要检查堆放的Socket连接是否关闭,如果操作完成,WSABUF结构里面就存有我们WSARecv来的数据。
7、在套接字上继续投递WSARecv请求,即重复步骤3·6
服务端代码
#include<stdlib.h>
#include<WinSock2.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
SOCKET socketArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAOVERLAPPED* overArray[WSA_MAXIMUM_WAIT_EVENTS];
WSABUF bufArray[WSA_MAXIMUM_WAIT_EVENTS];
int N = 0;
DWORD WINAPI ThreadProc(LPVOID lpRarameter);
int main()
{
WSADATA wd;
if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
{
cout << "WSAStartup error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
//
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET)
{
cout << "socket error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
sockaddr_in addr;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addr.sin_family = AF_INET;
addr.sin_port = htons(8000);
int bindRet = bind(s, (sockaddr*)&addr, sizeof sockaddr);
if (bindRet == SOCKET_ERROR)
{
cout << " bind error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
int listenRet = listen(s, 0);
if (listenRet == SOCKET_ERROR)
{
cout << "listen error " << WSAGetLastError() << endl;
exit(EXIT_FAILURE);
}
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
while (true)
{
SOCKET sClient = accept(s, NULL, NULL);
if (sClient == INVALID_SOCKET)
{
cout << "accept error" << WSAGetLastError() << endl;
continue;
}
cout << sClient << " 进入聊天室" << endl;
char temp[30] = {'\0'};
sprintf_s(temp, "欢迎%d进入聊天室", sClient);
send(sClient, temp, strlen(temp), 0);
WSAEVENT event = WSACreateEvent();
WSAOVERLAPPED* pOver = new WSAOVERLAPPED;
pOver->hEvent = event;
WSABUF wsabuf;
wsabuf.buf = new char[100]{ '\0' };
wsabuf.len = 100;
socketArray[N] = sClient;
eventArray[N] = event;
bufArray[N] = wsabuf;
overArray[N++] = pOver;
DWORD flags = 0;
DWORD numberOfByRecvd = 0;
int wsaRecvRet = WSARecv(sClient, &wsabuf, 1, &numberOfByRecvd, &flags, pOver, NULL);
if (wsaRecvRet == SOCKET_ERROR)
{
int err = WSAGetLastError();
if (err != WSA_IO_PENDING)
{
cout << "WSARecv 失败" << endl;
}
}
}
}
DWORD WINAPI ThreadProc(LPVOID lpRarameter)
{
while (true)
{
int waitRet = WSAWaitForMultipleEvents(N, eventArray, false, 1000, false);
if (waitRet == WSA_WAIT_FAILED)
{
continue;
}
int index = waitRet - WSA_WAIT_EVENT_0;
WSAResetEvent(eventArray[index]);
WSAOVERLAPPED* pOver = overArray[index];
DWORD lpcbTransfer = 0;
DWORD lpdwFlags = 0;
int ret2 = WSAGetOverlappedResult(socketArray[index], pOver, &lpcbTransfer, true, &lpdwFlags);
if (ret2 && lpcbTransfer > 0)
{
cout << socketArray[index] << "说:" << bufArray[index].buf << endl;
ZeroMemory(bufArray[index].buf, 100);
DWORD numberOfRecv = 0;
DWORD flags = 0;
WSARecv(socketArray[index], &bufArray[index], 1, &numberOfRecv, &flags, pOver, NULL);
}
else
{
cout << socketArray[index] << "离开了" << endl;
closesocket(socketArray[index]);
WSACloseEvent(eventArray[index]);
delete overArray[index];
delete[]bufArray[index].buf;
for (int i = index; i < N - 1; i++)
{
socketArray[i] = socketArray[i + 1];
eventArray[i] = eventArray[i + 1];
overArray[i] = overArray[i + 1];
bufArray[i] = bufArray[i + 1];
}
}
}
}