Windows Sockets

       Sockets有两种模式,同步模式和异步模式。创建一个Socket后默认是同步的,可以通过ioctlsocket的FIONBIO命令设置异步模式。单独使用同步模式,效率会提高,但是不易控制连接的断开;单独使用异步模式,只有轮询,以控制但是效率自然低下。Windows Sockets提供了五种I/O模型,有效地提高了效率。当然,这五种模型主要是针对服务器的,因为服务器需要维护多个客户端的链路。

      1,Select模型

      Select模型使用select函数检测Socket集合中的若干个Socket是否有关心的事件(可读、可写、发生异常),根据检测结果执行相关操作。这种模式使得可以用select函数同步检测事件,然后就可确保相关的I/O操作可以立即完成,提高了效率。代码框架如下:

int g_iTotalConn = 0;
SOCKET g_CliSocketArr[FD_SETSIZE];

sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 ...

 bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
 listen(sListen, 3);
  CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
 while (TRUE)
 {
  sClient = accept(sListen, (sockaddr*)&client, &iAddrSize);

  //新连接,状态回调函数
  g_CliSocketArr[g_iTotalConn++] = sClient;
 }

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
 int i;
 fd_set fdread;
 int ret;
 struct timeval tv = {1, 0};
 char szMessage[MSGSIZE];

 while (TRUE)
 {
  FD_ZERO(&fdread);
  for (i = 0; i < g_iTotalConn; i++)
  {
   FD_SET(g_CliSocketArr[i], &fdread);
  }
  // We only care read event
  ret = select(0, &fdread, NULL, NULL, &tv);
  if (ret == 0)
  {
   // Time expired
   continue;
  }
  for (i = 0; i < g_iTotalConn; i++)
  {
   if (FD_ISSET(g_CliSocketArr[i], &fdread))
   {
    // A read event happened on g_CliSocketArr
    ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0);
    if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
    {
     // Client socket closed
     //连接断开,状态回调函数

     closesocket(g_CliSocketArr[i]);
     if (i < g_iTotalConn-1)
     {
      g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];
     }
    }
    else
    {
     // We reveived a message from client
 // 新数据,数据回调函数     

    }
   }
  }
 }
}

 

      2:AsyncSelect模型

      这种模型通过Windows的消息传递方式通知客户关心的事件发生。因而必须创建一个窗口,在窗口的消息回调函数中处理自定义消息。具体可以在窗口的Create消息中创建Socket,并绑定Socket关心的事件与此窗口的处理消息。然后再此处理消息中进行相关操作。代码框架如下:

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 WSADATA wsaData;
 static SOCKET sListen;
 SOCKET sClient;
 SOCKADDR_IN local, client;
 int ret, iAddrSize = sizeof(client);
 char szMessage[MSGSIZE];
 switch (message)
 {
 case WM_CREATE:
   WSAStartup(0x0202, &wsaData);
   sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 ...

  bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
  listen(sListen, 3);
  WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT);
  return 0;

  case WM_SOCKET:
  if (WSAGETSELECTERROR(lParam))
  {

  //连接断开,状态回调函数
   closesocket(wParam);
   break;
  }
  switch (WSAGETSELECTEVENT(lParam))
  {
  case FD_ACCEPT:
   // Accept a connection from client
   sClient = accept(wParam, (sockaddr*)&client, &iAddrSize);

   //新连接,状态回调函数
   // Associate client socket with FD_READ and FD_CLOSE event
   WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);
   break;

  case FD_READ:
   ret = recv(wParam, szMessage, MSGSIZE, 0);
   if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
   {

    //连接断开,状态回调函数
    closesocket(wParam);
   }
   else
   {
   //新数据,数据回调函数 

   }
   break;

  case FD_CLOSE:

   //连接关闭,状态回调函数
   closesocket(wParam);
   break;
  }
  return 0;
 }

 return DefWindowProc(hwnd, message, wParam, lParam);
}

 

      3:EventSelect模型

      这种模型将用户关心的事件和某个内核事件对象关联起来。当事件到达是自动向事件发送信号。因而不用创建窗口,代码框架如下:

int g_iTotalConn = 0;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];

sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 ...
 bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
 listen(sListen, 3);
 CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
 while (TRUE)
 {
  sClient = accept(sListen, (sockaddr*)&client, &iAddrSize);
   //新连接,状态回调函数

  g_CliSocketArr[g_iTotalConn] = sClient;
  g_CliEventArr[g_iTotalConn] = WSACreateEvent();
  WSAEventSelect(g_CliSocketArr[g_iTotalConn], g_CliEventArr[g_iTotalConn], FD_READ|FD_CLOSE);
  g_iTotalConn++;
 }

 

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
 int ret, index;
 WSANETWORKEVENTS NetworkEvents;
 char szMessage[MSGSIZE];
 while (TRUE)
 {
  ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
  if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
  {
   continue;
  }
  index = ret - WSA_WAIT_EVENT_0;
  WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &NetworkEvents);
  if (NetworkEvents.lNetworkEvents & FD_READ)
  {
   // Receive message from client
   ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);
   if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
   {

   //连接关闭,状态回调函数
    Cleanup(index);
   }
   else
   {
   //收到数据,数据回调函数 

   }
  }
  if (NetworkEvents.lNetworkEvents & FD_CLOSE)
  {

   //连接关闭,状态回调函数
   Cleanup(index);
  }
 }

 return 0;
}

 

