新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 中文XML论坛 - 专业的XML技术讨论区计算机技术与应用『 C/C++编程思想 』 → 很幽默的讲解六种Socket I/O模型[转帖] 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 6050 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: 很幽默的讲解六种Socket I/O模型[转帖] 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     longshentailang 帅哥哟,离线,有人找我吗?
      
      
      威望:1
      等级:计算机学士学位
      文章:325
      积分:2990
      门派:XML.ORG.CN
      注册:2006/6/20

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给longshentailang发送一个短消息 把longshentailang加入好友 查看longshentailang的个人资料 搜索longshentailang在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看longshentailang的博客楼主
    发贴心情 很幽默的讲解六种Socket I/O模型[转帖]


    作者:      来源:zz     发表时间:2006-07-30  

    信息来源:幻影论坛     作  者: 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;

    三: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;


       收藏   分享  
    顶(0)
      




    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/11/29 22:46:00
     
     longshentailang 帅哥哟,离线,有人找我吗?
      
      
      威望:1
      等级:计算机学士学位
      文章:325
      积分:2990
      门派:XML.ORG.CN
      注册:2006/6/20

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给longshentailang发送一个短消息 把longshentailang加入好友 查看longshentailang的个人资料 搜索longshentailang在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看longshentailang的博客2
    发贴心情 

    四: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。这一切都是由系统自动调配的,我们无需过问。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/11/29 22:47:00
     
     longshentailang 帅哥哟,离线,有人找我吗?
      
      
      威望:1
      等级:计算机学士学位
      文章:325
      积分:2990
      门派:XML.ORG.CN
      注册:2006/6/20

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给longshentailang发送一个短消息 把longshentailang加入好友 查看longshentailang的个人资料 搜索longshentailang在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看longshentailang的博客3
    发贴心情 
    不好意思,由于前些天在C/C++版块发不了帖,导致发了很多无用的帖子。现在补发一下!
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/11/29 22:50:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客4
    发贴心情 
    很不错,支持一下。继续努力

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2006/11/30 11:25:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2025/4/20 7:47:18

    本主题贴数4,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    62.500ms