Linux 上的 select 函数
select 函数用于检测在一组 socket 中是否有事件就绪。事件分为以下三类:
- 读就绪事件
- 在 socket 内核中,接收缓冲区中的字节数大于或等于低水位标记 SO_RCVLOWAT,此时调用 recv 或 read 函数可以无阻塞地读该文件描述符,并且返回值大于 0。
- TCP 连接的对端关闭连接,此时本端调用 recv 和 read 函数对 socket 进行读操作,recv 或 read 函数会返回 0 值。
- 在监听 socket 上有新的连接请求。
- 在 socket 上有未处理的错误。
- 写就绪事件
- 在 socket 内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小)大于或等于低水位标记 SO_SENDLOWAT 时,可以无阻塞的写,并且返回值大于 0。
- socket 的写操作被关闭(即调用了 close 或 shutdown 函数)时,对一个写操作被关闭的 socket 进行写操作,会触发 SIGPIPE 信号。
- socket 使用阻塞 connect 连接成功或失败时。
- 异常就绪事件
函数签名:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
//timeval类型:
struct timeval
{
long tv_sec; /* 秒 */
long tv_usec; /* 微秒 */
}
//fd_set结构体类型,简写如下
typedef struct
{
long int __fds_bits[16]; //可以看做 128 bit 的数组
} fe_set;
__fds_bits 是 long int 类型的数组, long int 占 8 字节,每字节都有 8bit,每个 bit 都对应一个 fd 的事件状态,0 表示无事件,1 表示有事件,数组长度是 16。因此一共可以表示 8816=1024 个 fd 的状态,这是 select 函数支持的最大 fd 数量。(位图法)
具体示例:
点击查看代码
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <sys/time.h>
#include <vector>
#include <errno.h>
//自定义代表无效 fd 的值
#define INVALID_FD -1
int main(int argc, char* argv[])
{
//创建一个监听 socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == INVALID_FD){
std::cout << "create listen socket error." << std::endl;
return -1;
}
//初始化服务器地址
struct sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bindaddr.sin_port = htons(3000);
if(bind(listenfd, (struct sockaddr*)&bindaddr, sizeof(bindaddr)) == -1){
std::cout << "bind listen socket error." << std::endl;
close(listenfd);
return -1;
}
//启动监听
if(listen(listenfd, SOMAXCONN) == -1){
std::cout << "listen error." << std::endl;
close(listenfd);
return -1;
}
//存储客户端 socket 的数组
std::vector<int> clientfds;
int maxfd;
while(true)
{
fd_set readset;
FD_ZERO(&readset);
//将监听的socket加入待检测的可读事件中
FD_SET(listenfd, &readset);
maxfd = listenfd;
//将客户端fd加入待检测的可读事件中
int clientfdslength = clientfds.size();
for(int i = 0; i < clientfdslength; i++){
if(clientfds[i] != INVALID_FD){
FD_SET(clientfds[i], &readset);
if(maxfd < clientfds[i]) maxfd = clientfds[i];
}
}
timeval tm;
tm.tv_sec = 1;
tm.tv_usec = 0;
//暂且只检测可读事件,不检测可写和异常事件
int ret = select(maxfd+1, &readset, NULL,NULL,&tm);
std::cout << "select success" << std::endl;
if(ret == -1){
//出错,退出程序
if(errno == EINTR){
break;
}
}else if(ret == 0){
//select函数超时,下次继续
continue;
}else{
//检测到某个socket有事件
if(FD_ISSET(listenfd, &readset)){
//监听socket的可读事件,表明有新的连接到来
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
//接受客户端连接
int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientaddrlen);
if(clientfd == INVALID_FD){
//接受连接出错,退出程序
break;
}
//只接受连接,不调用recv收取任何数据
std::cout << "accept a client connection, fd: " << clientfd << std::endl;
clientfds.push_back(clientfd);
}
else{
//假设对端发来的数据长度不超过63个字符
char recvbuf[64];
int clientfdslength = clientfds.size();
for(int i = 0; i < clientfdslength; i++){
if(clientfds[i] != INVALID_FD && FD_ISSET(clientfds[i], &readset)){
memset(recvbuf, 0, sizeof(recvbuf));
//非监听socket, 接收数据
int length = recv(clientfds[i], &recvbuf, 64, 0);
if(length <= 0){
//收取数据出错
std::cout << "recv data error, clientfd:" << clientfds[i] << std::endl;
close(clientfds[i]);
//不直接删除该元素,将该位置的元素标记为 INVALID_FD
clientfds[i] = INVALID_FD;
continue;
}
std::cout << "clientfd:" << clientfds[i] << ", recv data:" << recvbuf << std::endl;
}
}
}
}
}
//关闭所有客户端 socket
int clientfdslength = clientfds.size();
for(int i = 0; i < clientfdslength; ++i){
if(clientfds[i] != INVALID_FD)
{
close(clientfds[i]);
}
}
//关闭监听socket
close(listenfd);
return 0;
}