Linux网络编程【六】:TCP协议高性能服务器(http)模型之I/O多路转接epoll

本文介绍了Linux下的epoll机制,它是用于处理大量并发连接的高性能服务器模型,特别是针对网络编程中的TCP协议。epoll相较于poll和select具有更好的CPU利用率,并提供了水平触发和边缘触发两种模式。epoll的系统调用包括epoll_create、epoll_ctl和epoll_wait。文章详细解释了这些调用的功能,并阐述了epoll的优点,如无最大并发限制、效率提升和减少内存拷贝。最后,文中给出了服务器的具体实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是epoll?

epoll是linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大

量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要

遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

注:epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空

间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

注:epoll文件描述符用完后,直接用close关闭即可,非常方便。事实上,任何被侦听的文件符只要其被关闭,那么它也会自动从被侦

听的文件描述符集合中删除,很是智能。

epoll相关的系统调用有:epoll_create, epoll_ctl和epoll_wait。

epoll_create:


返回值:>0:非空文件描述符;
-1:函数调用失败,同时会自动设置全局变量errno;
epoll_ctl:用来添加/修改/删除需要侦听的文件描述符及其事件.
epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。


op:表示动作,为以下三个宏任意一个(根据需要):添加,修改,删除



.epoll_wait:接收发生在被侦听的描述符上的,用户感兴趣的IO事件。


收集在epoll监控的事件中已经就绪的事件。
events:是分配好的epoll_event结构体数组,epoll将会把就绪的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中)。
maxevents:告之内核这个events有多大,这个 maxevents的值不能小于创建epoll_create()时的size
timeout:是超时时间(毫秒)
    1.0表示轮询非阻塞,立即返回;
    2.-1将不确定,也有说法说是永久阻塞。
    3.大于0,以timeput事件轮询返回
返回值:
    1.成功,返回对应I/O上已准备好的文件描述符数
    2.0表示已超时。
    3.-1,发生错误。

epoll的优点:
本身没有最大并发连接的限制,仅受系统中进程能打开的最大文件数目限制;
效率提升:只有活跃的socket才会主动的去调用callback函数;
省去不必要的内存拷贝:epoll通过内核与用户空间mmap同一块内存实现。
当然,以上的优缺点仅仅是特定场景下的情况:高并发,且任一时间只有少数socket是活跃的。





服务器具体实现代码:

(客户端代码在前几篇博客写过几个版本,在此不再赘述)

/*************************************************************************
	> File Name: epoll.c
	> Author: liumin
	> Mail: 1106863227@qq.com 
	> Created Time: Sun 11 Jun 2017 02:42:50 PM CST
 ************************************************************************/

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

static void Usage(char* proc)
{
	printf("Usage: %s [local_ip] [local_port]\n",proc);
}

int startup(const char* _ip, int _port)
{
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0)
	{
		perror("socket");
		return 2;
	}

	int opt = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(_port);
	local.sin_addr.s_addr = inet_addr(_ip);

	if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
	{
		perror("bind");
		return 3;
	}

	if(listen(sock, 10) < 0)
	{
		perror("listen");
		return 4;
	}
	return sock;
}

typedef struct fd_buf
{
	int fd;
	char buf[10240];
}fd_buf_t,*fd_buf_p;

static void* alloc_fd_buf(int fd)
{
	fd_buf_p tmp = (fd_buf_p)malloc(sizeof(fd_buf_t));
	if(!tmp)
	{
		perror("malloc");
		return NULL;
	}
	tmp->fd = fd;
	return tmp;
}

int main(int argc, char* argv[])
{
	if(argc != 3)
	{
		Usage(argv[0]);
		return 1;
	}

	int listen_sock = startup(argv[1], atoi(argv[2]));

	int epollfd = epoll_create(256);
	if(epollfd < 0)
	{
		perror("epoll_create");
		close(listen_sock);
		return 5;
	}

	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.ptr = alloc_fd_buf(listen_sock);
    
	epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev);

	int nums = 0;
	struct epoll_event evs[256];
	int timeout = -1;

	while(1)
	{
		switch(nums = epoll_wait(epollfd, evs, 256, timeout))
		{
			case -1:
				perror("epoll_wait");
				break;
			case 0:
				printf("timeout...\n");
				break;
			default:
				{
					printf("nums : %d \n",nums);
					int i = 0;
					for(; i < nums; i++)
					{
						fd_buf_p fp = (fd_buf_p)evs[i].data.ptr;
						if(fp->fd == listen_sock && (evs[i].events & EPOLLIN))
						{
							struct sockaddr_in client;
							socklen_t len = sizeof(client);
							int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
							if(new_sock < 0)
							{
								perror("accept");
								continue;
							}
							printf("get a new client: ip:%s ,port:%d \n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));

							ev.events = EPOLLIN;
							ev.data.ptr = alloc_fd_buf(new_sock);
							epoll_ctl(epollfd, EPOLL_CTL_ADD, new_sock, &ev);
						}//if
						else if(fp->fd != listen_sock)
						{
			 				if(evs[i].events & EPOLLIN)
							{
		 						ssize_t s = read(fp->fd, fp->buf, sizeof(fp->buf));
								if(s > 0)
								{
	 								fp->buf[s] = 0;
									printf("client say# %s\n", fp->buf);
									ev.events = EPOLLOUT;
									ev.data.ptr = fp;
									epoll_ctl(epollfd, EPOLL_CTL_MOD, fp->fd, &ev);
								}
								else if(s <= 0)
								{
	 								printf("client quit!\n");
									close(fp->fd);
									epoll_ctl(epollfd, EPOLL_CTL_DEL, fp->fd, NULL);
									free(fp);
								}
								else
								{}
							}
							else if(evs[i].events & EPOLLOUT)
							{
					 			const char* msg = "HTTP/1.0 200 OK\r\n\r\n<html><h1>hello epoll!</h1><html>";
								write(fp->fd, msg, strlen(msg));
								close(fp->fd);
								//这里主要用于浏览器测试,所以在输出后关闭文件描述符
								//若使用客户端、Telnet远程登录测试 可以去掉 实现连续发送信息  
								epoll_ctl(epollfd, EPOLL_CTL_DEL, fp->fd, NULL);
								free(fp);
							}
							else
							{}
						}
						else
						{}
					}//for
				}
				break;
		}
	}

	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值