实现TCP并发服务器之五(epoll函数)

epoll是poll函数的加强版,因为针对同时监听大量文件描述符时poll函数比较低效率。select受到监听描述符个数的限制,比如我的示例三中的数值是1024,同时监听1025个描述符就不可能了。epoll模型不受这个限制,可打开的文件描述符远大于这个数字。IO效率也不会因为FD的增加而线性下降,我们来看看epoll怎么实现TCP并发服务器吧。

以下是服务器端代码,客户端代码仍用多进程的那个版本

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<fcntl.h>

#define MAX_LISTEN 5
#define PORT 1987
#define IP "127.0.0.1"
#define MAX_EVENTS 100

int setnonblocking(int sock)
{
	int opts;
	opts = fcntl(sock,F_GETFL);
	if (opts < 0) {
		return 0;
	}
	opts = opts | O_NONBLOCK;
	if (fcntl(sock,F_SETFL,opts) < 0) {
		return 0;
	}
	return 1;
}

int main()
{
	int conn_fd;
	int sock_fd = socket(AF_INET,SOCK_STREAM,0);
	if (sock_fd < 0) {
		perror("create socket failed");
		exit(1);
	}

	struct sockaddr_in addr_client;
	socklen_t client_size = sizeof(struct sockaddr_in);

	struct sockaddr_in addr_serv;
	memset(&addr_serv, 0, sizeof(addr_serv));
	addr_serv.sin_family = AF_INET;
	addr_serv.sin_port = htons(PORT);
	addr_serv.sin_addr.s_addr = inet_addr(IP);

	if (bind(sock_fd,(struct sockaddr *)&addr_serv,sizeof(struct sockaddr_in)) < 0) {
		perror("bind error");
		exit(1);
	}

	if (listen(sock_fd,MAX_LISTEN) < 0) {
		perror("listen failed");
		exit(1);
	}

	//
	int recv_num;
	int send_num;
	char recv_buf[100];
	char send_buf[100];	

	//初始化epoll描述符
	int epfd = epoll_create(MAX_EVENTS);
	if (epfd <= 0) {
		perror("create epoll failed!");
		exit(1);
	}
	int i, nready;
	struct epoll_event ev,events[MAX_EVENTS];

	//注册sock_fd描述符监听事件
	ev.data.fd = sock_fd;
	ev.events = EPOLLIN | EPOLLET;
	epoll_ctl(epfd,EPOLL_CTL_ADD,sock_fd,&ev);

	while (1) {

		nready = epoll_wait(epfd, events, MAX_EVENTS, 500);

		for (i = 0;i < nready;i ++) {
			//如果是sock_fd描述符可读,则创建新连接,并添加监听事件
			if (events[i].data.fd == sock_fd) {
				conn_fd = accept(sock_fd, (struct sockaddr *)&addr_client, &client_size);
				if (conn_fd < 0) {
					perror("accept failed");
					exit(1);
				}
				setnonblocking(conn_fd);
				ev.data.fd = conn_fd;
				ev.events = EPOLLIN | EPOLLET;
				epoll_ctl(epfd,EPOLL_CTL_ADD,conn_fd,&ev);
				
			}
			else if(events[i].events & EPOLLIN && events[i].data.fd >= 0) {
				//如果描述符可读,则读取数据后修改描述符状态去监听可写状态
				recv_num = recv(events[i].data.fd, recv_buf, sizeof(recv_buf), 0);
				if (recv_num <= 0) {
					close(events[i].data.fd);
					events[i].data.fd = -1;
				} 
				
				ev.data.fd = events[i].data.fd;
				ev.events = EPOLLOUT | EPOLLET;
				epoll_ctl(epfd,EPOLL_CTL_MOD,events[i].data.fd,&ev);
			}
			else if(events[i].events & EPOLLOUT) {
			
				sprintf(send_buf, "server proc got %d bytes\n", recv_num);
				send_num = send(events[i].data.fd, send_buf, strlen(send_buf), 0);
				if (send_num <= 0) {
					close(events[i].data.fd);
					events[i].data.fd = -1;
				}
				
				ev.data.fd = events[i].data.fd;
				ev.events = EPOLLIN | EPOLLET;
				epoll_ctl(epfd,EPOLL_CTL_MOD,events[i].data.fd,&ev);
			}
		}
		
	}
		
	close(sock_fd);
	return 0;
}
epoll模型也是比较好用的,主要是三个函数:epoll_create,epoll_ctl以及epoll_wait

int epoll_create(int size)函数生成一个epoll专用的文件描述符,size表示这个epoll描述符上能关注socket的个数,大小自己可根据内存情况设定。

int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);函数可以控制某个文件描述符上的事件‘注册,修改,删除。

int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)函数等待I/O事件发生。

可以明显看出,操作系统仅仅把能够IO操作的描述符返回给我们,而不像poll以及select一样需要自己轮询查找,性能提升可见是明显的了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值