select
select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变
举例说明:假设我们钓鱼买了100个鱼竿,然后都放好开始钓鱼,但是我们不想等待,于是我们让一个叫select的人替我们监督哪些鱼竿上有鱼咬钩了,然后让select通知我们回来把鱼钓上来。
说白了就是我们在批量IO时候不想等待,让select帮我们等,等可以IO了就通知我们。
select函数原型
以fd_set *readfds来解释中间3个参数含义
以只读为例,通过代码理解select机制(需要搭配第三放数组使用,代码里体现)
代码内容为我作为server端进行listen,利用select函数将我关心的listen套接字进行监督,
当有新的链接上来,放入维护sock的fd_array数组中
仔细看注释
#include <iostream>
#include <string>
#include <sys/select.h>
#include "Sock.hpp"
#define NUM (sizeof(fd_set) * 8)
int fd_array[NUM]; //内容>=0,合法的fd,如果是-1,该位置没有fd
static void Usage(std::string proc)
{
std::cout << "Usage: " << proc << " port" << std::endl;
}
// ./select_server 8080
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t port = (uint16_t)atoi(argv[1]);
int listen_sock = Sock::Socket();
Sock::Bind(listen_sock, port);
Sock::Listen(listen_sock);
for (int i = 0; i < NUM; i++)
{
fd_array[i] = -1;
}
// accept: 不应该,accept的本质叫做通过listen_sock获取新链接
// 前提是listen_sock上面有新链接,accept怎么知道有新链接呢??
// 不知道!!!accept阻塞式等待
// 站在多路转接的视角,我们认为,链接到来,对于listen_sock,就是读事件就绪!!!
// 对于所有的服务器,最开始的时候,只有listen_sock
//事件循环
fd_set rfds;
//因为listen_sock是固定存在的,因此接下来遇到的新sock都是从4开始,放入fd_arry的位置是从1开始
fd_array[0] = listen_sock;//此时的listen_sock等于3
for (;;)
{
FD_ZERO(&rfds);//先将位图置为0
int max_fd = fd_array[0];
for (int i = 0; i < NUM; i++)
{
if (fd_array[i] == -1)
continue;
//下面的都是合法的fd
FD_SET(fd_array[i], &rfds); //所有要关心读事件的fd,添加到rfds中
if (max_fd < fd_array[i])
{
max_fd = fd_array[i]; //更新最大fd
}
}
struct timeval timeout = {0, 0}; // 5s
// 我们的服务器上的所有的fd(包括listen_sock),都要交给select进行检测!!
// recv,read,write,send,accept : 只负责自己最核心的工作:真正的读写(listen_sock:accept)
int n = select(max_fd + 1, &rfds, nullptr, nullptr, nullptr); //暂时阻塞
switch (n)
{
case -1:
std::cerr << "select error" << std::endl;
break;
case 0:
std::cout << "select timeout" << std::endl;
break;
default:
std::cout << "有fd对应的事件就绪啦!" << std::endl;
for (int i = 0; i < NUM; i++)//需要循环遍历找到就绪的fd
{
if (fd_array[i] == -1)
continue;
//下面的fd都是合法的fd,合法的fd不一定是就绪的fd
//第一次一定是listen套接字成功
if (FD_ISSET(fd_array[i], &rfds))//找到fd为1的
{
std::cout << "sock: " << fd_array[i] << " 上面有了读事件,可以读取了" << std::endl;
// 一定是读事件就绪了!!!
// 就绪的fd就在fd_array[i]保存!
// read, recv时,一定不会被阻塞!
// 读事件就绪,就一定是可以recv,read吗??不一定!!
if (fd_array[i] == listen_sock)//需要先判断fd是不是我们一开始存入的listen套接字
{
std::cout << "listen_sock: " << listen_sock << " 有了新的链接到来" << std::endl;
// accept
int sock = Sock::Accept(listen_sock);//这个才是真正的新连接
if (sock >= 0)
{
std::cout << "listen_sock: " << listen_sock << " 获取新的链接成功" << std::endl;
// 获取成功
// recv,read了呢?绝对不能!
// 新链接到来,不意味着有数据到来!!什么时候数据到来呢?不知道
// 可是,谁可以最清楚的知道那些fd,上面可以读取了?select!
// 无法直接将fd设置进select,但是,好在我们有fd_array[]!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// 需要为新的fd在fd_array中找一个新位置来存放他,通过下一次循环走if (fd_array[i] == listen_sock)对应
// 的else语句来进行read操作!!!
// 很重要不是第一次就开始read而是下一次循环!!!!!!!!!!!!!!!!!!!!
int pos = 1;
for (; pos < NUM; pos++)
{
if (fd_array[pos] == -1)
break;
}
// 1. 找到了一个位置没有被使用
if (pos < NUM)
{
std::cout << "新链接: " << sock << " 已经被添加到了数组[" << pos << "]的位置" << std::endl;
fd_array[pos] = sock;
}
else
{
// 2. 找完了所有的fd_array[],都没有找到没有被使用位置
// 说明服务器已经满载,没法处理新的请求了
std::cout << "服务器已经满载了,关闭新的链接" << std::endl;
close(sock);
}
}
}
else//这里才是进行普通的读sock的地方,一定是第一次拿到,放到fd_array数组中第二次循环才能对其操作
{
// 普通的sock,读事件就绪啦!
// 可以进行读取啦,recv,read
// 可是,本次读取就一定能读完吗?读完,就一定没有所谓的数据包粘包问题吗?
// 但是,我们今天没法解决!我们今天没有场景!仅仅用来测试
std::cout << "sock: " << fd_array[i] << " 上面有普通读取" << std::endl;
char recv_buffer[1024] = {0};
ssize_t s = recv(fd_array[i], recv_buffer, sizeof(recv_buffer) - 1, 0);
if (s > 0)
{
recv_buffer[s] = '\0';
std::cout << "client[ " << fd_array[i] << "]# " << recv_buffer << std::endl;
}
else if (s == 0)//关闭连接需要将fd_array原来的位置清空留给以后用
{
std::cout << "sock: " << fd_array[i] << "关闭了, client退出啦!" << std::endl;
//对端关闭了链接
close(fd_array[i]);
std::cout << "已经在数组下标fd_array[" << i << "]"
<< "中,去掉了sock: " << fd_array[i] << std::endl;
fd_array[i] = -1;
}
else//同上elseif注释
{
//读取失败
close(fd_array[i]);
std::cout << "已经在数组下标fd_array[" << i << "]"
<< "中,去掉了sock: " << fd_array[i] << std::endl;
fd_array[i] = -1;
}
}
}
}
break;
}
}
return 0;
}