网络编程笔记<2>

来源:互联网 发布:企业库存管理优化方案 编辑:程序博客网 时间:2024/06/03 18:07

 网络编程笔记<1>(2011-11-11 16:46)

 

题外话:
HANDLE WINAPI
CreateIoCompletionPort(   
__in HANDLE FileHandle,   
__in_opt HANDLE ExistingCompletionPort,   
__in ULONG_PTR CompletionKey,   
__in DWORD NumberOfConcurrentThreads); //这里是允许的最大"并发"线程数目,为0表与CPU个数一样

功能1. 创建完成端口对象
    HANDLE m_hCompLetePort, = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
功能2. 将句柄同完成端口关联
 CreateIoCompletionPort((HANDLE) loAcceptSocket, m_hCompLetePort, (ULONG_PTR)lpNewTcpContext,0)
 我这里是将连接了的socket 同完成端口关联起来,监听它。

--------
start 中有个 acceptevent
WSAEventSelect(m_oLsnSocket.m_oSocket, m_hAcceptEvent,FD_ACCEPT),

上面初始化完毕后,就开始投递异步Accept了
BOOL CTcpIocpServer::EnlargeAcceptList(void)
{
 if(m_i32UnConnectCnt <= (SInt32)m_oUnConnect.size())
 {
  NOTICE_LOG("ERROR:CTcpIocpServer::EnlargeAcceptList 最大未连接数已达到所允许的上限!m_i16MaxUnAcceptCnt:%d m_oAccept.size():%d",m_i32UnConnectCnt, m_oUnConnect.size());
  return FALSE;
 }

 SOCKET loAcceptSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL,0, WSA_FLAG_OVERLAPPED);
 if (INVALID_SOCKET == loAcceptSocket)
 {
  ERROR_LOG("ERROR:CTcpIocpServer::EnlargeAcceptList WSASocket 失败 ERRIR:%d", GetLastError());
  return FALSE;
 }

 // 继承监听socket 被设置的特性
 setsockopt(loAcceptSocket, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char*)& m_oLsnSocket.m_oSocket, sizeof(m_oLsnSocket.m_oSocket));

 // 申请重叠结构
 SOverLappedEx* lpOverlappedEx = AllocSOverLappedEx(e_IoAccept);

 if (NULL == lpOverlappedEx)
 {
  ERROR_LOG("ERROR: CTcpIocpServer::EnlargeAcceptList AllocSOverLappedEx(e_IoAccept)失败!");
  DEF_CLOSE_SOCKET(loAcceptSocket);
  return FALSE;
 }

 //插入未连接队列
 m_oUnConnectLock.lock();
 m_oUnConnect.insert(lpOverlappedEx);
 m_oUnConnectLock.unlock();

 ULong  lulLen = 0;
 SInt32 li32AddrLen = sizeof(SOCKADDR_IN);

 //申请TCP连接
 CTCPContext* lpNewTcpContext =m_oConnectedCache.ApplyForObj();
 lpNewTcpContext->m_oSocket = loAcceptSocket;
 // 建立双向映射
 lpOverlappedEx->m_lpRelateContect = lpNewTcpContext;
 lpOverlappedEx->m_lpRelateContect->m_pOverLap = lpOverlappedEx;

 if (CreateIoCompletionPort((HANDLE) loAcceptSocket, m_hCompLetePort, (ULONG_PTR)lpNewTcpContext,0) == NULL)
 {
  GiveBackSOverLappedEx(lpOverlappedEx);
  ERROR_LOG("ERROR: CTcpIocpServer::InitLsnSocket CreateIoCompletionPort 失败 GetLastError:%d!",GetLastError());
  return FALSE;
 }

 // 接受连接请求
 DWORD dwBytes = 0;
 BOOL lbSuccess = AcceptEx( m_oLsnSocket.m_oSocket,
                     loAcceptSocket,
       lpOverlappedEx->m_oWsaBuffer.buf,
        (lpOverlappedEx->m_oWsaBuffer.len -(li32AddrLen+16)*2),
        li32AddrLen+16,
        li32AddrLen+16,
        &lulLen,
        (LPOVERLAPPED)lpOverlappedEx);

 if ((TRUE != lbSuccess) && (ERROR_IO_PENDING != WSAGetLastError()))
 {
  ERROR_LOG("ERROR:CTcpIocpServer::EnlargeAcceptList AcceptEx失败 Error:%d", WSAGetLastError());
  m_oUnConnectLock.lock();
  m_oUnConnect.erase(lpOverlappedEx);
  m_oUnConnectLock.unlock();
  GiveBackSOverLappedEx(lpOverlappedEx);
  return FALSE;
 }
 return TRUE;
}

