Unix C学习之IO多路复用之epoll
程序员文章站
2022-06-14 11:02:17
...
(一)epoll, select, poll区别
图片来自select、poll和epoll的总结对比
文章参考select、poll、epoll之间的区别(搜狗面试)
-
select它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
-
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
-
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))
-
select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
(二)epoll代码
用epoll实现服务器t_net.c
工具函数,server.c
用到了,与epoll无关
#include <t_net.h>
int s_bind(int type, uint16_t port){
SA4 serv;//具体的ipv4地址结构
//创建socket设备,返回该设备的文件描述符
int sfd = socket(AF_INET, type, 0);
if(-1 == sfd) E_MSG("socket", -1);
//初始化serv成员
serv.sin_family = AF_INET;
serv.sin_port = htons(port);
serv.sin_addr.s_addr = htons(INADDR_ANY);
//将本地地址(ip地址和端口)绑定到socket设备sfd
int b = bind(sfd, (SA *)&serv, sizeof(serv));
if(-1 == b) E_MSG("bind", -1);
//将sfd设置为被动连接状态。监听客户端连接的请求,将客户端到来的连接请求放入到未决连接队列中
return sfd;
}
int s_listen(int type, uint16_t port, int backlog){
int fd = s_bind(type, port);
if(-1 == fd) return -1;
int lis = listen(fd, backlog);
if(-1 == lis) E_MSG("listen", -1);
return fd;
}
//不保存客户端IP地址和端口号
int n_accept(int fd){
int cfd = accept(fd, NULL, NULL);
if(-1 == cfd) E_MSG("accept", -1);
return cfd;
}
//输出客户端IP地址和端口号
int h_accept(int fd){
SA4 cli;
char IP[32];
socklen_t len = sizeof(SA4);
int cfd = accept(fd, (SA *)&cli, &len);
if(-1 == cfd) E_MSG("accept", -1);
printf("ip:%s\n", inet_ntop(AF_INET, &cli.sin_addr, IP, 32));
return cfd;
}
server.c
服务器代码,epoll具体使用流程
#include <unistd.h>
#include <t_net.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <sys/epoll.h>
int events_handle(int, struct epoll_event *);
int main(void){
//创建一个epoll实例
int epfd = epoll_create(4);
if(-1 == epfd) E_MSG("epoll_create", -1);
//创建socket设备,返回该设备的文件描述符
//绑定到4466端口
int sfd = s_listen(SOCK_STREAM, 4466, 5);
if(-1 == sfd) return -1;
//将sfd添加到epoll中
//sfd的哪个事件需要代理
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sfd;
int v = epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev);
if(-1 == v) E_MSG("epoll_ctl", -1);
struct epoll_event events[100];
printf("accept...\n");
while(1){
//EPOLL等待事件的发生
int maxfd = epoll_wait(epfd, events, 100, -1);
if(-1 == maxfd) E_MSG("epoll_wait", -1);
//有监测的事件的发生,maxfd代表有多少个事件
for(int i=0; i<maxfd; i++){
if(events[i].data.fd == sfd){
//说明未决连接队列非空
int cfd = h_accept(sfd);
if(-1 == cfd) return -1;
//将连接描述符添加到epoll
ev.events = EPOLLIN;
ev.data.fd = cfd;
v = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
if(-1 == v) E_MSG("epoll_ctl", -1);
}else{//有客户端请求i/o的到来
events_handle(epfd, &events[i]);
}
}
}
return 0;
}
int events_handle(int epfd, struct epoll_event *ev){
char buf[1024];
int fd = ev->data.fd;
int r = read(fd, buf, 1024);
//处理客户端的信息
for(int i=0; i<r; i++)
buf[i] = toupper(buf[i]);
write(fd, buf, r);
if(!strcmp(buf, "BYEBYE")){
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, ev);
close(fd);
}
return 0;
}
client.c
客户端代码
#include <unistd.h>
#include <string.h>
#include <t_net.h>
int main(int argc, char *argv[]){
char buf[128];
char msg[128];
SA4 serv;
//创建一个通讯端点,返回该设备的文件描述符
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == fd) E_MSG("socket", -1);
//初始化serv的成员
serv.sin_family = AF_INET;
serv.sin_port = htons(4466);
//127.0.0.1 text--->binary
inet_pton(AF_INET, argv[1], &serv.sin_addr);
//使用fd设备向服务器发起连接
int c = connect(fd, (SA *)&serv, sizeof(serv));
if(-1 == c) E_MSG("connect", -1);
//到这里,已经和服务器建立了连接
//向服务器发送请求信息
while(1){
char *tmp = fgets(msg, sizeof(msg), stdin);
if(strlen(tmp) == sizeof(msg)-1 && tmp[sizeof(msg)-2] != '\n'){
scanf("%*[^\n]");
scanf("%*c");
}
tmp[strlen(msg)-1] = '\0';
int count = write(fd, msg, strlen(msg)+1);
if(-1 == count) E_MSG("write", -1);
//阻塞等待服务器的响应消息
//将响应结果存放到buf
int r = read(fd, buf, 128);
//处理响应结果,将得到的字符串输出到屏幕
if(!strcmp(buf, "BYEBYE")) break;
count = write(1, buf, r);
if(-1 == count) E_MSG("write", -1);
printf("\n");
}
//关闭本次连接
close(fd);
return 0;
}
上一篇: 五子棋