CSDN首页 空间 新闻 论坛 Blog 下载 读书 网摘 搜索 .NET Java 视频 接项目 求职 在线学习 买书 程序员 通知
可用分押宝游戏火热进行中... 专题改版:Java Web 专题
CSDN社区
搜索 收藏 打印 关闭
CSDN社区 >  C++ Builder >  网络及通讯开发

问关于WINDOWS平台上TCP完成端口通讯的1个基本概念

楼主boyla(土著巫师)2005-12-09 11:36:37 在 C++ Builder / 网络及通讯开发 提问

本人最近在做一个金融数据传输的实时发送系统,通讯协议为TCP,考虑到在线人数可能上万,所以采用完成端口IO模型,有一些内容<<WINDOWS网络编程技术(MS   PRESS)>>交待的不是很清楚,所以想请教:  
   
          在用CreateIoCompletionPort(...)和完成端口关联好的SOCKET上,假如WSASend(...)想发3K数据(函数会立即返回),是不是GetQueuedCompletionStatus(...)不出错返回时就一定表示3K数据已发送,而不是少于3k,也就是说不会出现部分数据发送的情况?  
          如果出现只有部分数据发送出去的情况,肯定要把余下的再次WSASent(...),直到所有的数据发送完毕,可是这样就会出现一个奇怪的怪圈:按完成端口的理论,如果每次投递一个WSASend(...)都要在前一个返回时再投递,效率会很差,不能发挥系统的性能,所以应投递多个待决的IO;可是如果投递多次发送操作,又出现部份数据发送完成的情况,势必造成给客户的数据失去时间上的顺序性,因为后面的投递有可能在你发现前面投递没发完蛋补发时,已提前发送了。:(我昏。  
          所以我想是不会出现部分数据发送的情况的,可是不敢肯定,望做过的朋友提供可靠说明。谢谢。  
  年底了,借这个问题向BCBer们问好。 问题点数:200、回复次数:31Top

1 楼h2plus0(~~~)回复于 2005-12-09 15:19:20 得分 0

虽然我没有特意研究这个问题,但根据   WSASend   ()定义,和我以前使用情况,我觉得  
        1。   如果发送立刻成功,可以返回小于   dwBufferCount   的   lpNumberOfBytesSent   。  
  (   GetQueuedCompletionStatus   会被通知   )  
        2。   如果发送pending,   lpNumberOfBytesSent     返回0,(   GetQueuedCompletionStatus   会被通知   )  
  Top

2 楼h2plus0(~~~)回复于 2005-12-09 15:27:07 得分 10

更正:  
   
        1。   如果发送立刻成功,可以返回小于   lpBuffers[0].len   的   lpNumberOfBytesSent   。  
  (   GetQueuedCompletionStatus   会被通知   )     一般我只会用到   dwBufferCount=1  
  Top

3 楼lhj(努力加油)回复于 2005-12-09 20:00:08 得分 10

没有专门研究完成端口,不过采用这个模型后  
  1,发送出去的数据需要缓存在内存中,即使发送返回pengding,也可以继续发送下一个。  
  2,等到对应的完成消息回来后,根据消息结果来决定缓存的数据可删除还是需要重发。  
  Top

4 楼Yans(跟贴是一种友谊)回复于 2005-12-13 13:44:38 得分 10

正在学习WINDOWS网络编程技术Top

5 楼boyla(土著巫师)回复于 2005-12-14 11:34:31 得分 0

首先感谢前面几位网友的回复,不过问题提出有几天了,一直没有热心的网友提供强有力的说明;完成端口的服务端开发是网络编程程序员的必然选择,目前炒得火热的游戏开发,例如QQ等服务端的开发,都是数万人同时在线,难到这样“高端的”开发,BCB程序员瞧不上眼吗?真搞不清现在的程序员都在学习些什么东东。郁闷,我是BCBer.  
  Top

6 楼mme(dog)回复于 2005-12-15 13:42:30 得分 10

放心吧,其实你只要是按顺序投递了WSASend,就肯定能按顺序接收到了  
  系统没有那么笨的,它会把你的顺序排列起来按顺序发送的。  
   
  当GetQueuedCompletionStatus   返回发送失败的时候,你就应该把该连接删除了,因为正常的情况下是不可能发送失败的。这个不是udp,你也不需要使用重发机制。Top

7 楼tompkins2000(AirHunter)回复于 2005-12-15 14:19:09 得分 10

想回答,可惜看不懂:)Top

