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

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

一 why

**
在前面的博客《linux进程间通信—本地socket套接字(五)—多路IO转接服务器实现一个server对应多个client—poll实现》,我们介绍了多路IO转接服务器之poll方式实现,今天我们使用epoll来实现多路IO转接服务器。
epoll是linux下多路复用IO接口select/poll的增强版本,能显著提高程序在大量并发连接中只有少量是活跃的情况下系统CPU的使用率;epoll和poll都是为了提升服务器性能的一种多路IO接口;
epoll和poll的特点都可以通过命令修改文件描述符数量

1. 用cat命令查看一个进程可以打开的socket描述符上限
    cat /proc/sys/fs/file-max
2. 如果由需要,可以通过修改配置温恩建的方式修改该上限值
    sudo vi /etc/security/limits.conf
    在文件尾部写入以下配置,soft软限制,hard硬限制,如下图所示
    soft nofile 65536
    hard nofile 100000

当连接的文件描述符比较多,但是监听的文件描述符比较少,那么epoll/poll性能较高;当连接的文件描述符比较多,但是监听的文件描述符比较多,那么epoll/poll和select性能相差不多。
**

二 what

**
基础API
(1) 创建

int epoll_create(int size)
size:监听数目

调用创建接口之后,相当于在内核中创建了一个红黑树(平衡二叉树),如下所示
linux进程间通信---本地socket套接字(七)---多路IO转接服务器实现一个server对应多个client---epoll实现_第2张图片
(2) 控制某个epoll监控的文件描述符上的事件 : 注册,修改,删除

typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
};

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd: epoll_create的句柄(句柄实际上是windows下面的叫法),以现实生活中举例来看
            句柄类比于把柄,我有了某人的把柄,基本上可以叫他干嘛就干嘛
op: 表示动作,用3个宏来表示
        EPOLL_CTL_ADD : 注册新的fd到epfd
        EPOLL_CTL_MOD : 修改已经注册的fd的监听时间
        EPOLL_CTL_DEL : 从epfd删除一个fd
fd : 待监听的文件描述符
event: 告诉内核需要监听的事件

调用epoll_ctl接口相当于向epfd指向的红黑树中添加子节点,假设我们分别添加了lfd,cfd1,cfd2到efd中

evt.events = EPOLLIN;
evt.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &evt);

evt.events = EPOLLIN;
evt.data.fd = cfd1;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd1, &evt);

evt.events = EPOLLIN;
evt.data.fd = cfd2;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd2, &evt);

红黑树当前的结构状态如下:
linux进程间通信---本地socket套接字(七)---多路IO转接服务器实现一个server对应多个client---epoll实现_第3张图片
(3) 等待所监控文件描述符上有事件产生,类似select()调用

int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
epfd: epoll_create的句柄
events: 用来存内核得到时间的集合,这里是一个数组,它是一个传出值
maxevents: 告知内核这个events有多大,这个值不能大于epoll_create()时的size
timeout: 超时时间
    -1,阻塞
    0,立即返回,不阻塞
    >0, ms
返回值: 成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1

当我们调用epoll_wait函数并且返回之后,evts数组如下(假设此时lfd和cfd1的读事件发生)
linux进程间通信---本地socket套接字(七)---多路IO转接服务器实现一个server对应多个client---epoll实现_第4张图片
epoll_wait返回之后,我们就需要判断当前文件描述符表示的是哪个文件描述符的事件,调用相应的处理,这里就是epoll和poll的一个区别,它能直接知道返回了几个对应的事件,不需要再做判断

for (i = 0; i < rval; i++) {
	== lfd   ---> accept
	== cfd1  ---> recv 
}

**

三 how

**
(1) 先创建socket

static int listenfd;

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

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

    rval = setsockopt(listenfd, 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(listenfd, (struct sockaddr*) &servaddr,
        sizeof(servaddr));
    if (rval < 0 ) {
        perror("bind fail");
        return -1;
    }

    rval = listen(listenfd, LISTENLEN);
    if (rval < 0 ) {
        perror("listen fail");
        return -1;
    }
}

(2) epoll监听
epoll监听我们放在main函数中实现,没有给提取出来

