老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。
  这和Socket模型非常类似。下面我就以老陈接收信件为例讲解SocketI/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; 
  三: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; 
  就这么简单,我们要做的就是建立一个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。这一切都是由系统自动调配的,我们无需过问。

注:iocp都要使用线程池,其中的线程数目一般为当前电脑中cpu个数的2倍。

几种winsock I/O模型比较:
select模型核心就是select函数,它可用于判断套接字上是否存在数据,或者能否向一个套接字写入数据。这个函数可以有效地防止应用程序在套接字处于阻塞模式中时,send或recv进入阻塞状态;同时也可以防止产生大量的WSAEWOULDBLOCK错误select的优势是能够从单个线程的多个套接字上进行多重连接及I/O。

WSAAsyncSelect 模型是以消息机制为基础,能够处理一定的客户连接量,但是扩展性也不是很好。因为消息泵很快就会阻塞,降低了消息处理的速度。WSAAsyncSelect和WSAEventSelect模型提供了读写数据能力的异步通知,但他们不提供异步数据传送,而重叠及完成端口提供异步数据的传送。

WSAEventSelect 模型以事件为基础的网络事件通知,但是与WSAAsyncSelect不同的是,它主要是由事件对象句柄完成的,而不是通过窗口。但是一个线程只能等待64个事件(需要开辟多个线程解决),伸缩性不如完成端口。

重叠模型可以使程序能达到更佳的系统性能。基本设计原理就是让应用程序使用重叠的数据结构,一次投递一个或多个I/O请求。针对这些提交的请求,在他们完成之后,应用程序可为他们提供服务。它又分为两种实现方法:事件通知和完成例程。重叠I/O模型事件通知依赖于等待事件通知的线程数(WSAWaitForMultipleEvents调用的每个线程,该I/O模型一次最多都只能支持64个套接字。),处理客户通信时,大量线程上下文的切换是它们共同的制约因素。

完成端口提供了最好的伸缩性,往往可以使系统达到最好的性能,是处理成千上万的套接字的首选。从本质上说,完成端口模型要求创建一个windows完成端口对象,该对象通过指定数量的线程,对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务。但是完成端口只是支持NT系统、WIN2000系统。

重叠模型和完成端口模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接被拷贝到投递的缓冲区。 而select模型、WSAAsyncSelect 模型、WSAEventSelect 模型,数据到达并拷贝到单套接字接收缓冲区中,此时应用程序会被告知可以读入的容量。当应用程序调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区,差别就体现出来了。

5种IO模式形象的比喻相关推荐

  1. 两种IO模式:Proactor与Reactor模式

    在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作. 在比较这两个模式之前,我们首先的搞明白 ...

  2. 聊聊 Linux 中的五种 IO 模型

    聊聊 Linux 中的五种 IO 模型 2016/04/21 · IT技术 · 8 评论 · iO, 同步, 异步, 阻塞, 非阻塞 分享到:0 本文作者: 伯乐在线 - 陶邦仁 .未经作者许可,禁止 ...

  3. 详解Linux 五种IO模型

    原文:https://www.jianshu.com/p/486b0965c296 上一篇 同步.异步.阻塞.非阻塞 已经通俗的讲解了,要理解同步.异步.阻塞与非阻塞重要的两个概念点了,没有看过的,建 ...

  4. Linux 五种IO模型

    想快速了解,看文末总结. 1 概念说明# 在进行解释之前,首先要说明几个概念: 用户空间和内核空间 进程切换 进程的阻塞 文件描述符 缓存 IO 1.1 用户空间与内核空间## 现在操作系统都是采用虚 ...

  5. IO模式-BIO,NIO,AIO

    目前常用的3种IO模式:分别是BIO.NIO和AIO. BIO BIO 全称Block-IO 是一种同步且阻塞的通信模式.是一个比较传统的通信方式,模式简单,使用方便.但并发处理能力低,通信耗时,依赖 ...

  6. 五种I/O 模式——阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/O

    From: http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520112163171778/ 五种I/O 模式: [1]        ...

  7. 引脚悬空是什么电平_STM32单片机必须掌握的八种IO口模式和引脚配置方式

    八种IO口模式STM32有八种IO口模式,分别是:模拟输入.浮空输入.上拉输入.下拉输入.开漏输出.推挽输出.复用开漏输出和复用推挽输出.1.模拟输入GPIO_Mode_AIN模拟输入,即关闭施密特触 ...

  8. STM32的8种IO口的模式

    一.推挽输出:可以输出高.低电平,连接数字器件:推挽结构一般是指两个三极管分别受两个互补信号的控制,总是在一个三极管导通的时候另一个截止.高低电平由IC的电源决定.         推挽电路是两个参数 ...

  9. STM32八种IO口模式区别,以及上拉输入、下拉输入、浮空输入、模拟输入的区别

    最近在看数据手册的时候,发现在 Cortex-M3 里,对于 GPIO 的配置种类有 8 种之多: (1)GPIO_Mode_AIN 模拟输入 (2)GPIO_Mode_IN_FLOATING 浮空输 ...

最新文章

  1. 【转】 一些NET的实用类,不错
  2. iOS UI_APPEARANCE_SELECTOR
  3. python 序列化_python之序列化
  4. WebService入门Demo
  5. Dom4j工具--XML的DOM解析(下)--写操作
  6. java里面object和string的相互转换
  7. Docker 镜像基本命令操作
  8. python入门经典100例-Python3经典100例(含习题答案) DOC 清晰版
  9. SCRUM 系列之一 ----- 认识SRCUM
  10. 数据库新手常犯的5个错误
  11. JSP基础--J2EE赢在起跑线
  12. axure 7.0 license key
  13. Android技巧之相对高度使用
  14. 婚礼策划|婚礼相册|情人节表白快闪动态PPT模板
  15. 星空主题设计理念_请星星设计理念
  16. openssl1.0.1 完美 升级到 1.0.1g脚本
  17. LightOJ 1220 Mysterious Bacteria
  18. 【论文速递】ISPRS2018 :基于增强极线几何约束以及自适应窗最小二乘匹配方法的立体SAR山区DSM
  19. 在vue中使用velocity动画库实现列表交错过渡
  20. shell脚本输出颜色

热门文章

  1. MapReduce分区-原理
  2. 数据库-事务-事务的特征
  3. MyBatis传入参数为List对象
  4. Netty--Future和Promise
  5. 域netbios名什么意思_域渗透(二):域环境搭建
  6. JDK 是如何判断两个对象是否相同的?判断的流程是什么?
  7. Python学习手册之Python介绍、基本语法(二)
  8. 软件推荐-有道超级计算器
  9. 上海全球“编程一小时”活动记
  10. 江苏:5G先行,智慧江苏再进一步