8 楼songhtao(三十年孤独)回复于 2005-12-15 14:30:18 得分 10

对于初次使用IOCP进行高性能服务器开发的朋友来说,可能会经常遇到一些莫名其妙的错误,让自己无从下手。  
   
  1、在程序创建监听套接字时,使用socket函数创建一个套接字时,总是报“INVALID_SOCKET”错误?  
  原因:出现此问题的原因,很可能是因为没有正确执行WSAStartUp函数引起的;  
  解决方法:请检查,是否使用WSAStartUp对winsock进行了初始化工作?如果进行了初始化,请检查初始化是否成功?  
   
  2、使用WSASend或WSARecv投递相应的发送或接收请求后,始终没有收到相应的GET函数完成返回通知?  
  原因:出现此问题的原因,绝大多数是因为函数参数没有进行正确的赋值。  
  解决方法:在执行wsasend和wsarecv操作前,请先将overlapped结构体使用memset进行清零。一个正确的调用格式如下:  
  [发送操作]  
     DWORD   ByteSend=0;  
     DWORD   Flags=0;  
     int   tmpResult=0;  
            ......  
   PPerHandleData   tmpData;  
   ......  
   memset(&(tmpData->Overlapped),   '\0',   sizeof(OVERLAPPED));//将overlapped结构清空  
   tmpData->Statu   =   ssSend;  
          tmpResult   =   WSASend(tmpData->socket,   &(tmpData->WSASendBuffer),   1,  
        &ByteSend,  
       Flags,  
       &(tmpData->Overlapped),  
       NULL);  
   
  [接收操作]  
     DWORD   byteRecv=0;  
     DWORD   Flags=0;  
     int   tmpResult=0;  
     ......  
     PPerHandleData   myHandlData;    
     ......    
     memset(&(myHandlData->Overlapped),   '\0',   sizeof(OVERLAPPED));  
     memset(myHandlData->RecvBuffer,   '\0',   CLIENT_BUFFER_SIZE);  
     myHandlData->WSARecvBuffer.buf   =   myHandlData->RecvBuffer;  
     myHandlData->WSARecvBuffer.len   =   CLIENT_BUFFER_SIZE;  
     myHandlData->socket   =   myClient->m_ClientSocket;  
     myHandlData->Statu   =   ssRecv;  
   
     tmpResult   =   WSARecv(myHandlData->socket,   &(myHandlData->WSARecvBuffer),   1,   (LPDWORD)&byteRecv,   (LPDWORD)&Flags,   (LPWSAOVERLAPPED)&(myHandlData->Overlapped),   0);  
   
   
  3、当投递了一个WSARecv或WSASend请求后,总是返回“ERROR_IO_PENDING”错误?  
  原因:“ERROR_IO_PENDING”,表示的是WSARecv或WSASend操作正在执行中,还没有执行完毕。  
  解决方法:此错误可以直接忽略,如果参数设置正确,当操作完成时,系统会通过GET函数返回执行的形式来通知发送或接收操作已经完成。  
   
  还有http://www.vczx.com/article/show.php?id=638这编文章也较详细。Top

9 楼FengSC(小猪快跑)回复于 2005-12-15 16:49:09 得分 10

原来我搞过关于IOCP的东西,当时在这提了问没有得到满意回复,后来自己研究了一下,基本知道情况了。  
   
  首先GetQueuedCompletionStatus函数的lpNumberOfBytesTransferred参数表示已经传送的字节,也就是发送或接收的字节。  
  还有,如果lpNumberOfBytesTransferred的字节数0,那么说明端口已经关闭。  
  如果发现lpNumberOfBytesTransferred的字节数和你想发送的字节数不等这种情况应该是发送出现问题了,你再次调用WSASent时应该会出错,也就是说如果已发送的字节数和你想发送的字节数不等,你就可以关闭这个连接了。Top

10 楼DelphiGuy()回复于 2005-12-15 17:10:43 得分 10

数万人同时在线并不是一台服务器,而是几十甚至更多服务器协同工作。  
  另外QQ这种软件对服务器的压力并不大,只是登陆、查找在线好友列表、获取离线消息等功能是访问服务器,与其他人之间的通讯就是P2P了(偶尔需要服务器转发)。  
  Top

