Select模型学习

来源:互联网 发布:手机淘宝取消退款流程 编辑:程序博客网 时间:2024/06/05 02:54
通过调用select函数可以确定一个或多个套接字的状态,判断套接字上是否有数据,或 者能否向一个套接字写入数据。

 

 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队列初始化成空队列。



涉及到的结构的定义:a、 fd_set结构:


#define FD_SETSIZE 64

typedef struct fd_set

{

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

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

} fd_set;     
fd_count为已设定socket的数量

fd_array为socket列表,FD_SETSIZE为最大socket数量,建议不小于64。这是微软建 议的。(是否是不应该大于64)


timeval结构:
struct timeval

{

  long tv_sec;               /* 时间的秒值 */

  long tv_usec;             /*时间的毫秒值 */

};

这个结构主要是设置select()函数的等待值,如果将该结构设置为(0,0),则select()函数会立即返回。


select函数各参数的作用:


1. nfds:没有任何用处,主要用来进行系统兼容用,一般设置为0。   

2. readfds:等待可读性检查的套接字组。

3. writefds;等待可写性检查的套接字组。   

4. exceptfds:等待错误检查的套接字组。   

5. timeout:超时时间。  

函数失败的返回值:调用失败返回SOCKET_ERROR,超时返回0。


readfds、writefds、exceptfds三个变量至少有一个不为空,同时这个不为空的套接字组种至少有一个socket,道理很简单,否则要select干什么呢。


举例:测试一个套接字是否可读:
fd_set fdread;

FD_SET(s,&fdread);       //加入套接字

if(FD_ISSET(s,&fread)   //是否存在fread中


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并检查返回值,直到成功为止,但是这样做效率很成问题,开销太大。
感谢天才的微软工程师吧,他们给我们提供了好的解决办法。


// link with Ws2_32.lib#pragma comment(lib,"Ws2_32.lib")#include <winsock2.h>#include <ws2tcpip.h>#include <stdio.h>#include <stdlib.h>   // Needed for _wtoiint __cdecl wmain(int argc, wchar_t **argv){system("title 服务器");// Declare and initialize variablesWSADATA wsaData = {0};int iResult = 0;iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);if (iResult != 0) {wprintf(L"WSAStartup failed: %d\n", iResult);return 1;}SOCKET sock = socket(AF_INET, SOCK_STREAM ,0);if (sock == INVALID_SOCKET) {wprintf(L"socket function failed with error = %d\n", WSAGetLastError() );iResult = closesocket(sock);if (iResult == SOCKET_ERROR) {wprintf(L"closesocket failed with error = %d\n", WSAGetLastError() );WSACleanup();return 1;}}sockaddr_in  aSerAddr;aSerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");aSerAddr.sin_family = AF_INET;aSerAddr.sin_port = htons(1989);int iRetBind = bind(sock,(sockaddr*)&aSerAddr,sizeof(sockaddr_in));int iRetLs = listen(sock,10);sockaddr_in  aClientAddr;int iLen= sizeof(sockaddr_in);SOCKET sockClient = INVALID_SOCKET;fd_set fdRead;timeval fdTime;fdTime.tv_sec = 2;fdTime.tv_usec= 0;while(1){FD_ZERO(&fdRead);//初始化fD_SETFD_SET(sock,&fdRead);//分配套接字句柄到相应的fd_setselect(0,&fdRead,NULL,NULL,&fdTime);//如果套接字句柄还在fd_set里,说明客户端已经有connect的请求发过来了,马上可以accept成功if(FD_ISSET(sock,&fdRead))sockClient = accept(sock,(sockaddr*)&aClientAddr,&iLen);else{printf("没有客户端连接\n");continue;}if(sockClient!=INVALID_SOCKET){printf("%s连接到服务器!\n",inet_ntoa(aClientAddr.sin_addr));send(sockClient,"连接成功",strlen("连接成功"),0);char buffer[100]={0};recv(sockClient,buffer,100,0);printf("客户端:%s",buffer);}}return 0;}




原创粉丝点击