linux进程间通信---本地socket套接字(八)---多路IO转接服务器实现一个server对应多个client---epoll反应堆实现

先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
linux进程间通信---本地socket套接字(八)---多路IO转接服务器实现一个server对应多个client---epoll反应堆实现_第1张图片
**

一 why

**
在前面的博客《linux进程间通信—本地socket套接字(七)—多路IO转接服务器实现一个server对应多个client—epoll实现》中,我们使用epoll机制来实现一个server对接多个client的方案。
我们在这里简单回顾一下之前的实现方案,其主要思想是:利用epoll_wait函数的返回值返回当前文件描述符读写事件发生的个数,然后依次遍历被我们添加进去的文件描述符,对比两者之间的文件描述符的值,也就是如下自然语言思想

for (i = 0; i < rval; i++) {  // 这里的rval就是epoll_wait的返回值,表示一共有多少个事件发生
	== lfd   ---> accept
	== cfd1  ---> recv 
}

考虑一下,这样做确实没有问题,但是效率比较低,因为需要程序去遍历多个文件描述符(结构体成员变量fd),逐个判断,如下

typedef union epoll_data {
    void        *ptr;
    int          fd;    // 这个就是文件描述符编号
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;

那么可否提高效率,避免逐个判断呢?这个就是epoll_data_t结构体成员变量*ptr的作用了。
**

二 what

**
我们可以思考,这个泛型指针*ptr究竟有什么作用呢?如何可以避免逐个判断的动作,直接知道当前文件描述符发生了什么操作,进而去实现某个操作呢?看下面的简图
linux进程间通信---本地socket套接字(八)---多路IO转接服务器实现一个server对应多个client---epoll反应堆实现_第2张图片
构造一个结构体,让ptr指针指向这个结构体,epoll_wait函数的第二个参数是一个传出参数,ptr指针就是data成员变量的ptr指针,将要操作的函数通过call_back注册到这个结构体中,就可以实现我们要的功能了。构造的结构体定义如下

typedef struct myevent_s {
    int fd;
    int events;
    void *arg;
    void (*call_back)(int fd, int events, void *arg);
    int status;
    char buf[BUFLEN];
    char send_buf[BUFLEN];
    int len;
    int send_len;
    long last_active;
} myevent_t;

**

三 how

**
在写代码之前,我们先看一下利用epoll反应堆机制实现sever的大概过程。文件描述符还是有监听文件描述符(lfd),客户端文件描述符(cfd)。

  1. 我们通过监听文件描述符来监听客户端的连接情况
  2. 通过客户端文件描述符来实现server和client之间的数据传输,一般地都是client先发送消息到server,所以server先设置为读事件,当server收到client的数据之后,再设置为写事件,回消息到client
epoll ---服务器 ---监听 --- cfd ---可读 ---epoll返回 ---read
   ---cfd从树上摘下 ---重新设置监听cfd写事件,操作 ---等待epoll_wait返回 
   ---处理(回写到客户端) ---cfd从树上摘下 ---重新设置监听cfd读事件,操作
   ---epoll继续监听

server代码

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFLEN	  1024
#define MAX_EVENTS 10
#define SERV_PORT 7809

typedef struct myevent_s {
    int fd;
    int events;
    void *arg;
    void (*call_back)(int fd, int events, void *arg);
    int status;
    char buf[BUFLEN];
    char send_buf[BUFLEN];
    int len;
    int send_len;
    long last_active;
} myevent_t;

static int lfd;
static int g_efd;
static myevent_t g_events[MAX_EVENTS+1];  // +1 ---> lfd

static void recvdata(int fd, int events, void *arg);
static void senddata(int fd, int events, void *arg);

/* 初始化 myevent_t */
static void eventset(myevent_t *ev, int fd, void (*call_back)(int fd, int events, void *arg),
    void *arg)
{
    ev->fd = fd;
    ev->events = 0;
    ev->arg = arg;
    ev->call_back = call_back;
    ev->status = 0;
    memset(ev->buf, 0, sizeof(ev->buf));
    ev->len = 0;
    ev->last_active = time(NULL);      //调用eventset函数的事件

    return ;
}

static void eventadd(int efd, int events, myevent_t *ev)
{
    struct epoll_event epv = {0, {0}};
    int op, rval = 0;

    epv.data.ptr = ev;
    epv.events = ev->events = events;   //EPOLLIN

    if (ev->status == 1) {   //已经在红黑树 g_efd里
        op = EPOLL_CTL_MOD;  //修改其属性
    } else {
        op = EPOLL_CTL_ADD;  //不在红黑树 g_efd里
        ev->status = 1;      //将其加入到红黑树 g_efd里
    }

    //先将lfd事件添加到epoll中,因为只有lfd检测到客户端的连接,然后才会
    //读取client发过来的数据
    rval = epoll_ctl(efd, op, ev->fd, &epv);
    if (rval < 0) {
        perror("epoll_ctl error");
    } else {
        printf("event add ok, fd=%d, op=%d, events[%0x]\n", ev->fd, op, events);
    }

    return ;
}

static void eventdel(int efd, myevent_t *ev)
{
    struct epoll_event epv = {0, {0}};

    if (ev->status == 0)   // 不在红黑树上
        return ;

    epv.data.ptr = ev;
    ev->status = 0;    // 修改状态

    // 从红黑树上 efd 摘除 ev->fd
    epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);
    return ;
}