11 楼BCB2006(i like bcb)回复于 2005-12-15 20:45:28 得分 10

学习了!!Top

12 楼boyla(土著巫师)回复于 2005-12-16 10:43:59 得分 0

在<<WIDNOWS网络编程技术(英文第二版)>>的例子里有这样一句说明:  
  The   important   thing   to   remember   with   IOCP   is   that   the   completion   events  
  may   occur   out   of   order.  
   
  还有一句“this   can   cause   problems   as   receive   N+1   may   complete   before   receive   N.”  
   
  我的英文不是很好,不过大致意思还是看得懂的:在完成端口上投递的I/O,其完成的先后顺序是不确定的,也就是说第N次的接收没返回,第N+1次的接收却返回了,那么我就要问第N+1次返回的数据是对方先发出来的,还是后发出来的?我想是先发出来的,但不敢肯定。这种事情如果发生在发送操作上简值就是恐怖,谁来保证哪个WSASend(...)的数据先被对方收到(TCP通讯)?  
   
  对大家的热心参与表示感谢,欢迎大家共享知识,共同进步.  
  Top

13 楼FengSC(小猪快跑)回复于 2005-12-16 10:56:48 得分 0

我每次只投递一个WSASend,等完成了再发起下一次WSASend,我实在没有想到有什么必要一次投递多个。Top

14 楼boyla(土著巫师)回复于 2005-12-16 11:29:36 得分 0

按照完成端口通讯方式的说明,如果每次都是在前一个操作返回后再投递下一个操作,底层的驱动程序会一直都很闲,也就是不能最大限度的发挥出效率和性能.可能是我理解的不对,感谢提供帮助.:)Top

15 楼zephyr007(道可道)回复于 2006-01-07 23:19:56 得分 10

个人Unix/Linux在网络编程方面的关于socket部分解释的更清楚些,我也是在linux环境下编写了一段时间的程序后才对socket有了更清晰的认识。建议楼主在Winsock之外再适当参考一下Unix相关知识Top

16 楼mme(dog)回复于 2006-01-13 13:30:25 得分 10

被楼主一说,好像是没有什么把握了。一下子发送多个出去,顺序怎么确定啊!  
   
  我不甘心,把WSASend的代码翻出来,看了一下,结果发现它是调用NtDeviceIoControlFile,直接把数据传递给驱动了。  
   
  我的结论是:只要你是在同一个线程内,把数据分包,一个个地发送,类似下面这样:  
  while(SendLen   <   BufferLen)  
  {  
  WSASend(Buffer+SendLen,   1024,   ...);  
  SendLen   +=   1024;  
  }  
  应该是按顺序发送出去的。  
  因为每个WSASend都传递一次数据给驱动,驱动如果不排序,那我就无语了,大吼:那还要异步干什么!!!。  
   
   
  我自己测试了一把,用完成端口一次发送了很多个包,结果没有一个顺序是错的(我在包里记录了顺序)。  
   
   
  Top

17 楼mme(dog)回复于 2006-01-13 13:33:28 得分 0

还有,如果数据量太大,我怀疑完成端口也支持不了上万个连接。我觉得最好就是用多个服务器。Top

18 楼iec(bcbtovs)回复于 2006-01-16 23:59:06 得分 10

markTop

19 楼tanlim(sunsos.net sunsos.cn)回复于 2006-01-17 00:17:24 得分 10

学习Top

20 楼jjwwang((空园歌独酌,春日赋闲居))回复于 2006-01-17 16:02:45 得分 10

支持mme(dog)   的观点.Top

21 楼Yans(跟贴是一种友谊)回复于 2006-01-18 15:32:46 得分 0

学习Top