这里有几个不得不提的API

1. setsockopt
  设置套接口的选项。
  #include <winsock.h>
  int PASCAL FAR setsockopt( SOCKET s, int level, int optname,
  const char FAR* optval, int optlen);
  s:标识一个套接口的描述字。
  level:选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。
  optname:需设置的选项。
  optval:指针,指向存放选项值的缓冲区。
  optlen:optval缓冲区的长度
    通过它,可为socket 设置我们自己所需要的特性。

这个的用法是
// 继承监听socket 被设置的特性
setsockopt(loAcceptSocket, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char*)& m_oLsnSocket.m_oSocket, sizeof(m_oLsnSocket.m_oSocket));

2. AcceptEx
(百度百科)
Windows套接字AcceptEx函数接受一个新的连接,返回本地和远程地址,
并接收由客户端应用程序发送的第一块数据。Windows 95/98不支持AcceptEx函数。   
注意:此函数是一个Microsoft特定的扩展,Windows Sockets规范。
有关详细信息,请参阅Microsoft扩展和Windows Sockets 2。   
BOOL AcceptEx(   
IN SOCKET sListenSocket,   
IN SOCKET sAcceptSocket,   
IN PVOID lpOutputBuffer,   
IN DWORD dwReceiveDataLength,   
IN DWORD dwLocalAddressLength,   
IN DWORD dwRemoteAddressLength,   
OUT LPDWORD lpdwBytesReceived,   
IN LPOVERLAPPED lpOverlapped   );   

返回值   如果没有错误发生,AcceptEx函数成功完成和真正的返回值。   
如果函数失败,AcceptEx返回FALSE。该WSAGetLastError函数可以调用返回扩展的错误信息。
如果WSAGetLastError返回ERROR_IO_PENDING,那么这次行动成功启动并仍在进行中。

下面是咱们的调研设置。...
 BOOL lbSuccess = AcceptEx( m_oLsnSocket.m_oSocket,  //侦听套接字。服务器应用程序在这个套接字上等待连接。
     loAcceptSocket,   //将用于连接的套接字。此套接字必须不能已经绑定或者已经连接
     lpOverlappedEx->m_oWsaBuffer.buf, //接收新建连接的所发送数据的第一个块、
     (lpOverlappedEx->m_oWsaBuffer.len -(li32AddrLen+16)*2),
     //定接收数据缓冲区lpOutputBuffer的大小。
     //这一大小应不包括服务器的本地地址的大小或客户端的远程地址,他们被追加到输出缓冲区。
     //如果dwReceiveDataLength是零,AcceptEx将不等待接收任何数据,而是尽快建立连接。
     li32AddrLen+16,//此值必须比所用传输协议的最大地址大小长16个字节
     li32AddrLen+16,//此值必须比所用传输协议的最大地址大小长16个字节
     &lulLen,//指向一个DWORD用于标识接收到的字节数。此参数只有在同步模式下有意义
     (LPOVERLAPPED)lpOverlappedEx);//一个OVERLAPPED结构,用于处理请求。此参数必须指定,它不能为空。


3.看看accept  线程函数了


