epoll 反应堆模型:
epoll ET模式 + 非阻塞、轮询 + void *ptr。
普通epoll模型创建流程
原来: socket、bind、listen – epoll_create 创建监听 红黑树 – 返回 epfd – epoll_ctl() 向树上添加一个监听fd – while(1)
{ ---- epoll_wait 监听 – 对应监听fd有事件产生 – 返回 监听满足数组。 – 判断返回数组元素 – lfd满足 – Accept – cfd 满足 – read() — 小->大 – write回去 }
epoll反应堆模型创建流程
socket、bind、listen – epoll_create 创建监听 红黑树 – 返回 epfd – epoll_ctl() 向树上添加一个监听fd – while(1)
{ ---- epoll_wait 监听 – 对应监听fd有事件产生 – 返回 监听满足数组。 – 判断返回数组元素 – lfd满足 – Accept – cfd 满足
– read() — 小->大 – cfd从监听红黑树上摘下 – EPOLLOUT – 回调函数 – epoll_ctl() – EPOLL_CTL_ADD 重新放到红黑上监听写事件
– 等待 epoll_wait 返回 – 说明 cfd 可写 – write回去 – cfd从监听红黑树上摘下 – EPOLLIN
– epoll_ctl() – EPOLL_CTL_ADD 重新放到红黑上监听读事件 – epoll_wait 监听 }
总结: 也就是说反应堆做的事是**不但要监听 cfd 的读事件、还要监听cfd的写事件。
**
反应堆的理解:加入IO转接之后,有了事件,server才去处理,这里反应堆也是这样,由于网络环境复杂,服务器处理数据之后,可能并不能直接写回去,比如遇到网络繁忙或者对方缓冲区已经满了这种情况,就不能直接写回给客户端。反应堆就是在处理数据之后,监听写事件,能写会客户端了,才去做写回操作。写回之后,再改为监听读事件。如此循环。
实现代码:
#include "wrap.h"
#include <sys/types.h>
#include <sys/time.h>
#include <ctype.h>
#include <sys/epoll.h>
#include <time.h>
#include <fcntl.h>
#define MAX_EVENTS 1024
#define SERV_PORT 9527
#define BUFLEN 1024
void revcdata(int fd, int events, void *arg);
void senddata(int fd, int events, void *arg);
//定义 my_events结构体
struct myevent_s
{
int fd; //要监听的文件描述符
int events; //对应的监听事件
void *arg; //泛型参数
void (*call_back)(int fd, int events, void *arg); //回调函数
int status; //是否在监听,1->在红黑树上监听, 0->不在
char buf[BUFLEN]; //处理缓冲区的数组
int len; //从缓冲区读到的长度
long last_active; //记录每次加入红黑树 g_efd的时间,
};
//定义epoll_create返回的文件描述符
int g_efd = 0;
struct myevent_s g_events[MAX_EVENTS+1]; //自定义结构体类型数组,最后一个指向listenfd
//将结构体 myevent_s 成员变量初始化
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)
{
ev->fd = fd;
ev->call_back = call_back;
ev->events = 0;
ev->arg = arg;
ev->status = 0;
//memset(ev->buf, 0, sizeof(ev->buf));
//ev->len = 0;
ev->last_active = time(NULL); //调用eventset函数的时间
}
//向epoll监听的红黑树 g_efd中添加一个文件描述符
void eventadd(struct myevent_s *ev, int efd, int events)
{
struct epoll_event tempEp = {0, {0}};
int op;
tempEp.data.ptr = ev; //有事件加入时, ptr就=ev 可以事件调用的函数
tempEp.events = ev->events = events;
//如果不在红黑树中
if (ev->status == 1) {
op = EPOLL_CTL_MOD;
}
else {
op = EPOLL_CTL_ADD;
ev->status = 1;
}
if(epoll_ctl(efd, op, ev->fd, &tempEp) < 0)
perr_exit("epoll_ctl faild\n");
else
printf("event add ok [fd=%d], op=%d, event[%0X]\n", ev->fd, op, events);
}
//从红黑树中删除节点
void eventdel(int efd, struct myevent_s *ev)
{
struct epoll_event epv = {0, {0}};
if(ev->status == 0)
return ;
epv.data.ptr = ev;
ev->status = 0;
epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);
printf("del fd:%d things:%d\n", ev->fd, ev->events);
}
//读事件 调用函数
void recvdata(int fd, int events, void *arg)
{
struct myevent_s *ev = (struct myevent_s *)arg;
int len;
len = recv(fd, ev->buf, sizeof(ev->buf), 0); //最后参数为0 可以把函数recv看作 read
eventdel(g_efd, ev); //将该节点从红黑树中删除
if(len > 0)
{
ev->len = len;
ev->buf[len] = '\0';
printf("C[%d]: len:%d %s\n", fd, len, ev->buf);
eventset(ev, fd, senddata, ev); //将回调函数改为 senddata
eventadd(ev, g_efd, EPOLLOUT); //将 fd加入红黑树中,监听写事件
}
else if (len == 0) {
close(ev->fd);
/* ev-g_events 地址相减得到偏移元素位置 */
printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
} else {
close(ev->fd);
printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
}
}
//写事件
void senddata(int fd, int events, void *arg)
{
struct myevent_s *ev = (struct myevent_s *)arg;
int len;
for(int i = 0; i < ev->len; i++)
{
ev->buf[i] = toupper(ev->buf[i]);
}
len = send(fd, ev->buf, ev->len, 0); //直接将数据 回写给客户端。未作处理
eventdel(g_efd, ev); //从红黑树g_efd中移除
if (len > 0) {
printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
//将该fd的 回调函数改为 recvdata
eventset(ev, fd, recvdata, ev);
eventadd(ev, g_efd, EPOLLIN); //从新添加到红黑树上, 设为监听读事件
} else {
close(ev->fd); //关闭链接
printf("send[fd=%d] error %s\n", fd, strerror(errno));
}
return ;
}
//listenfd 的 accept函数
void acceptConn(int lfd, int events, void *arg)
{
struct sockaddr_in clie_addr;
bzero(&clie_addr, sizeof(clie_addr));
socklen_t clie_len = sizeof(clie_addr);
int cfd,i;
if((cfd = accept(lfd, (struct sockaddr*)&clie_addr, &clie_len)) == -1)
{
printf("%s: accept, %s\n", __func__, strerror(errno));
return ;
}
do
{
for(i = 0; i < MAX_EVENTS; i++)
//从全局数组g_events中找到一个空闲元素
if(g_events[i].status == 0)
break;
if(i == MAX_EVENTS)
{
printf("%s: max connnect limit[%d]\n", __func__, MAX_EVENTS);
break;
}
//给cfd设置一个 myevent_s 结构体, 回调函数 设置为 recvdata
eventset(&g_events[i], cfd, recvdata, &g_events[i]);
eventadd(&g_events[i], g_efd, EPOLLIN);
}while(0);
printf("new connect [%s:%d][time:%ld], pos[%d]\n",
inet_ntoa(clie_addr.sin_addr), ntohs(clie_addr.sin_port), g_events[i].last_active, i);
}
//创建socket, 初始化 listenfd
void initListenSocket(int efd, short port)
{
struct sockaddr_in serv_addr;
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int lfd = socket(AF_INET, SOCK_STREAM, 0);
//设置非阻塞
int flag = fcntl(lfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(lfd, F_SETFL, flag);
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));
Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
Listen(lfd, 128);
//初始化my_events结构体
eventset(&g_events[MAX_EVENTS], lfd, acceptConn, &g_events[MAX_EVENTS]);
eventadd(&g_events[MAX_EVENTS], efd, EPOLLIN | EPOLLET);
}
int main()
{
unsigned short port = SERV_PORT;
g_efd = epoll_create(MAX_EVENTS + 1); //创建红黑树,返回 根fd
if(0 > g_efd)
perr_exit("epoll_create falied..\n");
initListenSocket(g_efd, port); //初始化监听socket
struct epoll_event events[MAX_EVENTS+1]; //保存已经满足就绪事件的文件描述符数组
printf("server running:port[%d]\n", port);
int checkpos = 0;
while(1)
{
//超时验证,每次测试100个连接,不测listenfd 当客户端超过120s没有和服务器通信,则关闭该连接
long now = time(NULL); //当前时间
for (int i = 0; i < 100; i++, checkpos++) { //一次循环检测100个。 使用checkpos控制检测对象
if (checkpos == MAX_EVENTS)
checkpos = 0;
if (g_events[checkpos].status != 1) //不在红黑树 g_efd 上
continue;
long duration = now - g_events[checkpos].last_active; //客户端不活跃的世间
if (duration >= 120) {
close(g_events[checkpos].fd); //关闭与该客户端链接
printf("[fd=%d] timeout\n", g_events[checkpos].fd);
eventdel(g_efd, &g_events[checkpos]); //将该客户端 从红黑树 g_efd移除
}
}
//监听红黑树 将满足事件的文件描述符加至 events数组中, 1秒没有事件满足,返回
int eventNums = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
if (eventNums < 0) {
printf("epoll_wait error, exit\n");
break;
}
for(int j = 0; j < eventNums; j++)
{
struct myevent_s *ev = (struct myevent_s*)events[j].data.ptr;
if((events[j].events & EPOLLIN) && (ev->events & EPOLLIN))
{
ev->call_back(ev->fd, events[j].events, ev->arg);
}
else
{
//printf("读事件失败\n");
}
if ((events[j].events & EPOLLOUT) && (ev->events & EPOLLOUT)) { //写就绪事件
ev->call_back(ev->fd, events[j].events, ev->arg);
}
else
{
//printf("写事件失败\n");
}
}
}
return 0;
}
错误封装函数wrap.h
#ifndef __WRAP_H__
#define __WRAP_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif
wrap.c
#include "wrap.h"
static ssize_t my_read(int fd, char *ptr);
void perr_exit(const char *s)
{
perror(s);
exit(-1);
}
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
int n;
again:
if ((n = accept(fd, sa, salenptr)) < 0) {
if ((errno == ECONNABORTED) || (errno == EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
}
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
if ((n = bind(fd, sa, salen)) < 0)
perr_exit("bind error");
return n;
}
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
n = connect(fd, sa, salen);
if (n < 0) {
perr_exit("connect error");
}
return n;
}
int Listen(int fd, int backlog)
{
int n;
if ((n = listen(fd, backlog)) < 0)
perr_exit("listen error");
return n;
}
int Socket(int family, int type, int protocol)
{
int n;
if ((n = socket(family, type, protocol)) < 0)
perr_exit("socket error");
return n;
}
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ( (n = read(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ((n = write(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
int Close(int fd)
{
int n;
if ((n = close(fd)) == -1)
perr_exit("close error");
return n;
}
/*参三: 应该读取的字节数*/ //socket 4096 readn(cfd, buf, 4096) nleft = 4096-1500
ssize_t Readn(int fd, void *vptr, size_t n)
{
size_t nleft; //usigned int 剩余未读取的字节数
ssize_t nread; //int 实际读到的字节数
char *ptr;
ptr = vptr;
nleft = n; //n 未读取字节数
while (nleft > 0) {
if ((nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0;
else
return -1;
} else if (nread == 0)
break;
nleft -= nread; //nleft = nleft - nread
ptr += nread;
}
return n - nleft;
}
ssize_t Writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
static ssize_t my_read(int fd, char *ptr)
{
static int read_cnt;
static char *read_ptr;
static char read_buf[100];
if (read_cnt <= 0) {
again:
if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { //"hello\n"
if (errno == EINTR)
goto again;
return -1;
} else if (read_cnt == 0)
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
/*readline --- fgets*/
//传出参数 vptr
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ((rc = my_read(fd, &c)) == 1) { //ptr[] = hello\n
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
*ptr = 0;
return n-1;
} else
return -1;
}
*ptr = 0;
return n;
}