static void senddata(int fd, int events, void *arg)
{
    myevent_t *ev = (myevent_t *)arg;
    int len;

    // 写文件描述符
    len = send(fd, ev->send_buf, ev->send_len, 0);

    // 将该节点从红黑树摘除
    eventdel(g_efd, ev);

    if (len > 0) {
        printf("now send: %s\n", ev->send_buf);

        // 将cfd设置给一个 g_events ,并添加回调函数 recvdata
        eventset(ev, fd, recvdata, ev);
        // 将cfd添加到红黑树 g_efd 中,监听读事件
        eventadd(g_efd, EPOLLIN, ev);
    } else {
        close(ev->fd);     // 关闭连接
        perror("send error");
    }
    
    return ;
}

static void recvdata(int fd, int events, void *arg)
{
    myevent_t *ev = (myevent_t *)arg;
    int len;

    // 读文件描述符
    len = recv(fd, ev->buf, sizeof(ev->buf), 0);

    eventdel(g_efd, ev);  // 将该节点从红黑树摘除

    if (len > 0) {
        ev->len = len;
        ev->buf[len] = '\0';
        printf("now recv: %s\n", ev->buf);

        memcpy(ev->send_buf, ev->buf, len);
        ev->send_len = len;
        // 将cfd设置给一个 g_events ,并添加回调函数 senddata
        eventset(ev, fd, senddata, ev);
        // 将cfd添加到红黑树 g_efd 中,监听写事件
        eventadd(g_efd, EPOLLOUT, ev);
    } else if (len == 0) {
        close(ev->fd);
        printf("fd=%d pos=%ld\n", fd, ev - g_events);
    } else {
        close(ev->fd);
        perror("recv error!");
    }

    return ;
}

static void acceptconn(int lfd, int events, void *arg)
{
    struct sockaddr_in cliaddr;
    socklen_t clilen;
    int i, cfd;
    int flag = 0;
    char str[INET_ADDRSTRLEN];

    cfd = accept(lfd, (struct sockaddr *)&cliaddr, &clilen);
    if (cfd < 0) {
        perror("accept error\n");
        return;
    }

    do {
        // 从 g_events 找一空闲元素, 类似于select中找值为 -1的元素
        for (i = 0; i < MAX_EVENTS; i++) {
            if (g_events[i].status == 0)
                break;
        }

        if (i >= MAX_EVENTS) {
            printf("max connect limit[%d]\n", MAX_EVENTS);
            break;
        }

        flag = fcntl(cfd, F_SETFL, O_NONBLOCK);
        if (flag < 0) {
            perror("fcntl O_NONBLOCK failed");
            break;
        }

        // 将cfd设置给一个 g_events ,并添加回调函数 recvdata
        eventset(&g_events[i], cfd, recvdata, &g_events[i]);

        // 将cfd添加到红黑树 g_efd 中,监听读事件
        eventadd(g_efd, EPOLLIN, &g_events[i]);
    } while(0);

    printf("received from %s at PORT %d\n", 
        inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
        ntohs(cliaddr.sin_port));

    return ;
}

