Linux环境下epoll并发服务器

摘要:本文记录epoll的相关知识和epoll服务器的开发过程。

三种多路复用的比较

IO多路复用是指,在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作。

模型文件描述符数量工作方式
select默认1024,可以通过宏定义修改轮询方式扫描文件描述符,遍历数组等,用户和内核之间存在大量文件描述符拷贝,内核开销随文件描述符增加而线性增加
poll不受限和select工作方式相同,poll是select更加底层的系统调用
epoll不受限I/O事件通知机制,只关心"活跃"连接,无需遍历文件描述符集,能够处理大量请求,一般百万级别

epoll的工作原理

  • 多路复用epoll技术就像信号处理机制一样,我们先安装(注册)需要关心的文件描述符,然后对其进行监测,一旦有某个描述符被触发,epoll机制就会通知进程该描述符可以进行IO操作。因为socket服务器和socket客户端之间是两个描述符之间依赖TCP协议进行通信,所以我们可以使用epoll实现多并发服务器。
  • epoll机制:红黑树 + 双链表数据结构 + TCP回调机制
  • 调用epoll_create():建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源),在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket,再建立一个rdllist双向链表,用于存储准备就绪的事件。
  • 调用epoll_ctl:向epoll对象中添加这100万个连接的套接字。
  • 调用epoll_wait:观察这个rdllist双向链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。
  • 所有添加到epoll对象中的事件都会与设备(如网卡)驱动程序建立回调关系(ep_poll_callback),把事件放到上面的rdllist双向链表中。

epoll API 简单说明

  • int epoll_create(int size):参数size指定了我们想要通过epoll实例来检查的文件描述符个数,出错返回-1。

  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev):
    epfd:是epoll_create()实例文件描述符。
    op:用来指定需要执行的操作

     	EPOLL_CTL_ADD:将描述符fd添加到epoll实例中的兴趣列表(红黑树)中
     	去。
     	EPOLL_CTL_MOD:修改描述符上设定的事件,需要用到由ev所指向的
     	结构体中的信息。
     	EPOLL_CTL_DEL:将文件描述符fd从epfd的兴趣列表中移除,该操作忽
     	略参数ev。
    

    fd:指明了要修改兴趣列表中的哪一个文件描述符的设定。
    ev:ev是指向结构体epoll_event的指针。

typedef union epoll_data
{
 void *ptr; /* Pointer to user-defind data */
 int fd; /* File descriptor */
 uint32_t u32; /* 32-bit integer */
 uint64_t u64; /* 64-bit integer */
} epoll_data_t;
struct epoll_event
{
 uint32_t events; /* epoll events(bit mask) */
 epoll_data_t data; /* User data */
};
  • int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout):

     epfd:是epoll_create()的返回值;
     evlis:t所指向的结构体数组中返回的是有关就绪态文件描述符的信息,数
     组evlist的空间由调用者负责申请;
     maxevents:指定所evlist数组里包含的元素个数;
     timeout:用来确定epoll_wait()的阻塞行为,有如下几种:
     如果timeout等于-1,调用将一直阻塞,直到兴趣列表中的文件描述符上有
     事件产生或者直到捕获到一个信号为止。
     如果timeout等于0,执行一次非阻塞式地检查,看兴趣列表中的描述符上产
     生了哪个事件。
     如果timeout大于0,调用将阻塞至多timeout毫秒,直到文件描述符上有事件
     发生,或者直到捕获到一个信号为止
    

epoll服务器实现过程

在这里插入图片描述

遇到的问题

1、竟然是多并发服务器,多个客户端可以与之连接通信,那么服务器需要开多少个端口,还是一个端口可以连接多个客户端?

答、一个端口就可以连接多个客户端,端口只是服务器中某一程序的唯一标识。根据这一标
识,客户端可以找到该服务器程序,并与之通信。当客户端连接上来时,服务器端重新分配
一个文件描述符,epoll对其进行统一管理。

2、边缘触发服务器同时接入两个客户端后,断开其中一个,会导致服务器退出程序?

答、原因是客户端断开后,我没有将文件描述符关闭。

2网络epoll中:正常连接之后,再次循环到位置的时候,从连接描述符文件描述符读数据错误,文件描述符是负数。

3、client编程时,打算使用alarm()结合signal实现定时器,30秒上报一次,可是超时时,程序不跑进信号处理程序,why?

