重叠IO技术在服务器端的应用

来源:互联网 发布:用网络机顶盒怎么看电视直播 编辑:程序博客网 时间:2024/06/01 08:22

在处理TCP并发线程上,一般我们采用一个线程处理接受客户端的连接,然后开出一个线程处理与这个客户端的交互过程.

但是,跟据机器的性能与操作系统的限制,一般在几百个并发线程,也就是同时处理几百个客户端连接.

 

为了处理更多的客户端连接,不得不考虑换一种网络模型,目前比较流行的有select,完成端口,重叠IO,本文主要介绍重叠IO的实现

 

//重叠IO投递WSARecv的例子

#include "stdafx.h"

#include <winsock2.h>

#include <vector>

#include "../../SJLib/rutil/Log.hpp"

#pragma comment(lib,"ws2_32.lib")

#pragma comment(lib,"../../SJLib/rutil/Debug/rutil.lib")

using namespace std;

using namespace SJLib;

 

#define MAX_SOCKET (WSA_MAXIMUM_WAIT_EVENTS)       //最大为64个事件句柄

#define DATA_BUFSIZE 4096                               //缓冲区大小

WSAEVENT eventArrays[MAX_SOCKET];                       //建立64个事件句柄,主要目的为了用WSAWaitForMultiEvent同时等待64个事件

 

typedef struct tagSocketInfo                            //如果打算支持send操作,还需要加入对应的WSAOVERLAPPEDEVENT,WSABUF,以满足不同时候的调用

{

     SOCKET                 _socket;                    //套接字

     SOCKADDR_IN            _address;                   //客户端主机地址与端口

     WSAOVERLAPPED      _overlapped;                //重叠IO最重要的结构WSAOVERLAPPED,msdn

     WSABUF                 _data;                           //WSARecv时需要用到的缓冲结构,len为长度,buf为程序提供的缓冲区指针

     char               _buffer[DATA_BUFSIZE];      //接收时用到的缓冲区,需要把它的地址赋给_data.buf

}SocketInfo,*LPSocketInfo;

 

SocketInfo SocketArrays[MAX_SOCKET];               //定义64个客户端连接信息

SOCKET hListen;                                              //服务端监听套接字

bool listenDone = false;                                //服务端是否打算退出

HANDLE hClientThread;                                   //接收数据线程句柄

 

 

BOOL WINAPI ShutdownHandler(DWORD dwCtrlType)      //用户关闭程序时,所做的释放操作

{

     listenDone = true;

     shutdown(hListen,0);

     closesocket(hListen);

 

     WaitForSingleObject(hClientThread,INFINITE);

     CloseHandle(hClientThread);

 

     for(int i=0;i<MAX_SOCKET;i++)

     {

         WSACloseEvent(eventArrays[i]);

     }

 

     InfoLog(<<"server exit"<<endl);

     exit(0);

     return TRUE;

}

 

DWORD WINAPI WorkerThread(LPVOID lpContext)

{

     int count = 0;

     while(!listenDone)                                               //判断服务端口是否退出

     {

         //精华就都在这里了,WSAWaitForMultipleEvents同时等待64个客户端数据到达事件,

         //第三个参数很重要,TRUE,也就是说当所有客户端都发数据过来,此函数再返回,FALSE则反

         //如果没有任何一个客户端发送数据过来,它将阻塞

         int index = WSAWaitForMultipleEvents(MAX_SOCKET,eventArrays,FALSE,WSA_INFINITE,FALSE);

         if(index!=WAIT_TIMEOUT)          //判断是否超时,当然这里不可能超时,因为我使用了无限等待

         {

              index -= WAIT_OBJECT_0;     //取入被触发的事件索引

              WSAResetEvent(eventArrays[index]);             //为了下一次使用,重置它

              LPSocketInfo client = &SocketArrays[index];

 

              InfoLog(<<count++<<" client recv buffer:"<<client->_buffer<<endl);         //这里就是本轮接受到的数据

              DWORD dwBytesTransferred;

              DWORD Flags = 0;

 

              //查询这次重叠IO的结果,主要是判断客户端是否断开

              WSAGetOverlappedResult(client->_socket,&client->_overlapped,&dwBytesTransferred, FALSE, &Flags);

              if(dwBytesTransferred == 0)          //断开

              {

                   closesocket(client->_socket);

                   continue;

              }

              else                                 //将继续接收下一轮数据,可以考虑在这里加入用WSASend回复客户端的代码,它也可以使用重叠IO技术,如果没有使用,就可能造成一个客户端的延迟拖慢了所有的

              {

                   DWORD dwBufferCount = 1, dwRecvBytes = 0, Flags = 0;

                   memset(&client->_overlapped,0,sizeof(WSAOVERLAPPED));        //必须重新初始化相当的结构数据,前面我没有做,结果只接收到几十次,就停下来了

                   client->_overlapped.hEvent = eventArrays[index];

                   memset(client->_buffer,0,DATA_BUFSIZE);

                   client->_data.len = DATA_BUFSIZE;

                   client->_data.buf = client->_buffer;

                   if(WSARecv(client->_socket ,&client->_data,1,&dwRecvBytes,&Flags,&client->_overlapped, NULL)==SOCKET_ERROR)//继续接收

                   {

                       if(WSAGetLastError() != WSA_IO_PENDING)

                       {

                            shutdown(client->_socket,0);

                            closesocket(client->_socket);

                            continue;

                       }

                   }

              }

 

             

         }

     }

    

     return 0;

}

 

