reactor

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);
            }
  • 事件处理器:执行对应事件的处理函数
  1. 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;
}
  1. 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;
}

  1. 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多路复用的区别

  1. IO 多路复用是针对IO 执行相应的处理, rector 则采用的是事件驱动。比如当服务端的监听套接字,有可读事件发生时调用accept函数去返回客户端套接字; 而reactor 框架下,当事件的类型是可读时,则通过事件分发器去派送事件进而调用事件处理器
  2. reactor 的IO多路复用器可以使用不同IO多路复用,包括select, poll, epoll

4. reactor 使用范围

5. 完整代码

  1. 服务端
#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;
}

  1. 客户端: 客户端不间断发送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;
}

学习链接

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值