WhyCan Forum

本站无需注册,无需积分,无需回复可下载所有资料,如果真的喜欢小站,请您注册之后请至少回复一个帖子激活Id,谢谢支持! 站长QQ: 516333132 (挖坑网/填坑网) admin@whycan.cn

您尚未登录。

#1 2017-09-09 20:24:09

cgpsky
会员
注册时间: 2017-09-07
累计积分: 7

网络编程之select;epoll;IOCP 陈兰若谈

陈兰若(我)网络编程不敢说是精通, 但也略有小成!有着基础网络封装的深入经验!不管Linux异步网络之基于select或是epoll的应用封装, 还是Window异步网络之基于WSAAsyncSelect、 WSAEventSelect, select(通用)的应用封装,亦或是完成端口IOCP的应用封装, 有人说IOCP也是异步网络, 对, 但又不全对! 下面我有详解!
      我把网络归为三类(IO级别来分, 没有谈到形而上更高架构应用的), 阻塞的, 非阻塞的(异步的), IOCP(当然也是异步的, 但有别于其他异步网络,会讲到, 别急)!

      阻塞的网络, 即阻塞socket IO 模型, 最简单,最low的, 哦对, 不能这样说(看我有这样), 实际上也有很多应用的。,为什么简单,换言之,每次处理网络输入输出操作(如send 或者 recv等)的时候, 线程都将挂起,直到操作完成为止; 通常可以这样设置socket
       u_long nNoBlock = 1;
       ioctlsocket(sockt, FIONBIO, &nNoBlock):
       就如我上面说的那样, 阻塞socket IO 模型的优点就是简单, 程序易于编写, 可用于快速原型开发!机构内部网路连接数不是很多比较实用! 多用户连接时, 需为每个用户开辟一个线程。

       非阻塞网络(异步网络,和IOCP不同的), 我通常将其命名为: 就绪通告socket IO模型, 实际上更为贴切。因为, socket IO 准备好了(接收网络数据时, 数据已经到了我们系统接收缓存队列中, 这时读取数据基本上都会成功;发送网络数据时, 数据已经到了我们系统发送缓存队列中, 这时发送就会基本成功),会通过各种方式通知我们的程序! 就是由于这种通知我们的方式不同, 将异步分为不同的类型。下面细说:
                     1, select的socket IO 模型, Linux和Window平台都可以应用,select模型的名字来源于select函数。当线程调用socket IO函数时, 比如
                     recv, 此时如果套接字(socket)处于阻塞模式,  则直接调用recv有可能会使线程阻塞; 如果套接字处于非阻塞模式。直接调用recv又有可
                     能会返回WSAEWOULDBLOCK--还是要在某个时候重新调用recv来读取数据,也就是说无论套接字是阻塞模式还是非阻塞模式, 直接调用
                     recv都有可能出现问题, select 模型, 线程要调用recv时, 不直接调用recv, 而是把recv的socket的句柄纳入select的监视之下(此处表达
                     非常贴切), 然后在调用select, 通常select:
                               int select(
                                   int  nfds,                    /*-------socket 句柄数-------*/
                                   fd_set*   readfds,       /*-------希望读取数据时被监视的socket 句柄集合-------*/
                                   fd_set*   writefds,       /*-------希望发送数据时被监视的socket 句柄集合-------*/
                                   fd_set*   exceptfds,     /*-------希望发送数据时被监视的socket 句柄集合-------*/
                                   const struct timeval* timeout) /*-----------超时设置---------------/
                      正是fd_set是集合性质的, select才具备了检测多个套接字(socket)的能力, 如果我们需要对某个套接字进行操作, 则需要把该套接字的
                      句柄填入这些fd_set集合中去, readfds 是针对读操作而言的, 可以包含accept, recv等, writefds则针对写操作,可以包含connect、
                      send等, 还是举个例子吧:
                                  假设现在有两个套接字, 他们的句柄分别是 hSocket1和hSocket2, 我们需要对hSocket1进行写操作, 而对hSocket2则需要进行
                                  读和写操作, 那么在调用select之前, 我们应该填充好readfds以及writefds, 如下:
                                   readfds:     hSocket2
                                   writefds:     hSocket1,  hSocket2
                     当select返回时, 它会更新readfds和writefds, 使它们 只 包含那些就绪的套接字句柄,比如说, 如果select 返回时, 仅有hSocket2的写
                     操作就绪, 那么readfds 和 writefds就变成如下所示:
                                   readfds:     hSocket2
                                   writefds:     hSocket1,  hSocket2
                     也就是说, 当select返回时, readfds 和 writefds 是原来的集合的子集, 这些子集中包含的就是已经就绪的套接字的句柄。因此在select返
                     回时,线程应该检查这些子集, 进而决定应该进行操作! 一般而言, 所有的socket(被select监视)的, 都应该填入exceptfds中, 进而确
                     定该套接字是否发送了异常和错误。
                     2.WSAAsyncSelect模型, 和 select模型一样, WSAAsyncSelect 模型也属于就绪通告socket IO模型的一种, 不一样的是select 模型使
                     用select函数和fd_set来通知线程I/O操作已经就绪, WSAAsyncSelect模型则使用Windows窗口过程和消息来通知:
                              int  WSAAsyncSelect(
                                      SOCKET s,
                                      HWND hWnd,
                                      u_int, wMsg,
                                      long lEvent);
                    做过Windows编程的, 一目了然, WSAAsyncSelect就是把一个socket句柄关联到一个窗口句柄hWnd(消息是wMsg), 当然, 我们我们可
                    以关联多个sockets句柄到同一个窗口。 这样一个线程就可以同时服务多个客户端连接 , 第三个参数就是我们自定义的消息, 当socket操作就
                    绪时, 就会该消息, 用相应的窗口过程处理即可, 简单吧。 开发是需要注意的细节很多, 在此不再赘述!@我, 再回复!

                    3.WSAEventSelect模型, 它既有select模型的特点, 也有WSAAsyncSelect模型的特点, Event, 即该模型使用了Windows的事件内核对
                    象(Event Kernel Object) 来作为就绪通告的通知方式。
                    int WSAEventSelect(
                          SOCKET s,
                          WSAEVENT hEventObject, /*------------事件--------------*/
                          long lEvent);
                    s 自然代表socket, hEvnetObject, 我们定义的事件, 第三个参数和前面WSAAsyncSelect函数的最后一个参数的含义是完全一样的, 即代
                    表我们说感兴趣的I/O消息, 如FD_READ, FD_WRITE等(WSAAsyncSelect的补充)!,Windows有些东西做的很人性化。 在此不再赘
                    述!@我, 再回复!

       IOCP( 完成端口), 前面已经说了, IOCP可以被认为是异步的, 不会让线程阻塞, 但是和前面讲的的就绪通告socket I/O 还是不一样。主要, 放大招
       了! PSPS 注意:
                      在前面的阐述中,  我们知道, 使用异步(就绪通告)socket I/O模型时, 系统会也会通知我们的线程, 而线程在收到通知后, 会去再次调
                      用相关的I/O函函数。但是使用IOCP(属于我起名曰 完成就绪通告)模型时, 系统在 完成(注意)I/O操作后也会通知我们的线程, 但是线
                      程收到通知后, 不再需要向就绪通告(异步, 可以叫传统异步)I/O模型一样, 再去调用相关的I/O函数,因为IOCP(完成就绪通告的一种)
                      的通知意味着相关的I/O的操作已经完成了。所以我叫它, 完成就绪通告, 关键是完成!
              完成就绪通告I/O(有些书叫重叠I/O)模型的代表又很多,譬如,  完成就绪通告I/O + 事件通告; 完成就绪通告I/O + 回调; 完成就绪通告I/O
              + 完成端口! 由于篇幅问题, 前面两个在此不展开讨论, 一句话: @我, 再回复!, 完成端口要重点说说:

              那么什么是IOCP呢, 其实是一种内核对象(我们知道,Windows 内核对象有多种,包括事件内核CEvent对象, 线程内核对象CTHread, 文件内
              核对象CFILE,IOCP也是一种),所以目前专属Windows(据说ACE,boost实现相应的功能)  IOCP这种内核对象比较特殊,因为它用于通知应用
              程序异步I/O完成的, 换句话说,当一个异步网络I/O 操作完成时,IOCP将通知应用程序 。 怎么实现的呢?     

              既然是一个内核对象, 那么就像事件内核对象的使用一样, 首先, 创建一个IOCP内核对象,API是:
                       HANDLE CreateIoCompletionPort( /*-----名字多么明白呀------*/
                                     HANDLE       hFile,
                                     HANDLE       hExistingCompletionPort,
                                     ULONG_PTR CompletionKey,
                                     DWORD       dwNumberOfConcurrentThreads);
             这个函数比较复杂牛逼, 关键是牛逼, 因为它有两个功能:创建一个IOCP以及把一个设备(在此是socket句柄)关联到一个存在的IOCP。当我们创
             建IOCP时, 可以忽略前面三个参数, 因为这三个参数是用于把设备关联到IOCP的, 可以使用下面的代码来创建一个IOCP:
                        HANDLE hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL,0, dwNumberOfConcurrentThreads);
             把套接字hSocket关联到IOCP上 则如下
                        hIop2 = CreateCompletionPort(hSocket, hIocp, 0, 0);
             其中参数hIocp 是上面的创建的, 成功返回的就是hIocp 即 hIocp2 == hIocp;
             那么怎样将IOCP帮到线程池呢,在线程函数里调用下面的函数即可
                          BOOL GetQueuedCompletionStatus(
                          HANDLE hCompletionPort,
                          PDWORD  pdwNumberOfBytesTransferred,
                          PULONG_PTR pComletionKey,
                          OVERLAPPED** ppOverlapped,
                          DWORD            dwMilliseconds);
            此函数返回, 代表所在线程被唤醒来处理我们应用的逻辑!当然还有很多细节需要注意, 开发时,遇到疑问时, @我,一起讨论!


