前几篇文章介绍了集中linux epoll的封装方式,本篇回归windows,介绍一个IOCP的封装。
首先介绍要达到的目的:
1) 导出基本接口,作为一个更高层跨平台网络库的基础组件
2) 导出的接口用法与epoll版本大致相当,以方便日后的跨平台封装
3) 默认的使用方式是单线程的,与epoll一致。
下面是接口文件:
/* Copyright (C) <2012> <huangweilook@21cn.com> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #ifndef _KENDYNET_H #define _KENDYNET_H //重叠结构 struct OverLapContext { OVERLAPPED m_overLapped; WSABUF* wbuf; DWORD buf_count; unsigned char m_Type; }; struct Socket; //socket的简单封装 typedef struct Socket { SOCKET sock; HANDLE complete_port; void (*RecvFinish)(struct Socket*,struct OverLapContext*,DWORD); void (*SendFinish)(struct Socket*,struct OverLapContext*,DWORD); }*Socket_t; int InitNetSystem(); void CleanNetSystem(); HANDLE CreateNetEngine(DWORD NumberOfConcurrentThreads); void CloseNetEngine(HANDLE); int RunEngine(HANDLE,DWORD timeout); int Bind2Engine(HANDLE,Socket_t); //now表示是否立即发起操作 int WSA_Send(Socket_t,struct OverLapContext*,int now,DWORD *lastErrno); int WSA_Recv(Socket_t,struct OverLapContext*,int now,DWORD *lastErrno); #endif
其中的Engine就是IOCP,使用方式就是RunEngine,在函数内部调用GetQueuedCompletionStatus以处理事件.事件处理完成后,
回调RecvFinish/SendFinish.在简单的单线程程序中,只需要创建一个engine,并在主线程调用RunEngine即可.当然,因为完成
队列其实是线程安全的,所以多个线程同时对一个IOCP调用engine也是可以的。但我不建议这么做,如果需要多线程,可以创建
N个engine和工作线程,各工作线程在自己的engine上执行RunEngine即可(与epoll版本一致).
下面贴出实现:
#include <winsock2.h> #include <WinBase.h> #include <Winerror.h> #include "KendyNet.h" #include "stdio.h" enum { IO_RECVREQUEST = 1<<1, //应用层接收请求 IO_SENDREQUEST = 1<<3, //应用层发送请求 IO_RECVFINISH = 1<<2,//接收完成 IO_SENDFINISH = 1<<4, //发送完成 }; enum { IO_RECV = (1<<1) + (1<<2), IO_SEND = (1<<3) + (1<<4), IO_REQUEST = (1<<1) + (1<<3), }; int InitNetSystem() { int nResult; WSADATA wsaData; nResult = WSAStartup(MAKEWORD(2,2), &wsaData); if (NO_ERROR != nResult) { printf("\nError occurred while executing WSAStartup()."); return -1; //error } return 0; } void CleanNetSystem() { WSACleanup(); } HANDLE CreateNetEngine(DWORD NumberOfConcurrentThreads) { HANDLE CompletePort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, NumberOfConcurrentThreads); if(NULL == CompletePort) { printf("\nError occurred while creating IOCP: %d.", WSAGetLastError()); } return CompletePort; } void CloseNetEngine(HANDLE CompletePort) { CloseHandle(CompletePort); } static int raw_Send(Socket_t s,struct OverLapContext *overLapped,DWORD *lastErrno) { DWORD dwFlags = 0; DWORD dwBytes = 0; int nBytesSend = WSASend(s->sock, overLapped->wbuf, overLapped->buf_count, &dwBytes, dwFlags, (OVERLAPPED*)overLapped, NULL); if(SOCKET_ERROR == nBytesSend) *lastErrno = WSAGetLastError(); return dwBytes; } static int raw_Recv(Socket_t s,struct OverLapContext *overLapped,DWORD *lastErrno) { DWORD dwFlags = 0; DWORD dwBytes = 0; int nBytesSend = WSARecv(s->sock, overLapped->wbuf, overLapped->buf_count, &dwBytes, &dwFlags, (OVERLAPPED*)overLapped, NULL); if(SOCKET_ERROR == nBytesSend) *lastErrno = WSAGetLastError(); return dwBytes; } typedef void (*CallBack)(struct Socket*,struct OverLapContext*,DWORD); int RunEngine(HANDLE CompletePort,DWORD timeout) { DWORD bytesTransfer; Socket_t socket; struct OverLapContext *overLapped = 0; DWORD lastErrno = 0; BOOL bReturn; CallBack call_back; for( ; ; ) { call_back = 0; bReturn = GetQueuedCompletionStatus( CompletePort,&bytesTransfer, (LPDWORD)&socket, (OVERLAPPED**)&overLapped,timeout); if(FALSE == bReturn || socket == NULL || overLapped == NULL) { break; } if(0 == bytesTransfer) { //连接中断 if(overLapped->m_Type & IO_RECV) call_back = socket->RecvFinish; else call_back = socket->SendFinish; bytesTransfer = 0; } else { if(overLapped->m_Type & IO_REQUEST) { overLapped->m_Type = overLapped->m_Type << 1; if(overLapped->m_Type & IO_RECVFINISH) bytesTransfer = raw_Recv(socket,overLapped,&lastErrno); else if(overLapped->m_Type & IO_SENDFINISH) bytesTransfer = raw_Send(socket,overLapped,&lastErrno); else { //出错 continue; } if(bytesTransfer <= 0 && lastErrno != 0 && lastErrno != WSA_IO_PENDING) bytesTransfer = 0; else continue; } if(overLapped->m_Type & IO_RECVFINISH) call_back = socket->RecvFinish; else if(overLapped->m_Type & IO_SENDFINISH) call_back = socket->SendFinish; else { //出错 continue; } } if(call_back) call_back(socket,overLapped,bytesTransfer); } return 0; } int Bind2Engine(HANDLE CompletePort,Socket_t socket) { HANDLE hTemp; if(!socket->RecvFinish || !socket->SendFinish) return -1; hTemp = CreateIoCompletionPort((HANDLE)socket->sock, CompletePort,(ULONG_PTR)socket, 0); if (NULL == hTemp) return -1; socket->complete_port = CompletePort; return 0; } int WSA_Send(Socket_t socket,struct OverLapContext *OverLap,int now,DWORD *lastErrno) { if(!socket->complete_port) return -1; ZeroMemory(&OverLap->m_overLapped, sizeof(OVERLAPPED)); if(!now) { OverLap->m_Type = IO_SENDREQUEST; PostQueuedCompletionStatus(socket->complete_port,1,(ULONG_PTR)socket,(OVERLAPPED*)OverLap); } else { OverLap->m_Type = IO_SENDFINISH; return raw_Send(socket,overLapped,lastErrno); } return 0; } int WSA_Recv(Socket_t socket,struct OverLapContext *OverLap,int now,DWORD *lastErrno) { if(!socket->complete_port) return -1; ZeroMemory(&OverLap->m_overLapped, sizeof(OVERLAPPED)); if(!now) { OverLap->m_Type = IO_RECVREQUEST; PostQueuedCompletionStatus(socket->complete_port,1,(ULONG_PTR)socket,(OVERLAPPED*)OverLap); return 0; } else { OverLap->m_Type = IO_RECVFINISH; return raw_Recv(socket,overLapped,lastErrno); } }
代码一点点,不做介绍了,下面是一个简单的echo测试
#include <stdio.h> #include <winsock2.h> #include <WinBase.h> #include <Winerror.h> #include "KendyNet.h" struct connection { struct Socket socket; WSABUF wsendbuf; WSABUF wrecvbuf; char sendbuf[128]; char recvbuf[128]; struct OverLapContext send_overlap; struct OverLapContext recv_overlap; }; void RecvFinish(struct Socket *s,struct OverLapContext *overLap,DWORD bytestransfer) { struct connection *c = (struct connection*)s; if(bytestransfer <= 0) free(c); else { DWORD lastErrno = 0; memcpy(c->sendbuf,c->recvbuf,bytestransfer); c->wsendbuf.len = bytestransfer; WSA_Send(s,&c->send_overlap,1,&lastErrno); } } void SendFinish(struct Socket *s,struct OverLapContext *overLap,DWORD bytestransfer) { struct connection *c = (struct connection*)s; if(bytestransfer <= 0) free(c); else { DWORD lastErrno = 0; WSA_Recv((Socket_t)c,&c->recv_overlap,1,&lastErrno); } } struct connection* CreateConnection(SOCKET s) { struct connection *c; c = malloc(sizeof(*c)); ZeroMemory(c, sizeof(*c)); c->socket.sock = s; c->socket.RecvFinish = &RecvFinish; c->socket.SendFinish = &SendFinish; c->wrecvbuf.buf = c->recvbuf; c->wrecvbuf.len = 128; c->recv_overlap.buf_count = 1; c->recv_overlap.wbuf = &c->wrecvbuf; c->wsendbuf.buf = c->sendbuf; c->wsendbuf.len = 0; c->send_overlap.buf_count = 1; c->send_overlap.wbuf = &c->wsendbuf; return c; } DWORD WINAPI Listen(void *arg) { HANDLE *iocp = (HANDLE*)arg; HANDLE complete_port = *iocp; struct sockaddr_in addr; int optval=1; //Socket属性值 unsigned long ul=1; struct linger lng; struct sockaddr_in ClientAddress; int nClientLength = sizeof(ClientAddress); struct connection *c; SOCKET ListenSocket; ListenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); if (INVALID_SOCKET == ListenSocket) { printf("\nError occurred while opening socket: %d.", WSAGetLastError()); return 0; } addr.sin_family = AF_INET; addr.sin_port = htons(8010); addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); setsockopt(ListenSocket,SOL_SOCKET,SO_EXCLUSIVEADDRUSE,(char*)&optval,sizeof(optval)); //禁止端口多用 setsockopt(ListenSocket,IPPROTO_TCP,TCP_NODELAY,(char*)&optval,sizeof(optval)); //不采用延时算法 setsockopt(ListenSocket,SOL_SOCKET,SO_DONTLINGER,(char*)&optval,sizeof(optval)); //执行立即关闭 lng.l_linger=0; lng.l_onoff=1; setsockopt(ListenSocket,SOL_SOCKET,SO_LINGER,(char*)&lng,sizeof(lng)); //禁止重用本地端口 if ((bind(ListenSocket, (struct sockaddr *)&addr, sizeof( struct sockaddr_in))) == SOCKET_ERROR) { closesocket(ListenSocket); return 0; } if((listen(ListenSocket, 5)) == SOCKET_ERROR) { closesocket(ListenSocket); return 0; } printf("listener 启动\n"); while(1) { SOCKET client = accept(ListenSocket, (struct sockaddr*)&ClientAddress, &nClientLength); if (INVALID_SOCKET == client) { continue; } if(ioctlsocket(client,FIONBIO,(unsigned long*)&ul)==SOCKET_ERROR) { closesocket(client); continue; } c = CreateConnection(client); Bind2Engine(complete_port,(Socket_t)c); //发出第一个读请求 WSA_Recv((Socket_t)c,&c->recv_overlap,0,0); } return 0; } int main() { HANDLE iocp; DWORD dwThread; InitNetSystem(); iocp = CreateNetEngine(0); CreateThread(NULL,0,Listen,&iocp,0,&dwThread); while(1) RunEngine(iocp,10); return 0; }