• winsock编程IOCP模型实现代码


    winsock编程IOCP模型实现代码

      话不多说,上代码。借鉴《windows核心编程》部分源码和CSDN小猪部分代码。

      stdafx.h依赖头文件:

     1 #include <iostream>
     2 #include <WinSock2.h>
     3 #include <MSWSock.h>
     4 #include <vector>
     5 #include "Singleton.h"
     6 #include "IOCPWrapper.h"
     7 #include "OverlappedIOInfo.h"
     8 #include "TaskSvc.h"
     9 
    10 using namespace std;

      其中,TaskSvc.h、Singleton.h源码可以在我的blog里面找到。

      IOCPWrapper.h源码:

     1 /******************************************************************************
     2 Module:  IOCP.h
     3 Notices: Copyright (c) 2007 Jeffrey Richter & Christophe Nasarre
     4 Purpose: This class wraps an I/O Completion Port.
     5 Revise:    IOCP封装类,由《windows核心编程》第10章示例程序源码改编所得
     6 ******************************************************************************/
     7 #pragma once   
     8 
     9 class CIOCP 
    10 {
    11 public:
    12    CIOCP(int nMaxConcurrency = -1)
    13    {
    14        m_hIOCP = NULL; 
    15        if (nMaxConcurrency != -1)
    16            Create(nMaxConcurrency);
    17    }
    18    ~CIOCP()
    19    {
    20        if (m_hIOCP != NULL) 
    21            VERIFY(CloseHandle(m_hIOCP)); 
    22    }
    23 
    24    //关闭IOCP
    25    BOOL Close()
    26    {
    27        BOOL bResult = CloseHandle(m_hIOCP);
    28        m_hIOCP = NULL;
    29        return(bResult);
    30    }
    31 
    32    //创建IOCP,nMaxConcurrency指定最大线程并发数量,0默认为cpu数量
    33    BOOL Create(int nMaxConcurrency = 0)
    34    {
    35        m_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, nMaxConcurrency);
    36        ASSERT(m_hIOCP != NULL);
    37        return(m_hIOCP != NULL);
    38    }
    39 
    40    //为设备(文件、socket、邮件槽、管道等)关联一个IOCP
    41    BOOL AssociateDevice(HANDLE hDevice, ULONG_PTR CompKey)
    42    {
    43        BOOL fOk = (CreateIoCompletionPort(hDevice, m_hIOCP, CompKey, 0) == m_hIOCP);
    44        ASSERT(fOk);
    45        return(fOk);
    46    }
    47 
    48    //为socket关联一个IOCP
    49    BOOL AssociateSocket(SOCKET hSocket, ULONG_PTR CompKey)
    50    {
    51        return(AssociateDevice((HANDLE) hSocket, CompKey));
    52    }
    53 
    54    //为iocp传递事件通知
    55    BOOL PostStatus(ULONG_PTR CompKey, DWORD dwNumBytes = 0,OVERLAPPED* po = NULL)
    56    {
    57        BOOL fOk = PostQueuedCompletionStatus(m_hIOCP, dwNumBytes, CompKey, po);
    58        ASSERT(fOk);
    59        return(fOk);
    60    }
    61 
    62    //从IO完成队列中获取事件通知。IO完成队列无事件时,该函数将阻塞
    63    BOOL GetStatus(ULONG_PTR* pCompKey, PDWORD pdwNumBytes,OVERLAPPED** ppo, DWORD dwMilliseconds = INFINITE)
    64    {
    65        return(GetQueuedCompletionStatus(m_hIOCP, pdwNumBytes,pCompKey, ppo, dwMilliseconds));
    66    }
    67 
    68    //获取IOCP对象
    69    const HANDLE  GetIOCP()
    70    {
    71        return m_hIOCP;
    72    }
    73 private:
    74     //IOCP句柄
    75    HANDLE m_hIOCP;
    76 };
    77 
    78 ///////////////////////////////// End of File /////////////////////////////////

    OverlappedIOInfo.h源码

     1 /******************************************************************************
     2 Module:  OverlappedIOInfo.h
     3 Notices: Copyright (c) 20161201  whg
     4 Purpose:
     5 IOCP网络编程模型中,需要用到GetQueuedCompletionStatus函数获取已完成事件。
     6 但该函数的返回参数无socket或buffer的描述信息。
     7 
     8 一个简单的解决办法,创建一个新的结构,该结构第一个参数是OVERLAPPED。
     9 由于AcceptEx、WSASend等重叠IO操作传入的是Overlapped结构体的地址,调用AcceptEx等重叠IO操作,
    10 在Overlapped结构体后面开辟新的空间,写入socket或buffer的信息,即可将socket或buffer的信息由
    11 GetQueuedCompletionStatus带回。
    12 
    13 参考《windows核心编程》和CSDN PiggyXP
    14 ******************************************************************************/
    15 
    16 #pragma once
    17 
    18 #define MAXBUF 8*1024
    19 
    20 enum IOOperType{
    21     TYPE_ACP,            //accept事件到达,有新连接请求    
    22     TYPE_RECV,            //数据接收事件
    23     TYPE_SEND,            //数据发送事件
    24     TYPE_CLOSE,            //关闭事件
    25     TYPE_NO_OPER
    26 };
    27 
    28 class COverlappedIOInfo :    public OVERLAPPED
    29 {
    30 public:
    31     COverlappedIOInfo(void)
    32     {
    33         m_sSock = INVALID_SOCKET;
    34         ResetOverlapped();
    35         ResetRecvBuffer();
    36         ResetSendBuffer();
    37     }
    38     ~COverlappedIOInfo(void)
    39     {
    40         if (m_sSock != INVALID_SOCKET)
    41         {
    42             closesocket(m_sSock);
    43             m_sSock = INVALID_SOCKET;
    44         }
    45     }
    46     void ResetOverlapped()
    47     {
    48         Internal = InternalHigh = 0;   
    49         Offset = OffsetHigh = 0;   
    50         hEvent = NULL;
    51     }
    52     void ResetRecvBuffer()
    53     {
    54         ZeroMemory(m_cRecvBuf,MAXBUF);
    55         m_recvBuf.buf = m_cRecvBuf;
    56         m_recvBuf.len = MAXBUF;
    57     }
    58     void ResetSendBuffer()
    59     {
    60         ZeroMemory(m_cSendBuf,MAXBUF);
    61         m_sendBuf.buf = m_cSendBuf;
    62         m_sendBuf.len = MAXBUF;
    63     }
    64 public:
    65     //套接字
    66     SOCKET        m_sSock;            
    67     //接收缓冲区,用于AcceptEx、WSARecv操作
    68     WSABUF        m_recvBuf;            
    69     char        m_cRecvBuf[MAXBUF];
    70     //发送缓冲区,用于WSASend操作
    71     WSABUF        m_sendBuf;
    72     char        m_cSendBuf[MAXBUF];    
    73     //对端地址
    74     sockaddr_in    m_addr;                
    75 };

    server.h

     1 #pragma once
     2 
     3 
     4 class CServer:public CTaskSvc
     5 {
     6 #define ACCEPT_SOCKET_NUM  10
     7 
     8 public:
     9     CServer(void);
    10     ~CServer(void);
    11     bool    StartListen(unsigned short port,std::string ip);
    12 
    13 protected:
    14     virtual void svc();
    15 
    16 private:
    17     //启动CPU*2个线程,返回已启动线程个数
    18     UINT    StartThreadPull();
    19     //获取AcceptEx和GetAcceptExSockaddrs函数指针
    20     bool    GetLPFNAcceptEXAndGetAcceptSockAddrs();
    21     //利用AcceptEx监听accept请求
    22     bool    PostAccept(COverlappedIOInfo* ol);
    23     //处理accept请求,NumberOfBytes=0表示没有收到第一帧数据,>0表示收到第一帧数据
    24     bool    DoAccept(COverlappedIOInfo* ol,DWORD NumberOfBytes=0);
    25     //投递recv请求
    26     bool    PostRecv(COverlappedIOInfo* ol);
    27     //处理recv请求
    28     bool    DoRecv(COverlappedIOInfo* ol);
    29     //从已连接socket列表中移除socket及释放空间
    30     bool    DeleteLink(SOCKET s);
    31     //释放3个部分步骤:
    32     //1:清空IOCP线程队列,退出线程
    33     //2: 清空等待accept的套接字m_vecAcps
    34     //3: 清空已连接的套接字m_vecContInfo并清空缓存
    35     void    CloseServer();
    36 private:
    37     //winsock版本类型
    38     WSAData                        m_wsaData;
    39     //端口监听套接字
    40     SOCKET                        m_sListen;
    41     //等待accept的套接字,这些套接字是没有使用过的,数量为ACCEPT_SOCKET_NUM。同时会有10个套接字等待accept
    42     std::vector<SOCKET>            m_vecAcps;            
    43     //已建立连接的信息,每个结构含有一个套接字、发送缓冲和接收缓冲,以及对端地址
    44     std::vector<COverlappedIOInfo*>    m_vecContInfo;    
    45     //操作vector的互斥访问锁
    46     CThreadLockCs                m_lsc;                    
    47     //IOCP封装类
    48     CIOCP                        m_iocp;        
    49     //AcceptEx函数指针
    50     LPFN_ACCEPTEX                m_lpfnAcceptEx;        
    51     //GetAcceptSockAddrs函数指针
    52     LPFN_GETACCEPTEXSOCKADDRS    m_lpfnGetAcceptSockAddrs;
    53 };
    54 
    55 typedef CSingleton<CServer> SERVER;

    server.cpp

      1 #include "StdAfx.h"
      2 #include "Server.h"
      3 
      4 CServer::CServer(void)
      5 {
      6     m_lpfnAcceptEx = NULL;
      7     m_lpfnGetAcceptSockAddrs = NULL;
      8     WSAStartup(MAKEWORD(2,2),&m_wsaData);
      9 }
     10 
     11 CServer::~CServer(void)
     12 {
     13     CloseServer();
     14     WSACleanup();
     15 }
     16 
     17 bool CServer::StartListen(unsigned short port,std::string ip)
     18 {
     19     //listen socket需要将accept操作投递到完成端口,因此,listen socket属性必须有重叠IO
     20     m_sListen = WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED);
     21     if(m_sListen == INVALID_SOCKET)
     22     {
     23         cout<<"WSASocket create socket error"<<endl;
     24         return false;
     25     }
     26     //创建并设置IOCP并发线程数量
     27     if (m_iocp.Create() == FALSE)
     28     {
     29         cout<<"IOCP create error,error code "<<WSAGetLastError()<<endl;
     30         return false;
     31     }
     32     //将listen socket绑定至iocp
     33     if (!m_iocp.AssociateSocket(m_sListen,TYPE_ACP))
     34     {
     35         cout<<"iocp Associate listen Socket error"<<endl;
     36         return false;
     37     }
     38     sockaddr_in service;
     39     service.sin_family = AF_INET;
     40     service.sin_port = htons(port);
     41     if (ip.empty())
     42     {
     43         service.sin_addr.s_addr = INADDR_ANY;
     44     }
     45     else
     46     {
     47         service.sin_addr.s_addr = inet_addr(ip.c_str());
     48     }
     49 
     50     if (bind(m_sListen,(sockaddr*)&service,sizeof(service)) == SOCKET_ERROR)
     51     {
     52         cout<<"bind() error,error code "<<WSAGetLastError()<<endl;
     53         return false;
     54     }
     55     cout<<"bind ok!"<<endl;
     56 
     57     if (listen(m_sListen,SOMAXCONN) == SOCKET_ERROR)
     58     {
     59         cout<<"listen() error,error code "<<WSAGetLastError()<<endl;
     60         return false;
     61     }
     62     cout<<"listen ok!"<<endl;
     63     //启动工作者线程
     64     int threadnum = StartThreadPull();
     65     cout<<"启动工作者线程,num="<<threadnum<<endl;
     66     //获取AcceptEx和GetAcceptSockAddrs函数指针
     67     if (!GetLPFNAcceptEXAndGetAcceptSockAddrs())
     68     {
     69         return false;
     70     }
     71     //创建10个acceptex
     72     for (int i=0;i<ACCEPT_SOCKET_NUM;i++)
     73     {
     74         //用accept
     75         COverlappedIOInfo* ol = new COverlappedIOInfo;
     76         if (!PostAccept(ol))
     77         {
     78             delete ol;
     79             return false;
     80         }
     81     }
     82 }
     83 
     84 bool CServer::GetLPFNAcceptEXAndGetAcceptSockAddrs()
     85 {
     86     DWORD BytesReturned = 0;
     87     //获取AcceptEx函数指针
     88     GUID GuidAcceptEx = WSAID_ACCEPTEX;
     89     if (SOCKET_ERROR == WSAIoctl(
     90         m_sListen,
     91         SIO_GET_EXTENSION_FUNCTION_POINTER,
     92         &GuidAcceptEx,
     93         sizeof(GuidAcceptEx),
     94         &m_lpfnAcceptEx,
     95         sizeof(m_lpfnAcceptEx),
     96         &BytesReturned,
     97         NULL,NULL))
     98     {
     99         cout<<"WSAIoctl get AcceptEx function error,error code "<<WSAGetLastError()<<endl;
    100         return false;
    101     }
    102     
    103     //获取GetAcceptexSockAddrs函数指针
    104     GUID GuidGetAcceptexSockAddrs = WSAID_GETACCEPTEXSOCKADDRS; 
    105     if (SOCKET_ERROR == WSAIoctl(
    106         m_sListen,
    107         SIO_GET_EXTENSION_FUNCTION_POINTER,
    108         &GuidGetAcceptexSockAddrs,
    109         sizeof(GuidGetAcceptexSockAddrs),
    110         &m_lpfnGetAcceptSockAddrs,
    111         sizeof(m_lpfnGetAcceptSockAddrs),
    112         &BytesReturned,
    113         NULL,NULL))
    114     {
    115         cout<<"WSAIoctl get GetAcceptexSockAddrs function error,error code "<<WSAGetLastError()<<endl;
    116         return false;
    117     }
    118     return true;
    119 }
    120 
    121 bool CServer::PostAccept(COverlappedIOInfo* ol)
    122 {
    123     if (m_lpfnAcceptEx == NULL)
    124     {
    125         cout << "m_lpfnAcceptEx is NULL"<<endl;
    126         return false;
    127     }
    128     SOCKET s = ol->m_sSock;
    129     ol->ResetRecvBuffer();
    130     ol->ResetOverlapped();
    131     ol->ResetSendBuffer();
    132     ol->m_sSock = WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED);
    133     if (ol->m_sSock == INVALID_SOCKET)
    134     {
    135         cout<<"WSASocket error ,error code "<<WSAGetLastError()<<endl;
    136         return false;
    137     }
    138     //这里建立的socket用来和对端建立连接,终会加入m_vecContInfo列表
    139     //调用acceptex将accept socket绑定至完成端口,并开始进行事件监听
    140     //这里需要传递Overlapped,new一个COverlappedIOInfo
    141     //AcceptEx是m_listen的监听事件,m_listen已经绑定了完成端口;虽然ol->m_sSock已经创建,
    142     //但未使用,现在不必为ol->m_sSock绑定完成端口。在AcceptEx事件发生后,再为ol->m_sSock绑定IOCP
    143     DWORD byteReceived = 0;
    144     if (FALSE == m_lpfnAcceptEx(
    145         m_sListen,
    146         ol->m_sSock,
    147         ol->m_recvBuf.buf,
    148         ol->m_recvBuf.len - (sizeof(SOCKADDR_IN)+16)*2,
    149         sizeof(SOCKADDR_IN)+16,
    150         sizeof(SOCKADDR_IN)+16,
    151         &byteReceived,
    152         ol))
    153     {
    154         DWORD res = WSAGetLastError();
    155         if (ERROR_IO_PENDING != res)
    156         {
    157             cout<<"AcceptEx error , error code "<<res<<endl;
    158             return false;
    159         }
    160     }
    161     std::vector<SOCKET>::iterator iter = m_vecAcps.begin();
    162     for (;iter != m_vecAcps.end(); iter++)
    163     {
    164         if (*iter == s)
    165         {
    166             *iter = ol->m_sSock;
    167         }
    168     }
    169     if (iter == m_vecAcps.end())
    170     {
    171         m_vecAcps.push_back(ol->m_sSock);
    172     }
    173     return true;
    174 }
    175 
    176 bool CServer::DoAccept(COverlappedIOInfo* ol,DWORD NumberOfBytes)
    177 {
    178     //分支用于获取远端地址。
    179     //如果接收TYPE_ACP同时收到第一帧数据,则第一帧数据内包含远端地址。
    180     //如果没有收到第一帧数据,则通过getpeername获取远端地址
    181     SOCKADDR_IN* ClientAddr = NULL;
    182     int remoteLen = sizeof(SOCKADDR_IN);
    183     if (NumberOfBytes > 0)
    184     {
    185         //接受的数据分成3部分,第1部分是客户端发来的数据,第2部分是本地地址,第3部分是远端地址。
    186         if (m_lpfnGetAcceptSockAddrs)
    187         {
    188             SOCKADDR_IN* LocalAddr = NULL;  
    189             int localLen = sizeof(SOCKADDR_IN);  
    190             m_lpfnGetAcceptSockAddrs(
    191                 ol->m_recvBuf.buf,
    192                 ol->m_recvBuf.len - (sizeof(SOCKADDR_IN)+16)*2,
    193                 sizeof(SOCKADDR_IN)+16,
    194                 sizeof(SOCKADDR_IN)+16,
    195                 (LPSOCKADDR*)&LocalAddr,
    196                 &localLen,
    197                 (LPSOCKADDR*)&ClientAddr,
    198                 &remoteLen);
    199             cout<<"收到新的连接请求,ip="<<inet_ntoa(ClientAddr->sin_addr)<<",port="<<ClientAddr->sin_port<<
    200                 "数据为:"<<ol->m_recvBuf.buf<<endl;
    201         }
    202     }
    203     else if (NumberOfBytes==0)
    204     {
    205         //未收到第一帧数据
    206         if (SOCKET_ERROR ==getpeername(ol->m_sSock,(sockaddr*)ClientAddr,&remoteLen))
    207         {
    208             cout<<"getpeername error,error code "<<WSAGetLastError()<<endl;
    209         }
    210         else
    211         {
    212             cout<<"收到新的连接请求,ip="<<inet_ntoa(ClientAddr->sin_addr)<<",port="<<ClientAddr->sin_port<<endl;
    213         }
    214     }
    215 
    216     COverlappedIOInfo* pol = new COverlappedIOInfo;
    217     pol->m_sSock = ol->m_sSock;
    218     pol->m_addr =  *ClientAddr;
    219     //服务端只收取recv,同时监听recv和send可用设计位偏移,用或运算实现
    220     if (m_iocp.AssociateSocket(pol->m_sSock,TYPE_RECV))
    221     {
    222         PostRecv(pol);
    223 
    224         m_vecContInfo.push_back(pol);
    225     }
    226     else
    227     {
    228         delete pol;
    229         return false;
    230     }
    231 
    232     return true;
    233 }
    234 
    235 bool CServer::DoRecv(COverlappedIOInfo* ol)
    236 {
    237     cout<<"收到客户端数据:ip="<<inet_ntoa(ol->m_addr.sin_addr)<<",port="<<ol->m_addr.sin_port<<
    238         ";内容="<<ol->m_recvBuf.buf<<endl;
    239     return true;
    240 }
    241 
    242 bool CServer::PostRecv(COverlappedIOInfo* ol)
    243 {
    244     DWORD BytesRecvd = 0;
    245     DWORD dwFlags = 0;
    246     ol->ResetOverlapped();
    247     ol->ResetRecvBuffer();
    248     int recvnum = WSARecv(ol->m_sSock,&ol->m_recvBuf,1,&BytesRecvd,&dwFlags,(OVERLAPPED*)ol,NULL);
    249     if (recvnum != 0)
    250     {
    251         int res = WSAGetLastError();
    252         if (WSA_IO_PENDING != res)
    253         {
    254             cout<<"WSARecv error,error code "<<res<<endl;
    255         }
    256     }
    257     return true;
    258 }
    259 
    260 UINT CServer::StartThreadPull()
    261 {
    262     //获取系统cpu个数启动线程
    263     SYSTEM_INFO si;
    264     GetSystemInfo(&si);
    265     //启动cpu数量*2个线程
    266     return Activate(si.dwNumberOfProcessors * 2);
    267 }
    268 
    269 bool CServer::DeleteLink(SOCKET s)
    270 {
    271     m_lsc.lock();
    272     std::vector<COverlappedIOInfo*>::iterator iter = m_vecContInfo.begin();
    273     for (;iter!=m_vecContInfo.end();iter++)
    274     {
    275         if (s == (*iter)->m_sSock)
    276         {
    277             COverlappedIOInfo* ol = *iter;
    278             closesocket(s);
    279             m_vecContInfo.erase(iter);
    280             delete ol;
    281             break;
    282         }
    283     }
    284     m_lsc.unlock();
    285     return true;
    286 }
    287 
    288 void CServer::svc()
    289 {
    290     while (true)
    291     {
    292         DWORD  NumberOfBytes = 0;
    293         unsigned long CompletionKey = 0;
    294         OVERLAPPED*    ol = NULL;
    295         if (FALSE != GetQueuedCompletionStatus(m_iocp.GetIOCP(),&NumberOfBytes,&CompletionKey,&ol,WSA_INFINITE))
    296         {
    297             if (CompletionKey == TYPE_CLOSE)
    298             {
    299                 break;
    300             }
    301             if (NumberOfBytes == 0 && (CompletionKey==TYPE_RECV || CompletionKey==TYPE_SEND))
    302             {
    303                 //客户端断开连接
    304                 cout<<"客户端断开连接,ip="<<inet_ntoa(olinfo->m_addr.sin_addr)<<",port="<<olinfo->m_addr.sin_port<<endl;
    305                 DeleteLink(olinfo->m_sSock);
    306                 continue;
    307             }
    308             COverlappedIOInfo* olinfo = (COverlappedIOInfo*)ol;
    309             switch (CompletionKey)
    310             {
    311             case TYPE_ACP:
    312                 {
    313                     DoAccept(olinfo,NumberOfBytes);
    314                     PostAccept(olinfo);
    315                 }
    316                 break;
    317             case TYPE_RECV:
    318                 {
    319                     DoRecv(olinfo);
    320                     PostRecv(olinfo);
    321                 }
    322                 break;
    323             case TYPE_SEND:
    324                 {
    325                 }
    326                 break;
    327             default:
    328                 break;
    329             }
    330         }
    331         else
    332         {
    333             int res = WSAGetLastError();
    334             switch(res)
    335             {
    336             case ERROR_NETNAME_DELETED:
    337                 {
    338                     COverlappedIOInfo* olinfo = (COverlappedIOInfo*)ol;
    339                     if (olinfo)
    340                     {
    341                         cout<<"客户端异常退出,ip="<<inet_ntoa(olinfo->m_addr.sin_addr)<<",port="<<olinfo->m_addr.sin_port<<endl;
    342                         DeleteLink(olinfo->m_sSock);
    343                     }
    344                 }
    345                 break;
    346             default:
    347                 cout<<"workthread GetQueuedCompletionStatus error,error code "<<WSAGetLastError()<<endl;
    348                 break;
    349             }
    350             continue;
    351         }
    352     }
    353     cout<<"workthread stop"<<endl;
    354 }
    355 
    356 void CServer::CloseServer()
    357 {
    358     //1:清空IOCP线程队列,退出线程,有多少个线程发送多少个PostQueuedCompletionStatus信息
    359     int threadnum = GetThreadsNum();
    360     for (int i=0;i<threadnum;i++)
    361     {
    362         if (FALSE == m_iocp.PostStatus(TYPE_CLOSE))
    363         {
    364             cout<<"PostQueuedCompletionStatus error,error code "<<WSAGetLastError()<<endl;
    365         }
    366     }
    367     //2:清空等待accept的套接字m_vecAcps
    368     std::vector<SOCKET>::iterator iter = m_vecAcps.begin();
    369     for (;iter != m_vecAcps.end();iter++)
    370     {
    371         SOCKET s = *iter;
    372         closesocket(s);
    373     }
    374     m_vecAcps.clear();
    375     //3:清空已连接的套接字m_vecContInfo并清空缓存
    376     std::vector<COverlappedIOInfo*>::iterator iter2 = m_vecContInfo.begin();
    377     for (;iter2 != m_vecContInfo.end();iter2++)
    378     {
    379         COverlappedIOInfo* ol = *iter2;
    380         closesocket(ol->m_sSock);
    381         iter2 = m_vecContInfo.erase(iter2);
    382         delete ol;
    383     }
    384     m_vecContInfo.clear();
    385 }

    调用方法,控制台程序main函数内加入

    1 SERVER::Instance()->StartListen(8828,"127.0.0.1");
    2 int outId;
    3 cin>>outId;
    4 if (outId == 0)
    5 {
    6     SERVER::Close();
    7 }

    输入0结束服务程序。

    测试结果1,接收数据

    测试结果2,客户端断开连接

      

  • 相关阅读:
    洛谷P3382 【模板】三分法(三分)
    BZOJ2194: 快速傅立叶之二(NTT,卷积)
    UOJ#206. 【APIO2016】Gap(交互,乱搞)
    洛谷P4245 【模板】MTT(任意模数NTT)
    快速数论变换(NTT)小结
    UOJ#34. 多项式乘法(NTT)
    BZOJ3864: Hero meet devil(dp套dp)
    POJ 1094 Sorting It All Out (拓扑排序)
    invesments 第三章 上
    51 EEPROM操作模板
  • 原文地址:https://www.cnblogs.com/hgwang/p/6126735.html
Copyright © 2020-2023  润新知