TCP IOCP服务器源代码

// Server.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include  <winsock2.h>
#include  <windows.h>
#include  <stdio.h>

#pragma comment(lib,"ws2_32.lib")
#define  DEFAULT_PORT 5018
#define  DATA_BUFSIZE 8192

typedef  struct _BUFFER_OBJ
{
 OVERLAPPED Overlapped;
 WSABUF     DataBuf;
 CHAR       Buffer[DATA_BUFSIZE];
 DWORD      BytesSEND;
 DWORD      BytesRECV;
}BUFFER_OBJ,*LPBUFFER_OBJ;

typedef  struct  _SOCKET_OBJ
{
 SOCKET Socket;
}SOCKET_OBJ, *LPSOCKET_OBJ;

DWORD WINAPI ServerWorkerThread(LPVOID lpParamer);

void  main( void )
{
 // 初始化
 WSADATA data;
    if(WSAStartup(MAKEWORD(2,2),&data) != 0)
 {
  printf("WSAStartup failed with error %d/n ", WSAGetLastError());
  return;
 }
 
    // 创建完成端口
 HANDLE hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL,0,0);
    if(hIocp ==  NULL)
 {
  printf(" CreateIoCompletionPort failed with error: %d/n", GetLastError());
  return;
 }

    // 获取CPU个数,创建2*CPU个数工作线程
 DWORD ThreadID;
 SYSTEM_INFO info;
 GetSystemInfo(&info);
    for(int i=0 ; i<info.dwNumberOfProcessors*2; i++)
 {
  HANDLE hThread = CreateThread(NULL,0,ServerWorkerThread, hIocp,0,&ThreadID);
  if(hThread ==  NULL)
        {
   printf("CreateThread() failed with error %d/n",GetLastError());
   return;
  }
  
  // 关闭线程句柄
  CloseHandle(hThread);
 }
 
 // 创建监听套接字
 SOCKET sListen = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
    if(sListen == INVALID_SOCKET)
 {
  printf( " WSASocket() failed with error %d/n " , WSAGetLastError());
  return;
 } 
 
 // 绑定监听端口
 SOCKADDR_IN local;
 local.sin_family = AF_INET;
 local.sin_addr.s_addr = htonl(INADDR_ANY);
 local.sin_port = htons(DEFAULT_PORT);
    if(bind(sListen,(PSOCKADDR)&local,sizeof (local)) == SOCKET_ERROR)
 {
  printf(" bind() failed with error %d/n",WSAGetLastError());
  return;
 }
 
 // 开始监听客户端连接
    if(listen(sListen,10) == SOCKET_ERROR)
 {
  printf("listen() failed with error %d/n",WSAGetLastError());
  return;
 }
 
 LPSOCKET_OBJ pSockObj = NULL;
 LPBUFFER_OBJ pBufferObj = NULL;
 SOCKET       sAccept;
 DWORD        RecvBytes;
 DWORD        Flags;

    // 等待客户连接......
    while (TRUE)
 {
  // 接受连接
  if((sAccept = WSAAccept(sListen,NULL,NULL,NULL,0)) == SOCKET_ERROR)
        {
   printf("WSAAccept() failed with error %d/n",WSAGetLastError());
   return;
  }
  
  // 分配空间
        if((pSockObj = (LPSOCKET_OBJ)GlobalAlloc(GPTR,sizeof(SOCKET_OBJ))) == NULL)
        {
   printf("GlobalAlloc() failed with error %d/n",GetLastError());
   return;
  }
  
  printf("Socket number %d connected/n",sAccept);
  pSockObj->Socket = sAccept;
  
  // 绑定被接受的套接字到完成端口上
        if(CreateIoCompletionPort((HANDLE)sAccept,hIocp,(DWORD)pSockObj,0) == NULL)
        {
   printf( " CreateIoCompletionPort failed with error %d/n " , GetLastError());
   return;
  }
  
  // 分配接收缓冲区
  if ((pBufferObj = (LPBUFFER_OBJ)GlobalAlloc(GPTR,sizeof(BUFFER_OBJ))) == NULL)
        {
   printf("GlobalAlloc() failed with error %d/n",GetLastError());
   return;
  }
  
  // 投递接收请求消息
  ZeroMemory( & (pBufferObj->Overlapped), sizeof(OVERLAPPED));
  pBufferObj->BytesSEND = 0 ;
  pBufferObj->BytesRECV = 0 ;
  pBufferObj->DataBuf.len = DATA_BUFSIZE;
  pBufferObj->DataBuf.buf = pBufferObj->Buffer;
  
  Flags = 0 ;
  if(WSARecv(sAccept,&(pBufferObj->DataBuf),1,&RecvBytes,&Flags,
   &(pBufferObj->Overlapped),NULL) == SOCKET_ERROR)
        {
   if(WSAGetLastError() != ERROR_IO_PENDING)
   {
    printf("WSARecv() failed with error %d/n ",WSAGetLastError());
    return;
   }
  }
 }
}

// 工作线程
DWORD WINAPI ServerWorkerThread(LPVOID lpParamer)
{
 HANDLE hIOCP = (HANDLE)lpParamer;

 DWORD        BytesTransferred;
 LPOVERLAPPED Overlapped;
 LPSOCKET_OBJ pSockObj;
 LPBUFFER_OBJ pBufferObj;
 DWORD        SendBytes;
 DWORD        RecvBytes;
 DWORD        Flags;
 
    while (TRUE)
 {
  // 有消息来了 
        if(GetQueuedCompletionStatus(hIOCP,&BytesTransferred,
   (LPDWORD)&pSockObj,(LPOVERLAPPED*)&pBufferObj,INFINITE) == 0)
        {
   printf("GetQueuedCompletionStatus failed with error %d/n",GetLastError());
   return 0;
  }
  
  // 有人已经退出了
  if(BytesTransferred == 0)
        {
   printf("Closing socket %d/n",pSockObj->Socket);
   
   if(closesocket(pSockObj->Socket) == SOCKET_ERROR)
   {
    printf("closesocket() failed with error %d/n",WSAGetLastError());
    return 0;
   }
   
   GlobalFree(pSockObj);
   GlobalFree(pBufferObj);
   continue;
  }
 
  if(pBufferObj->BytesRECV == 0)
        {
   pBufferObj->BytesRECV = BytesTransferred;
   pBufferObj->BytesSEND = 0;
  }
  else
  {
   pBufferObj->BytesSEND += BytesTransferred;
  }
  

  // 投递发送请求消息
  if(pBufferObj->BytesRECV > pBufferObj->BytesSEND)
        { 
   ZeroMemory(&(pBufferObj->Overlapped),sizeof(OVERLAPPED));
   pBufferObj->DataBuf.buf = pBufferObj->Buffer + pBufferObj->BytesSEND;
   pBufferObj->DataBuf.len = pBufferObj->BytesRECV - pBufferObj->BytesSEND;
   
   if (WSASend(pSockObj->Socket,&(pBufferObj->DataBuf),1 ,&SendBytes,0,
    &(pBufferObj->Overlapped), NULL) == SOCKET_ERROR)
   {
    if(WSAGetLastError() != ERROR_IO_PENDING)
    {
     printf("WSASend() failed with error %d/n",WSAGetLastError());
     return 0;
    }
   }
  }
  else  // 再次投递接收请求消息
  {
   pBufferObj->BytesRECV = 0;
   
   Flags = 0;
   ZeroMemory(&(pBufferObj->Overlapped),sizeof(OVERLAPPED));
   pBufferObj->DataBuf.len = DATA_BUFSIZE;
   pBufferObj->DataBuf.buf = pBufferObj->Buffer;
   
   if(WSARecv(pSockObj->Socket,&(pBufferObj->DataBuf),1,&RecvBytes,&Flags,
    &(pBufferObj->Overlapped), NULL) == SOCKET_ERROR)
   {
    if(WSAGetLastError() != ERROR_IO_PENDING)
    {
     printf("WSARecv() failed with error %d/n",WSAGetLastError());
     return 0;
    }
   }
  }
 }
}

