• 基于WSAEventSelect模型的服务器设计


    本文参考于《Windows网络与通信程序设计》

    基于WSAEventSelect模型的服务器设计

    对于WSAEventSelect模型,一个工作线程最多能够处理64个套接字,故采用线程池来管理大于64个套接字的请求。

    //定义套接字结构体

    typedef struct _SOCKET_OBJ

    {

      SOCKET s;          //套接字句柄

      HANDLE event;         //与此套接字相关的事件对象句柄

      sockaddr_in addrRemote;   //客户端地址信息

      _SOCKET_OBJ *pNext;    //指向下一个SOCKET_OBJ对象,形成链表

    } SOCKET_OBJ, *PSOCKET_OBJ;

    //申请一个套接字对象,初始化它的成员

    PSOCKET_OBJ GetSocketObj(SOCKET s)

    {

      PSOCKET_OBJ pSocket = (PSOCKET_OBJ)::GloblAlloc(GPTR, sizeof(SOCKET_OBJ));

      if (pSocket != NULL)

      {

        pSocket->s = s;

        pSocket->enent = ::WSACreateEvent();

      }

      return pSocket;

    }

    //释放一个套接字对象

    void FreeSocketObj(PSOCKET_OBJ pSocket)

    {

      ::CloseHandle(pSocket->event);

      if (pSocket->s != INVALID_SOCKET)

      { ::closesocket(pSocket->s); }

      ::GlobalFree(pSocket);

    }

    //定义线程结构体

    typedef struct _THREAD_OBJ

    {

      HANDLE enents[WSA_MAXIMUM_WAIT_EVENTS]; //记录当前线程要等待的事件对象句柄

      int nSocketCount; //记录当前线程处理的套接字数量<= WSA_MAXIMUM_WAIT_EVENTS

      PSOCKET_OBJ pSockHeader; //当前线程处理的套接字对象列表,指向表头

      PSOCKET_OBJ pSockTail;      //指向表尾

      CRITICAL_SECTION cs; //关键代码段变量,为的是同步对本结构对象的访问

      _THREAD_OBJ *pNext; //指向下一个THREAD_OBJ对象,为的是连成一个表

    }THREAD_OBJ, *PTHREAD_OBJ;

    //全局变量

    PTHREAD_OBJ g_pThreadList; //指向线程对象列表表头

    CRITICAL_SECTION g_cs;  //同步对此全局变量的访问

    //申请一个线程对象,初始化它的成员,并将它添加到线程对象列表中

    PTHREAD_OBJ GetThreadObj()

    {

      PTHREAD_OBJ pThread = (PTHREAD_OBJ)::GlobalAlloc(GPTR, sizeof(THREAD_OBJ));

      if (pThread != NULL)

      {

        ::InitislizeCriticlSection(&pThread->cs);

        //创建一个事件对象,用于指示该线程的句柄数组需要重建

        pThread->events[0] = ::WSACreateEvent();

        //将申请的线程对象添加到列表

        ::EnterCriticalSection(&g_cs);

        pThread->pNext = g_pThreadList;

        g_pThreadLsit = pThread;  

        ::LeaveCriticalSection(&g_cs);

      } 

      return pThread;

    }

    //释放一个线程对象,将它从线程对象列表中移除

    void FreeThreadObj(PTHREAD_OBJ pThread)

    {

      ::EnterCriticalSection(&g_cs);

      PTHREAD_OBJ p = g_pThreadList;

      if (p == pThread ) //是第一个

      { g_pThreadList = p->pNext; }

      else

      {

        while (p != NULL && p->pNext != pThread)

        { p = p->pNext; }

        if (p != NULL)

        {

          p->pNext = pThread->pNext;

        }

      }

      ::LeaveCriticalSection(&g_cs);

      //释放资源

      ::CloseHande(pThread->events[0]);

      ::DeleteCriticalSection(&pThread->cs);

      ::GlobalFree(pThread);

    }

    //重写建立线程对象的events数组

    void RebulidArray(PTHREAD_OBJ pThread)

    {

      ::EnterCriticalSection(&pThread->cs);

      PSOCKET_OBJ pSocket = pThread->pSockHeader;

      int n = 1; // 从第一个开始写,第0个用于指示需要重建了

      while(pSocket != NULL)

      {

        pThread->events[n++] = pSocket->event;

        pSocket = pSocket->pNext;

      }

      ::LeaveCriticalSection(&pThread->cs);

    }

    LONG g_nTatolConnections;   //总共连接数量

    LONG g_nCurrentConnections;  //当前连接数量

     

    //向一个线程的套接字列表中插入一个套接字

    BOOL InsertSocketObj(PTHREAD_OBJ pThread, PSOCKET_OBJ pSocket)

    {

      BOOL bRet = FALSE;

      ::EnterCriticalSection(&pThread->cs);

      if (pThread->nSocketCount < WSA_MAXIMUM_WAIT_EVENTS -1)

      {

        if (pThread->pSocketHeader == NULL)

        { pThread->pSocketHeader = pThead->pSocketTail = pSocket;}

        else

        {

          pThread->pSockTail->pNext = pSocket;

          pThread->pSockTail = pSocket;

        }

        pThead->nSocketCount++;

        bRet = TRUE;

      }

      ::LeaveCriticalSection(&pThread->cs);

      //插入成功,说明成功处理了客户的连接请求

      if (bRet)

      {

        ::InterlockedIncrement(&g_nTatolConnections);

        ::InterlockedIncrement(&g_nCurrentConnections);

      }

      return bRet;

    }

    //将一个套接字对象安排给空闲的线程处理

    void AssignToFreeThread(PSOCKET_OBJ pSocket)

    {

      pSocket->pNext = NULL;

      ::EnterCriticalSection(&g_cs);

      PTHREAD_OBJ pThread = g_pThreadList;

      while (pThread != NULL)

      {

        if (InsertSocketObj(pThread,pSocket)) break;

          pThread = pThread->pNext;

      }

      //没有空闲的线程,为这个套接字创建新的线程

      if (pThread == NULL)

      {

        pThread = GetThreadObj();

        InsertSocketObj(pThread,pSocket);

        ::CreateThread(NULL, 0 , ServerThread, pThread, 0 , NULL);

      }

      ::LeaveCriticalSection(&g_cs);

      //指示线程重建句柄数组

      ::WSASetEvent(pThread->events[0]);

    }

    //从给定线程的套接字对象列表中移除一个套接字对象

    void RemoveSocketObj(PTHREAD_OBJ pThread, PSOCKET_OBJ pSocket)

    {

      ::EnterCriticakSection(&pThread->cs);

      //在套接字对象列表中查找指定的套接字对象,找到之后将之移除

      PSOCKET_OBJ pTest = pThread->pSockHeader;

      if (pTest == pSocket)

      {

        if (pThread->pSockHeader == pThread->pSockTail)

          pThread->pSockTail = pThread->pSockHeader = pTest->pNext;

        else

          pThread->pSockHeader = pTest->pNext;

      }

      else

      {

        while (pTest != NULL && pTest->pNext != pSocket)

          pTest = pTest->pNext;

        if (pTest != NULL)

        {

          if (pThread->pSockTail == pSocket) pThread->pSockTail = pTest;

          pTest->pNext = pSocket->pNext;

        }

      }

      pThread->nSocketCount--;

      ::LeaveCriticalSection(&pThread->cs);

      ::WSASetEvent(pThread->event[0]); //指示线程重建句柄数组

      ::InterlockedDecrement(&g_nCurrentConnections); //说明一个连接中断

    }

    //处理I/O的线程

    DWORD WINAPI ServerThread(LPVOID lpParam)

    {

      //取得本线程对象的指针

      PTHREAD_OBJ pThread = (PTHREAD_OBJ)lpParam;

      while(TRUE)

      {

        //等待网络事件

        int nIndex = ::WSAWaitForMultipleEvents(

            pThread->nSocketCount + 1, pThread->events,FALSE,WSA_INFINITE,FALSE);

        nIndex = nIndex - WSA_WAIT_EVENT_0;

        //查看受信的事件对象

        for (int i = nIndex; i < pThread->nSocketCount + 1; i++)

        {

          nIndex = ::WSAWaitForMultipleEvents(1, &pThread->events[i], TRUE,1000,FALSE);

          if (nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)

            continue;

          else

          {

            if (i == 0) //enents[0]受信,重建数组

            {

              RebuildArray(pThread);

              //如果没有客户I/O处理了,则本线程退出

              if (pThread->nSocketCount == 0)

              {

                FreeThreadObj(pThread);

                return 0;

              }

              ::WSAResetEvent(pThread->events[0]);

            }

            else

            {

              PSOCKET_OBJ pSocket = (PSOCKET_OBJ)FindSocketObj(pThread, i);

              if (pSocket != NULL)

              {

                if (!HandleIO(pThread, pSocket))

                  RebuildArray(pThread);

              }

              else

                printf("Unable to find socket object \n");

            }

          }

        }

      }

      return 0;

    }

    //根据事件对象在events数组中的索引查找相应的套接字对象

    PSOCKET_OBJ FindSocketObj(PTHREAD_OBJ pThread, int nIndex) //nIndex从1开始

    {

      //在套接字列表中查询

      PSOCKET_OBJ pSocket = pThread->pSockHeader;

      while (--nIndex)

      {

        if (pSocket == NULL)

          return NULL;

        pSocket = pSocket->pNext;

      }  

      return pSocket; 

    }

    //具体发生的网络事件

    BOOL HandleIO(PTHREAD_OBJ pThread, PSOCKET_OBJ pSocket)

    {

      //获取具体发生的网络事件

      WSANETWORKEVENTS event;

      ::WSAEnumNetworkEvents(pSocket->s, pSocket->event,&event);

      do

      {

        if (event.lNetworkEvents & FD_READ) //套接字可读

        {

          if (event.iErrorCode[FD_READ_BIT] == 0)

          {

            char szText[256];

            int nRecv = ::recv(pSocket->s,szText,strlen(szText),0);

            if (nRecv > 0)

            {

              szText[nRecv] = '\0';

              printf("接收到数据: %s \n",szText);

            }

          }

          else

            break;

        } 

      else if (event.lNetworkEvents & FD_CLOSE) //套接字关闭

        break;

      else if (event.lNetworkEvents & FD_WRITE) //套接字可写

      {  

        if (event.iErrorCode[FD_WRITE_BIT] == 0)

        { }

        else

          break;

        return TRUE;

      }

     }while(FALSE);

      RemoveSocketObj(pThread,pSocket);

      FreeSocketObj(pSocket);

      return FALSE;

    }

    //主线程

    int main()

    {

      USHORT nPort = 4567; //此服务器监听端口

      SOCKET sListen = ::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

      sockaddr_in sin;

      sin.sin_family = AF_INET;

      sin.sin_port = htons(nPort);

      sin.sin_addr.S_un.S_addr = INADDR_ANY;

      if (::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)

      {

        printf("Failed bind() \n");

        return -1;

      }

      ::listen(sListen,200);

      //创建事件对象,并关联到监听的套接字

      WSAEVENT event = ::WSACreateEvent();

      ::WSAEventSelect(sListen, event, FD_ACCEPT|FD_CLOSE);

      ::InitializeCriticalSection(&g_cs);

      //处理客户连接请求,打印状态信息

      while(TRUE)

      {

        int nRet = ::WaitForSingleObject(event, 5*1000);

        if (nRet == WAIT_FAILED)

        {

          printf("Failed WaitForSingleObject() \n");

          break;

        }

        else if (nRet == WSA_WAIT_TIMEOUT)

        {

          printf("\n");

          printf(" TatolConnections:%d\n",g_nTatolConnections);

          printf("CurrentConnections:%d\n",g_nCurrentConnections);

          continue;

        }

        else  //有新的连接

        {

          ::ResetEvent(event);

          //循环处理所有的未决的连接请求

          while(TRUE)

          {

            sockaddr_in si;

            int nLen = sizeof(si);

            SOCKET sNew = ::accept(sListen, (sockaddr*)&si, &nLen);

            if (sNew == SOCKET_ERROR)

              break;

            PSOCKET_OBJ pSocket = GetSocketObj(sNew);

            pSocket->addrRemote = si;

            ::WSAEventSelect(pSocket->s, pSocket->evnet, FD_READ|FD_CLOSE|FD_WRITE);

            AssignToFreeThread(pSocket);

          } 

        }

      }

      ::DeleteCriticalSection(&g_cs);

      return 0;

    }

  • 相关阅读:
    洛谷 P1337 [JSOI2004]平衡点 / 吊打XXX 解题报告
    牛客练习赛 小D的剑阵 解题报告
    牛客练习赛 小A与最大子段和 解题报告
    牛客练习赛 小D的Lemon 解题报告
    牛客练习赛 小A与任务 解题报告
    洛谷 P1452 Beauty Contest 解题报告
    洛谷 P4100 [HEOI2013]钙铁锌硒维生素 解题报告
    【模板】矩阵求逆
    洛谷 P4097 [HEOI2013]Segment 解题报告
    连接数据库的增删改查
  • 原文地址:https://www.cnblogs.com/pbreak/p/1863299.html
Copyright © 2020-2023  润新知