`

基于winsock的阻塞和非阻塞通信模型

阅读更多
copy:http://hi.baidu.com/yaofly/blog/item/a0668b4bf71449f882025c50.html
这文章貌似不错,sorry,习惯用貌似了~


摘要:在应用程序开发中,经常涉及各式各样的机器的交互通信问题。在Windows操作系统下,可以使用MFC中的CSocket,也可以使用以Windows Api为基础的Winsock等等。本文主要描述了Winsock的两种实现方式,即阻塞方式和非阻塞方式。并对应这两种方式,描述了Select模式和IOCP模式。
关键字:Winsock Blocking NonBlocking Select模式 完成端口(IOCP)模式
一、Winsock简介

对于众多底层网络协议,Winsock是访问它们的首选接口。而且在每个Win32平台上,Winsock都以不同的形式存在着。Winsock是网络编程接口,而不是协议。在Win32平台上,Winsock接口最终成为一个真正的“与协议无关”接口,尤其是在Winsock 2发布之后。

Win32平台提供的最有用的特征之一是能够同步支持多种不同的网络协议。Windows重定向器保证将网络请求路由到恰当的协议和子系统;但是,有了Winsock,就可以编写可直接使用任何一种协议的网络应用程序了。

在广泛使用的windows平台下,winsock2被简单包装为一组庞大的Api库,通过WSA Start up加载的关于Winsock版本的信息,初始了winsock相关的dll和lib,在成功调用了WSA Startup之后,即可设计自主的与通信有关的行为,当确认了执行完操作后,调用相应的WSA Cleanup,释放对winsock DLL的引用次数。几乎所有在windows平台下可使用的通信框架都是由Winsock扩展而来的。

这里,之所以要再提windows下的winsock Api编程,并不多余,虽然也可以使用CSocket或ACE(ADAPTIVE Communication Environment)框架,但直接对较底层的本地操作系统API,会使我们更深的理解隐藏在框架下的实现,有时也可以解决一些实用问题。

本文涉及的主要是Winsock中面向连接(TCP)部分。

二、阻塞和非阻塞

阻塞socket,又被称为锁定状态的socket。非阻塞socket,又被称为非锁定状态的socket。当调用一个Winsock Api时, Api的调用会耗费一定的CPU时间。当一个操作完成之后才返回到用户态,称其为阻塞,反之,则为非阻塞。当调用Api产生一个基于流协议(TCP)的socket时,系统开辟了接收和发送两个缓冲区,所以操作实际都是用户缓冲区和系统缓冲区的数据交互,并不是实际操作的完成,阻塞也是如此。如果综合被TCP协议默认使用的nagle算法,在数据包(TCP Payload)比较短时,协议可能推迟其发送,就不难想象了。

很多做过Winsock通信的人,都知道非阻塞的socket,也就是异步socket的效率远远高于阻塞socket。可是除了直观的认识外,底层的原理又是什么呢?首先,阻塞的socket的最合理方式是:先知道socket句柄的可读写性,再发起动作。因为如果不做检测而直接发起操作,那么TCP的默认超时将让你后悔不该这么鲁莽。于是,在一次动作中,完成了两次从用户态到系统态的状态转换,异步的socket实际只告诉系统,所期望的操作,而不完成这种操作,系统会对每次提交的操作进行排队。当指定的操作完成时,系统态跳转至用户态。这样,只用了一次状态转换。其次,由于阻塞的特性,它比非阻塞占用了更多的CPU时间。

当然,程序总是要适合需求,有时阻塞的socket亦可满足要求。

三、Select模式

select模式是Winsock中最常见的I/O模型。之所以称其为“select模式”,是由于它的中心思想是利用select函数,实现对I/O的管理。利用select函数,判断套接字上是否存在数据,或者能否向一个套接字写入数据。设计这个函数,唯一的目的是防止应用程序在套接字处于锁定模式中时,在一次I/O绑定调用(如send或recv)过程中,被迫进入“锁定”状态。

对select不再赘述,msdn已经给出了详细的解释。这里要讨论下面两个问题。

1.Select的fd_set数组可承受的最大数

细心的coder可能都注意到了msdn中对FD_SETSIZE(winsock2.h)宏的说明,可以在包含winsock2.h之前重新定义这个宏,它将允许在一个select操作中处理更多的socket句柄(>64)。但是为何定义就是64呢?这不仅是unix的遗留,更是select处理能力的一种衡量标准,过多的socket句柄检测毕竟会影响到对已存在操作的socket的响应。一个最合理的建议是,当程序运行在多CPU的机器上时,可以从逻辑上将socket句柄分为数个组,每组都小于64,用多个线程对每组socket进行select,这样可以增加程序的响应能力。如果是单CPU,则可将FD_SETSIZE增大至256,适当放大timeout。这时每个socket上吞吐量如果还很大,CPU利用率也据高不下,那就要考虑换种模型。

2.Select在多端口侦听中的应用

众所周知,在winsock api中,accept()是一个典型的阻塞操作,通常是建立一个侦听线程来单独执行accept()。如果程序要完成多于一个端口的侦听,自然,建立数个线程也是一个办法,但这里最好使用select。Msdn中解释了这种应用:readfds可以检测出当前listening的socket句柄上是否有有效的connect发生。把本地listening的socket句柄置入readfds,当某个socket有效时,对它调用accept(),此时,发现accept()会立刻成功返回,一个线程就完成了多端口的侦听。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics