网络编程:epoll 反应堆模式

epoll 反应堆模式

1、头文件和宏定义

2、系统 epoll_event 事件结构体

3、函数声明

4、main 函数

5、初始化 init_socklisten_tree

6、lfd 连接就绪处理函数 listenproc_lfd

7、cfd 读就绪处理函数 readproc_cfd,非阻塞循环读

8、cfd 写就绪处理函数 write_cfd

9、从监听树,删除/添加节点,上树、下树,断开客户端连接

10、设置非阻塞函数,非阻塞读写函数


1、头文件和宏定义

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/types.h>
#include <errno.h>

#define serv_ip     "127.0.0.1"
#define serv_port   6666
#define MAX_EVENTS   100

2、系统 epoll_event 事件结构体

/*

union epoll_data {
	void        *ptr;             // ptr
        int          fd;              // fd
        uint32_t     u32;
        uint64_t     u64;
} epoll_data_t;

struct epoll_event {
        uint32_t     events;          // Epoll events
        epoll_data_t data;            // User data variable
}; 

*/

3、函数声明

// 自定义事件参数结构体,用于给 epoll_event.data.ptr 赋值
struct Para {
	int fd;                                  // cfd
	char* buf;                               // 动态分配内存,对应不同的连接
	int size;                                // read 时的bufsize,或者,要写的长度
	void (*callback)(int, struct Para*);     // readproc_cfd/write_cfd/listenproc_lfd
};
void add_event(int epfd, uint32_t events, struct Para *p, 
                        int fd, char *buf, int size, void (*callback)(int, struct Para*));
void del_event(int epfd, int fd);

void init_socklisten_tree(int *_epfd, int *_lfd, struct Para *para);
void setfdnonblock(int fd);
void listenproc_lfd(int epfd, struct Para *p);
void readproc_cfd(int epfd, struct Para *p);
void write_cfd(int epfd, struct Para *p);
void disconnect(int epfd, struct Para *p);

int readn(int fd, char *buf, int len);
int writen(int fd, char *buf, int len);
int readloop(int fd, char *buf, int size, int onetime_readlen);

4、main 函数

int main()
{
	int i;
	int epfd;                                  // 监听树
	int lfd;                                   // lfd
	struct Para *para;                         // lfd对应节点的参数
	struct epoll_event evts[MAX_EVENTS];       // 就绪事件数组

	init_socklisten_tree(&epfd, &lfd, para);   // init

	while(1)
	{
		// wait  evts
		int nfds = epoll_wait(epfd, evts, MAX_EVENTS, -1);   // 从 epfd 等待 evts
		if(nfds < 0){
			perror("epoll_wait");
			break;
			//exit(EXIT_FAILURE);
		}

		// 处理数组 evts
		for(i = 0; i < nfds; i++){
			struct Para* p = (struct Para*)evts[i].data.ptr;  // p 决定了具体调用哪一个函数
			p->callback(epfd, p);      // listenproc_lfd、readproc_cfd、write_cfd
		}

	}

	close(lfd);
	free(para);
	printf("server close...\n");

	return 0;
}

5、初始化 init_socklisten_tree

void init_socklisten_tree(int *_epfd, int *_lfd, struct Para *para){
	int lfd;
	int epfd;
	struct sockaddr_in serv_addr;            // 设置服务器地址

	// 创建socket
	lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd < 0){
		perror("socket error");
		exit(1);
	}                                  

	// lfd 绑定 serv_addr
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(serv_port);
	inet_pton(AF_INET, serv_ip, &serv_addr.sin_addr.s_addr);
	if(bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){
		perror("bind error");
		exit(1);
	}

	// 设置最大同时连接数
	listen(lfd, 128);                                                      

	// 创建监听树  create  epfd   
	epfd = epoll_create(MAX_EVENTS);
	if(epfd < 0){
		perror("epoll_create");
		exit(EXIT_FAILURE);
	}

	// add lfd    ctl  ev
	para = (struct Para*)malloc(sizeof(struct Para));   // 设置lfd参数,缓冲及回调

	// lfd 上树
	add_event(epfd, EPOLLIN, para, lfd, NULL, 0, listenproc_lfd);   

	*_epfd = epfd;
	*_lfd = lfd;
}

6、lfd 连接就绪处理函数 listenproc_lfd

void listenproc_lfd(int epfd, struct Para *p){

	int lfd = p->fd;

	char clie_ipbuf[15];
	struct sockaddr_in clie_addr;
	socklen_t clie_addr_len = sizeof(clie_addr);

	int cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len);   
	printf("client ip: %s, client port: %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_ipbuf, (socklen_t)sizeof(clie_ipbuf)), ntohs(clie_addr.sin_port));

        // 设置非阻塞
	setfdnonblock(cfd);    

	// 设置cfd参数,缓冲和回调,add cfd-in
	struct Para* p2 = (struct Para*)malloc(sizeof(struct Para));
	p2->buf = (char *)malloc(BUFSIZ);

	// 上树
	add_event(epfd, EPOLLIN | EPOLLET, p2, cfd, p2->buf, BUFSIZ, readproc_cfd);
}

7、cfd 读就绪处理函数 readproc_cfd,非阻塞循环读

