总体上来说select函数的作用:
确定一个或多个套接口的状态,本函数用于确定一个或多个套接口的状态,对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息,用fd_set结构来表示一组等待检查的套接口,在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select()返回满足条件的套接口的数目。
简单来说select用来填充一组可用的socket句柄,当满足下列之一条件时:
1.可以读取的sockets。当这些socket被返回时,在这些socket上执行recv/accept等操作不会产生阻塞;
2.可以写入的sockets。当这些socket被返回时,在这些socket上执行send等不会产生阻塞;
3.返回有错误的sockets。
select()的机制中提供一fd_set的数据结构,实际上市一long类型的数组,每一个数组元素都能与一打开的文件句柄(或者是其他的socket句柄,文件命名管道等)建立联系,建立联系的工作实际上由程序员完成,当调用select()的时候,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程那一socket或文件可读。
函数原型
int select(int nfds, fd_set FAR* readfds, fd_set FAR* writefds, fd_set FAR* exceptfds, const struct timeval FAR* timeout);
select的处理函数一般有:
同时和select配对使用的还有:
FD_CLR(s, *set)
Removes the descriptor s from set.
FD_ISSET(s, *set)
Nonzero if s is a member of the set. Otherwise, zero.
FD_SET(s, *set)
Adds descriptor s to set.
FD_ZERO(*set)
Initializes the set to the null set.
应该都知道,accept()函数是一个阻塞的函数,当有客户端请求连接服务端时才返回。如下面的例子
server端:
<span style="font-family:SimHei;font-size:18px;">#include <iostream>
#include <Windows.h>
using namespace std;
#define PORT 4000
#define IP_ADDRESS "127.0.0.1"
#pragma comment(lib, "WS2_32.lib")
int main(int argc, char* argv[])
{
WSADATA Ws;
SOCKET ServerSocket, CientSocket;
struct sockaddr_in LocalAddr, ClientAddr;
int Ret = 0;
int AddrLen = 0;
HANDLE hThread = NULL;
//Init Windows Socket
if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0)
{
cout << "Init Windows Socket Failed::" << GetLastError() << endl;
return -1;
}
ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ServerSocket == INVALID_SOCKET)
{
cout << "Create Socket Failed::" << GetLastError() << endl;
return -1;
}
LocalAddr.sin_family = AF_INET;
LocalAddr.sin_addr.s_addr = inet_addr(IP_ADDRESS);
LocalAddr.sin_port = htons(PORT);
memset(LocalAddr.sin_zero, 0x00, 8);
Ret = bind(ServerSocket, (struct sockaddr*)&LocalAddr, sizeof(LocalAddr));
if (Ret != 0)
{
cout << "Bind Socket Failed::" << GetLastError() << endl;
return -1;
}
Ret = listen(ServerSocket, 10);
if (Ret != 0)
{
cout << "listen Socket Failed::" << GetLastError() << endl;
return -1;
}
cout << "服务端已经启动" << endl;
char RecvBuffer[MAX_PATH];
while (true)
{
AddrLen = sizeof(ClientAddr);
CientSocket = accept(ServerSocket, (struct sockaddr*)&ClientAddr, &AddrLen);//阻塞的
if (CientSocket == INVALID_SOCKET)
{
cout << "Accept Failed::" << GetLastError() << endl;
break;
}
cout << "客户端连接::" << inet_ntoa(ClientAddr.sin_addr) << ":" << ClientAddr.sin_port << endl;
int Ret = 0;
while (true)
{
memset(RecvBuffer, 0x00, sizeof(RecvBuffer));
Ret = recv(CientSocket, RecvBuffer, MAX_PATH, 0);
if (Ret == 0 || Ret == SOCKET_ERROR)
{
cout << "客户端退出!" << endl;
break;
}
cout << "接收到客户信息为:" << RecvBuffer << endl;
}
CloseHandle(hThread);
}
closesocket(ServerSocket);
closesocket(CientSocket);
WSACleanup();
return 0;
}</span>
client端:
<span style="font-family:SimHei;font-size:18px;">#include <iostream>
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
#define PORT 4000
#define IP_ADDRESS "127.0.0.1"
int main(int argc, char* argv[])
{
WSADATA Ws;
SOCKET CientSocket;
struct sockaddr_in ServerAddr;
int Ret = 0;
int AddrLen = 0;
HANDLE hThread = NULL;
char SendBuffer[MAX_PATH];
//Init Windows Socket
if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0)
{
cout << "Init Windows Socket Failed::" << GetLastError() << endl;
return -1;
}
//Create Socket
CientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (CientSocket == INVALID_SOCKET)
{
cout << "Create Socket Failed::" << GetLastError() << endl;
return -1;
}
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.s_addr = inet_addr(IP_ADDRESS);
ServerAddr.sin_port = htons(PORT);
memset(ServerAddr.sin_zero, 0x00, 8);
Ret = connect(CientSocket, (struct sockaddr*)&ServerAddr, sizeof(ServerAddr));
if (Ret == SOCKET_ERROR)
{
cout << "Connect Error::" << GetLastError() << endl;
return -1;
}
else
{
cout << "连接成功!" << endl;
}
while (true)
{
cin.getline(SendBuffer, sizeof(SendBuffer));
Ret = send(CientSocket, SendBuffer, (int)strlen(SendBuffer), 0);
if (Ret == SOCKET_ERROR)
{
cout << "Send Info Error::" << GetLastError() << endl;
break;
}
}
closesocket(CientSocket);
WSACleanup();
return 0;
}</span>
上面的例子一个客户端只能接收处理一个客户端的请求!
加上select的应用,就能让一个server处理来自多个客户端的请求:
改写后的server端
<span style="font-family:SimHei;font-size:18px;">#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
#define PORT 4000
bool InitAndListern(SOCKET &sListen)
{
WSADATA wsaData;
sockaddr_in local;
WORD version = MAKEWORD(2, 0);
int ret = WSAStartup(version, &wsaData);
if (ret != 0)
{
printf("WSAStarup failed\n");
return 0;
}
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons((u_short)PORT);
sListen = socket(AF_INET, SOCK_STREAM, 0);
if (sListen == INVALID_SOCKET)
{
printf("Initial socket failed\n");
return 0;
}
if (bind(sListen, (sockaddr*)&local, sizeof(local)) != 0)
{
printf("Bind socket failed\n");
return 0;
}
if (listen(sListen, 10) != 0)
{
printf("Listen socket failed\n");
return 0;
}
return 1;
}
int main()
{
SOCKET sListen;
if (InitAndListern(sListen) == 0)
return 0;
printf("Server wait for client connect...\n");
fd_set fdSocket;
FD_ZERO(&fdSocket);
FD_SET(sListen, &fdSocket);//将sListen添加进该集合
while (true)
{
fd_set fdRead = fdSocket;
int nRet = select(NULL, &fdRead, NULL, NULL, NULL);//
if (nRet <= 0)
break;
for (int i = 0; i < (int)fdSocket.fd_count; ++i)
{
if (FD_ISSET(fdSocket.fd_array[i], &fdRead))
{
if (fdSocket.fd_array[i] == sListen)
{
sockaddr_in addrRemote;
int nAddrLen = sizeof(addrRemote);
SOCKET sNew = ::accept(sListen, (sockaddr*)&addrRemote, &nAddrLen);
FD_SET(sNew, &fdSocket);
printf("Clietn %s connected\n", inet_ntoa(addrRemote.sin_addr));
}
else
{
char buffer[1024];
memset(buffer, 0, 1024);
int nRecev = recv(fdSocket.fd_array[i], buffer, 1024, 0);
if (nRecev > 0)
{
printf("Received Client Msg:%s\n", buffer);
send(fdSocket.fd_array[i], buffer, strlen(buffer), 0);
}
else
{
closesocket(fdSocket.fd_array[i]);
FD_CLR(fdSocket.fd_array[i], &fdSocket);
}
}
}
}
}
return 0;
}</span>
select()的socket只有在有东西的时候才会读入,其实现过程不过如下:
....
int sockfd;
fd_set fdR;
struct timeval timeout=...;
...
for(;;){
FD_ZERO(&fdR);
FD_SET(&fdR);
switch(select(sockfd+1, &fdR, NULL, &timeout){
case -1:
error;
case 0:
error;
default:
if(FDD_ISSET(sockfd)){
accept();
...
}
}
}
select()方法中,所有文件描述符都是阻塞的。使用select判断一组文件描述符中是否有一个可读(写),如果没有就阻塞,直到有一个的时候就唤醒。