22 楼redpower(常宁)回复于 2006-01-20 22:03:39 得分 0

 
  回复人:boyla(土著巫师)   (   五级(中级))   信誉:99   2005-12-14   11:34:31   得分:0  
  ?    
   
  首先感谢前面几位网友的回复,不过问题提出有几天了,一直没有热心的网友提供强有力的说明;完成端口的服务端开发是网络编程程序员的必然选择,目前炒得火热的游戏开发,例如QQ等服务端的开发,都是数万人同时在线,难到这样“高端的”开发,BCB程序员瞧不上眼吗?真搞不清现在的程序员都在学习些什么东东。郁闷,我是BCBer.  
   
  这是误解了CIO.  
  你的应用我看了一下,推荐你用异步选择模型,CIO对你的需求不适合。  
  另外一个就是   数万人同时在线在并且都保持Active基本上是不可能的,如果有这样多的活动连接,请考虑多机集群,unix工作站或者超级小型机。然后采用AIO模型实现。否则windows早就不堪重负了。  
  CIO最好用在多处理器并行的情况下,cpu数量越多,与异步选择的差别就越大,如果只有一个,新能提高不会超过15%,我的测试情况时高负载情况下高13%.  
  Top

23 楼mmking33(潜水可达海底1万米以下)回复于 2006-01-26 14:21:58 得分 10

socket   I/O模型详解【转】    
     
   
   
  《Socket   I/O模型全接触》    
  作  者:   flyinwuhan   (制怒·三思而后行)    
   
  本文简单介绍了当前Windows支持的各种Socket   I/O模型,如果你发现其中存在什么错误请务必赐教。  
   
  一:select模型  
  二:WSAAsyncSelect模型  
  三:WSAEventSelect模型  
  四:Overlapped   I/O   事件通知模型  
  五:Overlapped   I/O   完成例程模型  
  六:IOCP模型  
   
  老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。  
  这和Socket模型非常类似。下面我就以老陈接收信件为例讲解Socket   I/O模型~~~  
   
  一:select模型  
   
  老陈非常想看到女儿的信。以至于他每隔10分钟就下楼检查信箱,看是否有女儿的信~~~~~  
  在这种情况下,"下楼检查信箱"然后回到楼上耽误了老陈太多的时间,以至于老陈无法做其他工作。  
  select模型和老陈的这种情况非常相似:周而复始地去检查......如果有数据......接收/发送.......  
   
  使用线程来select应该是通用的做法:  
  procedure   TListenThread.Execute;  
  var  
  addr   :   TSockAddrIn;  
  fd_read   :   TFDSet;  
  timeout   :   TTimeVal;  
  ASock,  
  MainSock   :   TSocket;  
  len,   i   :   Integer;  
  begin  
  MainSock   :=   socket(   AF_INET,   SOCK_STREAM,   IPPROTO_TCP   );  
  addr.sin_family   :=   AF_INET;  
  addr.sin_port   :=   htons(5678);  
  addr.sin_addr.S_addr   :=   htonl(INADDR_ANY);  
  bind(   MainSock,   @addr,   sizeof(addr)   );  
  listen(   MainSock,   5   );  
   
  while   (not   Terminated)   do  
  begin  
  FD_ZERO(   fd_read   );  
  FD_SET(   MainSock,   fd_read   );  
  timeout.tv_sec   :=   0;  
  timeout.tv_usec   :=   500;  
  if   select(   0,   @fd_read,   nil,   nil,   @timeout   )   >   0   then   //至少有1个等待Accept的connection  
  begin  
  if   FD_ISSET(   MainSock,   fd_read   )   then  
  begin  
  for   i:=0   to   fd_read.fd_count-1   do   //注意,fd_count   <=   64,也就是说select只能同时管理最多64个连接  
  begin  
  len   :=   sizeof(addr);  
  ASock   :=   accept(   MainSock,   addr,   len   );  
  if   ASock   <>   INVALID_SOCKET   then  
  ....//为ASock创建一个新的线程,在新的线程中再不停地select  
  end;    
  end;    
  end;    
  end;   //while   (not   self.Terminated)  
   
  shutdown(   MainSock,   SD_BOTH   );  
  closesocket(   MainSock   );  
  end;  
   
  二:WSAAsyncSelect模型  
   
  后来,老陈使用了微软公司的新式信箱。这种信箱非常先进,一旦信箱里有新的信件,盖茨就会给老陈打电话:喂,大爷,你有新的信件了!从此,老陈再也不必频繁上下楼检查信箱了,牙也不疼了,你瞅准了,蓝天......不是,微软~~~~~~~~  
  微软提供的WSAAsyncSelect模型就是这个意思。  
   
  WSAAsyncSelect模型是Windows下最简单易用的一种Socket   I/O模型。使用这种模型时,Windows会把网络事件以消息的形势通知应用程序。  
  首先定义一个消息标示常量:  
  const   WM_SOCKET   =   WM_USER   +   55;  
  再在主Form的private域添加一个处理此消息的函数声明:  
  private  
  procedure   WMSocket(var   Msg:   TMessage);   message   WM_SOCKET;  
  然后就可以使用WSAAsyncSelect了:  
  var  
  addr   :   TSockAddr;  
  sock   :   TSocket;  
   
  sock   :=   socket(   AF_INET,   SOCK_STREAM,   IPPROTO_TCP   );  
  addr.sin_family   :=   AF_INET;  
  addr.sin_port   :=   htons(5678);  
  addr.sin_addr.S_addr   :=   htonl(INADDR_ANY);  
  bind(   m_sock,   @addr,   sizeof(SOCKADDR)   );  
   
  WSAAsyncSelect(   m_sock,   Handle,   WM_SOCKET,   FD_ACCEPT   or   FD_CLOSE   );  
   
  listen(   m_sock,   5   );  
  ....  
   
  应用程序可以对收到WM_SOCKET消息进行分析,判断是哪一个socket产生了网络事件以及事件类型:  
   
  procedure   TfmMain.WMSocket(var   Msg:   TMessage);  
  var  
  sock   :   TSocket;  
  addr   :   TSockAddrIn;  
  addrlen   :   Integer;  
  buf   :   Array   [0..4095]   of   Char;  
  begin  
  //Msg的WParam是产生了网络事件的socket句柄,LParam则包含了事件类型  
  case   WSAGetSelectEvent(   Msg.LParam   )   of  
  FD_ACCEPT   :  
  begin  
  addrlen   :=   sizeof(addr);  
  sock   :=   accept(   Msg.WParam,   addr,   addrlen   );  
  if   sock   <>   INVALID_SOCKET   then  
  WSAAsyncSelect(   sock,   Handle,   WM_SOCKET,   FD_READ   or   FD_WRITE   or   FD_CLOSE   );  
  end;  
   
  FD_CLOSE   :   closesocket(   Msg.WParam   );  
  FD_READ   :   recv(   Msg.WParam,   buf[0],   4096,   0   );  
  FD_WRITE   :   ;  
  end;    
  end;Top