static int socket_server_init(int efd)
{
    struct sockaddr_in servaddr;
    int opt = 1;
    int rval = 0;

    lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd < 0) {
        perror("create socket fail");
        return -1;
    }

    //将 socket 设为非阻塞
    fcntl(lfd, F_SETFL, O_NONBLOCK);

    eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);
    eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);

    rval = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR,
        &opt, sizeof(opt));
    if (rval < 0) {
        perror("setsockopt fail");
        return -1;
    }

    bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);
    
    rval = bind(lfd, (struct sockaddr*) &servaddr,
        sizeof(servaddr));
    if (rval < 0 ) {
        perror("bind fail");
        return -1;
    }

    rval = listen(lfd, MAX_EVENTS);
    if (rval < 0 ) {
        perror("listen fail");
        return -1;
    }
}
 
int main(int argc, char **argv)
{
    struct epoll_event evt;
    struct epoll_event evts[MAX_EVENTS+1];
    int rval = 0, nready;
    int i, checkops = 0;

    //创建g_efd
    g_efd = epoll_create(MAX_EVENTS);
    if (g_efd == -1) {
        perror("epoll_create error");
        return 0;
    }

    rval = socket_server_init(g_efd);
    if (rval < 0) {
        printf("socket_server_init fail\n");
        return 0;
    }

    while (1) {
        nready = epoll_wait(g_efd, evts, MAX_EVENTS, -1);
        if (nready <= 0) {
            perror("epoll_wait error");
            return 0;
        }

        for (i = 0; i < nready; i++) {
            myevent_t *ev = (myevent_t *)evts[i].data.ptr;
            
            if ((evts[i].events & EPOLLIN) && (ev->events & EPOLLIN)) {
                ev->call_back(ev->fd, evts[i].events, ev->arg);
            }
            if ((evts[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) {
                ev->call_back(ev->fd, evts[i].events, ev->arg);
            }
        }
    }
}

client代码还是和之前的一样

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT  7809
#define BUFFER_SIZE 1024
#define LOCAL_LOOP_BACK_IP "127.0.0.1"

int main(int argc, char **argv)
{
    struct sockaddr_in servaddr;
    char sendbuf[BUFFER_SIZE] = {0};
    char recvbuf[BUFFER_SIZE] = {0};
    int client_fd;

    //定义IPV4的TCP连接的套接字描述符
    client_fd = socket(AF_INET,SOCK_STREAM, 0);

    //set sockaddr_in
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(LOCAL_LOOP_BACK_IP);
    servaddr.sin_port = htons(PORT);  //服务器端口

    //连接服务器,成功返回0,错误返回-1
    if (connect(client_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        perror("connect");
        exit(1);
    }
    printf("connect server(IP:%s).\n",LOCAL_LOOP_BACK_IP);

    //客户端将控制台输入的信息发送给服务器端,服务器原样返回信息
    while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
    {
        send(client_fd, sendbuf, strlen(sendbuf),0); ///发送
        if(strcmp(sendbuf,"exit\n")==0)
        {
            printf("client exited.\n");
            break;
        }
        recv(client_fd, recvbuf, sizeof(recvbuf),0); ///接收
        printf("client receive: %s\n", recvbuf);
 
        memset(sendbuf, 0, sizeof(sendbuf));
        memset(recvbuf, 0, sizeof(recvbuf));
    }

    close(client_fd);
    return 0;
}

你可能感兴趣的:(linux系统)