int main(int argc, char **argv)
{
    struct epoll_event evt;
    struct epoll_event evts[EPOLL_FD_MAX_NUM];
    struct sockaddr_in cliaddr;
    socklen_t clilen;
    int epfd = -1;
    int rval = 0, nready, n;
    int i, j, connfd, sockfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

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

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

    //先将listenfd事件添加到epoll中,因为只有listenfd检测到客户端的连接,然后才会
    //读取client发过来的数据
    evt.events = EPOLLIN;
    evt.data.fd = listenfd;
    rval = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &evt);
    if (rval < 0) {
        perror("epoll_ctl error");
        return 0;
    }

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

        for (i = 0; i < nready; i++) {
            if (evts[i].events != EPOLLIN) //如果不是读事件,继续循环
                continue;

            if (evts[i].data.fd == listenfd) {    // listenf事件
                clilen = sizeof(cliaddr);
                /* 因为检测到了listenfd读事件,这个时候调用accept就不会阻塞 */
                connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
                printf("received from %s at PORT %d\n", 
                    inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                    ntohs(cliaddr.sin_port));

                //将检测到的client读事件添加进来,给epoll监听
                evt.events = EPOLLIN;
                evt.data.fd = connfd;
                rval = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &evt);
                if (rval < 0) {
                    perror("epoll_ctl error");
                    return 0;
                }
            } else {    // cfd事件
                memset(buf, 0, sizeof(buf));
                sockfd = evts[i].data.fd;
                n = read(sockfd, buf, MAXLINE);
                if (n < 0) {
                    perror("read error");

                    //将当前文件描述符删除,沒有必要继续监听当前客户端读事件
                    rval = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
                    if (rval < 0) {
                        perror("epoll_ctl delete error");
                        return 0;
                    }
                    close(sockfd);
                } else if (n == 0) { //说明客戶端先关闭连接
                    //将当前文件描述符删除,沒有必要继续监听当前客户端读事件
                    rval = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
                    if (rval < 0) {
                        perror("epoll_ctl delete error");
                        return 0;
                    }

                    printf("client[%d] close connect\n", sockfd);
                    close(sockfd);
                } else { //收到有效数据
                    printf("server receive %s\n", buf);
                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    write(sockfd, buf, n);
                }
            }
        }
    }
}

server完整代码如下

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

#define MAXLINE	  1024
#define LISTENLEN 10
#define SERV_PORT 7809
#define EPOLL_FD_MAX_NUM 10

static int listenfd;

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

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

    rval = setsockopt(listenfd, 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(listenfd, (struct sockaddr*) &servaddr,
        sizeof(servaddr));
    if (rval < 0 ) {
        perror("bind fail");
        return -1;
    }

    rval = listen(listenfd, LISTENLEN);
    if (rval < 0 ) {
        perror("listen fail");
        return -1;
    }
}
 
int main(int argc, char **argv)
{
    struct epoll_event evt;
    struct epoll_event evts[EPOLL_FD_MAX_NUM];
    struct sockaddr_in cliaddr;
    socklen_t clilen;
    int epfd = -1;
    int rval = 0, nready, n;
    int i, j, connfd, sockfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

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

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

    //先将listenfd事件添加到epoll中,因为只有listenfd检测到客户端的连接,然后才会
    //读取client发过来的数据
    evt.events = EPOLLIN;
    evt.data.fd = listenfd;
    rval = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &evt);
    if (rval < 0) {
        perror("epoll_ctl error");
        return 0;
    }

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

        for (i = 0; i < nready; i++) {
            if (evts[i].events != EPOLLIN) //如果不是读事件,继续循环
                continue;

            if (evts[i].data.fd == listenfd) {    // listenf事件
                clilen = sizeof(cliaddr);
                /* 因为检测到了listenfd读事件,这个时候调用accept就不会阻塞 */
                connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
                printf("received from %s at PORT %d\n", 
                    inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                    ntohs(cliaddr.sin_port));

                //将检测到的client读事件添加进来,给epoll监听
                evt.events = EPOLLIN;
                evt.data.fd = connfd;
                rval = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &evt);
                if (rval < 0) {
                    perror("epoll_ctl error");
                    return 0;
                }
            } else {    // cfd事件
                memset(buf, 0, sizeof(buf));
                sockfd = evts[i].data.fd;
                n = read(sockfd, buf, MAXLINE);
                if (n < 0) {
                    perror("read error");

                    //将当前文件描述符删除,沒有必要继续监听当前客户端读事件
                    rval = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
                    if (rval < 0) {
                        perror("epoll_ctl delete error");
                        return 0;
                    }
                    close(sockfd);
                } else if (n == 0) { //说明客戶端先关闭连接
                    //将当前文件描述符删除,沒有必要继续监听当前客户端读事件
                    rval = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
                    if (rval < 0) {
                        perror("epoll_ctl delete error");
                        return 0;
                    }

                    printf("client[%d] close connect\n", sockfd);
                    close(sockfd);
                } else { //收到有效数据
                    printf("server receive %s\n", buf);
                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    write(sockfd, buf, n);
                }
            }
        }
    }
}

客户端代码如下

#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;
}

**

四 test

**
编译

gcc 10_server.c -o 10_server
gcc 10_client.c -o 10_client

运行

./10_server
./10_client

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