unix网络编程(三) 两种模式下的epoll服务端

1.水平触发和边沿触发

Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。

Level Triggered (LT) 水平触发只要有数据都会触发。

LT模式是默认的工作模式,在这种模式下epoll相当于一个效率较高的poll。使用ET模式需要在内核的事件表注册文件描述符的EPOLLET事件。

2.水平触发下的epoll服务器

编写epoll服务器的核心步骤:

  • 创建绑定监听IP和端口的套接字lfd
  • 创建内核事件表epfd
  • 注册lfd  到内核事件表epfd
  • 循环监听内核事件触发epoll_wait()
  • 触发事件的套接字连接分为监听套接字和连接套接字
  • 处理监听套接字(accept--->新连接注册到事件表)
  • 处理连接套接字(read读数据、处理数据)
#include<stdio.h>
#include<string.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<stdlib.h>
#include<libgen.h>
#include "pub.h"
#define OPENMAX 1024

int main(int argc, char *argv[])
{
	if(argc<3)
	{
		printf("usage: ./%s ip_address port_num\n", basename(argv[0]));
		return -1;
	}
	char *ip = argv[1];
	int port = atoi(argv[2]);

	int lfd = -1, cfd = -1, epfd = -1, nready=-1, ret = -1;
	// 绑定、监听
	lfd = socketBind(ip, port);
	if(-1 == lfd)
		return -1;

	epfd = epoll_create(OPENMAX);
	if(epfd < 0)
	{
		perror("epoll_create");
		return -1;
	}

	// 向epoll事件表注册lfd和lfd的事件(上树)
	struct epoll_event tmp_ev, client_ev[OPENMAX];
	memset(client_ev, 0, sizeof(client_ev));

	tmp_ev.events = EPOLLIN;
	tmp_ev.data.fd = lfd;
	ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tmp_ev);
	if(-1 == ret)
	{
		perror("epoll_ctl");
		return -1;
	}
	
	char buf[1024] = "";
	// 循环处理epoll事件
	while(1)
	{
		nready = epoll_wait(epfd, client_ev, OPENMAX, -1);
		if(-1 == nready)
		{
			perror("epoll_wait");
			break;
		}
		for(int i=0; i<nready; i++)
		{
			cfd = client_ev[i].data.fd;
			// lfd请求连接事件
			if(cfd == lfd)
			{
				cfd = Accept_with_print(lfd);
				if(-1 == cfd)
					return -1;

				// 注册cfd
				tmp_ev.events = EPOLLIN;
				tmp_ev.data.fd = cfd;
				ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tmp_ev);
				if(-1 == ret)
				{
					perror("epoll_ctl");
					return -1;
				}
			}
			// cfd 读取事件
			else
			{
				int num = read(cfd, buf, sizeof(buf));
				if(num < 0)
				{
					perror("read");
					return -1;
				}
				else if(num == 0)
				{
					// 先将cfd删除注册,再关闭cfd。
					ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
					if(-1 == ret)
					{
						perror("epoll_ctl");
						return -1;
					}
					close(cfd);
					printf("client closed\n");
				}
				else
				{
					write(cfd, buf, num);
				}
			}
		}
	}

	// 先将lfd删除注册,再关闭lfd
	ret = epoll_ctl(epfd, EPOLL_CTL_DEL, lfd, NULL);
	if(-1 == ret)
	{
		perror("epoll_ctl");
		return -1;
	}
	close(lfd);
	close(epfd);

	return 0;
}

2.边缘触发下的epoll服务器

边缘触发与水平触发服务器的核心步骤基本相似,其不同在于注册新连接事件时需要注册边缘EPOLLET模式,此外读写数据需要采用循环的方式,因此文件描述符应该设置为非阻塞模式。

编写边缘epoll服务器的核心步骤:

  • 创建绑定监听IP和端口的套接字lfd
  • 创建内核事件表epfd
  • 注册lfd  到内核事件表epfd
  • 循环监听内核事件触发epoll_wait()
  • 触发事件的套接字连接分为监听套接字和连接套接字
  • 处理监听套接字(accept--->新连接注册到事件表(注册EPOLLET模式))
  • 处理连接套接字(循环read读数据(非阻塞)、处理数据)

 epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

#include<stdio.h>
#include<string.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<stdlib.h>
#include<libgen.h>
#include "pub.h"
#include<fcntl.h>

#define OPENMAX 1024
// 边缘模式ET,需要将fd设为非阻塞读取,避免循环读数据时阻塞。
int setnonblocking(int fd)
{
	int old_flag = fcntl(fd, F_GETFL);
	int new_flag = old_flag | O_NONBLOCK;
	fcntl(fd, F_SETFL, new_flag);
	return new_flag;
}
int main(int argc, char *argv[])
{
	if(argc<3)
	{
		printf("usage: ./%s ip_address port_num\n", basename(argv[0]));
		return -1;
	}
	char *ip = argv[1];
	int port = atoi(argv[2]);

	int lfd = -1, cfd = -1, epfd = -1, nready=-1, ret = -1;
	// 绑定、监听
	lfd = socketBind(ip, port);
	if(-1 == lfd)
		return -1;

	epfd = epoll_create(OPENMAX);
	if(epfd < 0)
	{
		perror("epoll_create");
		return -1;
	}

	// 向epoll事件表注册lfd和lfd的事件(上树)
	struct epoll_event tmp_ev, client_ev[OPENMAX];
	memset(client_ev, 0, sizeof(client_ev));

	tmp_ev.events = EPOLLIN;
	tmp_ev.data.fd = lfd;
	ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tmp_ev);
	if(-1 == ret)
	{
		perror("epoll_ctl");
		return -1;
	}
	
	char buf[10] = "";
	// 循环处理epoll事件
	while(1)
	{
		nready = epoll_wait(epfd, client_ev, OPENMAX, -1);
		if(-1 == nready)
		{
			perror("epoll_wait");
			break;
		}
		for(int i=0; i<nready; i++)
		{
			cfd = client_ev[i].data.fd;
			// lfd请求连接事件
			if(cfd == lfd)
			{
				cfd = Accept_with_print(lfd);
				if(-1 == cfd)
					return -1;
				//cfd设为非阻塞
				setnonblocking(cfd);

				// 注册cfd 使用边沿触发的方式
				tmp_ev.events = EPOLLIN | EPOLLET;
				tmp_ev.data.fd = cfd;
				ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tmp_ev);
				if(-1 == ret)
				{
					perror("epoll_ctl");
					return -1;
				}

			}
			// cfd 读取事件
			else
			{
				while(1)
				{
					int num = read(cfd, buf, sizeof(buf));
					if(num < 0)
					{
						/* 数据读取完毕返回值小于0,errno设置为EAGAIN或者EWOULDBLOCK */
						if((errno == EAGAIN) || (errno == EWOULDBLOCK))
						{
							break;
						}
						perror("read");
						return -1;
					}
					else if(num == 0)
					{
						// 先将cfd删除注册,再关闭cfd。
						ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
						if(-1 == ret)
						{
							perror("epoll_ctl");
							return -1;
						}
						close(cfd);
						printf("client closed\n");
						break;
					}
					else
					{
						write(cfd, buf, num);
					}
				}
			}
		}
	}

	// 先将lfd删除注册,再关闭lfd
	ret = epoll_ctl(epfd, EPOLL_CTL_DEL, lfd, NULL);
	if(-1 == ret)
	{
		perror("epoll_ctl");
		return -1;
	}
	close(lfd);
	close(epfd);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_55796594/article/details/127847276
今日推荐