测试也可以很技术!

【测试技术】I/O模型之一:select模型(一)

上一篇 / 下一篇  2012-11-27 20:33:51 / 个人分类:win socket网络编程

Win socket编程I/O模型之一:select模型()

 

-----牛奋原创,转载注明出处,谢谢!-----

 

上一讲主要讲了windows socket编程的基础知识,完成了与服务器之间的首次对话。但是这显然不能满足我们,我们需要测试服务器的并发处理能力,就要同时与服务器建立很多的连接,并且在众多的连接上进行交流。

 

如果学过多线程的朋友,首先想到的思路可能是,没建立一个连接,我就创建一个线程来处理,在这个线程里面进行与服务器的交流(发送和接收数据)。这种思路也是可以的,实现起来也比较简单,但是CPU就得被你累死了。因为你想,你要创建500个连接,就得启动500个线程,对吧,那CPU就得不断的在这500个线程之间进行切换,而线程之间的切换时很耗用CPU运行时间的,最后CPU就忙着切换了,其他事情都别干了,所以这种效率极其的低下,不适合并发数较多的时候使用。

不过,很好的是,windows考虑到了上述的问题,提供了几种解决方案,也就是几种I/O模型,今天开始讲第一个模型:select模型。

 

在将select模型之前,先来阐述一个概念,叫“异步与同步”。

 

同步:同步就是客户端与服务器之间的交流是这样处理的,客户端先send一条数据给服务器,然后recv等在这里,只要服务器不返回数据,就一直傻等在这里,其他什么事情也不干,直到服务器返回为止。当然,服务器什么时候返回数据了,客户端也能马上收到。客户端与服务器之间的交流是同步的。

异步:异步的交流是这样的,客户端send一条数据给服务器,然后就去干别的事情了,它不关心服务器什么时候会返回数据,等到服务器返回数据的时候呢,它会收到一个通知说,服务器已经返回数据啦,你可以来取了,这个时候再去取。

同步和异步最大的区别就是:会不会傻等。等的我花都谢了啊......

 

那么select模型是干什么的呢,首先select模型管理的socket都是同步的,但是呢它做了一个改进,它扮演了一个邮箱的角色。当你把socket交给select模型管理的时候,select模型就不断的在你的socket上进行监测,然后对你的socket进行分类,然后告诉你,哪些socket是可以用来发送数据的,哪些socket收到了服务器返回的数据了,然后呢,你就可以在那些可以发送数据的socket上发送数据,从收到服务器返回数据的socket上接收数据啦。

当然,select管理socket的能力不是很强,单select只能管理64(FD_SETSIZE)socket,如果需要管理更多的socket就需要维护一个线程池了,或者程序中显示定义更改FD_SETSIZE的值。

 

先来看select是怎么工作的:

1) 先介绍一个结构体fd_set

winsock2.h中式这样定义的

typedef struct fd_set {

       u_int fd_count;              /* how many are SET? */

       SOCKET fd_array[FD_SETSIZE];  /* an array of SOCKETs */

} fd_set;

 

2) select模型处理的所有步骤都是基于上面这个结构体,select会将你交给它的socket都放到上面的SOCKET数组中,然后在对这个数组进行轮询。

 

3) select具体有以下几步:

//假设你已经创立了MAX_SOCKET_COUNTSOCKET,并且与服务器建立了连接

SOCKET sClients[MAX_SOCKET_COUNT];

 

//第一步:声明三个fd_set,分别对应SOCKET可能的三种状态

fd_set fdSend; //专门用来存储可以send数据的SOCKET

fd_set fdRecv; //专门用来存储可以recv数据的SOCKET

fd_set fdExcept;//专门用来存储出现异常的SOCKET

 

//第二步:调用FD_ZERO宏,将声明的fd_set清零

FD_ZERO(&fdSend);

FD_ZERO(&fdRecv);

FD_ZERO(&fdExcept);

 

//第三步:调用FD_SET宏,将sClients数组中的SOCKET们全部放到邮箱(fd_set)里面去

//注意,这里三个邮箱都要放

for (i=0;i<MAX_SOCKET_COUNT;i++)

{

       FD_SET(sClients[i],&fdSend);

       FD_SET(sClients[i],&fdRecv);

       FD_SET(sClients[i],&fdExcept);

}

 

//第四步:select开始轮询这些SOCKET了,并判断SOCKET上发生了哪些事情

timeval tv = {0,1}; //设置超时时间,如果设成0,则select立即返回

 

//将三个邮箱都交给select处理,处理完以后,

//fdRecv中保留的就是有recv到数据的SOCKET

//fdSend中保留的就是可以send数据的SOCKET

//fdExcept中保留的就是存在异常的SOCKET

select(0,&fdRecv,&fdSend,&fdExcept,&tv);

//int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
/*参数列表
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,在Windows中这个参数的值无所谓,可以设置不正确。
fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。
struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态:
第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

*/

 

//第五步:调用FD_ISSET宏,确定某个SOCKET到底在哪个fd_set中,然后干对应的事情

for (i=0;i<MAX_SOCKET_COUNT;i++)

{

       if (FD_ISSET(sClients[i],&fdRecv))

       {

              //sClients[i]仍然在fdRecv中,说明上面接收到了服务端发来的数据,调用recv获取数据

              char szRecvBuf[256];

              ZeroMemory(szRecvBuf,sizeof(szRecvBuf)/sizeof(char));

              int iRecvBytes = recv(sClients[i],szRecvBuf,256,0);

              printf("client [%i] recv data from server! data = %s\n",sClients[i],szRecvBuf);

              

              //特别注意!如果这里recv到数据长度为0,说明服务端已经关闭了这个链接

              if (iRecvBytes == 0)

              {

                     printf("server shut down the socket [%i]!\n",sClients[i]);

              }

       }

 

       else if (FD_ISSET(sClients[i],&fdSend))

       {

              //sClients[i]仍然在fdSend中,说明可以在这个SOCKET上向服务器发送数据了

              char szSendBuf[] = "Hello,I'm client!";

              send(sClients[i],szSendBuf,strlen(szSendBuf),0);

              printf("client [%i] send data!\n",sClients[i]);

       }

       

       else if (FD_ISSET(sClients[i],&fdExcept))

       {

              //sClients[i]仍然在fdExcept中,说明这个SOCKET出现了异常

              printf("client [%i] maybe except!\n",sClients[i]);

              //关闭这个链接

              closesocket(sClients[i]);

       }

}

 

然后用while(TRUE)不断的重复上面几个步骤,就可以达到同时处理多个SOCKET的目的了。

但是从上面的代码也可以看出,要完成select整个过程,需要两个for循环,就是不断的轮询,这样的工作机制显然无法同时处理太多的SOCKET。单线程中最多只能处理64个,如果想处理更多的SOCKET只能去维护一个线程池了,这个下一节再讲。

 

详细代码参见附件。

 

   原则还是多练!代码永远不是在看别人的教程中学会的!

TAG:

 

评分:0

我来说两句

日历

« 2024-04-22  
 123456
78910111213
14151617181920
21222324252627
282930    

数据统计

  • 访问量: 3206
  • 日志数: 3
  • 建立时间: 2012-11-26
  • 更新时间: 2012-12-06

RSS订阅

Open Toolbar