/*******************************************
连接线程切换
*******************************************/
void CTcpIocpServer::AcceptThread(void)
{
 NOTICE_LOG("NOTICE:CTcpIocpServer Accept线程启动");

 ULong lulWaitResult = -1;
 SLong lslAcceptCnt = 0;
 SInt64 li64RecTime = 0;
 SInt64 li64LastTime = 0;
 Socket loTemp = INVALID_SOCKET;
 SOverLappedEx* lpOverLapPtr = NULL;
 ULong  lulOptVal= 0;
 SInt32 li32OptLen = sizeof(lulOptVal);
 SInt32 li32Result = 0;
 CTCPContext* lpTcpContextPtr = NULL;

 while (TRUE == m_bWorkingState)
 {
  Sleep(1);

  lulWaitResult = WSAWaitForMultipleEvents(1, &m_hAcceptEvent, FALSE, 1000, FALSE);
  
  if (FALSE == m_bWorkingState)
   break;

  if (WSA_WAIT_EVENT_0 == lulWaitResult || m_oUnConnect.size() < 2) //已无连接可用
  {
   WSAResetEvent(m_hAcceptEvent);  
   for (lslAcceptCnt = 0; lslAcceptCnt < 20; lslAcceptCnt++)
   {
    EnlargeAcceptList();
   }
  }

  li64RecTime = CPubfuncs::GetCurTime();

  if (li64RecTime - li64LastTime >= DEF_SECOND_10)
  {
   li64LastTime = CPubfuncs::GetCurTime();
   //检查空连接(用户已建立连接,但在规定时间40内没发包)
   m_oUnConnectLock.lock();

   stdext::hash_set<SOverLappedEx*>::iterator lnOverlapIter = m_oUnConnect.begin();
   while (lnOverlapIter != m_oUnConnect.end())
   {
    lpOverLapPtr = *lnOverlapIter;
    if (!lpOverLapPtr->m_lpRelateContect)
    {
     lnOverlapIter++;
     continue;
    }
    
    li32Result = getsockopt(lpOverLapPtr->m_lpRelateContect->m_oSocket,SOL_SOCKET,
          SO_CONNECT_TIME,(char*)&lulOptVal, &li32OptLen);

    if ((SOCKET_ERROR == li32Result) || ((lulOptVal != 0xFFFFFFFF) && (lulOptVal > 20)))
    { 
     if (lpOverLapPtr->m_lpRelateContect->m_oSocket != INVALID_SOCKET)
     {
      NOTICE_LOG("NOTICE:连接超时 Socket:%d Time:%d",lpOverLapPtr->m_lpRelateContect->m_oSocket, lulOptVal);
      GiveBackSOverLappedEx(lpOverLapPtr);
      lnOverlapIter = m_oUnConnect.erase(lnOverlapIter);
      continue;
     }
    }
    lnOverlapIter++;
   }
   m_oUnConnectLock.unlock();

   //检查无效连接
     m_oConnectedLock.lock();
     stdext::hash_set<CTCPContext*>::iterator lnIterator = m_oConnected.begin();
     while (lnIterator != m_oConnected.end())
     {
      lpTcpContextPtr = *lnIterator;
     //如果是无效连接
    if (INVALID_SOCKET == lpTcpContextPtr->m_oSocket)
    {
     lnIterator = m_oConnected.erase(lnIterator);
     memset(&lpTcpContextPtr->m_oSockAddr,0 ,sizeof(sockaddr_in));
     m_oConnectedCache.GiveBackObj(lpTcpContextPtr);
    }
    else
     lnIterator++;
     }

   INFO_LOG("INFO: 未:%d 已:%d %I64d:入/S %I64d:断/S %I64d:收/S %I64d:发/S ",
   m_oUnConnect.size(),
   m_oConnected.size(),
   m_i64PerTimeConnected/10,
   m_i64PerTimeCutDown/10,
   m_i64PerTimeRecvCnt/10,
   m_i64PerTimeSendCnt/10);

   m_i64PerTimeConnected = 0;
   m_i64PerTimeCutDown   = 0;
     m_oConnectedLock.unlock();
   m_i64PerTimeRecvCnt = 0;
   m_i64PerTimeSendCnt = 0;
  }
 }
 NOTICE_LOG("NOTICE:CTcpIocpServer Accept线程退出");
}

这个线程函数中,主要做了三处检查工作:
3.1. 连接Event及未连接队列检查:
  lulWaitResult = WSAWaitForMultipleEvents(1, &m_hAcceptEvent, FALSE, 1000, FALSE);
  if (FALSE == m_bWorkingState)
   break;
  if (WSA_WAIT_EVENT_0 == lulWaitResult || m_oUnConnect.size() < 2) //已无连接可用
  {
   WSAResetEvent(m_hAcceptEvent);  
   for (lslAcceptCnt = 0; lslAcceptCnt < 20; lslAcceptCnt++)
   {
     EnlargeAcceptList();
   }
  }