int _tmain(int argc, _TCHAR* argv[])

{

     Log::initializeLog(Log::Cout|Log::VSDebugWindow,Log::Debug,"SimpleServer");

 

     SetConsoleCtrlHandler(ShutdownHandler,TRUE);

     WSADATA data = {0};

     WSAStartup(0x0202,&data);

 

     hListen = socket(AF_INET,SOCK_STREAM,0);                     //建立服务器端口

     struct sockaddr_in my_addr;

     memset(&my_addr,0,sizeof(sockaddr_in));

     my_addr.sin_family = AF_INET;

     my_addr.sin_port = htons(8000);

 

     my_addr.sin_addr.s_addr=inet_addr("192.168.1.230");

 

     int reuse = 0;

     if (setsockopt(hListen, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse,sizeof(reuse)) != 0)

     {

         closesocket(hListen);

         ErrLog(<<"SocketBaseException: setsockopt server address fault"<<endl);

         return -1;

     }

 

     if(bind(hListen, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) != 0)

     {

         ErrLog(<<"SocketBaseException: bind server address fault"<<endl);

         return -1;

     }

 

     ULONG NonBlock = 1;

 

     if(listen(hListen,5) == SOCKET_ERROR)                        //开始监听

     {

         ErrLog(<<"listen socket fault"<<endl);

         return -1;

     }

    

     for(int i=0;i<MAX_SOCKET;i++)                                    //先把64个事件创建起来

     {

         eventArrays[i] = WSACreateEvent();

     }

 

     HANDLE hClientThread = CreateThread(NULL,0,WorkerThread,NULL,0,0);         //启动数据接收处理线程

 

     int count = 0;

     while(!listenDone)

     {

         LPSocketInfo client = &SocketArrays[count];                      //取出一个客户端连接句柄,等待客户端连入

        

          int addressLen = sizeof(client->_address);

         client->_socket = accept(hListen,(sockaddr*)&client->_address,&addressLen);         //在这里等待客户端连接

         const char *host = inet_ntoa(client->_address.sin_addr);                      

         InfoLog(<<"client host "<<host<<":"<<client->_address.sin_port<<endl);              //有客户端连入,打印对方主机地址与端口

        

         client->_overlapped.hEvent = eventArrays[count];                           //取入一个事先建立好的事件句柄分配给当前客户端

         client->_data.len = DATA_BUFSIZE;                                              //指定缓冲区大小

         client->_data.buf = client->_buffer;                                       //指定缓冲区地址

         DWORD dwBufferCount = 1, dwRecvBytes = 0, Flags = 0;

         count++;                                                                       //计算器加一,注意,没有处理当客户端大于64以上的代码

 

         int nRel;

         //第一轮的接收,注意这里并没有阻塞,数据到达时,将会把它所使用的事件句柄设为有信号,也就是在WorkerThread里处理

         if((nRel = WSARecv(client->_socket ,&client->_data,1,&dwRecvBytes,&Flags,&client->_overlapped, NULL))

              == SOCKET_ERROR)

         {

              if(nRel==0)//客户端断开连接

              {

                   shutdown(client->_socket,0);

                   closesocket(client->_socket);

                   continue;

              }

              if(WSAGetLastError() != WSA_IO_PENDING)//如果结果不是WSA_IO_PENDING,就有可能是客户端一个包也没有发就断开了

              {

                   shutdown(client->_socket,0);

                   closesocket(client->_socket);

                   continue;

              }

         }

     }

    

     return 0;

}