基于epoll的socket服务端通信【Linux】
epoll是Linux内核中的一种可扩展I/O事件处理机制,最早在Linux2.5.44内核中引入,可以用于代替POSIX select和pll系统调用,并且在具有大量应用程序请求时能够获得较好的性能(此时被监视的文件描述符数量非常大,与旧的select和poll系统调用完成所需O(n)不同,epoll能够在O(1)时间内完成操作,所以性能相当好),epoll与FreeBSD的kqueue类似,都面向用户空间提供了自己的文件描述符来操作。
相关函数介绍
int epoll_create(int size)
函数说明:创建一个树根
参数说明:
- size:最大节点数,此参数在Linux2.6.8已被忽略,但必须传一个大于0的数
返回值:
- 成功:返回一个大于0的文件描述符,代表整个树的树根
- 失败:返回-1,并设置errno值
int epoll_ct(int epfd, int op, int fd, struct epoll_event* event)
函数说明:将要监听的节点fd在epoll树上添加,删除和修改
参数说明:
- epfd:epoll树的根节点
- op:fd在epoll树上的操作。可选值:EPOLL_CTL_ADD(添加)、EPOLL_CTL_MOD(修改)、EPOLL_CTL_DEL(删除)
- fd:要操作的文件描述符
- event
typedef union epoll_data{
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
struct epoll_event{
uint32_t events; //可选值:EPOLLIN(可读事件)、EPOLLOUT(可写事件)、EPOLLERR(异常事件)
epoll_data_t data;
}
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout)
函数说明:委托内核监听epoll树的事件节点
函数参数:
- epfd:epoll树的根节点
- events:传出参数,是一个结构体数组
- maxevents:events数组的大小
- timeout:
- -1:表示阻塞
- 0:表示不阻塞
- 大于0: 表示阻塞超时时长
epoll_wait()返回的数组中的事件节点的值不会被修改,是当时上epoll树的时候设置的值。
使用epoll模型开发服务器流程:
- 创建socket,得到监听文件描述符ldf------socket()
- 设置端口复用------setsockopt()
- 绑定------bind()
- 监听------listen()
- epoll操作伪代码
//创建一颗epoll树
int epfd = epoll_create();
//将监听文件描述符上树
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data,fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD,lfd,&ev);
struct epoll_event events[1024];
while(1)
{
nready = epoll_wait(epfd, events,1024,-1);
if(nready<0){
if(errno==EINTR)//被信号中断
{
continue;
}
break;
}
for(i=0;i<nready;i++){
sockfd = events[i].data.fd;
//有客户端请求到来
if(sockfd==lfd)
{
cfd = accept(sockfd,NULL,NULL);
//将cfd对应的读事件上epoll树
ev.data.fd=cfd;
ev.events=EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
continue;
}
//有客户端发送数据过来
n=read(sockfd, buf,sizeof(buf));
if(n<=0){
close(sockfd);
//将sockfd对应的事件节点从epoll树上删除
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL);
perror("read error or client closed");
continue;
}else{
write(sockfd,buf,n);
}
}
}
使用epoll开发socket服务端通信代码:
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<string.h>
int main() {
int lfd, cfd,sockfd,err_log;
struct sockaddr_in serv_add, client_add;
struct epoll_event ev;
int epfd,nready,n;
socklen_t len = sizeof(client_add);
struct epoll_event client[1024];
char buf[BUFSIZ];
//创建socket
lfd = socket(AF_INET, SOCK_STREAM, 0);
//设置文件描述符端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
//绑定bind
serv_add.sin_family = AF_INET;
serv_add.sin_addr.s_addr = htonl(INADDR_ANY);
serv_add.sin_port = htons(8888);
err_log = bind(lfd, (struct sockaddr*)&serv_add, sizeof(serv_add));
if (err_log == -1) {
perror("bind fail!");
exit(-1);
}
err_log = listen(lfd, 128);
if (err_log) {
perror("listen fail!");
exit(-1);
}
//创建一棵epoll树
epfd = epoll_create(1024);
if (epfd < 0) {
perror("create epoll error");
return -1;
}
//将lfd上epoll树
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
while (1) {
nready = epoll_wait(epfd, client, 1024, -1);
if (nready<0) {
if (errno = EINTR) {
continue;
}
break;
}
for (int i = 0; i < nready; i++) {
sockfd = client[i].data.fd;
//有客户端链接请求过来
if (sockfd == lfd) {
cfd = accept(lfd, (struct sockaddr*)&client_add, &len);
printf("A new connection is established,ip:%s, port:%d\n", inet_ntoa(client_add.sin_addr), htons(client_add.sin_port));
//将cfd上epoll树
ev.data.fd = cfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
continue;
}
//有客户端数据发过来
while (true) {
memset(buf, 0x00, sizeof(buf));
n = read(sockfd, buf, sizeof(buf));
if (n <= 0) {
perror("client close!\n");
//从epoll树上删除sockfd
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
close(sockfd);
break;
}
else
{
printf("recv:%s",buf);
for (int k = 0; k < n; k++) {
buf[k] = toupper(buf[k]);
}
write(sockfd, buf, n);
if (n < BUFSIZ) {
break;
}
}
}
}
}
close(epfd);
close(lfd);
return 0;
}