winsock IO 模型之五:完成端口

该模型通过注册一个完成端口,并每次有新的连接时都将acceptsocket与该完成端口联系在一起,并创建多个工作线程来分别处理每个acceptsocket上的IO操作。

假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能。

从本质上说,完成端口模型利用了Wi n 3 2重叠I / O机制。在这种机制中,象W S A S e n d
和W S A R e c v这样的Winsock API调用会立即返回。此时,需要由我们的应用程序负责在以后
的某个时间,通过一个O V E R L A P P E D结构,来接收调用的结果。在完成端口模型中,要想做
到这一点,需要使用G e t Q u e u e d C o m p l e t i o n S t a t u s(获取排队完成状态)函数,让一个或多个工作者线程在完成端口上等待。

在参考书中看到这点“单I / O 操作数据可以是追加到一个O V E R L A P P E D结构末尾的、任意数量的字节。假如一个函数要求用到一个O V E R L A P P E D结构,我们便必须将这样的一个结构传递进去,以满足它的要求。要想做到这一点,一个简单的方法是定义一个结构,然后将O V E R L A P P E D结构作为新结构的第一个元素使用”。对这个“必须为这个结构体的第一个元素”有所怀疑,通过测试,发现事实确实如此。但是why?

以下是代码部分:

#include <winsock2.h>
//#include <windows.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32")
#define PORT 5150
#define DATA_BUFSIZE 8192

typedef struct

 OVERLAPPED Overlapped;
 WSABUF DataBuf;
 
 CHAR Buffer[DATA_BUFSIZE];
 DWORD BytesSEND;
 DWORD BytesRECV;
 SOCKET Socket;
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;


typedef struct
{
 
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;


DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);

int main(void)
{
 SOCKADDR_IN InternetAddr;
 SOCKET Listen;
 SOCKET Accept;
 HANDLE CompletionPort;
 SYSTEM_INFO SystemInfo;
 LPPER_HANDLE_DATA PerHandleData;
 LPPER_IO_OPERATION_DATA PerIoData;
 int i;
 DWORD RecvBytes;
 DWORD Flags;
 DWORD ThreadID;
 WSADATA wsaData;
 DWORD Ret;

 if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)
 {
  printf("WSAStartup failed with error %d/n", Ret);
  return 0;
 }

 // Setup an I/O completion port.

 if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)
 {
  printf( "CreateIoCompletionPort failed with error: %d/n", GetLastError());
  return 0;
 }

 // Determine how many processors are on the system.

 GetSystemInfo(&SystemInfo);

 // Create worker threads based on the number of processors available on the
 // system. Create two worker threads for each processor.

 for(i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
 {
  HANDLE ThreadHandle;

  // Create a server worker thread and pass the completion port to the thread.

  if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,
   0, &ThreadID)) == NULL)
  {
   printf("CreateThread() failed with error %d/n", GetLastError());
   return 0;
  }

  // Close the thread handle
  CloseHandle(ThreadHandle);
 }

 // Create a listening socket

 if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
  WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
 {
  printf("WSASocket() failed with error %d/n", WSAGetLastError());
  return 0;
 }

 InternetAddr.sin_family = AF_INET;
 InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
 InternetAddr.sin_port = htons(PORT);

 if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
 {
  printf("bind() failed with error %d/n", WSAGetLastError());
  return 0;
 }

 // Prepare socket for listening

 if (listen(Listen, 5) == SOCKET_ERROR)
 {
  printf("listen() failed with error %d/n", WSAGetLastError());
  return 0;
 }

 // Accept connections and assign to the completion port.

 while(TRUE)
 {
  if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
  {
   printf("WSAAccept() failed with error %d/n", WSAGetLastError());
   return 0;
  }

  // Create a socket information structure to associate with the socket
 

  // Associate the accepted socket with the original completion port.

  printf("Socket number %d connected/n", Accept);
  //PerHandleData->Socket = Accept;

  if (CreateIoCompletionPort((HANDLE) Accept, CompletionPort, (DWORD) PerHandleData,
   0) == NULL)
  {
   printf("CreateIoCompletionPort failed with error %d/n", GetLastError());
   return 0;
  }

  // Create per I/O socket information structure to associate with the
  // WSARecv call below.

  if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR,         sizeof(PER_IO_OPERATION_DATA))) == NULL)
  {
   printf("GlobalAlloc() failed with error %d/n", GetLastError());
   return 0;
  }

  ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
  PerIoData->BytesSEND = 0;
  PerIoData->BytesRECV = 0;
  PerIoData->DataBuf.len = DATA_BUFSIZE;
  PerIoData->DataBuf.buf = PerIoData->Buffer;
  PerIoData->Socket = Accept;

  Flags = 0;
  if (WSARecv(Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
   &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
  {
   if (WSAGetLastError() != ERROR_IO_PENDING)
   {
    printf("WSARecv() failed with error %d/n", WSAGetLastError());
    return 0;
   }
  }
 }
}

DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
 HANDLE CompletionPort = (HANDLE) CompletionPortID;
 DWORD BytesTransferred;
 LPOVERLAPPED Overlapped;
 LPPER_HANDLE_DATA PerHandleData;
 LPPER_IO_OPERATION_DATA PerIoData;
 DWORD SendBytes, RecvBytes;
 DWORD Flags;

 while(TRUE)
 {

  if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
   (LPDWORD)&PerIoData, (LPOVERLAPPED *) &PerIoData, INFINITE) == 0)
  {
   printf("GetQueuedCompletionStatus failed with error %d/n", GetLastError());
   return 0;
  }


  // First check to see if an error has occured on the socket and if so
  // then close the socket and cleanup the SOCKET_INFORMATION structure
  // associated with the socket.

  if (BytesTransferred == 0)
  {
   

   //GlobalFree(PerHandleData);
   GlobalFree(PerIoData);
   continue;
  }

  // Check to see if the BytesRECV field equals zero. If this is so, then
  // this means a WSARecv call just completed so update the BytesRECV field
  // with the BytesTransferred value from the completed WSARecv() call.

  if (PerIoData->BytesRECV == 0)
  {
   PerIoData->BytesRECV = BytesTransferred;
   PerIoData->BytesSEND = 0;
  }
  else
  {
   PerIoData->BytesSEND += BytesTransferred;
  }

  if (PerIoData->BytesRECV > PerIoData->BytesSEND)
  {

   // Post another WSASend() request.
   // Since WSASend() is not gauranteed to send all of the bytes requested,
   // continue posting WSASend() calls until all received bytes are sent.

   ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

   PerIoData->DataBuf.buf = PerIoData->Buffer + PerIoData->BytesSEND;
   PerIoData->DataBuf.len = PerIoData->BytesRECV - PerIoData->BytesSEND;

   if (WSASend(PerIoData->Socket, &(PerIoData->DataBuf), 1, &SendBytes, 0,
    &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
   {
    if (WSAGetLastError() != ERROR_IO_PENDING)
    {
     printf("WSASend() failed with error %d/n", WSAGetLastError());
     return 0;
    }
   }
  }
  else
  {
   PerIoData->BytesRECV = 0;

   // Now that there are no more bytes to send post another WSARecv() request.

   Flags = 0;
   ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

   PerIoData->DataBuf.len = DATA_BUFSIZE;
   PerIoData->DataBuf.buf = PerIoData->Buffer;

   if (WSARecv(PerIoData->Socket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
    &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
   {
    if (WSAGetLastError() != ERROR_IO_PENDING)
    {
     printf("WSARecv() failed with error %d/n", WSAGetLastError());
     return 0;
    }
   }
  }
 }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值