epoll 反应堆模式
7、cfd 读就绪处理函数 readproc_cfd,非阻塞循环读
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;
}