客户端跟服务器都有一个连接,客户端A与客户端B进行即时通信,A发数据给服务器,服务器通过listenfd,先判断fd有没有数据可读,如果有数据就读出来通过协议判断是发给谁的,然后发给相应的客户端,这就是即时通信。 随着连接越来越多,就会出现一些问题,例如许多io过来,其实只有少数需要处理,还有随着连接数的增多,sendbuff里边会装满了,就会导致不可写,所以send之前,得去判断是否可写。
大量的io而言,每个是否可读可写,所以需要让fd与相应的事件对应一个回调函数
reactor就是对io进行一个管理,对不同的io走不同的回调函数,与所谓的多进程多线程没关系,但是在做法上边,为了提高性能与逻辑,后边会慢慢地引入多线程的方法
一、reactor
reactor是一个框架模型,将对io事件(accept、read、write…)的监测(io多路复用技术实现监测)转换成对业务逻辑(网络编程关注的问题:连接的建立、断开、数据的到达、发送)的处理。reactor是一种对fd及其绑定的事件的集中处理的方法,本身和多线程无关,但是在实际应用中,可以根据实际需要在合适的地方引入多线程,也可以将监听和客户端连接的socket分别交给两个reactor反应器。
基于io,fd,socket在口语中是一个意思
组成:
非阻塞io(socket)+io多路复用技术(select、epio)
io函数是否阻塞取决于对应的socket是否设置为非阻塞的。(默认阻塞)
特征:
基于事件循环(有一个while(true)),以事件驱动或事件回调的方式来实现业务逻辑。
readable–>readcb
writeable–>wtrtecb
二、reactor的实现
当设置回调函数分别设置时,可以在同一次时间循环中处理所有事件。
一个反应器,管理所有需要监听的fd(item)
数据组织结构可以用效率更高的,epoll内部用的就是红黑树。
接口函数:
注册事件函数,该阶段事件为用户关心的网络编程的事件。
注销事件函数
启动事件循环,事件循环内部将io事件转换成用户关心的事件。
listenfd的作用,跟send和recv的fd的处理方法是不一样的,作用是tcp刚开始监听这个端口的时候,比如说访问百度的服务器,通过网页对外提供一个端口,这个listenfd是对外提供的,处理listenfd的只有accept这一个函数。listenfd有事件可读的时候,通过accept去处理,recv和send是处理不了的,因为这两个是要带数据出来的,accept再分配一个新的fd,所以只有listenfd是用accept。
这么多io,我们用什么数据结构去存储呢,accept返回一个fd会对应一个item存在,换句话说,我们用什么数据结构去存这些item比较好,有红黑树和链表,我们这里用链表来做
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <pthread.h>
#define MAXLNE 4096
#define POLL_SIZE 1024
#define BUFFER_LENGTH 1024
#define MAX_EPOLL_EVENT 1024
typedef int NCALLBACK(int fd, int event, void *arg);//函数指针
//在这个里边需要考虑是否有fd即可读即可写,来判断写几个回调函数,函数其实几个或者一个都可以写
struct nitem{//数据结构:对应每个fd的item:
int fd;
int events;
//int (*callback)(int fd, int event, void *arg);//
NCALLBACK *readcb;//EPOLLIN//int (*readcb)(int fd, int event, void *arg);//
NCALLBACK *writecb//int (*writecb)(int fd, int event, void *arg);//当前的回调函数,EPOLLOUT
NCALLBACK *acceptcb//int (*acceptcb)(int fd, int event, void *arg);//处理listenfdEPOLLIN
unsigned char sbuffer[BUFFER_LENGTH];//如果是字符的话我们一般设置成无符号char型
int slength;//send的长度
unsigned char rbuffer[BUFFER_LENGTH];
int rlength;//recv的长度
};
//如果下边items[fd]的下标超过1024怎么办呢,我们可以定义一个这样的结构体,每一块存储1024个,如果fd超过1024,n=fd/1024,n等于几就执行几次next,就能找到对应的地方,链表
struct itemblock {
struct itemblock *next;
struct nitem *items;//items的数组
};
struct reator {//用来存储epoll的fd,以及所有的item,epoll管理所有fd
int epfd;
struct itemblock *first;
struct itemblock **last;//这是一个通用的写法,二级指针的原因是,*last指向的是链表最后那个节点,last指向的是next,last指向的是一个block,block的首地址的next,也就是*last指向的第一个的next
};
int init_reactor(struct reactor *r);
struct reactor *R;
struct reactor *instance = NULL;
struct reactor *getInastance(void) {//单例模式
if(instance == NULL) {
instance = malloc(sizeof(struct reactor));
if (instance == NULL) return NULL;
memset(instance, 0, sizeof(struct reactor));
if (0 > init_reactor(insatance)) {
return NULL;
}
}
return instance;
}
int nreactor_del_event(int fd, NCALLBACK cb, int event, void *arg)
{
struct reactor *r = getInstance();
struct epoll_event ev = {0};
ev.data.ptr = arg;
epoll_ctl(r->epfd, EPOLL_CTL_DEL, fd, &ev);
r->head->items[fd].events = 0;
return 0;
}
int read_callback(int fd, int event, void *arg)//
{
struct reactor *R = getInstance();
unsigned char *buffer = R->head->items[fd].rbuffer;
int ret = recv(fd, buffer, BUFFER_LENGTH, 0);
if (ret == 0) { // 先delet再close
nreactor_del_event(fd, NULL, 0, NULL);
close(fd);
} else if (ret > 0) {
unsigned char *sbuffer = R->head->items[fd].sbuffer;
memcpy(sbuffer, buffer, ret);
R->head->items[fd].slength = ret;
printf("readcb: %s\n", sbuffer);
nreactor_set_event(fd, write_callback, WRITE_CB, NULL);
}
}
int write_callback(int fd, int event, void *arg)//
{
struct reactor *R = getInstance();
unsigned char *sbuffer = R->head->items[fd].sbuffer;
int length = R->head->items[fd].slength;
int ret = send(fd, sbuffer, length, 0);
if (ret < length) {//没有发送完,没有处理完
nreactor_set_event(fd, write_callback, WRITE_CB, NULL);
} else {
nreactor_set_event(fd, read_callback, READ_CB, NULL);
}
return 0;
}
int accept_callback(int fd, int event, void *arg)//
{
int connfd;
struct sockaddr_in client;
socklen_t len = sizeof(client);
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
nreactor_set_event(connfd, read_callback, READCB, NULL);
}
int init_server(int port)
{
int listenfd;
struct sockaddr_in servaddr;
char buff[MAXLNE];
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
if (listen(listenfd, 10) == -1) {
printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
return listenfd;
}
int init_reactor(struct reactor *r) {
if (r==NULL) {
return -1;
}
int epfd = epoll_create(1);//创建epoll
r->epfd = epfd;
r->head = (struct itemblock *)malloc(MAX_EPOLL_EVENT*sizeof(struct itemblock));
//接下来就要构建fd和这个item的关系,通过fd能够找到item,有人说nitem那个结构体不是有fd这个属性吗,请注意这个fd是nitem里边的,也就是我们处理的时候,epoll在处理里边带的fd返回event的fd的时候,也就是下边代码中int clientfd = events[i].data.fd;这一行的时候,这个fd怎么找到对应的item在什么地方,我们用一个粗暴的方式,直接采用下标存储,直接用数组
if(r->head == NULL) {
close(epfd);
return -2;
}
r->head->items = malloc(MAX_EPOLL_EVENT*sizeof(struct nitem));
if (r->head->items == NULL) {
free(r->head);
close(epfd);
return -2;
}
memset(r->head, 0, sizeof(struct itemblock));
r->head->next = NULL;
return 0;
}
#define READ_CB 0
#define WRITE_CB 1
#define ACCEPT_CB 2
//accept
int nreactor_set_event(int fd, NCALLBACK cb, int event, void *arg)//设置事件
{
struct reactor *r = getInstance();
struct epoll_event ev = {0};
ev.data.ptr = arg;
if (event == READ_CB) {
r->head->items[fd].fd = fd;
r->head->items[fd].readcb = cb;
r->head->items[fd].arg = arg;
ev.events = EPOLLIN;
}else if (event == WRITE_CB) {
r->head->items[fd].fd = fd;
r->head->items[fd].writecb = cb;
r->head->items[fd].arg = arg;
ev.events = EPOLLout;
} else if (event == ACCEPT_CB) {
r->head->items[fd].fd = fd;
r->head->items[fd].acceptcb = cb;
r->head->items[fd].arg = arg;
ev.events = EPOLLIN;
}
epoll_ctrl(r->epfd, fd, EPOLL_CTL_ADD, fd, &ev);
return 0;
}
int reactor_loop(int listenfd)
{
// 接下来我们要做的事情就是把listenfd给set进去,设置一个事件
struct reactor *R = getInstance();
struct epoll_event events[POLL_SIZE] = {0};
while (1) {
int nready = epoll_wait(R->epfd, events, POLL_SIZE, 5);
if (nready == -1) {
continue;
}
int i = 0;
for (i = 0;i < nready;i ++) {
int clientfd = events[i].data.fd;
//关于这里的if与else的添加,取决于之前struct nitem里边的回调函数定义,如果只有一个,那就是if else if,不存在同一个事件在多个函数处理。这里有三个回调,读和写有可能同时发生,但是listen不可能和读写同时发生的。现在三个一起处理是没有问题的,如果只有一个事件,就是else if
if (clientfd == listenfd) {
R->head->items[listenfd].acceptcb(listenfd, 0, NULL);
}
if (events[i].events & EPOLLIN) {
R->head->items[listenfd].readcb(clientfd, 0, NULL);
}
if(events[i].events & EPOLLOUT) {
R->head->items[listenfd].writecb(clientfd, 0, NULL);
}
}
}
close(listenfd);
return 0;
}
int main(int argc, char **argv)
{
int connfd, n;
int listenfd = init_server(9999);//设置端口
nreactor_set_event(listenfd, accept_callback, ACCEPTCB, NULL);
reactor_loop(listenfd);
return 0;
}
三、水平触发LT和边缘ET触发:
边缘触发是循环地去读数据,边缘触发效率高,毕竟每次系统调用返回复制数据(比如epoll_wait,复制rdlist)都是有一定开销的,一般使用边缘触发。边缘触发时内核只会在数据到达发送一次信号,因此用户空间(框架)需要循环调用read直到内核缓冲区数据全部取出,不然就取不到了。因此,边缘触发时一定要将socket设置为非阻塞的,因为最后一次读取的时候内核缓冲区可能是没有数据的,不能让它阻塞在这里。
水平触发只读一次,只要监听的文件描述符fd有数据,就会触发epoll_wait的返回值,这是默认的epoll_wait的方式,关于水平和边缘触发,后面会继续讨论
int read_callback(int fd, int event, void *arg) {
struct reactor *R = getInstance();
unsigned char *buffer = R->head->items[fd].rbuffer;
#if 0 //ET
int idx = 0, ret = 0;
while (idx < BUFFER_LENGTH) {
ret = recv(fd, buffer+idx, BUFFER_LENGTH-idx, 0);
if (ret == -1) {
break;
} else if (ret > 0) {
idx += ret;
} else {// == 0
break;
}
}
if (idx == BUFFER_LENGTH && ret != -1) {
nreactor_set_event(fd, read_callback, READ_CB, NULL);
} else if (ret == 0) {
nreactor_set_event
//close(fd);
} else {
nreactor_set_event(fd, write_callback, WRITE_CB, NULL);
}
#else //LT
int ret = recv(fd, buffer, BUFFER_LENGTH, 0);
if (ret == 0) { // fi
nreactor_del_event(fd, NULL, 0, NULL);
close(fd);
} else if (ret > 0) {
unsigned char *sbuffer = R->head->items[fd].sbuffer;
memcpy(sbuffer, buffer, ret);
R->head->items[fd].slength = ret;
printf("readcb: %s\n", sbuffer);
nreactor_set_event(fd, write_callback, WRITE_CB, NULL);
}
#endif