void Cleanup(int index)
{
 closesocket(g_CliSocketArr[index]);
 WSACloseEvent(g_CliEventArr[index]);
 if (index < g_iTotalConn-1)
 {
  g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn-1];
  g_CliEventArr[index] = g_CliEventArr[g_iTotalConn-1];
 }

 g_iTotalConn--;
}

 

      4:重叠模式

      重叠模式类似重叠I/O,内部使用异步Socket模式操作,可以通过事件或回调函数通知客户操作完成。

      通过事件通知的代码框架如下:

typedef struct
{
 WSAOVERLAPPED overlap;
 WSABUF        Buffer;
 char          szMessage[MSGSIZE];
 DWORD         NumberOfBytesRecvd;
 DWORD         Flags;
} PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

int                     g_iTotalConn = 0;
SOCKET                  g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT                g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
LPPER_IO_OPERATION_DATA g_pPerIoDataArr[MAXIMUM_WAIT_OBJECTS];

sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 ...

 bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
 listen(sListen, 3);
  CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
 while (TRUE)
 {
  // Accept a connection
  sClient = accept(sListen, (sockaddr*)&client, &iAddrSize);
  //新连接,状态回调函数

  g_CliSocketArr[g_iTotalConn] = sClient;
   g_pPerIoDataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(
   GetProcessHeap(),
   HEAP_ZERO_MEMORY,
   sizeof(PER_IO_OPERATION_DATA));
  g_pPerIoDataArr[g_iTotalConn]->Buffer.len = MSGSIZE;
  g_pPerIoDataArr[g_iTotalConn]->Buffer.buf = g_pPerIoDataArr[g_iTotalConn]->szMessage;
  g_CliEventArr[g_iTotalConn] = g_pPerIoDataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();
   WSARecv(g_CliSocketArr[g_iTotalConn],
   &g_pPerIoDataArr[g_iTotalConn]->Buffer,
   1,
   &g_pPerIoDataArr[g_iTotalConn]->NumberOfBytesRecvd,
   &g_pPerIoDataArr[g_iTotalConn]->Flags,
   &g_pPerIoDataArr[g_iTotalConn]->overlap,
   NULL);
  g_iTotalConn++;
 }
 
 closesocket(sListen);
 WSACleanup();
 return 0;
}

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
 int ret, index;
 DWORD cbTransferred;
 while (TRUE)
 {
  ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
  if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
  {
   continue;
  }
  index = ret - WSA_WAIT_EVENT_0;
  WSAResetEvent(g_CliEventArr[index]);
  WSAGetOverlappedResult(g_CliSocketArr[index],
   &g_pPerIoDataArr[index]->overlap,
   &cbTransferred,
   TRUE,
   &g_pPerIoDataArr[g_iTotalConn]->Flags);
  if (cbTransferred == 0)
  {
   // 连接断开,状态回调函数
   Cleanup(index);
  }
  else
  {
   // 新数据,数据回调函数

   // Launch another asynchronous operation
   WSARecv(g_CliSocketArr[index],
    &g_pPerIoDataArr[index]->Buffer,
    1,
    &g_pPerIoDataArr[index]->NumberOfBytesRecvd,
    &g_pPerIoDataArr[index]->Flags,
    &g_pPerIoDataArr[index]->overlap,
    NULL);
  }
 }
 
 return 0;
}

void Cleanup(int index)
{
 closesocket(g_CliSocketArr[index]);
 WSACloseEvent(g_CliEventArr[index]);
 HeapFree(GetProcessHeap(), 0, g_pPerIoDataArr[index]);
 if (index < g_iTotalConn-1)
 {
  g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn-1];
  g_CliEventArr[index] = g_CliEventArr[g_iTotalConn-1];
  g_pPerIoDataArr[index] = g_pPerIoDataArr[g_iTotalConn-1];
 }
 
 g_pPerIoDataArr[--g_iTotalConn] = NULL;
}

 

      回调函数代码框架:

SOCKET g_sNewClientConnection;
BOOL g_bNewConnectionArrived = FALSE;

sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 ...
 bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
 listen(sListen, 3);
 CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
 while (TRUE)
 {
  // Accept a connection
  g_sNewClientConnection = accept(sListen, (sockaddr*)&client, &iAddrSize);
  g_bNewConnectionArrived = TRUE;
  //新连接,状态回调函数

 }

 return 0;
}

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
 LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
 while (TRUE)
 {
  if (g_bNewConnectionArrived)
  {
   // Launch an asynchronous operation for new arrived connection
   lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
    GetProcessHeap(),
    HEAP_ZERO_MEMORY,
    sizeof(PER_IO_OPERATION_DATA));
   lpPerIOData->Buffer.len = MSGSIZE;
   lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
   lpPerIOData->sClient = g_sNewClientConnection;

   WSARecv(lpPerIOData->sClient,
    &lpPerIOData->Buffer,
    1,
    &lpPerIOData->NumberOfBytesRecvd,
    &lpPerIOData->Flags,
    &lpPerIOData->overlap,
    CompletionRoutine);
   g_bNewConnectionArrived = FALSE;
  }
  SleepEx(1000, TRUE);
 }

 return 0;
}

void CALLBACK CompletionRoutine(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
 LPPER_IO_OPERATION_DATA lpPerIOData = (LPPER_IO_OPERATION_DATA)lpOverlapped;
 if (dwError != 0 || cbTransferred == 0)
 {
  // 连接断开,状态回调函数

  closesocket(lpPerIOData->sClient);
  HeapFree(GetProcessHeap(), 0, lpPerIOData);
 }
 else
 {
  //新数据,数据回调函数

  // Launch another asynchronous operation
  memset(&lpPerIOData->overlap, 0, sizeof(WSAOVERLAPPED));
  lpPerIOData->Buffer.len = MSGSIZE;
  lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
  WSARecv(lpPerIOData->sClient,
   &lpPerIOData->Buffer,
   1,
   &lpPerIOData->NumberOfBytesRecvd,
   &lpPerIOData->Flags,
   &lpPerIOData->overlap,
   CompletionRoutine);
 }
}

 

      5:完成端口模型

      完成端口模型应该是效率最高的I/O模型了,其他模型要么通过内核通知,要么为每个客户创建一个服务线程,通过检测通知。完成端口基于重叠I/O,采用了线程池技术,对于客户数目众多,变化频率高的服务器很实用。代码框架如下:

typedef enum
{
 RECV_POSTED
} OPERATION_TYPE;

typedef struct
{
 WSAOVERLAPPED  overlap;
 WSABUF         Buffer;
 char           szMessage[MSGSIZE];
 DWORD          NumberOfBytesRecvd;
 DWORD          Flags;
 OPERATION_TYPE OperationType;
} PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
 GetSystemInfo(&sysinfo);
 for (i = 0; i < sysinfo.dwNumberOfProcessors; i++)
 {
  CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId);
 }
  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 ...
 bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN));
  listen(sListen, 3);
 while (TRUE)
 {
  // Accept a connection
  sClient = accept(sListen, (sockaddr*)&client, &iAddrSize);
  //新连接,状态回调函数

  // Associate the newly arrived client socket with completion port
  CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);
  // Launch an asynchronous operation for new arrived connection
  lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(
   GetProcessHeap(),
   HEAP_ZERO_MEMORY,
   sizeof(PER_IO_OPERATION_DATA));
  lpPerIOData->Buffer.len = MSGSIZE;
  lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
  lpPerIOData->OperationType = RECV_POSTED;
  WSARecv(sClient,
   &lpPerIOData->Buffer,
   1,
   &lpPerIOData->NumberOfBytesRecvd,
   &lpPerIOData->Flags,
   &lpPerIOData->overlap,
   NULL);
 }

 PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL);
 CloseHandle(CompletionPort);
 closesocket(sListen);
 WSACleanup();
 return 0;
}

DWORD WINAPI WorkerThread(LPVOID CompletionPortID)
{
 HANDLE CompletionPort = (HANDLE)CompletionPortID;
 DWORD dwBytesTransferred;
 SOCKET sClient;
 LPPER_IO_OPERATION_DATA lpPerIOData = NULL;
 while (TRUE)
 {
  GetQueuedCompletionStatus(
   CompletionPort,
   &dwBytesTransferred,
   (DWORD*)&sClient,
   (LPOVERLAPPED*)&lpPerIOData,
   INFINITE);
  if (dwBytesTransferred == 0xFFFFFFFF)
  {
   return 0;
  }
  if (lpPerIOData->OperationType == RECV_POSTED)
  {
   if (dwBytesTransferred == 0)
   {
    // 连接断开,状态回调函数
    closesocket(sClient);
    HeapFree(GetProcessHeap(), 0, lpPerIOData);
   }
   else
   {
    //新数据,数据回调函数

    // Launch another asynchronous operation for sClient
    memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));
    lpPerIOData->Buffer.len = MSGSIZE;
    lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
    lpPerIOData->OperationType = RECV_POSTED;
    WSARecv(sClient,
     &lpPerIOData->Buffer,
     1,
     &lpPerIOData->NumberOfBytesRecvd,
     &lpPerIOData->Flags,
     &lpPerIOData->overlap,
     NULL);
   }
  }
 }

 return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值