24 楼mmking33(潜水可达海底1万米以下)回复于 2006-01-26 14:22:22 得分 0

 
  三:WSAEventSelect模型  
   
  后来,微软的信箱非常畅销,购买微软信箱的人以百万计数......以至于盖茨每天24小时给客户打电话,累得腰酸背痛,喝蚁力神都不好使~~~~~~  
  微软改进了他们的信箱:在客户的家中添加一个附加装置,这个装置会监视客户的信箱,每当新的信件来临,此装置会发出"新信件到达"声,提醒老陈去收信。盖茨终于可以睡觉了。  
   
  同样要使用线程:  
  procedure   TListenThread.Execute;  
  var  
  hEvent   :   WSAEvent;  
  ret   :   Integer;  
  ne   :   TWSANetworkEvents;  
  sock   :   TSocket;  
  adr   :   TSockAddrIn;  
  sMsg   :   String;  
  Index,  
  EventTotal   :   DWORD;  
  EventArray   :   Array   [0..WSA_MAXIMUM_WAIT_EVENTS-1]   of   WSAEVENT;  
  begin  
  ...socket...bind...  
  hEvent   :=   WSACreateEvent();  
  WSAEventSelect(   ListenSock,   hEvent,   FD_ACCEPT   or   FD_CLOSE   );  
  ...listen...  
   
  while   (   not   Terminated   )   do  
  begin  
  Index   :=   WSAWaitForMultipleEvents(   EventTotal,   @EventArray[0],   FALSE,   WSA_INFINITE,   FALSE   );  
  FillChar(   ne,   sizeof(ne),   0   );  
  WSAEnumNetworkEvents(   SockArray[Index-WSA_WAIT_EVENT_0],   EventArray[Index-WSA_WAIT_EVENT_0],   @ne   );  
   
  if   (   ne.lNetworkEvents   and   FD_ACCEPT   )   >   0   then  
  begin  
  if   ne.iErrorCode[FD_ACCEPT_BIT]   <>   0   then  
  continue;  
   
  ret   :=   sizeof(adr);  
  sock   :=   accept(   SockArray[Index-WSA_WAIT_EVENT_0],   adr,   ret   );  
  if   EventTotal   >   WSA_MAXIMUM_WAIT_EVENTS-1   then//这里WSA_MAXIMUM_WAIT_EVENTS同样是64  
  begin  
  closesocket(   sock   );  
  continue;  
  end;  
   
  hEvent   :=   WSACreateEvent();  
  WSAEventSelect(   sock,   hEvent,   FD_READ   or   FD_WRITE   or   FD_CLOSE   );  
  SockArray[EventTotal]   :=   sock;  
  EventArray[EventTotal]   :=   hEvent;  
  Inc(   EventTotal   );  
  end;  
   
  if   (   ne.lNetworkEvents   and   FD_READ   )   >   0   then  
  begin  
  if   ne.iErrorCode[FD_READ_BIT]   <>   0   then  
  continue;  
  FillChar(   RecvBuf[0],   PACK_SIZE_RECEIVE,   0   );  
  ret   :=   recv(   SockArray[Index-WSA_WAIT_EVENT_0],   RecvBuf[0],   PACK_SIZE_RECEIVE,   0   );  
  ......  
  end;  
  end;  
  end;  
   
   
  四:Overlapped   I/O   事件通知模型  
   
  后来,微软通过调查发现,老陈不喜欢上下楼收发信件,因为上下楼其实很浪费时间。于是微软再次改进他们的信箱。新式的信箱采用了更为先进的技术,只要用户告诉微软自己的家在几楼几号,新式信箱会把信件直接传送到用户的家中,然后告诉用户,你的信件已经放到你的家中了!老陈很高兴,因为他不必再亲自收发信件了!  
   
  Overlapped   I/O   事件通知模型和WSAEventSelect模型在实现上非常相似,主要区别在"Overlapped",Overlapped模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock   I/O请求。这些提交的请求完成后,应用程序会收到通知。什么意思呢?就是说,如果你想从socket上接收数据,只需要告诉系统,由系统为你接收数据,而你需要做的只是为系统提供一个缓冲区~~~~~  
  Listen线程和WSAEventSelect模型一模一样,Recv/Send线程则完全不同:  
  procedure   TOverlapThread.Execute;  
  var  
  dwTemp   :   DWORD;  
  ret   :   Integer;  
  Index   :   DWORD;  
  begin  
  ......  
   
  while   (   not   Terminated   )   do  
  begin  
  Index   :=   WSAWaitForMultipleEvents(   FLinks.Count,   @FLinks.Events[0],   FALSE,   RECV_TIME_OUT,   FALSE   );  
  Dec(   Index,   WSA_WAIT_EVENT_0   );  
  if   Index   >   WSA_MAXIMUM_WAIT_EVENTS-1   then   //超时或者其他错误  
  continue;  
   
  WSAResetEvent(   FLinks.Events[Index]   );  
  WSAGetOverlappedResult(   FLinks.Sockets[Index],   FLinks.pOverlaps[Index],   @dwTemp,   FALSE,   FLinks.pdwFlags[Index]^   );  
   
  if   dwTemp   =   0   then   //连接已经关闭  
  begin  
  ......  
  continue;  
  end   else  
  begin  
  fmMain.ListBox1.Items.Add(   FLinks.pBufs[Index]^.buf   );  
  end;  
   
  //初始化缓冲区  
  FLinks.pdwFlags[Index]^   :=   0;  
  FillChar(   FLinks.pOverlaps[Index]^,   sizeof(WSAOVERLAPPED),   0   );  
  FLinks.pOverlaps[Index]^.hEvent   :=   FLinks.Events[Index];  
  FillChar(   FLinks.pBufs[Index]^.buf^,   BUFFER_SIZE,   0   );  
   
  //递一个接收数据请求  
  WSARecv(   FLinks.Sockets[Index],   FLinks.pBufs[Index],   1,   FLinks.pdwRecvd[Index]^,   FLinks.pdwFlags[Index]^,   FLinks.pOverlaps[Index],   nil   );  
  end;  
  end;  
   
  五:Overlapped   I/O   完成例程模型  
   
  老陈接收到新的信件后,一般的程序是:打开信封----掏出信纸----阅读信件----回复信件......为了进一步减轻用户负担,微软又开发了一种新的技术:用户只要告诉微软对信件的操作步骤,微软信箱将按照这些步骤去处理信件,不再需要用户亲自拆信/阅读/回复了!老陈终于过上了小资生活!  
   
  Overlapped   I/O   完成例程要求用户提供一个回调函数,发生新的网络事件的时候系统将执行这个函数:  
  procedure   WorkerRoutine(   const   dwError,   cbTransferred   :   DWORD;   const  
  lpOverlapped   :   LPWSAOVERLAPPED;   const   dwFlags   :   DWORD   );   stdcall;  
  然后告诉系统用WorkerRoutine函数处理接收到的数据:  
  WSARecv(   m_socket,   @FBuf,   1,   dwTemp,   dwFlag,   @m_overlap,   WorkerRoutine   );  
  然后......没有什么然后了,系统什么都给你做了!微软真实体贴!  
  while   (   not   Terminated   )   do//这就是一个Recv/Send线程要做的事情......什么都不用做啊!!!  
  begin  
  if   SleepEx(   RECV_TIME_OUT,   True   )   =   WAIT_IO_COMPLETION   then   //  
  begin  
  ;  
  end   else  
  begin  
  continue;  
  end;  
  end;  
   
  六:IOCP模型  
   
  微软信箱似乎很完美,老陈也很满意。但是在一些大公司情况却完全不同!这些大公司有数以万计的信箱,每秒钟都有数以百计的信件需要处理,以至于微软信箱经常因超负荷运转而崩溃!需要重新启动!微软不得不使出杀手锏......  
  微软给每个大公司派了一名名叫"Completion   Port"的超级机器人,让这个机器人去处理那些信件!  
   
  "Windows   NT小组注意到这些应用程序的性能没有预料的那么高。特别的,处理很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的[没有被挂起和等待发生什么事],Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文[Context],线程就没有得到很多CPU时间来做它们的工作。大家可能也都感觉到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小,但也远不是没有开销的。我们不妨设想一下:如果事先开好N个线程,让它们在那hold[堵塞],然后可以将所有用户的请求都投递到一个消息队列中去。然后那N个线程逐一从消息队列中去取出消息并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源,也提高了线程的利用率。理论上很不错,你想我等泛泛之辈都能想出来的问题,Microsoft又怎会没有考虑到呢?"-----摘自nonocast的《理解I/O   Completion   Port》  
   
  先看一下IOCP模型的实现:  
   
  //创建一个完成端口  
  FCompletPort   :=   CreateIoCompletionPort(   INVALID_HANDLE_value,   0,0,0   );  
   
  //接受远程连接,并把这个连接的socket句柄绑定到刚才创建的IOCP上  
  AConnect   :=   accept(   FListenSock,   addr,   len);  
  CreateIoCompletionPort(   AConnect,   FCompletPort,   nil,   0   );  
   
  //创建CPU数*2   +   2个线程  
  for   i:=1   to   si.dwNumberOfProcessors*2+2   do  
  begin  
  AThread   :=   TRecvSendThread.Create(   false   );  
  AThread.CompletPort   :=   FCompletPort;//告诉这个线程,你要去这个IOCP去访问数据  
  end;  
   
  OK,就这么简单,我们要做的就是建立一个IOCP,把远程连接的socket句柄绑定到刚才创建的IOCP上,最后创建n个线程,并告诉这n个线程到这个IOCP上去访问数据就可以了。  
   
  再看一下TRecvSendThread线程都干些什么:  
   
  procedure   TRecvSendThread.Execute;  
  var  
  ......  
  begin  
  while   (not   self.Terminated)   do  
  begin  
  //查询IOCP状态(数据读写操作是否完成)  
  GetQueuedCompletionStatus(   CompletPort,   BytesTransd,   CompletKey,   POVERLAPPED(pPerIoDat),   TIME_OUT   );  
   
  if   BytesTransd   <>   0   then  
  ....;//数据读写操作完成  
   
  //再投递一个读数据请求  
  WSARecv(   CompletKey,   @(pPerIoDat^.BufData),   1,   BytesRecv,   Flags,   @(pPerIoDat^.Overlap),   nil   );  
  end;  
  end;  
   
  读写线程只是简单地检查IOCP是否完成了我们投递的读写操作,如果完成了则再投递一个新的读写请求。  
  应该注意到,我们创建的所有TRecvSendThread都在访问同一个IOCP(因为我们只创建了一个IOCP),并且我们没有使用临界区!难道不会产生冲突吗?不用考虑同步问题吗?  
  呵呵,这正是IOCP的奥妙所在。IOCP不是一个普通的对象,不需要考虑线程安全问题。它会自动调配访问它的线程:如果某个socket上有一个线程A正在访问,那么线程B的访问请求会被分配到另外一个socket。这一切都是由系统自动调配的,我们无需过问。  
   
  Top

