reactor 基础概念
1. reactor大致框架
IO多路复用器通常可以采用select ,poll , epoll 去监听事件,根据事件类型(可读,可写)调度事件分离器通知对应的事件处理器去处理IO
2. reactor构成
- 多路复用器:在linux 环境中,通过由操作提供的系统调用(select,poll ,epoll),加上主循环(mainloop) 去监听内核是否有对应的事件发生。
代码示例
该示例中通过while循环中的epoll_wait不断轮询内核缓冲区是否有数据可读, 如果有可读事件发生时,通过事件分离器,也就是代码中提及的两个if
判断去回调函数
while(1){ //mainloop
int nready = epoll_wait(epfd, events, 1024, -1);
int i = 0;
for(i = 0; i < nready; i++){
int connfd = events[i].data.fd;
if(events[i].events &EPOLLIN){
int count = connlist[connfd].recv_t.recv_callback(connfd); //思考题: 1. connfd不在此处体现,能不能封装到底层
printf("recv: count: %d<--buffer: %s\n", count, connlist[i].rbuffer);
}else if(events[i].events &EPOLLOUT){
printf("send --> buffer: %s\n", connlist[connfd].wbuffer);
int count = connlist[connfd].send_callback(connfd);
}
}
}
- 事件分发器:多路复用器将接收到的事件分发给对应的事件处理器,事件处理器再去执行对应的业务。这样做可以减少代码的耦合性,使得业务代码和IO多路复用器的代码的减少耦合,代码便于阅读和维护 。
if(events[i].events &EPOLLIN){
int count = connlist[connfd].recv_t.recv_callback(connfd); //思考题: 1. connfd不在此处体现,能不能封装到底层
printf("recv: count: %d<--buffer: %s\n", count, connlist[i].rbuffer);
}else if(events[i].events &EPOLLOUT){
printf("send --> buffer: %s\n", connlist[connfd].wbuffer);
int count = connlist[connfd].send_callback(connfd);
}
- 事件处理器:执行对应事件的处理函数
- recv_cb 用于接收缓冲区数据
//用于接收缓冲区数据
int recv_cb(int fd){
char *buffer = connlist[fd].rbuffer;
int idx = connlist[fd].rlen;
int count = recv(fd, buffer+idx, BUFFER_LENGTH, 0);
if(count == 0){
printf("disconnect\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
return -1;
}
memcpy(connlist[fd].wbuffer, connlist[fd].rbuffer, connlist[fd].rlen);
connlist[fd].wlen += connlist[fd].rlen;
connlist[fd].rlen += count;
set_event(fd, EPOLLOUT, 0); //将fd 设置为可写事件
return count;
}
- send_cb 用于向缓冲区发送数据,这里的缓冲区指的是内核缓冲区
//向缓冲区发送数据
int send_cb(int fd){
char *buffer = connlist[fd].wbuffer;
int idx = connlist[fd].wlen;
int count = 0;
if(buffer){
count = send(fd, buffer, idx, 0);
set_event(fd, EPOLLIN, 0);
}
return count;
}
- accept_cb 用于返回客户端套接字,并对返回客户端套接字进行事件类型的设定 ,注册事件处理器(本质是进行处理函数指针的赋值)
int accept_cb(int fd){
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr *)&clientaddr, &len);
if(clientfd < 0){
return -1;
}
set_event(clientfd, EPOLLIN, 1);
connlist[clientfd].fd = clientfd;
memset(connlist[clientfd].rbuffer, 0, BUFFER_LENGTH);
connlist[clientfd].rlen = 0;
memset(connlist[clientfd].wbuffer, 0, BUFFER_LENGTH);
connlist[clientfd].wlen = 0;
connlist[clientfd].recv_t.recv_callback = recv_cb;
connlist[clientfd].send_callback = send_cb;
return clientfd;
}
3. reactor 与一般的IO多路复用的区别
- IO 多路复用是针对IO 执行相应的处理, rector 则采用的是事件驱动。比如当服务端的监听套接字,有可读事件发生时调用accept函数去返回客户端套接字; 而reactor 框架下,当事件的类型是可读时,则通过事件分发器去派送事件进而调用事件处理器
- reactor 的IO多路复用器可以使用不同IO多路复用,包括select, poll, epoll
4. reactor 使用范围
5. 完整代码
- 服务端
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#define BUFFER_LENGTH 1024
typedef int(*RCALLBACK)(int fd);
struct conn_item{
int fd;
char rbuffer[BUFFER_LENGTH]; //只管接收缓冲区的数据
int rlen; //记录接受从缓冲区数据的字节数
char wbuffer[BUFFER_LENGTH]; //只管往缓冲区发送数据
int wlen; //记录发送缓冲区的数据的字节数
//封装成联合体,可以用于监听套接字和clientfd, 进行回调
union{
RCALLBACK accept_callback;
RCALLBACK recv_callback;
}recv_t;
RCALLBACK send_callback;
};
int epfd = 0;
struct conn_item connlist[1024] = {0};
//用于设定fd 的事件类型,此函数用于cliendfd recv 后设置其fd 为EPOLLOUT, send 后设置fd为 EPOLLIN
int set_event(int fd, int event, int flag){
struct epoll_event ev;
ev.events = event;
ev.data.fd = fd;
if(flag){ //1:添加fd 0:修改fd mod
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
}else{
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}
return 0;
}
//用于接收缓冲区数据
int recv_cb(int fd){
char *buffer = connlist[fd].rbuffer;
int idx = connlist[fd].rlen;
int count = recv(fd, buffer+idx, BUFFER_LENGTH, 0);
if(count == 0){
printf("disconnect\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
return -1;
}
memcpy(connlist[fd].wbuffer, connlist[fd].rbuffer, connlist[fd].rlen);
connlist[fd].wlen += connlist[fd].rlen;
connlist[fd].rlen += count;
set_event(fd, EPOLLOUT, 0); //将fd 设置为可写事件
return count;
}
//向缓冲区发送数据
int send_cb(int fd){
char *buffer = connlist[fd].wbuffer;
int idx = connlist[fd].wlen;
int count = 0;
if(buffer){
count = send(fd, buffer, idx, 0);
set_event(fd, EPOLLIN, 0);
}
return count;
}
//
int accept_cb(int fd){
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int clientfd = accept(fd, (struct sockaddr *)&clientaddr, &len);
if(clientfd < 0){
return -1;
}
set_event(clientfd, EPOLLIN, 1);
connlist[clientfd].fd = clientfd;
memset(connlist[clientfd].rbuffer, 0, BUFFER_LENGTH);
connlist[clientfd].rlen = 0;
memset(connlist[clientfd].wbuffer, 0, BUFFER_LENGTH);
connlist[clientfd].wlen = 0;
connlist[clientfd].recv_t.recv_callback = recv_cb;
connlist[clientfd].send_callback = send_cb;
return clientfd;
}
int main(int argc, char const *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(6000);
if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)) < 0){
perror("bind");
return -1;
}
if(listen(sockfd, 10) < 0){
perror("listen");
return -1;
}
connlist[sockfd].fd = sockfd;
connlist[sockfd].recv_t.accept_callback = accept_cb;
epfd = epoll_create(1);
set_event(sockfd, EPOLLIN, 1);
struct epoll_event events[1024] = {0};
while(1){ //mainloop
int nready = epoll_wait(epfd, events, 1024, -1);
int i = 0;
for(i = 0; i < nready; i++){
int connfd = events[i].data.fd;
if(events[i].events &EPOLLIN){
int count = connlist[connfd].recv_t.recv_callback(connfd); //思考题: 1. connfd不在此处体现,能不能封装到底层
printf("recv: count: %d<--buffer: %s\n", count, connlist[i].rbuffer);
}else if(events[i].events &EPOLLOUT){
printf("send --> buffer: %s\n", connlist[connfd].wbuffer);
int count = connlist[connfd].send_callback(connfd);
}
}
}
return 0;
}
- 客户端: 客户端不间断发送hello 字符串,各位看官可以根据自己需要,来修改对应的代码逻辑
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#define BUFFSIZ 1024
char buffer[BUFFSIZ];
int main(int argc, char const *argv[])
{
int client_sock, ret;
struct sockaddr_in server_address;
client_sock = socket(AF_INET, SOCK_STREAM, 0);
if (client_sock == -1)
{
perror("create client sock failed");
exit(EXIT_FAILURE);
}
ret = inet_pton(AF_INET, "192.168.126.128", &server_address.sin_addr.s_addr);
if (ret == -1)
{
perror("inet_pton error");
exit(EXIT_FAILURE);
}
server_address.sin_family = AF_INET;
server_address.sin_port = htons(6000);
ret = connect(client_sock, (struct sockaddr *)&server_address, sizeof(server_address));
if (ret == -1)
{
perror("connect error");
exit(EXIT_FAILURE);
}
while (1)
{
//printf("client: ");
//memset(buffer, 0, BUFFSIZ);
send(client_sock, "hello", strlen("hello"), 0);
sleep(1);
}
return 0;
}