WinSock三种选择IO模型
- 格式:docx
- 大小:30.46 KB
- 文档页数:11
Winsock工作模型首先得弄清楚同步、异步、阻塞、非阻塞的概念。
同步和异步是针对通讯的工作模式,阻塞和非阻塞是指socket的I/O操作。
实际上对于socket,只存在阻塞和非阻塞,同步与异步是在程序实现上有所不同。
以阻塞的方式执行recv函数,在没有收到数据前,此函数是不会返回的,所以这很容易执行函数的线程处于等待I/O上的数据状态,然后被挂起。
非阻塞就不一样,执行recv时候不管有没有数据都立即返回,有数据时返回数据,没数据时返回错误。
非阻塞可以带来程序的高效,也带来了写程序中必须注意的地方,非阻塞情况下,发送与接收数据时候,要用户自己管理自己的缓冲区,并且要记录发送与接受的位置,因为很可能发送与接受数据的任务不能一次完成,需要多次调用send和recv才可以完成。
本来同步异步是用来表示通讯模式的,通信的同步,主要是指客户端在发送请求后,必须得在服务端有回应后才发送下一个请求。
所以这个时候的所有请求将会在服务端得到同步。
通信的异步,指客户端在发送请求后,不必等待服务端的回应就可以发送下一个请求,这样对于所有的请求动作来说将会在服务端得到异步,这条请求的链路就象是一个请求队列,所有的动作在这里不会得到同步的。
但是个人感觉,在说到socket的同步异步时候,同步跟阻塞概念差不多,都是有了结果才返回,异步则是告诉系统我要recv数据,然后马上返回,等待数据来了后,系统跟程序说数据到了,然后程序再recv数据。
引用在网上看到的比较好的描述“阻塞block 是指,你拨通某人的电话,但是此人不在,于是你拿着电话等他回来,其间不能再用电话。
同步大概和阻塞差不多。
非阻塞nonblock 是指,你拨通某人的电话,但是此人不在,于是你挂断电话,待会儿再打。
至于到时候他回来没有,只有打了电话才知道。
即所谓的“轮询/ poll”。
异步是指,你拨通某人的电话,但是此人不在,于是你叫接电话的人告诉那人(leave a message),回来后给你打电话(call back)。
学习Winsock API编程Windpows Sockets 是广泛应用的、开放的、支持多种协议的网络编程接口,主要由winsock.h头文件和动态链接库winsock.dll组成。
一、套接字套接字(Sockes)是通信的基础,是支持TCP/IP协议的网络通信的基本操作单元。
可以将套接字看作是不同主机之间的进程进行双向通信的端点。
根据通信网络的特性,套接字可以分为以下两类。
1、流套接字流套接字提供没有边界的数据流(即字节流),能够确保数据流以正确的顺序无重复地被送达,使用于处理大量数据。
流套接字是面向连接的。
2、数据报套接字数据报套接字支持双向数据流,此数据流不能保证按顺序和不重复送达,也不能保证数据传输的可靠性。
数据报套接字是无连接的。
Winsock对有可能阻塞的函数提供了两种处理方式:阻塞方式和非阻塞方式。
在阻塞方式下,收发数据的函数在被调用后一直等到传送完毕或出错才能返回,期间不能进行任何操作。
在非阻塞方式下,函数被调用后立即返回,当网络传送完后,由Winsock给应用程序发送一个消息,通知操作完成。
在编程时,应尽量使用非阻塞模式。
二、Winsock的启动和终止由于Winsock服务是以动态链接库的形式实现的,所以在使用前必须调用WSAStartup函数对其进行初始化,协商Winsock的版本支持,并分配必要的资源。
WSAStartup函数声明如下:int WSAStartup(WORD wVersionRequested,LPWSADATA IpWSAData);参数说明:◇wVersionRequested:指定加载的Winsock版本,通常高位字节指定Winsock 的副版本,低位字节指定Winsock的主版本,然后用MAKEWORD(X,Y)宏获取该值。
◇IpWSAData:WSADATA数据结构指针,其中WSADATA结构的定义如下:Typedef struct WSAData{WORD wVersion; //期望使用的Winsock版本WORD wHighVersion; //返回现有Winsock最高版本char szDescription[WSADESCRIPTION_LEN+1];//套接字实现描述、char szSystemStatus[WSASYS_STATUS_LEN+1];//状态或配置信息unsigned short iMaxSockets; //最大套接字数unsigned short iMaxUdpDg; //最大数据报长度char FAR * IpVendorInfo; //保留}WSADATA,FAR *LPWSADATA;在应用程序关闭套接字连接后,还需要调用WSACleanup 函数终止对Winsock 库的使用,并释放资源,函数声明如下:int WSACleanup(void);三、 Winsock 编程模型不论是流套接字还是数据报套接字编程,一般都采用客户端/服务器模式,其运行原理基本类似。
Windows网络编程系列教程之Select模型讲一下套接字模式和套接字I/O模型的区别。
先说明一下,只针对Winsock,如果你要骨头里挑鸡蛋把UNIX下的套接字概念来往这里套,那就不关我的事。
套接字模式:阻塞套接字和非阻塞套接字。
或者叫同步套接字和异步套接字。
套接字模型:描述如何对套接字的I/O行为进行管理。
Winsock提供的I/O模型一共有五种:select,WSAAsyncSelect,WSAEventSelect,Overlapped,Completion。
今天先讲解select。
1:select模型(选择模型)先看一下下面的这句代码:int iResult = recv(s, buffer,1024);这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返回,不然就会一直阻塞在那里。
在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永远没数据发送过来,那么程序就会被永远锁死。
这个问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差。
Select模型就是为了解决这个问题而出现的。
再看代码:int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);iResult = recv(s, buffer,1024);这一次recv的调用不管套接字连接上有没有数据可以接收都会马上返回。
原因就在于我们用ioctlsocket把套接字设置为非阻塞模式了。
不过你跟踪一下就会发现,在没有数据的情况下,recv确实是马上返回了,但是也返回了一个错误:WSAEWOULDBLOCK,意思就是请求的操作没有成功完成。
看到这里很多人可能会说,那么就重复调用recv并检查返回值,直到成功为止,但是这样做效率很成问题,开销太大。
在《套接字socket及C/S通信的基本概念》和《WinSock编程基础》中,我们介绍了套接字的基本概念和WinSock API的基本调用规范。
我们讨论了阻塞模式/非阻塞模式和同步I/O和异步I/O等话题。
从概念的角度,阻塞模式因其简洁易用便于快速原型化,但在应付建立连接的多个套接字或在数据的收发量不均、时间不定时却极难管理。
另一方面,我们需要对非阻塞模式套接字的 WinSock API调用频繁返回的WSAEWOULDBLOCK错误加以判断处理也显得难于管理。
WinSock套接字I/O模型提供了管理I/O 完成通知的方法,帮助应用程序判断套接字何时可供读写。
共有6中类型的套接字I/O模型可让WinSock应用程序对I/O进行管理,它们包括blocking(阻塞)、select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、overlapped(重叠)以及completionport(完成端口)。
本文讨论三种选择(都带select)模型。
1.基于套接字集合的select模型(1)select模型概述该模型时最初设计是在不使用UNIX操作系统的计算机上实现的,它们采用的是Berkeley套接字方案。
select模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。
之所以称其为“select模型”,是由于它的“中心思想”便是利用select函数,实现对I/O的管理!使用select模型,一般需要调用ioctlsocket 函数将一个套接字从锁定模式切换为非锁定模式。
// 将套接字s设置为非阻塞模式unsigned long nonBlocking = 1;ioctlsocket(s, FIONBIO, (u_long*)&nonBlocking);select模型本质上是一种分类处理思想,预先声明几个FD_SET(fd_set 结构)集合(使用FD_ZERO初始化),例如ReadSet,WriteSet,然后调用宏FD_SET(s,&ReadSet)将关注FD_READ事件的套接字s添加到ReadSet 集合,调用宏FD_SET(s,&WriteSet)将关注FD_WRITE事件的套接字s添加到WriteSet集合。
在Winsock中,重叠I/O(Overlapped I/O)模型能达到更佳的系统性能,高于之前的选择模型,异步选择模型和事件选择模型。
重叠模型的基本设计原理便是让应用程序使用一个重叠的数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock I/O 请求。
针对这些提交的请求,在它们完成之后,我们的应用程序会收到通知,于是我们就可以对数据进行处理了。
使用重叠I/O模型必须使用WSA_FLAG_OVERLAPPED 这个标志,创建一个套接字。
例如:1 SOCKET s = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);如果使用socket这个函数的话,会默认自动使用WSA_FLAG_OVERLAPPED。
创建好套接字后,将其与本地接口绑定到一起之后便可以开始重叠I/O操作了。
不过需要注意的时,在重叠I/O的状态下不能使用send、recv等函数,他们被WSASend、WSARecv函数替换掉了。
基本上都是以WSA开头的Winsock函数,主要有一下几种:∙WSASend∙WSASendTo∙WSARecv∙WSARecvFrom∙WSAloctl∙AcceptEx∙TrnasmitFile使用时还要指定一个WSAOVERLAPPED结构(可选)。
通常情况下,调用这些函数会返回WSA_IO_PENDING,这就说明函数调用成功了,但是I/O函数还没完成。
当调用这些函数的时候,如果指定了WSAOVERLAPPED参数,函数完成后会立即返回,无论套接字是否为阻塞模式,可以通过等待时间对象通知或者通过完成例程来得知I/O请求是否成功。
通常情况下第一种时间通知的方法比较常用。
这里只需要注意一点,重叠函数(如:WSARecv)的参数中都有一个Overlapped 参数,我们可以假设是把我们的WSARecv这样的操作“绑定”到这个重叠结构上,提交一个请求,而不是将操作立即完成,其他的事情就交给重叠结构去做,而其中重叠结构又要与Windows的事件对象“绑定”在一起,这样我们调用完WSARecv 以后就可以“坐享其成”,等到重叠操作完成以后,自然会有与之对应的事件来通知我们操作完成,然后我们就可以来根据重叠操作的结果取得我们想要的数据了。
Linux⽹络编程的5种IO模型:多路复⽤(select、poll、epoll)背景我们在上⼀讲中,对于其中的阻塞/⾮阻塞IO 进⾏了说明。
这⼀讲我们来看多路复⽤机制。
IO复⽤模型 ( I/O multiplexing )所谓I/O多路复⽤机制,就是说通过⼀种机制,可以监视多个描述符,⼀旦某个描述符就绪(⼀般是读就绪或者写就绪),能够通知程序进⾏相应的读写操作。
这种机制的使⽤需要额外的功能来配合: select、poll、epollselect、poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后⾃⼰负责进⾏读写,也就是说这个读写过程是阻塞的。
select时间复杂度O(n)它仅仅知道了,有I/O事件发⽣了,却并不知道是哪那⼏个流(可能有⼀个,多个,甚⾄全部),我们只能⽆差别轮询所有流,找出能读出数据,或者写⼊数据的流,对他们进⾏操作。
所以select具有O(n)的⽆差别轮询复杂度,同时处理的流越多,⽆差别轮询时间就越长。
poll时间复杂度O(n)poll本质上和select没有区别,它将⽤户传⼊的数组拷贝到内核空间,然后查询每个fd对应的设备状态,但是它没有最⼤连接数的限制,原因是它是基于链表来存储的.epoll时间复杂度O(1)epoll可以理解为event poll,不同于忙轮询和⽆差别轮询,epoll会把哪个流发⽣了怎样的I/O事件通知我们。
所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。
(复杂度降低到了O(1))在多路复⽤IO模型中,会有⼀个内核线程不断去轮询多个socket的状态,只有当真正读写事件发⽣时,才真正调⽤实际的IO读写操作。
因为在多路复⽤IO模型中,只需要使⽤⼀个线程就可以管理多个socket,系统不需要建⽴新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有读写事件进⾏时,才会使⽤IO资源,所以它⼤⼤减少了资源占⽤。
Winsock 的I/O操作:1、两种I/O模式阻塞模式:执行I/O操作完成前会一直进行等待,不会将控制权交给程序。
套接字默认为阻塞模式。
可以通过多线程技术进行处理。
非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权。
这种模式使用起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回WSAEWOULDBLOCK错误。
但功能强大。
为了解决这个问题,提出了进行I/O操作的一些I/O模型,下面介绍最常见的三种:Windows Socket五种I/O模型——代码全攻略如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。
Windows操作系统提供了选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)和完成端口(Completion Port)共五种I/O模型。
每一种模型均适用于一种特定的应用场景。
程序员应该对自己的应用需求非常明确,而且综合考虑到程序的扩展性和可移植性等因素,作出自己的选择。
我会以一个回应反射式服务器(与《Windows网络编程》第八章一样)来介绍这五种I/O模型。
我们假设客户端的代码如下(为代码直观,省去所有错误检查,以下同):#include <WINSOCK2.H>#include <stdio.h>#define SERVER_ADDRESS "137.117.2.148"#define PORT 5150#define MSGSIZE 1024#pragma comment(lib, "ws2_32.lib")int main(){WSADATA wsaData;SOCKET sClient;SOCKADDR_IN server;char szMessage[MSGSIZE];int ret;// Initialize Windows socket libraryWSAStartup(0x0202, &wsaData);// Create client socketsClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// Connect to servermemset(&server, 0, sizeof(SOCKADDR_IN));server.sin_family = AF_INET;server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);server.sin_port = htons(PORT);connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));while (TRUE){printf("Send:");gets(szMessage);// Send messagesend(sClient, szMessage, strlen(szMessage), 0);// Receive messageret = recv(sClient, szMessage, MSGSIZE, 0);szMessage[ret] = '\0';printf("Received [%d bytes]: '%s'\n", ret, szMessage);}// Clean upclosesocket(sClient);WSACleanup();return 0;}客户端所做的事情相当简单,创建套接字,连接服务器,然后不停的发送和接收数据。
引言:经过三天的努力,我终于把Winsock I/O 模型,做了一个基本了解,最终还把最为复杂高效的CompletionPort(完成端口)模型看懂了,可以说这是一场比较大的胜利。
下面来着重介绍Completion Port(完成端口)。
一、学习前提:1、会winsock 基础编程,也就是说你要明白winsock 的Tcp 和udp编程流程。
2、学习完成端口前最好是先去好好看下重叠I/O 模型,因为完成端口模型,有一部分还是重叠I/O模型的东西,要是不懂那你就歇菜了。
3、会CreateThread 创建线程最好是明白原理。
二、模型特性:在使用一个技术前如果连这个技术的特性都不了解,那你就真是一个….。
1、首先要强调的是完成端口模型是只适合Window NT 或Windows2000 等windows系统,所以在其他系统上用不了。
2、完成端口模型是一种复杂但是可以用时管理数百甚至数千的套接字的一种模型所以当你的系统要同时处理非常多的通信请求完成端口是最好的选择。
3、一般我们使用完成端口的时候都是按照系统CPU数量来创建IO服务线程的,所以如果你的系统cpu数量越多那么性能当然会越好。
三、模型原理:这个模型原理其实是我个人的一个理解和概括,所以不一定完全正确各位看官见谅不要说我坑你了。
其实所谓的完成端口模型就是先创建一个完成端口(注意我们在创建完成端口的时候指定了这个完成端口同时执行的IO服务线程数量,一般为cpu数量),并创建若干(说若干是因为我们一般是根据cpu数量来创建线程的,这样做是因为每个cpu执行一个线程就避免的线程的切换影响效率,但是实际我们还是会创建大于cpu数量一定值的线程,虽然前面指定了完成端口同时执行的线程只有cpu个数,但是因为有的线程会出现挂起的情况,多创建一点就可以减少因为挂起而使cpu闲置)的服务线程(将完成端口作为参数传递给线程)然后将完成端口和我们关心的套接字关联在一起,并且在关联的时候我们还可以传递一个自定义的结构体。
Winsock编程的五种模型上面介绍的仅仅是最简单的winsock通讯的方法,而实际中很多网络通讯的却很多难以解决的意外情况.例如,Winsock提供了两种套接字模式:锁定和非锁定.当我们使用锁定套接字的时候,我们使用的很多函数,例如accpet,send,recv等等,如果没有数据需要处理,这些函数都不会返回,也就是说,你的应用程序会阻塞在那些函数的调用处.而如果使用非阻塞模式,调用这些函数,不管你有没有数据到达,他都会返回,所以,有可能我们在非阻塞模式里,调用这些函数大部分的情况下会返回失败,所以就需要我们来处理很多的意外出错.这显然不是我们想要看到的情况.我们可以采用Winsock的通讯模型来避免这些情况的发生。
Winsock提供了五种套接字I/O模型来解决这些问题.他们分别是select(选择),WSAAsyncSelect(异步选择),WSAEventSelect (事件选择), overlapped(重叠) , completionport(完成端口) .我们在这里详细介绍一下select,WSAASyncSelect两种模型.select模型是最常见的I/O模型.使用int select( int nfds , fd_set FAR* readfds , fd_set FAR* writefds , fd_set FAR* exceptfds ,const struct timeval FAR * timeout ) ;函数来检查你要调用的socket套接字是否已经有了需要处理的数据.select包含三个socket队列,分别代表:readfds ,检查可读性,writefds,检查可写性,exceptfds,例外数据.timeout是select函数的返回时间.例如,我们想要检查一个套接字是否有数据需要接收,我们可以把套接字句柄加入可读性检查队列中,然后调用select,如果,该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,所以我们只要检查该套接字句柄是否还存在于可读性队列中,就可以知道到底有没有数据需要接收了.Winsock提供了一些宏用来操作套接字队列fd_set.FD_CLR( s,*set) 从队列set删除句柄s.FD_ISSET( s, *set) 检查句柄s是否存在与队列set中.FD_SET( s,*set )把句柄s添加到队列set中.FD_ZERO( *set ) 把set队列初始化成空队列.WSAAsyncSelect(异步选择)模型:WSAASyncSelect模型就是把一个窗口和套接字句柄建立起连接,套接字的网络事件发生时时候,就会把某个消息发送到窗口,然后可以在窗口的消息响应函数中处理数据的接收和发送.int WSAAsyncSelect( SOCKET s, HWND hWnd , unsigned int wMsg , long lEvent ) ; 这个函数可以把套接字句柄和窗口建立起连接,wMsg 是我们必须自定义的一个消息.lEvent就是制定的网络事件.包括FD_READ , FD_WRITE , FD_ACCEPT, FD_CONNECT , FD_CLOSE .几个事件.例如,我需要接收FD_READ , FD_WRITE , FD_CLOSE 的网络事件.可以调用WSAAsyncSelect( s , hWnd , WM_SOCKET , FD_READ | FD_WRITE | FD_CLOSE ) ;这样,当有FD_READ , FD_WRITE 或者FD_CLOSE网络事件时,窗口hWnd将会收到WM_SOCKET消息,消息参数的lParam标志了是什么事件发生.其实大家应该见过这个模型,因为MFC的CSocket类,就是使用这个模型.。
关于重叠I/O,参考《WinSock重叠I/O模型》;关于完成端口的概念及内部机制,参考译文《深度探索I/O完成端口》。
完成端口对象取代了WSAAsyncSelect中的消息驱动和WSAEventSelect中的事件对象,当然完成端口模型的内部机制要比WSAAsyncSelect和WSAEventSelect模型复杂得多。
IOCP内部机制如下图所示:在WinSock中编写完成端口程序,首先要调用CreateIoCompletionPort 函数创建完成端口。
其原型如下:WINBASEAPI HANDLE WINAPICreateIoCompletionPort(HANDLE FileHandle,HANDLE ExistingCompletionPort,DWORD CompletionKey,DWORD NumberOfConcurrentThreads );第一次调用此函数创建一个完成端口时,通常只关注NumberOfConcurrentThreads,它定义了在完成端口上同时允许执行的线程数量。
一般设为0,表示系统内安装了多少个处理器,便允许同时运行多少个线程为完成端口提供服务。
每个处理器各自负责一个线程的运行,避免了过于频繁的线程上下文切换。
hCompletionPort =CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)这个类比重叠I/O事件通知模型中(WSA)CreateEvent。
然后再调用GetSystemInfo(&SystemInfo);取得系统安装的处理器的个数SystemInfo.dwNumberOfProcessors,根据CPU数创建线程池,在完成端口上,为已完成的I/O请求提供服务。
一般线程池的规模,即线程数 = CPU数*2+2。
下面的代码片段演示了线程池的创建。
// 创建线程池,规模为CPU数的两倍for(int i= 0; i< SystemInfo.dwNumberOfProcessors* 2; i++) {HANDLE ThreadHandle;// 创建一个工作线程,并将完成端口作为参数传递给它。
摘要:在应用程序开发中,经常涉及各式各样的机器的交互通信问题。
在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中的IO模型socket为了实现非阻塞模式,winsock提供了几种不同的套接字I/O模型对I/O进行管理,包括:select(选择),WSAAsyncSelect(异步选择),WSAEventSelect(事件选择),Overlapped(重叠)以及Completion port(完成端口),另可以用ioctlsocket(SOCKET s, FIOBIO, int &cmd )设置非阻塞模式,不过这样会非常的复杂。
select是winsock中最常见的i/o模型。
通过调用select函数可确定一个或多个套接字的状态,判断套接字上是否存在数据,或都能否向一个套接字写入数据。
它既能防止应用程序在套接字处于阻塞模式时,在一次i/o操作后被阻塞,同时也防止在套接字处于非锁定模式中时,产生WSAEWOULDBLOCK错误。
1.select函数int select( int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout );其中,参数nfds可忽略,仅仅起到与Berkeley套接字兼容的作用。
readfds,writefds,exceptfds三个fd_set数据类型的参数分别为指向等待可读性检查的套接字组,等待可写性检查的套接字组和指向等待错误检查的套接字组的指针。
在这三个fd_set参数中,至少有一个不为NULL,在任何不为空的集合中,必须包含至少一个套接字句柄,否则,select()函数便没有任何东西可以等待了。
参数timeout为一timeval结构数据,用于指定select()最多等待时间,对阻塞操作则为NULL。
timeval结构的格式为:struct timeval{long tv_sec; //秒long tv_usec; //毫秒}其中,tv_sec字段以秒为单位指定等待时间。
第8章Winsock I/O方法本章重点是如何在Wi n d o w s套接字应用程序中对I / O(输入/输出)操作进行管理。
Wi n s o c k分别提供了“套接字模式”和“套接字I / O模型”,可对一个套接字上的I / O行为加以控制。
其中,套接字模式用于决定在随一个套接字调用时,那些Wi n s o c k函数的行为。
而另一方面,套接字模型描述了一个应用程序如何对套接字上进行的I / O进行管理及处理。
要注意的是,“套接字I / O模型”与“套接字模式”是无关的。
套接字模型的出现,正是为了解决套接字模式存在的某些限制。
Wi n s o c k提供了两种套接字模式:锁定和非锁定。
本章第一部分将详细介绍这两种模式,并阐释一个应用程序如何通过它们管理I / O。
如大家在本章的后面部分所见,Wi n s o c k提供了一些有趣的I / O模型,有助于应用程序通过一种“异步”方式,一次对一个或多个套接字上进行的通信加以管理。
这些模型包括s e l e c t(选择)、W S A A s y n c S e l e c t(异步选择)、W S A E v e n t S e l e c t(事件选择)、Overlapped I/O(重叠式I / O)以及Completion port(完成端口)等等。
到本章结束时,我们打算对各种套接字模式以及I / O模型的优缺点进行总结。
同时,帮助大家判断到底哪一种最适合自己应用程序的要求。
所有Wi n d o w s平台都支持套接字以锁定或非锁定方式工作。
然而,并非每种平台都支持每一种I / O模型。
如表8 - 1所示,在当前版本的Windows CE 中,仅提供了一个I / O模型。
Windows 98和Windows 95(取决于安装的是Winsock 1还是Winsock 2)则支持大多数I / O模型,唯一的例外便是I / O完成端口。
而到了Windows NT和最新发布的Windows 2000中,每种I / O模型都是支持的。
试验3 SELECT I / O模型服务端的程序实现通过这次试验,要掌握Winsock提供了两种套接字模式:锁定和非锁定,一个应用程序如何通过它们管理I / O,Winsock提供了一些I / O模型,s e l e c t模型有助于应用程序通过一种“异步”方式,一次对一个或多个套接字上进行的通信加以管理,防止应用程序在套接字处于锁定模式中时,在一次I / O绑定调用(如s e n d或r e c v)过程中,被迫进入“锁定”状态;同时防止在套接字处于非锁定模式中时,产生W S A E W O U L D B L O C K错误。
相关内容说明select模型s e l e c t(选择)模型是Wi n s o c k中最常见的I / O模型。
之所以称其为“ s e l e c t模型”,是由于它的“中心思想”便是利用s e l e c t函数,实现对I / O的管理!最初设计该模型时,主要面向的是某些使用U n i x操作系统的计算机,它们采用的是B e r k e l e y套接字方案。
s e l e c t模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。
由于Winsock 1.1向后兼容于B e r k e l e y套接字实施方案,所以假如有一个B e r k e l e y套接字应用使用了s e l e c t函数,毋需对其进行任何修改,便可正常运行。
利用s e l e c t函数,我们判断套接字上是否存在数据,或者能否向一个套接字写入数据。
之所以要设计这个函数,唯一的目的便是防止应用程序在套接字处于锁定模式中时,在一次I / O绑定调用(如s e n d或r e c v)过程中,被迫进入“锁定”状态;同时防止在套接字处于非锁定模式中时,产生W S A E W O U L D B L O C K错误。
在《套接字socket及C/S通信的基本概念》和《WinSock编程基础》中,我们介绍了套接字的基本概念和WinSock API的基本调用规范。
我们讨论了阻塞模式/非阻塞模式和同步I/O和异步I/O等话题。
从概念的角度,阻塞模式因其简洁易用便于快速原型化,但在应付建立连接的多个套接字或在数据的收发量不均、时间不定时却极难管理。
另一方面,我们需要对非阻塞模式套接字的 WinSock API调用频繁返回的WSAEWOULDBLOCK错误加以判断处理也显得难于管理。
WinSock套接字I/O模型提供了管理I/O 完成通知的方法,帮助应用程序判断套接字何时可供读写。
共有6中类型的套接字I/O模型可让WinSock应用程序对I/O进行管理,它们包括blocking(阻塞)、select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、overlapped(重叠)以及completionport(完成端口)。
本文讨论三种选择(都带select)模型。
1.基于套接字集合的select模型(1)select模型概述该模型时最初设计是在不使用UNIX操作系统的计算机上实现的,它们采用的是Berkeley套接字方案。
select模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。
之所以称其为“select模型”,是由于它的“中心思想”便是利用select函数,实现对I/O的管理!使用select模型,一般需要调用ioctlsocket 函数将一个套接字从锁定模式切换为非锁定模式。
// 将套接字s设置为非阻塞模式unsigned long nonBlocking = 1;ioctlsocket(s, FIONBIO, (u_long*)&nonBlocking);select模型本质上是一种分类处理思想,预先声明几个FD_SET(fd_set结构)集合(使用FD_ZERO初始化),例如ReadSet,WriteSet,然后调用宏FD_SET(s,&ReadSet)将关注FD_READ事件的套接字s添加到ReadSet集合,调用宏FD_SET(s,&WriteSet)将关注FD_WRITE 事件的套接字s添加到WriteSet集合。
其中宏FD_SET(SOCKET s, fd_set set)将s添加到set集合。
从根本上说,fd_set数据类型代表着一系列按关注事件分类的套接字集合。
然后再调用select函数,对声明的集合ReadSet或WriteSet进行扫描,其函数原型如下:int WSAAPI select(int nfds,fd_set FAR * readfds,fd_set FAR * writefds,fd_set FAR *exceptfds,const struct timeval FAR * timeout );其中,第一个参数 nfds会被忽略,一般赋值0。
之所以仍然要提供这个参数,只是为了保持与早期的Berkeley套接字应用程序的兼容。
其他的三个fd_set参数,一个用于检查可读性(readfds),一个用于检查可写性(writefds),另一个用于例外数据(exceptfds)。
最后一个参数timeout 用于决定select()等待I/O操作完成时最大忍耐时间,在等待时间内select()函数阻塞。
当timeout为空时,无限等待直到有I/O完成;当*timeout=0时,select()函数立即返回,用做轮询。
例如我们只关注FD_READ事件,则select(0,&ReadSet,NULL,NULL,NULL)。
WinSock要求这三个fd_set 参数至少有一个不为NULL,而在其他平台下经常只关注最后一个参数用于实现相当于sleep()的延时功能。
select()函数用于判断套接字上是否存在数据(any data incoming?)或者能否向一个套接字写数据(output buffer available?)。
调用select()会修改每个fd_set结构,它扫描注册到集合ReadSet和WriteSet中的套接字是否有读写事件发生,若有,则对集合进行更新,删除那些不存在待决I/O操作的套接字句柄。
select()完成后,返回所有仍在fd_set集合中的套接字句柄总数。
然后,我们需要遍历查询之前注册到某个集合中的套接字是否仍为其中一部分。
这需要调用FD_ISSET(SOCKET s, fd_set set)来测试套接字是否属于关注同类事件的套接字集合set。
若是,则对待决的I/O进行处理(再次recv()/send()执行真正的拷贝)。
(2)select模型的应用实例由于select模型源于Berkeley套接字方案,故常用作实现跨平台的POLL组件。
在Linux下,select和poll是一个级别的,以下梳理了经典开源通信库中用到的select模型。
(1)curl/lib/select.h(c)中的Curl_socket_ready()调用。
/** This is an internal function used for waiting for read orwrite* events on a pair of file descriptors. It uses poll() whena fine* poll() is available, in order to avoid limits withFD_SETSIZE,* otherwise select() is used.*/(2)thttpd/fdwatch.h(c)中fdwatch()中的WATCH()调用。
/* fdwatch.h - header file for fdwatch package**** This package abstracts the use of theselect()/poll()/kqueue()** system calls. The basic function of these calls is to watch a set** of file descriptors for activity.**/(3)Apache Httpd/httpd/srclib/apr/下的include/apr_poll.h中定义了Pollset Methods 的枚举变量apr_pollset_method_e。
poll/unix/select.c中的apr_poll()和impl_pollset_poll()调用。
(4)nginx/Windows使用的是 Win32的 API ,而不是Cygwin模拟的。
当前只有select 这种网络模式,所以你不能指望它拥有高性能和高可扩展性。
nginx/src/event/modulesngx_poll_module.cngx_select_module.c/ngx_win32_select_module.c中的ngx_select_process_events()调用。
(5)其他C++ Sockets Library中的SocketHandler::ISocketHandler_Select()Jrtplib中的RTPUDPv4Transmitter::WaitForIncomingData() live555中的blockUntilReadable()和BasicTaskScheduler::SingleStep()PeerCast中的WSAClientSocket::checkTimeout()……(3)select模型的局限性select模型的优势在于能够从单个线程的多个套接字上进行多重连接及I/O管理,这样就避免了伴随阻塞套接字和多重连接的线程剧增。
但可以加到fd_set结构中的最大套接字数量FD_SETSIZE在WINSOCK2.H中定义为64,底层程序强加了一个fd_set的最大值,通常情况下是1024。
当然,我们可以分批FD_SET()→select()→FD_ISSET()来突破此限制。
select模型可以跨平台,对于千路并发的中小型服务器差不多够用。
在具体平台开发网络通信程序时,可以结合平台特性,发挥平台机制优势。
2.基于Windows消息处理WSAAsyncSelcet模型WinSock提供了一个有用的异步I/O通知模型。
利用这个模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知。
具体的做法是在创建好一个套接字后,调用WSAAsyncSelect函数,它的函数原型如下:int WSAAPI WSAAsyncSelect(SOCKET s,HWND hWnd,u_int wMsg,long lEvent);调用WSAAsyncSelect()函数时,套接字即自动设置为非阻塞模式。
这个函数完成的功能是,将参数一所指定的套接字s(包括监听套接字和会话套接字)上感兴趣的一系列网络事件以位或|掩码组合形式(FD_XXX|FD_XXX)注册到参数四lEvent中,然后将lEvent中的网络事件通知绑定到参数二指定的窗口hWnd和参数三指定的自定义消息wMsg进行处理。
对于标准的Windows例程(常称为“WindowProc”),这个模型充分利用了Windows窗口消息处理机制。
该模型亦得到了MFC(Microsoft Foundation Class,微软基础类库)对象CSocket的采纳。
由于使用Windows消息机制,故要想在应用程序中使用WSAAsyncSelect 模型,首先必须用CreateWindow()函数创建一个窗口,再为该窗口提供一个窗口过程处理函数(WindowProc)。
然后在WindowProc中读取自定义的WM_SOCKET消息内容,针对不同的网络事件进行相关处理。
参考《VC网络通信API概览》中的CAsyncSocket/CSocket。
网络事件消息的wParam参数为对应发生该事件的套接字句柄,lParam参数的高字位(一般用WSAGETSELECTERROR宏取得HIWORD)包含出错码,lParam参数的低字位(一般用WSAGETSELECTEVENT宏取得LOWORD)则标识了网络事件代码(FD_XXX)。
一般先检查高位,再检查低位进行网络事件的处理。
在实际使用时,要注意各个网络事件(FD_XXX)发生的时机判断并进行合理的I/O处理。
WSAAsyncSelect模型适合合作性的多任务消息GUI环境,优点是它可以在系统开销不大的情况下同时处理多个连接,而select模型则需要建立fd_set结构。