select函数原型解释与select_server的实现
select函数原型及相关参数介绍
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
1.函数功能
select函数是Linux系统下的用于多路复用IO的一个系统接口,它可以同时监听多个filefd。
2.返回值
返回值类型为int , 主要有三种结果,>0 , <0 , =0 ,其中大于0表示select监听的IO事件就绪的个数
例如读事件有两个就绪,写事件有三个就绪,select返回值是5。=0表示等待超时,即在你设定的等
待时间内没有任何IO事件就绪,等待时间为函数里的timeval结构体。
3.函数参数
nfds=max[filefd]+1
1.nfds
nfds的值是最大文件描述符加一,这么做的原因是方面对集合进行遍历。
2.readfds,writefds,exceptfds
readfds里面放的是文件描述符,表示用户想让OS关注readfds里面文件描述符的读事件,同理writefds里面放的也是文件描述符,表示用户想让OS关注writefds里面文件描述符的写事件,exceptfds关注异常事件。有任何事件就绪,select返回值都会在原有基础上+1(在等待时间内)。
3.fd_set
fd_set是一个数据类型,通常用于表示文件描述符的集合,用于在多路复用 I/O 模型中传递需要监视的文件描述符。在 Unix/Linux 等操作系统中,fd_set 是通过位图实现的,用于管理文件描述符的状态。
//fd_set 是一个数据类型,通常用于表示文件描述符的集合,用于在多路复用 I/O 模型中传递需要监视的文件描述符。在 Unix/Linux 等操作系统中,fd_set 是通过位图实现的,用于管理文件描述符的状态。
FD_SET(int fd, fd_set* fds);//将文件描述符fd添加到 集合fds中
FD_CLR(int fd, fd_set* fds);//将指定的文件描述符从集合fds中删除
FD_ZERO(fd_set *fdset);//用于将 fd_set 集合中的所有文件描述符清零,即初始化为空集合。
FD_ISSET(int fd, fd_set *fdset);//判断指定的文件描述符 fd 是否在 fd_set 集合中,若在则返回非零值,表示该文件描述符在集合中;否则返回 0,表示不在集合中。
//流程
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd1, &readfds); // 将文件描述符 fd1 加入到 readfds 集合中
FD_SET(fd2, &readfds); // 将文件描述符 fd2 加入到 readfds 集合中
/*
值得注意的是,一般来说,我们会先创建集合,紧接着对集合清0,然后再添加指定的fd
*/
4.struct timeval* timeout
timeout是select阻塞等待的时间,由用户自行设置。
3.不足
前面提到nfds为最大文件描述符+1,这样是为了方便遍历,select返回之后用户需要自己去遍历到底是哪个文件描述符的哪个事件就绪,时间复杂度较高。具体看代码
2.select_server简易版服务器代码实现
#pragma once
#include <sys/select.h>
#include "Socket.hpp"
using namespace std;
const static int g_backlog = 5;
const static int g_port = 8888;
const static int num = sizeof(fd_set) * 8;
class SelectServer
{
private:
void HandlerEvent(fd_set &rfds)
{
//查询文件描述符是否在rfds
if (FD_ISSET(_listensocket->GetSockFd(), &rfds))
{
// 1.select 收到连接请求,这里去链接即可,并且不会阻塞
string peerip;
uint16_t peerport;
// 这个Sockt* s将来要添加到rfds中
Socket *s = _listensocket->AcceptConnection(&peerip, &peerport);
if (s == nullptr)
return;
// 连接成功
cout << "accept success" << endl;
//DO SOMETHING
}
}
public:
SelectServer(uint16_t port = g_port)
: _port(port), _listensocket(make_unique<TcpSocket>())
{
}
~SelectServer() {}
void InitServer()
{
_listensocket->BuildListenSocketMethod(_port, g_backlog);
// 把_rfds_array清0
for (int i = 0; i < num; i++)
{
_rfds_array[i] = nullptr;
}
}
void Loop()
{
while (1)
{
fd_set rfds;
//清空rfds中的文件描述符
FD_ZERO(&rfds);
//将文件描述符添加到rfds
FD_SET(_listensocket->GetSockFd(), &rfds);
struct timeval timeout
{
30, 0
};
int n = select((_listensocket->GetSockFd()) + 1, &rfds, nullptr, nullptr, &timeout);
if (n == 0)
{
cout << "select timeout,last time:" << timeout.tv_sec << timeout.tv_usec << endl;
}
else if (n < 0)
{
cout << "select failed" << endl;
}
else
{
cout << "select wait success , go HandlerEvent" << endl;
HandlerEvent(rfds);
}
}
}
private:
unique_ptr<TcpSocket> _listensocket;
uint16_t _port;
Socket *_rfds_array[num];
};