windows套接字I/0模型-重叠IO(overlapped)模型
2023-5-11 15:54:29 Author: 安全狗的自我修养(查看原文) 阅读量:14 收藏

1.适用于除Windows CE之外的各种Windows平台.在使用这个模型之前应该确保该系统安装了Winsock2.重叠模型的基本设计原理是使用一个重叠的数据结构,一次投递一个或多个Winsock I/O请求。在重叠模型中,收发数据使用WSA开头的函数。

2.WSA_FLAG_OVERLAPPED标志:要使用重叠模型。在创建套接字的时候,必须加上该标志。

SOCKET s=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);

假如使用的是socket函数,而非WSASocket函数,那么会默认设置WSA_FLAG_OVERLAPPED标志。若随一个WSAFLAGOVERLAPPED结构一起调用这些以WSA开头的函数(AcceptEx和TRansmiteFile函数例外),函数会立即完成并返回,无论套接字是否设为阻塞模式3.重叠模型在网络事件完成后,可以有两种方式通知应用程序:事件通知和完成例程

3.事件通知:事件对象与WSAOVERLAPPED进行绑定实现网络事件完成后通过事件进行通知。

4.WSAOVERLAPPED结构:

typedef struct WSAOverlapped{    DWORD Internal;    DOWRD InternalHigh;    DWORD Offset;    DWORD OffsetHigh;    WSAEVENT hEvent;}WSAOVERLAPPED,FAR* LPWSAOVERLAPPED;

此处,程序员可以使用的是最后一个参数hEvent,其余的不用管。通过该参数,重叠结构可以与事件对象进行绑定,以实现网络事件完成后,通过事件对象进行通知应用程序。事件对象通知方式是通过创建一个事件对象,把该对象赋值给重叠结构的hEvent参数即可实现绑定。在此再次提醒大家注意:WSAWaitForMultipleEvents函数一次最多只能等待64个事件对象。

5.WSAGetOverlappedResult函数:重叠请求完成后,接着需要调用WSAGetOverlappedResult(取得重叠结构)函数,判断那个重叠调用到底是成功,还是失败.

BOOL WSAGetOverlappedResult(    SOCKET s,//套接字    LPWSAOVERLAPPED lpOverlapped,//重叠结构    LPDWORD lpcbTransfer,//对应一个DWORD(双字节)变量,一次重叠实际传输(接收或者发送)的字节数    BOOL fWait,//参数用于决定函数是否应该等待一次重叠操作完成。LPWORD lpdwFlags    );

重叠操作完成,函数返回TRUE,否则,返回FALSE。返回FALSE通常都是有一下几种情况造成的.

(1)重叠I/O操作仍处在未完成状态

(2)重叠操作已经完成,但含有错误

(3)重叠操作的完成状态不可判决,因为在提供给函数WSAGetOverlappedResult的一个或多个参数中,存在着错误。失败后,由lpcbTransfer参数指向的值不会进行更新,而且我们的应用程序应调用WSAGetLastError函数

6.基于事件通知的重叠模型编程步骤如下:

(1) 创建一个套接字,绑定本机端口,在指定的端口上监听连接请求。

(2)接受连接请求。

(3)为接受的套接字新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄。也将事件对象句柄分配给一个事件数组,以便稍后由函数WSAWaitForMultipleEvents使用。

(4)在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构。注意函数通常会以失败告终,返回SOCKETERROR错误状态WSAIOPENDING(I/O操作尚未完成)。

(5)使用步骤3)的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入“已传信”状态(换言之,等待那个事件的“触发”)。

(6)WSAWaitForMultipleEvents函数完成后,针对事件数组,调用WSAResetEvent(重设事件)函数,从而重设事件对象,并对完成的重叠请求进行处理。

(7)使用WSAGetOverlappedResult函数,判断重叠调用的返回状态是什么。

(8)在套接字上投递另一个重叠WSARecv请求。

(9)重复步骤5 ) ~ 8 )。

服务端设计案例

