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;
}