当前位置: 代码迷 >> 综合 >> 【转载整理】Winsock 非阻塞IO
  详细解决方案

【转载整理】Winsock 非阻塞IO

热度:77   发布时间:2024-01-12 23:47:43.0

先来说一下同步/异步、阻塞/非阻塞的问题。

同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。

异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。

非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

这两组概念之间是相似而有区别的,在这里就不细究了。

在Winsock中实现异步的方法有很多,Winsock的IO模型有下面六种:

一:select模型
二:WSAAsyncSelect模型
三:WSAEventSelect模型
四:Overlapped I/O 事件通知模型
五:Overlapped I/O 完成例程模型
六:IOCP模型

一:select模型

老陈非常想看到女儿的信。以至于他每隔10分钟就下楼检查信箱,看是否有女儿的信~~~~~
在这种情况下,“下楼检查信箱”然后回到楼上耽误了老陈太多的时间,以至于老陈无法做其他工作。

二:WSAAsyncSelect模型

后来,老陈使用了微软公司的新式信箱。这种信箱非常先进,一旦信箱里有新的信件,盖茨就会给老陈打电话:喂,大爷,你有新的信件了!从此,老陈再也不必频繁上下楼检查信箱了,牙也不疼了,你瞅准了,蓝天……不是,微软~~~~~~~~

三:WSAEventSelect模型

后来,微软的信箱非常畅销,购买微软信箱的人以百万计数……以至于盖茨每天24小时给客户打电话,累得腰酸背痛,喝蚁力神都不好使~~~~~~
微软改进了他们的信箱:在客户的家中添加一个附加装置,这个装置会监视客户的信箱,每当新的信件来临,此装置会发出“新信件到达”声,提醒老陈去收信。盖茨终于可以睡觉了。

四:Overlapped I/O 事件通知模型

后来,微软通过调查发现,老陈不喜欢上下楼收发信件,因为上下楼其实很浪费时间。于是微软再次改进他们的信箱。新式的信箱采用了更为先进的技术,只要用户告诉微软自己的家在几楼几号,新式信箱会把信件直接传送到用户的家中,然后告诉用户,你的信件已经放到你的家中了!老陈很高兴,因为他不必再亲自收发信件了!

五:Overlapped I/O 完成例程模型

老陈接收到新的信件后,一般的程序是:打开信封—-掏出信纸—-阅读信件—-回复信件……为了进一步减轻用户负担,微软又开发了一种新的技术:用户只要告诉微软对信件的操作步骤,微软信箱将按照这些步骤去处理信件,不再需要用户亲自拆信/阅读/回复了!老陈终于过上了小资生活!

六:IOCP模型

微软信箱似乎很完美,老陈也很满意。但是在一些大公司情况却完全不同!这些大公司有数以万计的信箱,每秒钟都有数以百计的信件需要处理,以至于微软信箱经常因超负荷运转而崩溃!需要重新启动!微软不得不使出杀手锏……
微软给每个大公司派了一名名叫“Completion Port”的超级机器人,让这个机器人去处理那些信件!

其实,上面每种模型都有优点,要根据程序需求而适当选择合适的模型,前面三种模型效率已经比较高,实现起来难道不大,很多一般的网络程序都采用前三种模型,只有对网络要求特别高的一些服务器才会考虑用后面的那些模型。MFC中的CAsyncSocket类就是用的WSAAsyncSelect模型,电驴中也是用的这种,不过在寻找对应socket的时候进行了优化,查找更快,在GridCast中采用的是WSAEventSelect模型,等待。


select模型是WINSOCK 异步I/O模型里最简单的一种,其之所以叫做select模型,是因为该模型的核心就是一个WSA函数select.

      该函数定义如下:

      int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

      n:该参数忽略,之所以保留是因为和早期的berkeley函数兼容

      readfds: 该参数为用于检查是否有数据需要读入(接收)的集合

      writefds:该参数为用于检查是否有数据需要写出(发送)的集合

      exceptfds:该参数为用于检查是否有"例外数据"需要读取的集合.

      timeout:设置select函数检查的超时时间,如果设为NULL,则无限制等待直到上述集合有改变为止,如果设置为0,则select函数立即返回。

      需要注意的是,上述三个集合之中,至少一个不为NULL,不为NULL的集合里,至少一个不为空,否则函数就没有等待的目标而导致该函数失去作用。

      其中fd_set为WINSOCK提供的一个文件集描述符(简单的说就是一个集合而已),里面能够容纳数个SOCKET句柄。这就不得不提到select模型的另外几个核心的函数.

     FD_SET(Socket s,fd_set *SET)   //将s句柄放入集合SET

     FD_ZERO(fd_set *SET)    //清空SET

     FD_CLR(Socket s,fd_set *SET)  //  从SET中清除s

     FD_ISSET(Socket s,fd_set *SET)   //检查s是否在集合SET内,是返回TRUE,否返回FALSE

       以上函数都非常便于使用,只需要将需要检查的SOCKET句柄按应用的需要放入相应的readfds,writefds,exceptfds即可,然后适时的调用select函数。select函数有个很重要的特性,就是会自动删除相应集合里不符合要求的SOCKET句柄,例如,SOCKET S作为需要检查的对象,用FD_SET放入readfds(当然readfds是自己申明的),然后用select函数进行检查,设置timeout为10s,那么如果10s内S没有需要读入的数据,select函数返回的时候会自动将S从readfds中删除,那么在用FD_ISSET检查的时候便可以分辨哪些是可读可写的。

      一般程序大致走向如下。

     1. SOCKET相关操作(create,bind,listen,accept……)

     2.  建立readfds,writefds等集合

     3.  FD_ZERO初始化集合

     4.  FD_SET将SOCKET句柄放入感兴趣的集合内

     5.  适时调用select

     6.  FD_ISSET检查感兴趣集合

     7.  返回 3(一般都是有这一步)