a.连接事件检测,没有了重置
b.m_oUnConnect 用完了 通过EnlargeAcceptList 扩大

3.2.连接超时检查

   stdext::hash_set<SOverLappedEx*>::iterator lnOverlapIter = m_oUnConnect.begin();
   while (lnOverlapIter != m_oUnConnect.end())
   {
    lpOverLapPtr = *lnOverlapIter;
    if (!lpOverLapPtr->m_lpRelateContect)
    {
     lnOverlapIter++;
     continue;
    }
    
    li32Result = getsockopt(lpOverLapPtr->m_lpRelateContect->m_oSocket,SOL_SOCKET,
          SO_CONNECT_TIME,(char*)&lulOptVal, &li32OptLen);

    if ((SOCKET_ERROR == li32Result) || ((lulOptVal != 0xFFFFFFFF) && (lulOptVal > 20)))
    { 
     if (lpOverLapPtr->m_lpRelateContect->m_oSocket != INVALID_SOCKET)
     {
      NOTICE_LOG("NOTICE:连接超时 Socket:%d Time:%d",lpOverLapPtr->m_lpRelateContect->m_oSocket, lulOptVal);
      GiveBackSOverLappedEx(lpOverLapPtr);
      lnOverlapIter = m_oUnConnect.erase(lnOverlapIter);
      continue;
     }
    }
    lnOverlapIter++;
   }
通过getsockopt 获取socket已经连入多久,如果在20秒内还未有数据传输发生在次Socket 上,干掉他(连了不传数据的socket 视为非法连接)。

3.3.无效连接检查

   //检查无效连接
     m_oConnectedLock.lock();
     stdext::hash_set<CTCPContext*>::iterator lnIterator = m_oConnected.begin();
     while (lnIterator != m_oConnected.end())
     {
      lpTcpContextPtr = *lnIterator;
     //如果是无效连接
    if (INVALID_SOCKET == lpTcpContextPtr->m_oSocket)
    {
     lnIterator = m_oConnected.erase(lnIterator);
     memset(&lpTcpContextPtr->m_oSockAddr,0 ,sizeof(sockaddr_in));
     m_oConnectedCache.GiveBackObj(lpTcpContextPtr);
    }
    else
     lnIterator++;
     }
m_oConnected放置的理论上应该都是 被创建好的等待连接或者已连入但是未有数据传输的socket 不应该存在无效的socket 如果有,干掉。


4.看看HandleRecvThread  线程函数了
  这个线程是用来读取接受到的缓冲队列中的数据,并投递到应用层去
ULong CTcpIocpServer::HandleRecvThread(void* apObj)
{
 CTcpIocpServer* lpThis  = NULL;
 lpThis = static_cast<CTcpIocpServer*>(apObj);
 if (NULL != lpThis)
  lpThis->HandleRecvThread();
 return 1;
}

void CTcpIocpServer::HandleRecvThread(void)
{
 CTCPOrgnlPack* lpTcpOrgnlPack = NULL;
 while (TRUE == m_bWorkingState)
 {
  if (NULL !=  (lpTcpOrgnlPack = (CTCPOrgnlPack *)m_oRecvDataQueue.Pop()))
  {
   // 投递
   m_pHandleIO->OnRecv((SInt16)lpTcpOrgnlPack->m_ulDataLen, lpTcpOrgnlPack->m_pContext, lpTcpOrgnlPack->m_pData);
   m_oMemPool->FreeBuffer((Byte*)lpTcpOrgnlPack->m_pData);
   m_oMemPool->FreeBuffer((Byte*)lpTcpOrgnlPack);
   lpTcpOrgnlPack = NULL;
   continue;
  }
  Sleep(10);
 }
}

函数逻辑很简单,这里我写了个简单的数据块内存池,和队列(没有用微软的)。具体内容,下回分解,就到这里吧今天...