Socket描述符准备好的条件
select函数的功能就是允许进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某个指定的时间之后才唤醒进程;
我们可以调用函数select并通知内核仅在以下情况发生时才返回:
A:集合{1,4,5}中的任何描述符准备好读;或
B:集合{2,7}中的任何描述符准备好写;或
C:集合{1,4}中的任何描述符有异常条件待处理;或
D:经过了10.2秒;
也就是说,通知内核,我们对哪些描述符感兴趣(读、写、异常条件)以及等待多长时间.我们所关心的描述符不受限于socket,任何描述符都可以用select函数来测试;
当select返回时,描述符集合中任何与没有准备好的描述符相对应的位都会被清0,所以,每次调用select时,我们都得将所有描述符集合中关心的位置都置为1;select的返回值表示的所有描述符中已准备好的描述符的个数;如果在任何描述符准备好之前定时器到,则返回0;返回-1表示出错(这是有可能的,比如:函数被一个捕获的信号中断);
引起select返回socket"准备好"的条件有以下三个说明:(TCP V2的图16.52)
一、下列四个条件中的任何一个满足时,socket准备好读:
1.socket接收缓冲区中已经接收的数据的字节数大于等于socket接收缓冲区低潮限度的当前值;对这样的socket的读操作不会阻塞,并返回一个大于0的值(即:准备好读入的数据的字节数).我们可以用socket选项SO_RCVLOWAT来设置此低潮限度,对于TCP和UDPsocket,其缺省值为1;
2.连接的读这一半关闭(即:接收到对方发过来的FIN的TCP连接).对于这样的socket的读操作将不阻塞,并且返回0(即:文件结束符,FIN包体长度为0字节);
3.socket是一个用于监听的socket,并且已经完成的连接数为非0.这样的soocket处于可读状态,是因为socket收到了对方的connect请求,执行了三次握手的第一步:对方发送SYN请求过来,使监听socket处于可读状态;正常情况下,这样的socket上的accept操作不会阻塞;
4.有一个socket有异常错误条件待处理.对于这样的socket的读操作将不会阻塞,并且返回一个错误(-1),errno则设置成明确的错误条件.这些待处理的错误也可通过指定socket选项SO_ERROR调用getsockopt来取得并清除;
二、下列三个条件中的任何一个满足时,socket准备好写:
1.socket发送缓冲区中的可用空间字节数大于等于socket发送缓冲区低潮限度的当前值,且(i):socket已连接(TCP socket),或者(ii):socket不要求连接(如:UDP socket).这意味着,如果我们将这样的socket设置为非阻塞模式,写操作将不会阻塞,并且返回一个正值(如:由传输层接收的字节数).我们可以用socket选项SO_SNDLOWAT来设置此低潮限度,对于TCP和UDP socket,其缺省值一般是2048Bytes;
2.连接的写这一半关闭.对于这样的socket的的写操作将产生信号SIGPIPE;
3.有一个socket异常错误条件待处理.对于这样的socket的写操作将不会阻塞并且返回一个错误(-1),errno则设置成明确的错误条件.这些待处理的错误也可以通过指定socket选项SO_ERROR调用getsockopt函数来取得并清除;
三、如果一个socket存在带外数据或者仍处于带外标记,那么它就有异常条件待处理;
注意:一个socket出错时,它由select标记为既可读又可写;
接收和发送低潮限度的目的是:在select返回可读或可写条件之前,应用进程可以对"有多少数据可以读或有多大空间可用于写"进行控制.例如:如果我们知道除非至少64字节的数据可用,否则我们的应用进程不能完成有效的工作,那么就可以将接收低潮限度设置为64字节,以防止小于64字节的数据准备好读时,select就唤醒我们;
只要UDP socket的发送低潮限度小于发送缓冲区大小(缺省关系常常如此),由于不需要连接,这样的UDP socket总是可写的;
一个Select模型在一个线程中最好同时只管理64个套接字,虽然可以重新定义FD_SETSIZE宏的大小,但是在一个线程中可能影响Select的效率,当SOCKET的数量大于64的时候,可以采用分段轮询或者采用多线程采用多个Select的方法来进行管理。例如套接字列表为128个,在第一次轮询时,将前64个放入队列中用Select进行状态查询,待本次操作全部结束后.将后64个再加入轮询队列中进行轮询处理.这样处理需要在非阻塞式下工作.以此类推,Select也能支持无限多个.
Select模型代码:
#include <WINSOCK2.H>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
#define PORT 55500
int g_iTotalConn = 0;
SOCKET g_CliSocketArr[FD_SETSIZE];
using namespace std;
DWORD WINAPI WorkerThread(LPVOID lpParameter);
int CALLBACK AccpetCondition(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);
int main()
{
WSADATA wsaData;
SOCKET sListen,sClient;
sockaddr_in siLocal,siClient;
int isiSize=sizeof(sockaddr_in);
DWORD dwThreadId;
WSAStartup(0x0202,&wsaData);
sListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
siLocal.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
siLocal.sin_family=AF_INET;
siLocal.sin_port=htons(PORT);
bind(sListen,(sockaddr*)&siLocal,sizeof(sockaddr));
listen(sListen,64);
CreateThread(NULL,0,WorkerThread,NULL,0,&dwThreadId);
while(true)
{
//sClient=accept(sListen,(sockaddr*)&siClient,&isiSize);
sClient = WSAAccept(sListen,(sockaddr*)&siClient,&isiSize,AccpetCondition,0);
if(sClient != INVALID_SOCKET)
{
g_CliSocketArr[g_iTotalConn++]=sClient;
}
else
{
cout<<"已操作最大连接数"<<endl;
}
}
return 0;
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int i;
fd_set fdRead;
int iRet;
timeval tv={1,0};
char szMessage[8292];
while(true)
{
if(!g_iTotalConn)
{
cout<<"套接字为空!"<<endl;
continue;
}
FD_ZERO(&fdRead);
for(i=0;i<g_iTotalConn;i++)
{
FD_SET(g_CliSocketArr[i],&fdRead);
}
iRet = select(0,&fdRead,NULL,NULL,&tv);
if(iRet == 0)
{
continue;
}
for(i = 0;i<g_iTotalConn;i++)
{
if(FD_ISSET(g_CliSocketArr[i],&fdRead))
{
iRet = recv(g_CliSocketArr[i],szMessage,8192,0);
if(iRet == 0 || (iRet == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
cout<<"socket Closed"<<endl;
if(i < g_iTotalConn-1)
{
g_CliSocketArr[i--]=g_CliSocketArr[--g_iTotalConn];
}
if(g_iTotalConn == 1)
{
g_iTotalConn --;
}
}
else
{
szMessage[iRet]='\0';
cout<<g_iTotalConn<<endl;
cout<<szMessage<<endl;
}
}
}
}
}
int CALLBACK AccpetCondition(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)
{
if(g_iTotalConn < FD_SETSIZE)
{
return CF_ACCEPT;
}
else
{
return CF_REJECT;
}
}