源码,有注释 // IOCPServer.h: interface for the CIOCPServer class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_) #define AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #pragma once #pragma comment(lib,"Ws2_32.lib") #pragma comment(lib,"Mswsock.lib") #include <winsock2.h> #include <Mswsock.h> #define BUFFER_SIZE 1024*4//I/O请求的缓冲区大小 #define MAX_THREAD 2 //I/O服务器线程数量 //缓冲区对象,它包含了在套接字上处理I/O操作的必要信息 struct CIOCPBuffer { WSAOVERLAPPED ol; SOCKET sClient; //AcceptEx接收的客户套接字 char *buff; //I/0操作使用的缓冲区 int nLen; //buff缓冲区(使用的)大小 ULONG nSequenceNumber;//此I/O的序列号 int nOperation; //操作类型 CIOCPBuffer *pNext; }; //per-Handle数据,它包含了一个套接字的信息 struct CIOCPContext { SOCKET s; //套接字句柄 SOCKADDR_IN addrLocal; //连接的本地地址 SOCKADDR_IN addrRemote;//连接的远程地址 BOOL bClosing; //套接字是否关闭 int nOutStandingRecv; //此套接字上抛出的重叠操作的数量 int nOutStandingSend; ULONG nReadSequence; //安排给接受的下一个序列号 ULONG nCurrentReadSequence;//当前要读的序列号 CIOCPBuffer *pOutOfOrderReads;//记录没有按顺序完成的读I/O CRITICAL_SECTION Lock; //保护这个结构 CIOCPContext *pNext; }; class CIOCPServer //处理线程 { public: CIOCPServer(void); ~CIOCPServer(void); //开始服务 BOOL Start(int nPort=4567,int nMaxConnections=2000, int nMaxFreeBuffers=200,int nMaxFreeContexts=100,int nInitialReads=4); //停止服务 void Shutdown(); //关闭一个连接和关闭所有连接 void CloseAConnection(CIOCPContext *pContext); void CloseAllConnection(); //取得当前的连接数量 ULONG GetCurrentConnection() { return m_nCurrentConnection; }; //向指定客户发送文本 BOOL SendText(CIOCPContext *pContext,char *pszText,int nLen); protected: //申请和释放缓冲区对象 CIOCPBuffer*AllocateBuffer(int nLen); void ReleaseBuffer(CIOCPBuffer *pBuffer); //申请和释放套接字上下文 CIOCPContext *AllocateContext(SOCKET s); void ReleaseContext(CIOCPContext *pContext); //释放空闲缓冲区对象列表和空闲上下文对象列表 void FreeBuffers(); void FreeContexts(); //向连接列表中添加一个连接 BOOL AddAConnection(CIOCPContext *pContext); //插入和移除未决的接受请求 BOOL InsertPendingAccept(CIOCPBuffer *pBuffer); BOOL RemovePendingAccept(CIOCPBuffer *pBuffer); //取得下一个要读取的 CIOCPBuffer *GetNextReadBuffer(CIOCPContext *pContext,CIOCPBuffer *pBuffer); //投递接受I/O,发送I/0,接受I/O BOOL PostAccept(CIOCPBuffer *pBuffer); BOOL PostSend(CIOCPContext *pContext,CIOCPBuffer *pBuffer); BOOL PostRecv(CIOCPContext *pContext,CIOCPBuffer *pBuffer); //事件通知函数 void HandleIO(DWORD dwKey,CIOCPBuffer *pBuffer,DWORD dwTrans,int nError); //建立一个新的连接 virtual void OnConnectionEstablished(CIOCPContext *pContext,CIOCPBuffer*); //一个连接关闭 virtual void OnConnectionClosing(CIOCPContext *pContext,CIOCPBuffer*); //在一个连接上发生错误 virtual void OnConnectionError(CIOCPContext *pContext,CIOCPBuffer*,int nError); //在一个连接上的读操作完成 virtual void OnReadCompleted(CIOCPContext *pContext,CIOCPBuffer*); //在一个连接上写操作完成 virtual void OnWriteCompleted(CIOCPContext *pContext,CIOCPBuffer*); protected: //记录空闲结构信息 CIOCPBuffer *m_pFreeBufferList; CIOCPContext *m_pFreeContextList; int m_nFreeBufferCount; int m_nFreeContextCount; CRITICAL_SECTION m_FreeBufferListLock; CRITICAL_SECTION m_FreeContextListLock; //记录抛出的Accept请求 CIOCPBuffer *m_pPendingAccepts; long m_nPendingAcceptCount; CRITICAL_SECTION m_PendingAcceptsLock; //记录连接列表 CIOCPContext *m_pConnectionList; int m_nCurrentConnection; CRITICAL_SECTION m_ConnectionListLock; //用于投递Accept请求 HANDLE m_hAcceptEvent; HANDLE m_hRepostEvent; LONG m_nRepostCount; //服务器监听端口 int m_nPort; int m_nInitialAccepts; int m_nInitialReads; int m_nMaxAccepts; int m_nMaxSends; int m_nMaxFreeBuffers; int m_nMaxFreeContexts; int m_nMaxConnections; //监听线程 HANDLE m_hListenThread; //完成端口句柄 HANDLE m_hCompletion; //监听套接字句柄 SOCKET m_sListen; //AcceptEx函数地址 LPFN_ACCEPTEX m_lpfnAcceptEx; //GetAcceptExSockaddrs函数地址 LPFN_GETACCEPTEXSOCKADDRS m_lpfnGetAcceptExSockaddrs; //用于通知监听线程退出 BOOL m_bShutDown; //记录服务是否启动 BOOL m_bServerStarted; private://线程函数 static DWORD WINAPI _ListenThreadProc(LPVOID lpParam); static DWORD WINAPI _WorkerThreadProc(LPVOID lpParam); }; #endif // !defined(AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_)
最近有项目要做一个高性能网络服务器,决定下功夫搞定完成端口(IOCP),最终花了一个星期终于把它弄清楚了,并用C++写了一个版本,效率很不错。 但,从项目的总体需求来考虑,最终决定上.net平台,因此又花了一天一夜弄出了一个C#版,在这与大家分享。 一些心得体会: 1、在C#中,不用去面对完成端口的操作系统内核对象,Microsoft已经为我们提供了SocketAsyncEventArgs类,它封装了IOCP的使用。请参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1。 2、我的SocketAsyncEventArgsPool类使用List对象来存储对客户端来通信的SocketAsyncEventArgs对象,它相当于直接使用内核对象时的IoContext。我这样设计比用堆栈来实现的好处理是,我可以在SocketAsyncEventArgsPool池中找到任何一个与服务器连接的客户,主动向它发信息。而用堆栈来实现的话,要主动给客户发信息,则还要设计一个结构来存储已连接上服务器的客户。 3、对每一个客户端不管还发送还是接收,我使用同一个SocketAsyncEventArgs对象,对每一个客户端来说,通信是同步进行的,也就是说服务器高度保证同一个客户连接上要么在投递发送请求,并等待;或者是在投递接收请求,等待中。本例只做echo服务器,还未考虑由服务器主动向客户发送信息。 4、SocketAsyncEventArgs的UserToken被直接设定为被接受的客户端Socket。 5、没有使用BufferManager 类,因为我在初始化时给每一个SocketAsyncEventArgsPool中的对象分配一个缓冲区,发送时使用Arrary.Copy来进行字符拷贝,不去改变缓冲区的位置,只改变使用的长度,因此在下次投递接收请求时恢复缓冲区长度就可以了!如果要主动给客户发信息的话,可以new一个SocketAsyncEventArgs对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。 6、测试结果:(在我的笔记本上时行的,我的本本是T420 I7 8G内存) 100客户 100,000(十万次)不间断的发送接收数据(发送和接收之间没有Sleep,就一个一循环,不断的发送与接收) 耗时3004.6325 秒完成 总共 10,000,000 一千万次访问 平均每分完成 199,691.6 次发送与接收 平均每秒完成 3,328.2 次发送与接收 整个运行过程中,内存消耗在开始两三分种后就保持稳定不再增涨。 看了一下对每个客户端的延迟最多不超过2秒。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值