当需要在一个或多个文件描述符上等待事件发生时,可以使用select函数。
select函数是一个阻塞调用,它会一直等待,直到指定的文件描述符上有事件发生或超时。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:select用于监测是哪个或哪些文件描述符产生事件;
参数:nfds: 监测的最大文件描述个数
(这里是个数,使用的时候注意,与文件中最后一次打开的文件
描述符所对应的值的关系是什么?)
readfds: 读事件集合; //读(用的多)
writefds: 写事件集合; //NULL表示不关心
exceptfds:异常事件集合;
timeout:超时检测 1
如果不做超时检测:传 NULL
select返回值: <0 出错
>0 表示有事件产生;
如果设置了超时检测时间:&tv
select返回值:
<0 出错
>0 表示有事件产生;
==0 表示超时时间已到;
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
void FD_CLR(int fd, fd_set *set);//将fd从表中清除
int FD_ISSET(int fd, fd_set *set);//判断fd是否在表中
void FD_SET(int fd, fd_set *set);//将fd添加到表中
void FD_ZERO(fd_set *set);//清空表1
创建一个文件描述符集合,用于指定你感兴趣的文件描述符。可以使用FD_ZERO、FD_SET、FD_CLR和FD_ISSET宏来操作文件描述符集合。
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd1, &readfds); // 将文件描述符fd1添加到集合中
FD_SET(fd2, &readfds); // 将文件描述符fd2添加到集合中
准备一个timeval结构体,指定select函数的超时时间,或设置为NULL表示没有超时限制。
struct timeval timeout;
timeout.tv_sec = 5; // 设置超时时间为5秒
timeout.tv_usec = 0;
将文件描述符集合和超时时间作为参数传递给select函数,等待事件发生。
int numReady = select(maxfd + 1, &readfds, NULL, NULL, &timeout);
if (numReady == -1) {
// 处理错误
} else if (numReady == 0) {
// 超时处理
} else {
// 有事件发生
// 遍历文件描述符集合,检查哪些文件描述符上有事件发生
for (int fd = 0; fd <= maxfd; ++fd) {
if (FD_ISSET(fd, &readfds)) {
// 该文件描述符上有事件发生
// 处理事件
}
}
}
#include
#include
#include
#include
#include
#include
#include
#include
#define N 20
int main(int argc, char const *argv[])
{
int pd;
pd = open("/dev/input/mouse0", O_RDONLY);
if (pd < 0)
{
perror("open mouse0 err.");
return -1;
}
//1.create fd_set table
fd_set readfds, tempfds;
FD_ZERO(&readfds); //清空
//2.add care file descriptor
FD_SET(0, &readfds);
FD_SET(pd, &readfds);
//注意参数
//3.maxfd
int maxfd = pd;
char buf[N] = "";
//4.Calling select functions
while (1)
{
//5.add tempfds
tempfds = readfds;
if (select(maxfd + 1, &tempfds, NULL, NULL, NULL) < 0)
{
perror("select err.");
return -1;
}
if (FD_ISSET(0, &tempfds))
{
fgets(buf, N, stdin);
printf("key:%s", buf);
}
if (FD_ISSET(pd, &tempfds))
{
int ret = read(pd, buf, N);
buf[ret] = '\0';
printf("mouse:%s\n", buf);
}
}
close(pd);
return 0;
}
//使用IO多路实现tcp绑定多个服务器
#include
#include /* See NOTES */
#include
#include
#include
#include
#include
#include
#include
/* superset of previous */
#define Port 1025
#define N 128
int main(int argc, char const *argv[])
{
char buf[N] = "";
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err.");
return -1;
}
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(Port);
saddr.sin_addr.s_addr = INADDR_ANY;
socklen_t len = sizeof(caddr);
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err.");
return -1;
}
if (listen(sockfd, 10) < 0)
{
perror("listen");
return -1;
}
fd_set readfds, tempfds;
FD_ZERO(&readfds);
FD_SET(0, &readfds);
FD_SET(sockfd, &readfds);
int maxfd = sockfd;
while (1) //循环位置注意下
{
tempfds = readfds;
if (select(maxfd + 1, &tempfds, NULL, NULL, NULL) < 0)
{
perror("select err.");
return -1;
}
if (FD_ISSET(0, &tempfds))
{
fgets(buf, N, stdin);
printf("%s", buf);
}
if (FD_ISSET(sockfd, &tempfds))
{
int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("accept err");
return -1;
}
else if (acceptfd == 0)
{
perror("client exit");
}
printf("port:%d\n", ntohs(caddr.sin_port));
printf("ip address:%s\n", inet_ntoa(caddr.sin_addr));
}
}
close(sockfd);
return 0;
}
poll函数是一种多路复用的机制,用于同时监视多个文件描述符的状态(通过控制静态数组)
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
struct pollfd *fds
关心的文件描述符数组struct pollfd fds[N];
nfds:个数
timeout: 超时检测
毫秒级的:如果填1000,1秒
如果-1,阻塞
struct pollfd {
int fd; /* 检测的文件描述符 */
short events; /* 检测事件 */
short revents; /* 调用poll函数返回填充的事件,poll函数一旦返回,将对应事件自动填充结构体这个成员。只需要判断这个成员的值就可以确定是否产生事件 */
};
事件: POLLIN :读事件
POLLOUT : 写事件
POLLERR:异常事件
poll函数使用一个名为struct pollfd的结构体数组来表示要监视的文件描述符以及监视的事件。每个结构体包含了一个文件描述符的信息和要监视的事件类型。可以通过创建并初始化一个pollfd结构体数组来准备监视的文件描述符。
对于每个要监视的文件描述符,设置其对应的文件描述符(fd字段)以及要监视的事件类型(events字段),如读事件(POLLIN)、写事件(POLLOUT)等。
使用创建好的pollfd结构体数组作为参数,调用poll函数来进行多路复用的操作。poll函数的原型如下:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll函数返回时,会修改pollfd结构体数组中的revents字段,指示发生了哪些事件。可以通过检查revents字段来确定哪些文件描述符发生了事件。
根据revents字段的值,处理相应文件描述符发生的事件,如读事件或写事件。可以使用条件语句或循环结构来处理多个文件描述符的事件。
如果需要继续监视文件描述符的事件,可以重复执行步骤2-5,以实现多次的多路复用。
//服务器端
#include
#include /* See NOTES */
#include
#include
#include
#include
#include
int main(int argc, char const *argv[])
{
int socket_fd;
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0)
{
perror("socket");
return -1;
}
struct sockaddr_in ip;
ip.sin_family = AF_INET;
ip.sin_port = ntohs(1025);
ip.sin_addr.s_addr = inet_addr("0.0.0.0");
if(connect(socket_fd, (struct sockaddr *)&ip, sizeof(ip))<0)
{
perror("connect");
return -1;
}
char arr[128];
int send_val;
while (1)
{
scanf("%s", arr);
send_val = send(socket_fd, arr, sizeof(arr), 0);
if (send_val < 0)
{
perror("send");
return -1;
}
else if (send_val == 0)
{
printf("server is exit");
break;
}
}
close(socket_fd);
return 0;
}
//poll函数实现tcp处理服务器
//客户端
#include
#include /* See NOTES */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define Port 1025
#define N 128
#define PollN 100
int main(int argc, char const *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("perror socket");
return -1;
}
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(Port);
saddr.sin_addr.s_addr = INADDR_ANY;
socklen_t len = sizeof(caddr);
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err.");
return -1;
}
if (listen(sockfd, 5) < 0)
{
perror("listen err.");
return -1;
}
//1.创建文件描述符表
struct pollfd fds[PollN];
memset(fds, 0, (sizeof(struct pollfd) * PollN));
//2.将关心得文件描述符添加到表中
fds[0].fd = 0;
fds[0].events = POLLIN;
fds[1].fd = sockfd;
fds[1].events = POLLIN;
int last = 1; //标记最大元素下标志
char buf[N];
while (1)
{ //3.调用poll函数
if (poll(fds, last + 1, -1) < 0) //-1表示无限阻塞
{
perror("poll err");
return -1;
}
//4.遍历结构体数组
for (int i = 0; i <= last; i++)
{
if (fds[i].revents == POLLIN) //fd是0得情况
//第二个会赋值第三个
{
if (fds[i].fd == 0)
{
fgets(buf, N, stdin);
printf("%s", buf);
}
else if (fds[i].fd == sockfd)//fd是sockfd情况
{
int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
if (acceptfd < 0)
{
perror("accept err.");
return -1;
}
printf("port:%d\n", ntohs(caddr.sin_port));
printf("Ipaddr:%s\n", inet_ntoa(caddr.sin_addr));
/***********************************/
last++;
fds[last].fd = acceptfd;
fds[last].events = POLLIN;
}
else//fd是acceptfd情况
{
size_t ret = recv(fds[i].fd, buf, N, 0);
if (ret < 0)
{
perror("recv err.");
}
else if (ret == 0)
{
//若是对面服务器退出得处理
perror("client exit.");
close(fds[i].fd);
/***************************************/
fds[i] = fds[last];
//把最后一个值直接拿过来替换就可,这里得数组不注重存储顺序
i--;
last--;
break;
}
else //链接成功
{
printf("%s\n", buf);
}
}
}
}
}
close(sockfd);
return 0;
}
epoll 是一种事件驱动的 I/O 复用机制,用于高效地处理大量的文件描述符(sockets、文件等)的并发 I/O 操作。它在 Linux 操作系统中提供,用于替代旧的 select和 poll 系统调用。
注意:epoll是 Linux 特有的系统调用,无法在其他操作系统上直接使用。其他操作系统通常使用不同的机制,如 kqueue(BSD 系统)和 IOCP(Windows)来实现类似的功能。
epoll底层原理和红黑树有关,先了解下红黑树(新手不必深究)。
红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,它在每个节点上增加了一个额外的属性表示节点的颜色,可以是红色或黑色。红黑树满足以下性质:
这些性质确保了红黑树的平衡性和高效性。由于红黑树是自平衡的,它的插入、删除和查找操作的时间复杂度都是对数时间 O(log n),其中 n 是树中节点的数量。
红黑树在很多编程语言的标准库中被广泛使用,特别适用于需要高效的插入和删除操作,并且需要保持有序性的场景。它常被用作实现映射(Map)和集合(Set)等数据结构的基础。
红黑树的算法相对复杂,包括了节点的插入、删除和旋转等操作。在实际应用中,通常使用现有的红黑树实现,而不需要手动实现它。许多编程语言和算法库都提供了红黑树的实现,可以直接使用这些库来获得红黑树的功能。
epoll 的底层原理涉及到 Linux 内核中的几个关键组件和数据结构。
#include
int epoll_create(int size);
功能:创建红黑树根节点
参数:size:不作为实际意义值 >0 即可
返回值:成功时返回epoll文件描述符,失败时返回-1。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:控制epoll属性
epfd:epoll_create函数的返回句柄。
op:表示动作类型。有三个宏 来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中
EPOLL_CTL_MOD:修改已注册fd的监听事件
EPOLL_CTL_DEL:从epfd中删除一个fd
Fd:需要监听的fd。
event:告诉内核需要监听什么事件
EPOLLIN:表示对应文件描述符可读
EPOLLOUT:可写
EPOLLPRI:有紧急数据可读;
EPOLLERR:错误;
EPOLLHUP:被挂断;
EPOLLET:触发方式,边缘触发;(默认使用边缘触发)
ET模式:表示状态的变化;
返回值:成功时返回0,失败时返回-1
typedef union epoll_data {
void* ptr;(无效)
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; / * Epoll事件* /
epoll_data_t data; / *用户数据变量* /
};
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
功能:等待事件的产生,类似于select的用法
epfd:句柄;
events:用来保存从内核得到事件的集合;
maxevents:表示每次能处理事件最大个数;
timeout:超时时间,毫秒,0立即返回,-1阻塞
成功时返回发生事件的文件描述个数,失败时返回-1
//使用epoll得方式完成多个客户端进行通信
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char const *argv[])
{
char buf[128] = "";
int socked = socket(AF_INET, SOCK_STREAM, 0);
if (socked < 0)
{
perror("socket err.");
exit(-1);
}
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(25535);
saddr.sin_addr.s_addr = INADDR_ANY;
socklen_t len = sizeof(caddr);
if (bind(socked, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err.");
exit(-1);
}
if (listen(socked, 10) < 0)
{
perror("listen err.");
exit(-1);
}
printf("listen is ok\n");
//上面得代码都一样。下面开始引入epoll
//1.创建一个表
struct epoll_event event;
struct epoll_event events[10];
//epoll引入和红黑树得概念
//>>1创建一颗树
int epfd = epoll_create(1);
//>>2添加关心得文件描述符到树中
event.data.fd = 0; //添加标准输入
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);
event.data.fd = socked;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, socked, &event);
while (1)
{
//>>3掉用epoll_wait等待事情发生
int ret = epoll_wait(epfd, events, 10, -1); //-1表示不进行超时检测
//epoll_wait返回事件得文件描述符个数
if (ret < 0)
{
perror("epoll_wait err.");
exit(-1);
}
for (int i = 0; i < ret; i++)
{
if (events[i].data.fd == 0)
{
fgets(buf, 128, stdin);
printf("stdin said:%s\n", buf);
}
else if (events[i].data.fd == socked)
{
int accepted = accept(socked, (struct sockaddr *)&caddr, &len);
if (accepted < 0)
{
perror("accept err.");
exit(-1);
}
printf("client:ip=%s port=%d\n", inet_ntoa(caddr.sin_addr), htons(caddr.sin_port));
//链接成功后还得添加到树上
event.data.fd = accepted;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, accepted, &event);
}
else //用户发送数据
{
int recvbyte = recv(events[i].data.fd, buf, sizeof(buf),0);
if (recvbyte < 0)
{
perror("recv err.");
exit(-1);
}
else if (recvbyte == 0)
{
printf("%d client exit\n", events[i].data.fd);
close(events[i].data.fd);
//下树操作
epoll_ctl(epfd, EPOLL_CTL_DEL,events[i].data.fd, NULL);
}
else
{
printf("%d %s", events[i].data.fd, buf);
}
}
}
}
close(socked);
return 0;
}
//client
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void show(void);
void list_client(int sockfd, char *buf, int size);
void put_client(int sockfd, char *buf, int size);
void get_client(int sockfd, char *buf, int size);
int main(int argc, char const *argv[])
{
//1.创建套接子
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("socket err.");
return -1;
}
//填充结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(25535);
saddr.sin_addr.s_addr = INADDR_ANY;
if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("connect err.");
return -1;
}
//收发消息
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
char buf[128];
while (1)
{
fgets(buf, sizeof(buf), stdin);
if (buf[strlen(buf) - 1] == '\n')
{
buf[strlen(buf) - 1] == '\0';
}
send(sockfd, buf, sizeof(buf), 0);
}
}
else
{
char buf[128];
int recvtype;
while (1)
{
recvtype = recv(sockfd, buf, sizeof(buf), 0);
if (recvtype < 0)
{
perror("recv err");
return -1;
}
printf("%s\n", buf);
}
}
close(sockfd);
return 0;
}
select
伪代码//函数原型select(nfds,&readfds,&writefds,&exportfds,&timeout)
1.fd_set readfds,tempfds;
FD_ZERO(&readfds);
2.FD_SET(0,&readfds)
FD_SET(sockfd,&readfds);
int maxfd=sockfd;
3.while(1)
{
tempfds=readfds;
int ret=select(maxfd+1,&tempfds,NULL,NULL,NULL);
4.if(FD_ISSET(0,&tempfds))
{
fgets()
}
if(FD_ISSET(sockfd,&tempfds))
{
acceptfd=accept();
FD_SET(acceptfd,&readfds);
if(maxfd < acceptfd)
maxfd=acceptfd;
}
for(int i=4;i<=maxfd;i++)
{
if(FD_ISSET(i,&tempfds))
{
recv();
if(退出)
{
close(i);
FD_CLR(i,&readfds);
if(i==maxfd)
maxfd--;
}
}
}
}
poll
伪代码//函数原型int poll(struct pollfd *fds, nfds_t nfds, int timeout);
1.struct pollfd fds[200]={};
/*struct pollfd
{
int fd;
short events;//检测事件 POLLIN POLLOUT
short revents;//poll函数返回用于判断,无0,有revents=events
}*/
2.fds[0].fd=0;
fds[0].events=POLLIN;
fds[1].fd=sockfd;
fds[1].events=POLLIN;
int last=1;
3.while(1)
{
int ret=poll(fds,last+1,-1);
for(int i=0;i<=last;i++)
{
if(fds[i].revents == fds[i].events)
{
if(fds[i].fd==0)
{
}else if(fds[i].fd==sockfd)
{
acceptfd=accept();
last++;
fds[last].fd=acceptfd;
fds[last].events=POLLIN;
}else
{
recv();
if(退出)
{
close(fds[i].fd);
fds[i]=fds[last];
last--;
i--;
}
}
}
}
}
epoll
伪代码int epfd=epoll_create(1)
epoll_ctl(epfd,op,fd,&event)
op:EPOLL_CTL_ADD EPOLL_CTL_DEL
struct epoll_event
{
int events;
xxx data.fd;
}
1.int epfd=epoll_create(1)
2. struct epoll_event event;
event.data.fd=0;
event.evnets=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,0,&event)
event.data.fd=sockfd;
event.evnets=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event)
3.while(1)
struct epoll_event events[20];
int ret=epoll_wait(epfd,events,20,-1);
for(int i=0;i