25 楼love_6823027(静雪轩)回复于 2006-02-01 20:25:54 得分 0

学习中  
  ~~~Top

26 楼elssann(睡睡裤)回复于 2006-02-05 10:42:42 得分 10

1:在某一个SOCKET连接上,假设你第一次发送3K数据,即使WSASend返回IO_PENDING,也不代表这3K数据能全部发送出去,至于发送出去多少,要看GetQueuedCompletionStatus返回后lpNumberOfBytes这个参数返回值是多少。如果没发送完,你还需要调用WSASend来发送第2次。具体可以参考PLATFORM   SDK里的IOCP例子里的代码。  
   
   
  2:在支持上万人的服务器上,没个SOCKET连接都在上一次数据全部发送完后再发送下一个,并不会影响多少效率。因为在这么多人的系统上,需要的是全部连接整体上的速度的平衡,而不是追求在某一个连接上有多快。Top

27 楼gogowhy(123)回复于 2006-02-07 16:18:44 得分 10

mTop

28 楼cnwhonker(龙神)回复于 2006-02-09 08:25:48 得分 10

好像不错Top

29 楼boyla(土著巫师)回复于 2006-02-18 23:17:39 得分 0

首先,感谢大家的参与,真心的感谢。  
      其次,说明一下:项目正在进行中,我的假设是:带宽足够,暂不考虑软、硬件的集群处理等方式,因为这种方式总是有用并且立马见效;目前我只是想从纯软件开发的角度考虑,不要让最后的瓶颈出现在服务端软件上,最大限度的发挥单机(多CPU)的替力。  
      最后,<<WIDNOWS网络编程技术(英文第二版)>>这本书绝对是WINDOWS平台上网络开发的权威书籍,可是总觉得一些CPIO基本概念交待的不是很清楚,虽然中文第一版我也已翻过N遍了。Top

