Socket模型之重叠I/O模型
这几天一直在看关于Socket编程的几种异步编程,我觉得关于重叠I/O模型的一些基本知识,我有必要记下来。
首先,我觉得重叠模型的有点就是允许应用程序使用重叠数据结构一次投递一个或多个异步I/O请求,当提交的请求完成之后,与之关联的重叠数据结构中关联的事件对象就会受信,然后应用程序就可以通过WSAWaitForMultipleEvents函数来判断是哪一个重叠结构中的事件对象受信,进而找到和该事件相关联的重叠结构和套接字对象。当知道这些后,我们就可以通过调用函数WSAGetOverlappedResult函数来函数取得重叠操作的结果。
在实际的编程过程中,我们需要按照下面几步来编写我们的Socket重叠模型的程序:
一、在服务器端
1、首先初始化Socket套接字。由于编写异步套接字所需要的函数包含在头文件WinSock2.h中,所以我们首先需要包含该头文件。另外还要链接WS2_32.lib库,这样我们才能够使用异步套接字的一些函数。
例如:
1 WSADATA wsaData;
2 int nRet = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
3 if(nRet != 0)
4 {
5 AfxMessageBox("初始化套接字失败!");
6 return ;
7 }
2、然后我们就可以创建一个套接字对象用于监听。
1 SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, 0);
3、绑定该套接字到SOCKADDR_IN结构
1 USHORT nPort = 6000;
2 SOCKADDR_IN sin;
3 sin.sin_family = AF_INET;
4 sin.sin_port = ::htons(nPort);
5 sin.sin_addr.S_un.S_addr = ::htonl(INADDR_ANY);
6 if(::bind(sListen, (SOCKADDR*)&sin, sizeof(sin)) == SOCKET_ERROR)
7 {
8 AfxMessageBox("绑定套接字失败!");
9 return ;
10 }
4、然后就可以监听来自客户端的套接字了。
1 if(::listen(sListen, 5) == SOCKET_ERROR)
2 {
3 AfxMessageBox("监听套接字失败!");
4 return ;
5 }
5、接下来我们就可以接收客户端的连接了。
1 SOCKADDR_IN clientAddr;
2 int nLen = sizeof(clientAddr);
3 SOCKET sockAccept = ::accept(sListen, (SOCKADDR*)&clientAddr, &nLen);
4 if(sockAccept == SOCKET_ERROR)
5 {
6 AfxMessageBox("接收客户端连接失败!");
7 return ;
8 }
6、由于要用到重叠模型来提交我们的操作,所以原来的recv、send、sendto、recvfrom等函数都要被替换为WSARecv、WSASend、WSASendto、WSARecvFrom函数来代替。例如:当我们要接收从客户端发来的消息时,我们可以按照下面的步骤来接收信息。
1 #define DATA_BUFFSIZE 4096
2 WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS]; //定义一个事件数组,最大容量为WSA_MAXIMUM_WAIT_EVENTS
3 SOCKET socketArray[WSA_MAXIMUM_WAIT_EVENTS]; //定义一个SOCKET数组,最大容量为WSA_MAXIMUM_WAIT_EVENTS
4 WSAOVERLAPPED overlappedArray[WSA_MAXIMUM_WAIT_EVENTS]; //定义一个重叠模型结构数组,
5 WSABUF Databuf; //定义一个WSABUF结构
6 int nTotalEvent = 0; //记录事件的总数
7 char buffer[DATA_BUFFSIZE];
8 ZeroMemory(buffer, sizeof(buffer));
9 eventArray[nTotalEvent] = ::WSACreateEvent(); //利用WSACreateEvent()函数来创建一个事件对象并赋值给eventArray[nTotalEvent]
10 Databuf.buf = buffer; //给WSABUF结构赋值
11 Databuf.len = DATA_BUFFSIZE;
12 ZeroMemory(&overlappedArray[nTotalEvent], sizeof(WSAOVERLAPPED));
13 overlappedArray[nTotalEvent].hEvent = eventArray[nTotalArray]; //将创建的事件对象和重叠结构绑定起来,
14 socketArray[nTotalEvent] = sockAccept;
15 DWORD dwBytes = 0;
16 DWORD dwFlags = 0;
17 if(::WSARecv(socketArray[nTotalEvent], &Databuf, 1, &dwBytes, &dwFlags, overlappedArray[nTotalEvent], NULL) == SOCKET_ERROR)
18 {
19 if(::WSAGetLastError() != WSA_IO_PENDING)//若函数返回WSA_IO_PENDING表示操作正在进行,不能立即完成,若不是,则出错了
20 {
21 ::closesocket(socketArray[nTotalEvent]);
22 socketArray[nTotalEvent] = INVALID_SOCKET;
23 ::WSACloseEvent(eventArray[nTotalEvent]);
24 }
25 }
7.当我们做完上面的工作后,下面我们就可以调用WSAWaitForMultipleEvents()函数来等待事件的发生了。若有事件发生则返回事件的索引,但我们应该减去一个常数WSA_WAIT_EVENT_0,才能得到在eventArray数组中的索引。
1 int nIndex = ::WSAWaitForMultipleEvents(nTotalEvent, eventArray, FALSE, WSA_INFINITE, FALSE);
2 if(nIndex == WSA_WAIT_FALIED)
3 {
4 AfxMessageBox("...失败");
5 return ;
6 }
7 nIndex = nIndex - WSA_WAIT_EVENT_0;
8 ::WSAResetEvent(eventArray[nIndex]); //当事件被触发时,应该重置该事件对象
由于第三个参数我们设为FALSE,那么当任意一件事件发生时,该函数就会返回,那么当同时有几件事件发生时,后面发生的事件就得不到处理,因此,我们可以循环for(int i = nIndexl; i < nTotalEvent; i++)来对后面的事件进行处理。
8、当事件被触发时,我们就可以通过调用WSAGetOverlappedResult()函数来取得在该事件上发生的操作了。
1 DWORD dwTranslateBytes = 0;
2 DWORD dwFlags = 0;
3 SOCKET & socketTemp = socketArray[nIndex];
4 WSAOVERLAPPED & overlappedTemp = overlappedArray[nIndex];
5 WSAGetOverlappedResult(socketTemp, overlappedTemp, &dwTranslateBytes, FALSE, &dwFlags);
6 if(dwTranslateBytes == 0) //若接收到的字节数为0,则说明客户端已经关闭连接了,则我们也可以把相应的套接字关闭了
7 {
8 ::closesocket(socketTemp);
9 socketTemp = INVALID_SOCKET;
10 ::WSACloseEvent(eventArray[nIndex]);
11 }
12 else
13 {
14 CString strTemp;
15 strTemp.Format("已经接收到数据:%s\n", Databuf.buf);
16 AfxMessageBox(strTemp);
17 }
9、到这里我们从客户端接收数据就完成了。但是还有几个问题就是,为了能够容纳更多的客户端连接我们应该创建额外的工作线程来处理和管理多客户端连接。在这里,我们只是说明一下重叠I/O模型的用法,就额外开辟两个线程,一个用来不断的利用监听套接字来对客户端进行监听,来进行和客户端连接,另外一个线程就是不断获取在重叠模型上发生的操作。