Select模型学习

通过调用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 _wtoi

int __cdecl wmain(int argc, wchar_t **argv)
{
	system("title 服务器");
	// Declare and initialize variables
	WSADATA 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_SET
		FD_SET(sock,&fdRead);//分配套接字句柄到相应的fd_set
		select(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;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值