ChefZ -- 磨劍錄 (A Coder's Log)

慣看秋月春風 一壺濁酒喜相逢 古今多少事 皆賦談笑中
posts - 42, comments - 3, trackbacks - 0, articles - 6

(转)Windows Socket Monitor

Posted on 2008-11-12 19:39 chefZ 阅读(538) 评论(0)  编辑 收藏 引用
来自微软的完成端口例子,就讲解一下它的使用套路吧
反正编程这个玩意,只要用过,自然就知道什么回事,一次不会再看一次,学习这个玩意,无他,勤奋而已。
奢谈效率等等,那只是孰能生巧上的功夫。
 

  这个例子是在console下的例子,算是一个echo服务器吧,
  跑起来后将在5150端口监听,一旦有个端口连接上来,发个数据给服务端口,它就echo回数据给那个端口. 直到那个连接中断.
 
 完成端口,其实理解成一个通道或管子就可以了,和管道也差不了多少,不过可以实现异步处理罢了,
 你这边往管子里丢数据,通过GetQueuedCompletionStatus来查管子那头出数据没,出了就处理,这个管子就是通过一个自定义有点特殊的结构来写入或读出数据而已.
 那个完成端口,其实就相当是标识那个数据块的句柄,

//下面请看例子
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>

#define PORT 5150
#define DATA_BUFSIZE 8192

#pragma comment(lib, "Ws2_32")

typedef struct                        //这个玩意就是灌数据,取数据的一个自定义数据结构

                                              //和那个wm_data差不了多少,不过就是老要塞一个OverLapped结构,
{
   OVERLAPPED Overlapped;
   WSABUF DataBuf;
   CHAR Buffer[DATA_BUFSIZE];                   
   DWORD BytesSEND;                                 //发送字节数
   DWORD BytesRECV;                               
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;


typedef struct
{
   SOCKET Socket;
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;


DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);


void 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;
   }

   //
   //完成端口的建立得搞2次,这是第一次调用,至于为什么?我问问你
   //
   if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)
   {
      printf( "CreateIoCompletionPort failed with error: %d\n", GetLastError());
      return;
   }
   //老套子api,不谈也罢
   GetSystemInfo(&SystemInfo);
 
   //发现2个CPU,那就开个双倍的线程跑吧
   for(i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
   {
      HANDLE ThreadHandle;
    
      //
      //完成端口挂到线程上面来了,就像管子把灌数据的和读数据的两头都连上了,          
     //
      if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,
         0, &ThreadID)) == NULL)
      {
         printf("CreateThread() failed with error %d\n", GetLastError());
         return;
      }    
      CloseHandle(ThreadHandle);
   }

   //
   //启动一个监听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;
   }

   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;
   } 

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

   //
   // 监听端口打开,就开始在这里循环,一有socket连上,WSAAccept就创建一个socket,
   // 这个socket 又和完成端口联上,
   //
   // 嘿嘿,完成端口第二次调用那个createxxx函数,为什么,留给人思考思考可能更深刻,
   // 反正这套路得来2次,
   // 完成端口completionport和accept socket挂起来了,
   //
   while(TRUE)
   {

    //主线程跑到这里就等啊等啊,但是线程却开工了,
      if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
      {
         printf("WSAAccept() failed with error %d\n", WSAGetLastError());
         return;
      }
    
      if ((PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA))) == NULL)
      {
         printf("GlobalAlloc() failed with error %d\n", GetLastError());
         return;
      }    
    
      PerHandleData->Socket = Accept;
    
      //
     //把这头和完成端口completionPort连起来
     //就像你把漏斗接到管子口上,开始要灌数据了
     //
      if (CreateIoCompletionPort((HANDLE) Accept, CompletionPort, (DWORD) PerHandleData,
         0) == NULL)
      {
         printf("CreateIoCompletionPort failed with error %d\n", GetLastError());
         return;
      }
    
      //
      //清管子的数据结构,准备往里面灌数据
      //
      if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR,sizeof(PER_IO_OPERATION_DATA))) == NULL)
      {
         printf("GlobalAlloc() failed with error %d\n", GetLastError());
         return;
      }

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

      Flags = 0;
    
      //
      //  accept接到了数据,就放到PerIoData中,而perIoData又通过线程中的函数取出,
     //
      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;
         }
      }
   }
}

//
//线程一但调用,就老在里面循环,
// 注意,传入的可是完成端口啊,就是靠它去取出管子中的数据
//
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)
   {
      //
      //在这里检查完成端口部分的数据buf区,数据来了吗?
      // 这个函数参数要看说明,
      // PerIoData 就是从管子流出来的数据,
      //PerHandleData 也是从管子里取出的,是何时塞进来的,
     //就是在建立第2次createIocompletionPort时
    //

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

      // 检查数据传送完了吗
      if (BytesTransferred == 0)
      {
         printf("Closing socket %d\n", PerHandleData->Socket);

         if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
         {
            printf("closesocket() failed with error %d\n", WSAGetLastError());
            return 0;
         }

         GlobalFree(PerHandleData);
         GlobalFree(PerIoData);
         continue;
      }   
     //
    //看看管子里面有数据来了吗?=0,那是刚收到数据
    //
      if (PerIoData->BytesRECV == 0)
      {
         PerIoData->BytesRECV = BytesTransferred;
         PerIoData->BytesSEND = 0;
      }
      else   //来了,
      {
         PerIoData->BytesSEND += BytesTransferred;
      }
 
      //
      // 数据没发完?继续send出去
     //
     if (PerIoData->BytesRECV > PerIoData->BytesSEND)
      {

         ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED)); //清0为发送准备
         PerIoData->DataBuf.buf = PerIoData->Buffer + PerIoData->BytesSEND;
         PerIoData->DataBuf.len = PerIoData->BytesRECV - PerIoData->BytesSEND;

       //1个字节一个字节发送发送数据出去
         if (WSASend(PerHandleData->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;

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

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

         if (WSARecv(PerHandleData->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;
            }
         }
      }
   }
}

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理