基于epoll的socket服务端通信Linux

基于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模型开发服务器流程:

  1. 创建socket,得到监听文件描述符ldf------socket()
  2. 设置端口复用------setsockopt()
  3. 绑定------bind()
  4. 监听------listen()
  5. 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
", 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!
");
					//从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;
}