答、原因是我将alarm(3)定时在while循环中,在3秒未超时时,程序又跑到这个地方,再次
开始定时,所以一直循环下去,就不会超时,不产生超时信号SIGALARM。

4、客户端每各三秒进行一次采样和上报数据,客户端正常发送,然而服务器第一次接收正常外,后面几次出现数据接收不完全,丢包现象?

答:原因是文件io操作不当,我是这样写的:
		客户端:read(fd,readBuff,sizeof(readBuff));//从服务器接收响应信息
					  write(fd,writeBuff,sizeof(writeBuff));//writeBuff暂存采集到温度
		服务器端:read(fd,readBuff,sizeof(readBuff));//readBuff暂存接收到的温度数据
						 write(fd,writeSponsBuff,sizeof(writeSponsBuff));//writeSponsBuff暂存响应客
						 户端的信息,比如:"server received from client datas successfully! "

此程序中read和write的数据大小不一致,read读到的数据大小只是客户端发送的数据,数据
大小小于readBuff,而服务器write参数使用的是sizeof(writeSponsBuff),发送了整个buff的数
据,即便是空的,那么就会读写速度不一致,导致丢包或重包,所以我将客户端和服务器端
write的第三个参数改为strlen(writeBuff)和strlen(writeSponsBuff),重新运行程序,进行数据
接收和发送,呵呵,这回接收正常。
注:strlen():计算出字符串长度;
sizeof():计算出数组大小,有时sizeof是操作符,sizeof()可以用来计算出数据类型的大小。

5、为什么边缘触发必须使用非阻塞??

ET 模式是一种边沿触发模型,在它检测到有 I/O 事件时,通过 epoll_wait 调用会得到有事件
通知的文件描述符,每于每一个被通知的文件描述符,如可读,则必须将该文件描述符一直
读到空,让 errno 返回 EAGAIN 为止,否则下次的 epoll_wait 不会返回余下的数据,会丢掉
事件。而如果你的文件描述符如果不是非阻塞的,那这个一直读或一直写势必会在最后一次
阻塞。

四、了解到的知识空缺

  • 行缓冲和全缓冲
    (1)标准出错是不带缓冲的;若是指向终端设备的流才是行缓冲的,否则是全缓冲的。
    (2)注:printf会把东西送到缓冲区,而如果缓冲区不刷新到话,你便不会在屏幕上看到东西,而能导致缓冲区刷新到情况有这些:
    ①强制刷新 标准输出缓存fflush(stdout);
    ②放到缓冲区到内容中包含/n /r ;
    ③缓冲区已满;
    ④需要从缓冲区拿东西到时候,如执行scanf;

(3)一般C库函数写入文件是全缓冲的,而写入显示器是行缓冲

  • alarm()
    alarm()是一个秒级的定时,当超时时,内核向进程发出SIGALARM信号。可以使用它结合信号量signal实现定时回调。

  • 边缘触发要使用非阻塞IO:边缘触发处理效率要比水平触发高。

  • sleep: 通过调用alarm()来设定报警时间,调用sigsuspend()将进程挂起在信号SIGALARM上,sleep()只能精确到秒级上
    nanosleep:通过使用定时器实现的;

  • 水平触发模式(LT)和边缘触发模式(ET)
    LT:epoll机制默认触发模式是水平触发模式,LT模式下,只要没有数据被获取,内核就会不断通知进程,也就是只要缓冲区有数据就会一直触发,那么系统调用增多,内核开销增大。
    ET:只有在缓冲区增加数据的那一刻才会触发。

代码实现:

server_main.c:


#include "server.h"


int main(int argc,char **argv)
{

	int		server_port;
	int		sockfd 	= -1;
	int		clienfd	= -1;
	int		re_val 	= -1;


	//参数解析
	re_val = argument_parse(argc, argv, &server_port);
	if(re_val < 0)
	{
		printf("参数解析错误!\n");
		exit(0);
	}

	//套接字初始化
	re_val = socket_init(server_port,&sockfd);
	if(re_val < 0)
	{
		printf("套接字初始化错误!\n");
	}

	//epoll服务器开始工作
	server_epoll_create(sockfd);

	return 0;
}

完整代码:
服务器端

客户端

测试

在这里插入图片描述client[5]:第一个客户端连接上服务器
client[6]:第二个客户端连接上服务器
client[7]:第三个客户端连接上服务器

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值