30 楼xichen(xichen)回复于 2006-02-20 15:54:30 得分 10

没发现这样的问题,我已经把一个vc下的完成端口模型改成bcb的了,使用很稳定,不过数据量也不算大,哈哈Top

31 楼etre(林荃)回复于 2006-04-07 10:38:39 得分 10

upTop

相关问题

  • com基本概念???
  • VC.NET基本概念
  • java基本概念问题
  • 一个基本概念
  • 基本概念问题
  • 基本概念问题
  • 一些基本概念
  • 请教C++基本概念
  • 请教基本概念
  • java基本概念的问题

关键词

  • 线程
  • 端口
  • 函数
  • 数据
  • 信件
  • 微软
  • socket
  • 信箱
  • 投递
  • myhandldata

得分解答快速导航

  • 帖主:boyla
  • h2plus0
  • lhj
  • Yans
  • mme
  • tompkins2000
  • songhtao
  • FengSC
  • DelphiGuy
  • BCB2006
  • zephyr007
  • mme
  • iec
  • tanlim
  • jjwwang
  • mmking33
  • elssann
  • gogowhy
  • cnwhonker
  • xichen
  • etre

相关链接

  • CSDN Blog
  • 技术文档
  • 代码下载
  • 第二书店
  • 读书频道

广告也精彩

反馈

请通过下述方式给我们反馈
反馈
提问
网站简介|广告服务|VIP资费标准|银行汇款帐号|网站地图|帮助|联系方式|诚聘英才|English|问题报告
世纪乐知(北京)网络技术有限公司 版权所有, 京 ICP 证 020026 号
北京创新乐知广告有限公司 提供技术支持
Copyright © 2000-2007, CSDN.NET, All Rights Reserved
GongshangLogo