void readproc_cfd(int epfd, struct Para *p){

	int n, i;
	int cfd = p->fd;
	char *buf = p->buf;
	int size = p->size;

	//读取
	//n = read(cfd, buf, size);
	bzero(buf, size);        //
	int onetime_readlen = 5;    // 一次读的最大长度 
	n = readloop(cfd, buf, size, onetime_readlen);   // 已读到的长度
	if(n < 0 && errno != EAGAIN)
	{
		perror("read error");
		exit(EXIT_FAILURE);		
	}
	if(n == 0){
		printf("client close...\n");
		disconnect(epfd, p);           // 下树,释放内存,关闭文件描述符
		return;
	}
	printf("bufsize = %d, read %d bytes\n", size, n);

	//处理
	printf("%s\n", buf);
	for(i=0; i<n; i++)	
	{
		buf[i] = toupper(buf[i]);
	}
	
	//删除读监听
	del_event(epfd, cfd);

	//添加写监听
	add_event(epfd, EPOLLOUT, p, cfd, buf, n, write_cfd);  // 参数对应于 epoll_event 和 Para 的顺序
}

8、cfd 写就绪处理函数 write_cfd

void write_cfd(int epfd, struct Para *p){

	int n;
	int cfd = p->fd;
	char *buf = p->buf;
	int size = p->size;

	//读取
	n = write(cfd, buf, size);
	printf("nsize = %d, write %d bytes\n", size, n);
	if(n < 0)
	{
		perror("write");
		exit(EXIT_FAILURE);		
	}
	else if(n == 0){
		printf("client close...\n");
		disconnect(epfd, p);           // 下树,释放内存,关闭文件描述符
		return;
	}


	// 删除cfd可写监听事件,删除cfd
	del_event(epfd, cfd);

	// 添加cfd可读监听事件,添加cfd
	add_event(epfd, EPOLLIN | EPOLLET, p, cfd, buf, BUFSIZ, readproc_cfd);
}

9、从监听树,删除/添加节点,上树、下树,断开客户端连接

void del_event(int epfd, int fd){
	// 删除cfd监听事件,删除cfd
	if(epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) < 0){
		perror("epoll_ctl");
		exit(EXIT_FAILURE);
	}
	return;
}

void add_event(int epfd, uint32_t events, struct Para *p, int fd, char *buf, int size, void (*callback)(int, struct Para*)){
	
	// 修改回调函数,读写大小
	p->fd = fd;            
	p->buf = buf;
	p->size = size;                     // 设置大小
	p->callback = callback;             // 设置回调函数

	// 添加cfd可写/可写监听事件,添加cfd
	struct epoll_event ev;
	ev.events = events;                 // 这里读写不一样
	ev.data.ptr = p;

	// 上树
	if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) < 0){
		perror("epoll_ctl");
		exit(EXIT_FAILURE);
	}
	return;
}

void disconnect(int epfd, struct Para *p){

	int cfd = p->fd;
	char *buf = p->buf;

	// 删除cfd可读监听事件,删除cfd
	if(epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL) < 0){
		perror("epoll_ctl");
		exit(EXIT_FAILURE);
	}
	close(cfd);	
	free(buf);
	free(p);
	return;
}

10、设置非阻塞函数,非阻塞读写函数

void setfdnonblock(int fd){
	int flag;

	flag = fcntl(fd, F_GETFL);   //获得flag
	flag |= O_NONBLOCK;
	fcntl(fd, F_SETFL, flag);
}

int readn(int fd, char *buf, int len){

	int readlen = 0;     // 已经读到的长度
	int restlen = len;   // 剩余未读的长度
	int n;

	while(restlen > 0){
		n = read(fd, buf + readlen, restlen);
		if(n < 0){
			if(errno = EINTR){   // 中断,继续读 
				continue;
			}
			break; // 返回读到的长度,一般EAGAIN退出能读指定长度,也有可能遇到其他错误读不完
		}
		else if(n == 0){             // 客户端断开,读数不再有意义,直接返回
			return 0;
		}
		readlen += n;
		restlen -= n;
	}
	if(readlen == 0) return -1;
	return readlen;
}

int writen(int fd, char *buf, int len){

	int writelen = 0;
	int restlen = len;
	int n;

	while(restlen > 0){
		n = write(fd, buf + writelen, restlen);
		if(n < 0){
			if(errno == EINTR){
				continue;
			}
			break;             
		}
		else if(n == 0){
			return 0;
		}
		writelen += n;
		restlen -= n;
	}
	if(writelen == 0) return -1;
	return writelen;
}


/* 非阻塞循环读,能读多少读多少,容许最后一次读出错 */
/*
	返回-1:  读错误 ERROR,OR,EAGAIN
	返回0:   客户端断开
	返回>0:  返回已读长度,最后一次读可能是 正常退出EAGAIN,也可能是读出错ERROR
*/
int readloop(int fd, char *buf, int size, int onetime_readlen){

	int n;         
	int readlen = 0;         // 已读到的长度
	while(readlen + onetime_readlen <= size){
		n = read(fd, buf + readlen, onetime_readlen);        // 每次读5个,直到读不到数据
		if(n < 0){
			if(errno == EINTR){     // 中断,可以继续读
				continue;
			}	
			break;            // EAGAIN说明读完,其他说明出错,容许最后一次读出错
		}
		else if(n == 0){
			return 0;   //客户端断开
		}
		readlen += n;
	}
	if(readlen == 0) return -1;	
	return readlen;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值