#include <Mswsock.h>#include <stdio.h>#include <windows.h>
CInitSock theSock;
#define BUFFER_SIZE 1024
typedef struct _SOCKET_OBJ{ SOCKET s; // 套节字句柄 int nOutstandingOps; // 记录此套节字上的重叠I/O数量 LPFN_ACCEPTEX lpfnAcceptEx; // 扩展函数AcceptEx的指针(仅对监听套节字而言)} SOCKET_OBJ, *PSOCKET_OBJ;
typedef struct _BUFFER_OBJ{ OVERLAPPED ol; // 重叠结构 char *buff; // send/recv/AcceptEx所使用的缓冲区 int nLen; // buff的长度 PSOCKET_OBJ pSocket; // 此I/O所属的套节字对象
int nOperation; // 提交的操作类型#define OP_ACCEPT 1#define OP_READ 2#define OP_WRITE 3
SOCKET sAccept; // 用来保存AcceptEx接受的客户套节字(仅对监听套节字而言) _BUFFER_OBJ *pNext;} BUFFER_OBJ, *PBUFFER_OBJ;
HANDLE g_events[WSA_MAXIMUM_WAIT_EVENTS]; // I/O事件句柄数组int g_nBufferCount; // 上数组中有效句柄数量PBUFFER_OBJ g_pBufferHead, g_pBufferTail; // 记录缓冲区对象组成的表的地址
// 申请套节字对象和释放套节字对象的函数PSOCKET_OBJ GetSocketObj(SOCKET s){ PSOCKET_OBJ pSocket = (PSOCKET_OBJ)::GlobalAlloc(GPTR, sizeof(SOCKET_OBJ)); if(pSocket != NULL) { pSocket->s = s; } return pSocket;}void FreeSocketObj(PSOCKET_OBJ pSocket){ if(pSocket->s != INVALID_SOCKET) ::closesocket(pSocket->s); ::GlobalFree(pSocket);}
PBUFFER_OBJ GetBufferObj(PSOCKET_OBJ pSocket, ULONG nLen){ if(g_nBufferCount > WSA_MAXIMUM_WAIT_EVENTS - 1) return NULL;
PBUFFER_OBJ pBuffer = (PBUFFER_OBJ)::GlobalAlloc(GPTR, sizeof(BUFFER_OBJ)); if(pBuffer != NULL) { pBuffer->buff = (char*)::GlobalAlloc(GPTR, nLen); pBuffer->ol.hEvent = ::WSACreateEvent(); pBuffer->pSocket = pSocket; pBuffer->sAccept = INVALID_SOCKET;
// 将新的BUFFER_OBJ添加到列表中 if(g_pBufferHead == NULL) { g_pBufferHead = g_pBufferTail = pBuffer; } else { g_pBufferTail->pNext = pBuffer; g_pBufferTail = pBuffer; } g_events[++ g_nBufferCount] = pBuffer->ol.hEvent; } return pBuffer;}
void FreeBufferObj(PBUFFER_OBJ pBuffer){ // 从列表中移除BUFFER_OBJ对象 PBUFFER_OBJ pTest = g_pBufferHead; BOOL bFind = FALSE; if(pTest == pBuffer) { g_pBufferHead = g_pBufferTail = NULL; bFind = TRUE; } else { while(pTest != NULL && pTest->pNext != pBuffer) pTest = pTest->pNext; if(pTest != NULL) { pTest->pNext = pBuffer->pNext; if(pTest->pNext == NULL) g_pBufferTail = pTest; bFind = TRUE; } } // 释放它占用的内存空间 if(bFind) { g_nBufferCount --; ::CloseHandle(pBuffer->ol.hEvent); ::GlobalFree(pBuffer->buff); ::GlobalFree(pBuffer); }}
PBUFFER_OBJ FindBufferObj(HANDLE hEvent){ PBUFFER_OBJ pBuffer = g_pBufferHead; while(pBuffer != NULL) { if(pBuffer->ol.hEvent == hEvent) break; pBuffer = pBuffer->pNext; } return pBuffer;}
void RebuildArray(){ PBUFFER_OBJ pBuffer = g_pBufferHead; int i = 1; while(pBuffer != NULL) { g_events[i++] = pBuffer->ol.hEvent; pBuffer = pBuffer->pNext; }}
BOOL PostAccept(PBUFFER_OBJ pBuffer){ PSOCKET_OBJ pSocket = pBuffer->pSocket; if(pSocket->lpfnAcceptEx != NULL) { // 设置I/O类型,增加套节字上的重叠I/O计数 pBuffer->nOperation = OP_ACCEPT; pSocket->nOutstandingOps ++;
// 投递此重叠I/O DWORD dwBytes; pBuffer->sAccept = ::WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); BOOL b = pSocket->lpfnAcceptEx(pSocket->s, pBuffer->sAccept, pBuffer->buff, BUFFER_SIZE - ((sizeof(sockaddr_in) + 16) * 2), sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16, &dwBytes, &pBuffer->ol); if(!b) { if(::WSAGetLastError() != WSA_IO_PENDING) return FALSE; } return TRUE; } return FALSE;};
BOOL PostRecv(PBUFFER_OBJ pBuffer){ // 设置I/O类型,增加套节字上的重叠I/O计数 pBuffer->nOperation = OP_READ; pBuffer->pSocket->nOutstandingOps ++;
// 投递此重叠I/O DWORD dwBytes; DWORD dwFlags = 0; WSABUF buf; buf.buf = pBuffer->buff; buf.len = pBuffer->nLen; if(::WSARecv(pBuffer->pSocket->s, &buf, 1, &dwBytes, &dwFlags, &pBuffer->ol, NULL) != NO_ERROR) { if(::WSAGetLastError() != WSA_IO_PENDING) return FALSE; } return TRUE;}
BOOL PostSend(PBUFFER_OBJ pBuffer){ // 设置I/O类型,增加套节字上的重叠I/O计数 pBuffer->nOperation = OP_WRITE; pBuffer->pSocket->nOutstandingOps ++;
// 投递此重叠I/O DWORD dwBytes; DWORD dwFlags = 0; WSABUF buf; buf.buf = pBuffer->buff; buf.len = pBuffer->nLen; if(::WSASend(pBuffer->pSocket->s, &buf, 1, &dwBytes, dwFlags, &pBuffer->ol, NULL) != NO_ERROR) { if(::WSAGetLastError() != WSA_IO_PENDING) return FALSE; } return TRUE;}
BOOL HandleIO(PBUFFER_OBJ pBuffer){ PSOCKET_OBJ pSocket = pBuffer->pSocket; // 从BUFFER_OBJ对象中提取SOCKET_OBJ对象指针,为的是方便引用 pSocket->nOutstandingOps --;
// 获取重叠操作结果 DWORD dwTrans; DWORD dwFlags; BOOL bRet = ::WSAGetOverlappedResult(pSocket->s, &pBuffer->ol, &dwTrans, FALSE, &dwFlags); if(!bRet) { // 在此套节字上有错误发生,因此,关闭套节字,移除此缓冲区对象。 // 如果没有其它抛出的I/O请求了,释放此缓冲区对象,否则,等待此套节字上的其它I/O也完成 if(pSocket->s != INVALID_SOCKET) { ::closesocket(pSocket->s); pSocket->s = INVALID_SOCKET; }
if(pSocket->nOutstandingOps == 0) FreeSocketObj(pSocket); FreeBufferObj(pBuffer); return FALSE; }
// 没有错误发生,处理已完成的I/O switch(pBuffer->nOperation) { case OP_ACCEPT: // 接收到一个新的连接,并接收到了对方发来的第一个封包 { // 为新客户创建一个SOCKET_OBJ对象 PSOCKET_OBJ pClient = GetSocketObj(pBuffer->sAccept);
// 为发送数据创建一个BUFFER_OBJ对象,这个对象会在套节字出错或者关闭时释放 PBUFFER_OBJ pSend = GetBufferObj(pClient, BUFFER_SIZE); if(pSend == NULL) { printf(" Too much connections! \n"); FreeSocketObj(pClient); return FALSE; } RebuildArray(); // 将数据复制到发送缓冲区 pSend->nLen = dwTrans; memcpy(pSend->buff, pBuffer->buff, dwTrans);
// 投递此发送I/O(将数据回显给客户) if(!PostSend(pSend)) { // 万一出错的话,释放上面刚申请的两个对象 FreeSocketObj(pSocket); FreeBufferObj(pSend); return FALSE; } // 继续投递接受I/O PostAccept(pBuffer); } break; case OP_READ: // 接收数据完成 { if(dwTrans > 0) { // 创建一个缓冲区,以发送数据。这里就使用原来的缓冲区 PBUFFER_OBJ pSend = pBuffer; pSend->nLen = dwTrans; // 投递发送I/O(将数据回显给客户) PostSend(pSend); } else // 套节字关闭 { // 必须先关闭套节字,以便在此套节字上投递的其它I/O也返回 if(pSocket->s != INVALID_SOCKET) { ::closesocket(pSocket->s); pSocket->s = INVALID_SOCKET; }
if(pSocket->nOutstandingOps == 0) FreeSocketObj(pSocket); FreeBufferObj(pBuffer); return FALSE; } } break; case OP_WRITE: // 发送数据完成 { if(dwTrans > 0) { // 继续使用这个缓冲区投递接收数据的请求 pBuffer->nLen = BUFFER_SIZE; PostRecv(pBuffer); } else // 套节字关闭 { // 同样,要先关闭套节字 if(pSocket->s != INVALID_SOCKET) { ::closesocket(pSocket->s); pSocket->s = INVALID_SOCKET; }
if(pSocket->nOutstandingOps == 0) FreeSocketObj(pSocket);
FreeBufferObj(pBuffer); return FALSE; } } break; } return TRUE;}

void main(){ // 创建监听套节字,绑定到本地端口,进入监听模式 int nPort = 4567; SOCKET sListen = ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); SOCKADDR_IN si; si.sin_family = AF_INET; si.sin_port = ::ntohs(nPort); si.sin_addr.S_un.S_addr = INADDR_ANY; ::bind(sListen, (sockaddr*)&si, sizeof(si)); ::listen(sListen, 200);
// 为监听套节字创建一个SOCKET_OBJ对象 PSOCKET_OBJ pListen = GetSocketObj(sListen);
// 加载扩展函数AcceptEx GUID GuidAcceptEx = WSAID_ACCEPTEX; DWORD dwBytes; WSAIoctl(pListen->s, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidAcceptEx, sizeof(GuidAcceptEx), &pListen->lpfnAcceptEx, sizeof(pListen->lpfnAcceptEx), &dwBytes, NULL, NULL);
// 创建用来重新建立g_events数组的事件对象 g_events[0] = ::WSACreateEvent();
// 在此可以投递多个接受I/O请求 for(int i=0; i<5; i++) { PostAccept(GetBufferObj(pListen, BUFFER_SIZE)); } ::WSASetEvent(g_events[0]); while(TRUE) { int nIndex = ::WSAWaitForMultipleEvents(g_nBufferCount + 1, g_events, FALSE, WSA_INFINITE, FALSE); if(nIndex == WSA_WAIT_FAILED) { printf("WSAWaitForMultipleEvents() failed \n"); break; } nIndex = nIndex - WSA_WAIT_EVENT_0; for(int i=0; i<=nIndex; i++) { int nRet = ::WSAWaitForMultipleEvents(1, &g_events[i], TRUE, 0, FALSE); if(nRet == WSA_WAIT_TIMEOUT) continue; else { ::WSAResetEvent(g_events[i]); // 重新建立g_events数组 if(i == 0) { RebuildArray(); continue; }
// 处理这个I/O PBUFFER_OBJ pBuffer = FindBufferObj(g_events[i]); if(pBuffer != NULL) { if(!HandleIO(pBuffer)) RebuildArray(); } } } }}

 


文章来源: http://mp.weixin.qq.com/s?__biz=MzkwOTE5MDY5NA==&mid=2247487962&idx=2&sn=c12e9d3ac0ed9b0e47d47bd435fd3ab3&chksm=c13f2293f648ab85373b4ce1d42fd3844e9772eca864a4053b8ecb6ce49509208bc61d58fe5b#rd
如有侵权请联系:admin#unsafe.sh