select
The select function determines the status of one or more sockets, waiting if necessary, to perform synchronous I/O.
Syntax
int select( _In_ int nfds,
_Inout_ fd_set *readfds,
_Inout_ fd_set *writefds,
_Inout_ fd_set *exceptfds,
_In_ const struct timeval *timeout);
Parameters
nfds [in]
Ignored. The nfds parameter is included only for compatibility with Berkeley sockets.
Windows下忽略。Linux才有意义。
readfds [in, out]
An optional pointer to a set of sockets to be checked for readability.
writefds [in, out]
An optional pointer to a set of sockets to be checked for writability.
exceptfds [in, out]
An optional pointer to a set of sockets to be checked for errors.
timeout [in]
The maximum time for select to wait, provided in the form of a TIMEVAL structure. Set the timeout parameter to null for blocking operations.
select函数最大等待时间。
设置为NULL时阻塞。
Return value
The select function returns the total number of socket handles that are ready and contained in the fd_set structures, zero if the time limit expired, or SOCKET_ERROR if an error occurred. If the return value is SOCKET_ERROR, WSAGetLastError can be used to retrieve a specific error code.
正常返回fd_set结构中准备好的(可读、可写或者发生异常)socket句柄的总个数。
发生错误时,返回SOCKET_ERROR。
timeout时,返回0
/*
* Structure used in select() call, taken from the BSD file sys/time.h.
*/
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
tv_usec单位为微秒,即一秒的1000000分之一。
FD_SET
FD_SET是什么
...\Microsoft SDKs\Windows\v7.0A\include\winsock2.h
#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;
typedef struct fd_set FD_SET;
可看出FD_SET包含两部分:
fd_count
SOCKET队列的长度。fd_array
SOCKET队列。最大为FD_SETSIZE。
FD_SET操作宏
FD_ISSET(s, *set)
用于检测一个描述符是否是fd_set集合的可读或者可写成员,不在返回0,是返回非0.
select使用方法
在调用select函数对套接字进行监视之前,必须将要监视的套接字分配给上述三个数组中的一个。然后调用select函数,再次判断需要监视的套接字是否还在原来的集合中。就可以知道该集合是否正在发生IO操作。
例如:应用程序想要判断某个套接字是否存在可读的数据,需要进行如下步骤:
1:将该套接字加入到readfds集合。
2:以readfds作为第二个参数调用select函数。
3:当select函数返回时,应用程序判断该套接字是否仍然存在于readfds集合。
4:如果该套接字存在与readfds集合,则表明该套接字可读。此时就可以调用recv函数接收数据。否则,该套接字不可读。
http://blog.csdn.net/ithzhang/article/details/8363951
调用select函数后,留在set中的socket须指定条件。
In summary, a socket will be identified in a particular set when select returns if:
readfds:
- If listen has been called and a connection is pending, accept will succeed. (新的连接请求)
- Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
- Connection has been closed/reset/terminated. (连接关闭/重置等)
writefds:
- If processing a connect call (nonblocking), connection has succeeded.(当前socket通过connect连接其他tcp服务器成功)
- Data can be sent.
exceptfds:
- If processing a connect call (nonblocking), connection attempt failed.
- OOB data is available for reading (only if SO_OOBINLINE is disabled).
select优缺点
优点:
int select( _In_ int nfds,
_Inout_ fd_set *readfds,
_Inout_ fd_set *writefds,
_Inout_ fd_set *exceptfds,
_In_ const struct timeval *timeout);
- 跨平台性比较好
- 可通过timeout设置超时时间
- 可同时等待多个套接字
缺点:
- 句柄数目受限
select的句柄数目受限,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE 1024
表示select最多同时监听1024个fd
- 随着FD的数目增长而效率降低
select() 接口并不是实现“事件驱动”的最好选择。
因为当需要探测的句柄值较大时,select() 接口本身需要消耗大量时间去轮询各个句柄。
很多操作系统提供了更为高效的接口,如 linux 提供了 epoll
http://www.open-open.com/lib/view/open1346029754786.html
select调用小demo
// pxSelect.cpp : 定义控制台应用程序的入口点。
// Zhaoliang Guo
// 20170617
// A demo for select
#include "stdafx.h"
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
FD_SET fdReadSet;
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsaData;
WORD wVersionRequested;
wVersionRequested = MAKEWORD(2, 2);
int ret = WSAStartup(wVersionRequested, &wsaData); //加载套接字库
if(ret!=0)
{
printf("WSAStartup() failed!\n");
return -1;
}
// 确认WinSock DLL支持版本2.2
if(LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion)!=2)
{
WSACleanup();
printf("Invalid WinSock version!\n");
return -1;
}
SOCKET sockServer = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrServ;
memset(&addrServ, 0, sizeof(sockaddr_in));
int nSockAddrLen = sizeof(SOCKADDR);
addrServ.sin_family = AF_INET;
addrServ.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrServ.sin_port = htons(6666);
bind(sockServer, (sockaddr *)&addrServ, nSockAddrLen);
listen(sockServer, 5);
// 超时时间
timeval tvTimeout;
tvTimeout.tv_sec = 2;
tvTimeout.tv_usec = 0;
SOCKET sockConnected;
int nAddrLen = sizeof(sockaddr_in);
char szSendBuffer[1024] = {0};
char szRecvBuffer[1024] = {0};
cout<<"waiting connection..."<<endl;
while (true)
{
FD_ZERO(&fdReadSet);
FD_SET(sockServer, &fdReadSet);
int nRet = select(0, &fdReadSet, NULL, NULL, &tvTimeout);
if (-1 == nRet)
{
cout<<"error. break..."<<endl;
break;
}
else if (0 == nRet) // timeout
{
cout<<"timeout. select again..."<<endl;
continue;
}
else
{
cout<<"connection coming..."<<endl;
/*
如果套接字句柄还在fd_set里
说明客户端已经有connect的请求发过来了
马上可以accept成功
*/
if (FD_ISSET(sockServer, &fdReadSet)) /*有新的客户端连接到来*/
{
sockaddr_in addrClient; // 用于保存客户端的网络节点的信息
sockConnected = accept(sockServer, (sockaddr *)&addrClient, &nSockAddrLen);
if(sockConnected != INVALID_SOCKET) //创建成功
{
ZeroMemory(szSendBuffer, 1024);
//inet_ntoa将结构转换为十进制的IP地址字符串
sprintf_s(szSendBuffer, 1024, "Welcome %s!", inet_ntoa(addrClient.sin_addr));
//成功建立连接后向客户端发送数据,结果将显示在客户端上
send(sockConnected, szSendBuffer, strlen(szSendBuffer) + 1, 0);
ZeroMemory(szRecvBuffer, 1024);
recv(sockConnected, szRecvBuffer, 1024, 0);
cout<<szRecvBuffer<<endl;
closesocket(sockConnected);
}
else
{
int nErrorCode = WSAGetLastError();
printf("the err code is:%d/n", nErrorCode);
}
}
}
}
closesocket(sockServer);
WSACleanup();
return 0;
}
References:
http://blog.csdn.net/kikilizhm/article/details/8201512
https://msdn.microsoft.com/en-us/library/windows/desktop/ms740141(v=vs.85).aspx
http://blog.csdn.net/ithzhang/article/details/8363951
http://blog.csdn.net/ysu108/article/details/7570571