有人会说, 现在网上开源的网络的框架那么多, IOCP也有封装的现成的。为什么要搞懂这些底层的原理。 像我, 也封装过IOCP, 异步(客户端)网络,但是我还是要说, 我们要知其然也要知其所以然, 为什么要这样, 我看到很多人用开源的框架, 总是出问题, 还怪人家框架写的有问题, 其实是他没有搞懂原理, 用法不得当, 愚蠢!进步之心不容明灭。SSH也值得写篇稿子!

最近编辑记录 cgpsky (2017-09-09 22:48:58)

离线

#2 2017-09-11 09:35:07

尧哥
会员
注册时间: 2017-09-06
累计积分: 7

Re: 网络编程之select;epoll;IOCP 陈兰若谈

离线

#3 2017-09-11 10:29:02

沉鱼
Administrator
注册时间: 2017-09-06
累计积分: 161

Re: 网络编程之select;epoll;IOCP 陈兰若谈

学习鹏哥的钻研精神

离线

#4 2017-09-13 18:39:24

熊哥哥
会员
注册时间: 2017-09-07
累计积分: 18

Re: 网络编程之select;epoll;IOCP 陈兰若谈

wink

离线

#5 2017-09-13 18:40:34

熊哥哥
会员
注册时间: 2017-09-07
累计积分: 18

Re: 网络编程之select;epoll;IOCP 陈兰若谈

FluxBB bbcode 测试

离线

#6 2019-01-28 12:03:46

xinyu_khan
会员
注册时间: 2019-01-15
累计积分: 31

Re: 网络编程之select;epoll;IOCP 陈兰若谈

厉害了

离线

页脚