C语言网络编程-tcp服务器实现

举报
仙士可 发表于 2023/02/03 15:38:17 2023/02/03
【摘要】 5种io模型------tcp服务器分为了5种io复用模型,分别是:阻塞io模型      非阻塞io模型io复用信号驱动io异步io  本文会讲前面3种io模型的tcp服务器实现(本文只做tcp服务器实现,客户端逻辑处理,接收数据等缓冲区不做深入说明)简单实现----首先,我们需要理解下tcp服务器的创建过程:1:通过socket函数创建一个套接字文件2:通过bind函数将本地一个地址和套...

5种io模型
------

tcp服务器分为了5种io复用模型,分别是:

阻塞io模型      

非阻塞io模型

io复用

信号驱动io

异步io  

本文会讲前面3种io模型的tcp服务器实现(本文只做tcp服务器实现,客户端逻辑处理,接收数据等缓冲区不做深入说明)

简单实现
----

首先,我们需要理解下tcp服务器的创建过程:

1:通过socket函数创建一个套接字文件

2:通过bind函数将本地一个地址和套接字捆绑

3:使用listen函数监听外部请求

4:使用accept函数接收外部请求

5:read,write,close 用于收,发,关闭客户端数据

  

好了,我们了解了tcp服务器的创建过程,就开始实现吧:

```cpp
#include <stdio.h>
#include <arpa/inet.h>//inet\_addr() sockaddr\_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()

#define BUFFER_SIZE 1024

int main() {
    char listen\_addr\_str\[\] = "0.0.0.0";
    size\_t listen\_addr = inet\_addr(listen\_addr_str);
    int port = 8080;
    int server\_socket, client\_socket;
    struct sockaddr\_in server\_addr, client_addr;
    socklen\_t addr\_size;
    char buffer\[BUFFER_SIZE\];//缓冲区大小

    int str_length;

    server\_socket = socket(PF\_INET, SOCK_STREAM, 0);//创建套接字

    bzero(&server\_addr, sizeof(server\_addr));//初始化
    server\_addr.sin\_family = INADDR_ANY;
    server\_addr.sin\_port = htons(port);
    server\_addr.sin\_addr.s\_addr = listen\_addr;

    if (bind(server\_socket, (struct sockaddr *) &server\_addr, sizeof(server_addr)) == -1) {
        printf("绑定失败\\n");
        exit(1);
    }
    if (listen(server_socket, 5) == -1) {
        printf("监听失败\\n");
        exit(1);
    }

    printf("创建tcp服务器成功\\n");
    addr\_size = sizeof(client\_addr);
    client\_socket = accept(server\_socket, (struct sockaddr *) &client\_addr, &addr\_size);
    printf("%d 连接成功\\n", client_socket);
    char msg\[\] = "恭喜你连接成功";
    write(client_socket, msg, sizeof(msg));

    while (1) {
        str\_length = read(client\_socket, buffer, BUFFER_SIZE);
        if (str_length == 0)    //读取数据完毕关闭套接字
        {
            close(client_socket);
            printf("连接已经关闭: %d \\n", client_socket);
            break;
        } else {
            printf("客户端发送数据:%s",buffer);
            write(client\_socket, buffer, str\_length);//发送数据
        }
    }

    return 0;
}
```

多客户端TCP服务器
----------

以上代码实现了一个服务器,并且可以接收一个客户端连接,和它互相收发信息,但是看代码很容易发现不支持多客户端,只支持一个,那么怎么才能实现支持多个客户端呢?我们稍微改一改这份代码:

```cpp
#include <stdio.h>
#include <arpa/inet.h>//inet\_addr() sockaddr\_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()

#define BUFFER_SIZE 1024

int main() {
    char listen\_addr\_str\[\] = "0.0.0.0";
    size\_t listen\_addr = inet\_addr(listen\_addr_str);
    int port = 8080;
    int server\_socket, client\_socket;
    struct sockaddr\_in server\_addr, client_addr;
    socklen\_t addr\_size;
    char buffer\[BUFFER_SIZE\];//缓冲区大小

    size\_t client\_arr\[100\];//存储客户端数组
    int client_length=0;//记录客户端数量

    int str_length;

    server\_socket = socket(PF\_INET, SOCK_STREAM, 0);//创建套接字

    bzero(&server\_addr, sizeof(server\_addr));//初始化
    server\_addr.sin\_family = INADDR_ANY;
    server\_addr.sin\_port = htons(port);
    server\_addr.sin\_addr.s\_addr = listen\_addr;

    if (bind(server\_socket, (struct sockaddr *) &server\_addr, sizeof(server_addr)) == -1) {
        printf("绑定失败\\n");
        exit(1);
    }
    if (listen(server_socket, 5) == -1) {
        printf("监听失败\\n");
        exit(1);
    }

    printf("创建tcp服务器成功\\n");

    while (1) {
        addr\_size = sizeof(client\_addr);
        client\_socket = accept(server\_socket, (struct sockaddr *) &client\_addr, &addr\_size);
        client\_arr\[client\_length\] = client_socket;
        client_length++;
        printf("%d 连接成功\\n", client_socket);
        char msg\[\] = "恭喜你连接成功";
        write(client_socket, msg, sizeof(msg));

        for (int i = 0; i < client_length; ++i) {
            if (client_arr\[i\]==0){
                continue;
            }
            str\_length = read(client\_arr\[i\], buffer, BUFFER_SIZE);
            if (str_length == 0)    //读取数据完毕关闭套接字
            {
                close(client_arr\[i\]);
                client_arr\[i\]=0;
                printf("连接已经关闭: %d \\n", client_arr\[i\]);
                break;
            } else {
                printf("客户端发送数据:%s",buffer);
                write(client\_arr\[i\], buffer, str\_length);//发送数据
            }
        }
    }
}
```

我们通过将client_socket存储到一个数组里,然后每次去遍历该数组,可以勉强实现一个所谓的多客户端tcp服务器,但是有个致命弱点:  

由于accept,read函数是阻塞的,导致这份代码,每次运行都得客户端连接,才能到下面的遍历代码,导致代码根本就没什么卵用:

A客户端连接好了,然后发送了条消息,服务器还得等到B客户端连接,才能接收到A的消息

,然后,B客户端发送好消息,需要C客户端连接,然后还得A客户端发送了条消息,才能遍历到B客户端的消息

  

多进程TCP服务器
---------

这样的话,这份代码根本没什么卵用啊!!!!!!该怎么解决这个问题呢?????

我们或许可以通过多进程去解决这个问题,每个进程只处理一条客户端,就不存在什么阻塞不阻塞的问题了:

```php
#include <stdio.h>
#include <arpa/inet.h>//inet\_addr() sockaddr\_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()
#include<sys/wait.h>//waitpid();

#define BUFFER_SIZE 1024

int main() {
    char listen\_addr\_str\[\] = "0.0.0.0";
    size\_t listen\_addr = inet\_addr(listen\_addr_str);
    int port = 8080;
    int server\_socket, client\_socket;
    struct sockaddr\_in server\_addr, client_addr;
    socklen\_t addr\_size;
    char buffer\[BUFFER_SIZE\];//缓冲区大小

    int str_length;
    pid_t pid;
    int status = 0;//初始化状态

    server\_socket = socket(PF\_INET, SOCK_STREAM, 0);//创建套接字

    bzero(&server\_addr, sizeof(server\_addr));//初始化
    server\_addr.sin\_family = INADDR_ANY;
    server\_addr.sin\_port = htons(port);
    server\_addr.sin\_addr.s\_addr = listen\_addr;

    if (bind(server\_socket, (struct sockaddr *) &server\_addr, sizeof(server_addr)) == -1) {
        printf("绑定失败\\n");
        exit(1);
    }
    if (listen(server_socket, 5) == -1) {
        printf("监听失败\\n");
        exit(1);
    }

    printf("创建tcp服务器成功\\n");

    while (1) {
        addr\_size = sizeof(client\_addr);
        client\_socket = accept(server\_socket, (struct sockaddr *) &client\_addr, &addr\_size);
        printf("%d 连接成功\\n", client_socket);
        char msg\[\] = "恭喜你连接成功";
        write(client_socket, msg, sizeof(msg));
        pid = fork();
        if (pid > 0) {
            sleep(1);//父进程,进行下次循环,读取客户端连接事件
            waitpid(-1, &status, WNOHANG | WUNTRACED | WCONTINUED);
            if (WIFEXITED(status)) {
                printf("status = %d\\n", WEXITSTATUS(status));
            }
            if (WIFSIGNALED(status)) { //如果子进程是被信号结束了 ,则为真
                printf("signal status = %d\\n", WTERMSIG(status));
                //R->T
            }
            if (WIFSTOPPED(status)) {
                printf("stop sig num = %d\\n", WSTOPSIG(status));
            }
            //T->R
            if (WIFCONTINUED(status)) {
                printf("continue......\\n");
            }
        } else if (pid == 0) {//子进程,进行阻塞式收发客户端数据
            while (1) {
                memset(buffer, 0, sizeof(buffer));
                str\_length = read(client\_socket, buffer, BUFFER_SIZE);
                if (str_length == 0)    //读取数据完毕关闭套接字
                {
                    close(client_socket);
                    printf("连接已经关闭: %d \\n", client_socket);
                    exit(1);
                } else {
                    printf("%d 客户端发送数据:%s \\n", client_socket, buffer);
                    write(client\_socket, buffer, str\_length);//发送数据
                }
            }
            break;
        } else {
            printf("创建子进程失败\\n");
            exit(1);
        }
    }

    return 0;
}
```

![仙士可博客](/Upload/image/ueditor/20181103/1541257715286178.png "仙士可博客")

  

通过多进程,我们可以实现一个较完美的多进程TCP服务器,这个服务器可以完美的去处理多个客户端的数据

但是,一个进程处理一个连接,如果连接多的时候,会造成进程的频繁创建销毁,进程开销会非常大,导致cpu占用太大

所以,直接使用多进程去处理,还是不够完美的

由第二个例子,我们可以发现,主要问题出在于accept,read函数的阻塞上面,有没有什么办法处理掉这个阻塞呢?

  

非阻塞式TCP服务器
----------

在c语言中,可以使用fcntl函数,将套接字设置为非阻塞的

```cpp
#include <stdio.h>
#include <arpa/inet.h>//inet\_addr() sockaddr\_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()
#include <fcntl.h>//非阻塞

#define BUFFER_SIZE 1024

int set\_non\_block(int socket) {
    int flags = fcntl(socket, F_GETFL, 0);
    flags |= O_NONBLOCK;
    return fcntl(socket, F_SETFL, flags);
}

int main() {
    char listen\_addr\_str\[\] = "0.0.0.0";
    size\_t listen\_addr = inet\_addr(listen\_addr_str);
    int port = 8080;
    int server\_socket, client\_socket;
    struct sockaddr\_in server\_addr, client_addr;
    socklen\_t addr\_size;
    char buffer\[BUFFER_SIZE\];//缓冲区大小

    size\_t client\_arr\[100\];//存储客户端数组
    int client_length = 0;//记录客户端数量

    int str_length;

    server\_socket = socket(PF\_INET, SOCK_STREAM, 0);//创建套接字

    bzero(&server\_addr, sizeof(server\_addr));//初始化
    server\_addr.sin\_family = INADDR_ANY;
    server\_addr.sin\_port = htons(port);
    server\_addr.sin\_addr.s\_addr = listen\_addr;

    if (bind(server\_socket, (struct sockaddr *) &server\_addr, sizeof(server_addr)) == -1) {
        printf("绑定失败\\n");
        exit(1);
    }
    if (listen(server_socket, 5) == -1) {
        printf("监听失败\\n");
        exit(1);
    }

    if (set\_non\_block(server_socket) == -1) {//设置非阻塞
        printf("设置非阻塞失败\\n");
        exit(1);
    }

    printf("创建tcp服务器成功\\n");

    while (1) {
        addr\_size = sizeof(client\_addr);
        client\_socket = accept(server\_socket, (struct sockaddr *) &client\_addr, &addr\_size);
        if (client_socket > 0) {//非阻塞下,无法读取返回-1
            client\_arr\[client\_length\] = client_socket;
            client_length++;
            if (set\_non\_block(client_socket) == -1) {//设置非阻塞
                printf("设置客户端非阻塞失败\\n");
                exit(1);
            }
            printf("%d 连接成功\\n", client_socket);
            char msg\[\] = "恭喜你连接成功";
            write(client_socket, msg, sizeof(msg));
        }

        for (int i = 0; i < client_length; ++i) {
            if (client_arr\[i\] == 0) {
                continue;
            }
            memset(&buffer, 0, sizeof(buffer));
            str\_length = read(client\_arr\[i\], buffer, BUFFER_SIZE);
            if (str_length==-1){//非阻塞下,无法读取返回-1
                continue;
            }
            if (str_length == 0)    //读取数据完毕关闭套接字
            {
                close(client_arr\[i\]);
                client_arr\[i\] = 0;
                printf("连接已经关闭: %d \\n", client_arr\[i\]);
                break;
            } else {
                printf("客户端发送数据:%s", buffer);
                write(client\_arr\[i\], buffer, str\_length);//发送数据
            }
        }
        usleep(100);//非阻塞下,如果全部socket无法读取(没有事件变化),则相当于是while(1),会使cpu繁忙
    }
}
```

这样,我们就实现了一个单进程多客户端的tcp服务器了,不需要多进程也能实现多客户端,但是看最后一行注释能发现一个问题:非阻塞下,会无限循环,让代码空转,这样浪费的性能也是巨大的,那我们该怎么完善呢?或许我们可以用到I/O复用模型

  

select机制TCP服务器
--------------

select是系统级别的功能,它可以同时阻塞探测多个socket,并且返回可调用的socket的数量  

原理图大概为:

![仙士可博客](/Upload/image/ueditor/20181103/1541258895461226.png "仙士可博客")

  

实现代码:

```cpp
#include <stdio.h>
#include <arpa/inet.h>//inet\_addr() sockaddr\_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()


#define BUFFER_SIZE 1024

int main() {
    char listen\_addr\_str\[\] = "0.0.0.0";
    size\_t listen\_addr = inet\_addr(listen\_addr_str);
    int port = 8080;
    int server\_socket, client\_socket;
    struct sockaddr\_in server\_addr, client_addr;
    socklen\_t addr\_size;
    char buffer\[BUFFER_SIZE\];//缓冲区大小

    int str_length;

    server\_socket = socket(PF\_INET, SOCK_STREAM, 0);//创建套接字

    bzero(&server\_addr, sizeof(server\_addr));//初始化
    server\_addr.sin\_family = INADDR_ANY;
    server\_addr.sin\_port = htons(port);
    server\_addr.sin\_addr.s\_addr = listen\_addr;

    if (bind(server\_socket, (struct sockaddr *) &server\_addr, sizeof(server_addr)) == -1) {
        printf("绑定失败\\n");
        exit(1);
    }
    if (listen(server_socket, 5) == -1) {
        printf("监听失败\\n");
        exit(1);
    }
    printf("创建tcp服务器成功\\n");


    fd\_set reads,copy\_reads;
    int fd\_max,fd\_num;
    struct timeval timeout;

    FD_ZERO(&reads);//初始化清空socket集合
    FD\_SET(server\_socket,&reads);
    fd\_max=server\_socket;


    while (1) {
        copy_reads = reads;
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;

        //无限循环调用select 监视可读事件
        if((fd\_num = select(fd\_max+1, &copy_reads, 0, 0, &timeout)) == -1) {
            perror("select error");
            break;
        }
        if (fd_num==0){//没有变动的socket
            continue;
        }

        for(int i=0;i<fd_max+1;i++){
            if(FD\_ISSET(i,&copy\_reads)){
                if (i==server\_socket){//server\_socket变动,代表有新客户端连接
                    addr\_size = sizeof(client\_addr);
                    client\_socket = accept(server\_socket, (struct sockaddr *) &client\_addr, &addr\_size);
                    printf("%d 连接成功\\n", client_socket);
                    char msg\[\] = "恭喜你连接成功";
                    write(client_socket, msg, sizeof(msg));
                    FD\_SET(client\_socket,&reads);
                    if(fd\_max < client\_socket){
                        fd\_max=client\_socket;
                    }
                }else{
                    memset(buffer, 0, sizeof(buffer));
                    str\_length = read(i, buffer, BUFFER\_SIZE);
                    if (str_length == 0)    //读取数据完毕关闭套接字
                    {
                        close(i);
                        printf("连接已经关闭: %d \\n", i);
                        FD_CLR(i, &reads);//从reads中删除相关信息
                    } else {
                        printf("%d 客户端发送数据:%s \\n", i, buffer);
                        write(i, buffer, str_length);//将数据发送回客户端
                    }
                }


            }
        }

    }

    return 0;
}
```

上面就是select机制的tcp实现代码,可以同时处理多客户端,性能比多进程好了很多,但这并不是说明select机制没有缺点了

在这份代码中,可以发现以下几点:  
1:客户端的socket标识符是存在一个fd\_set类型中的集合中的,客户端大小由fd\_set大小决定,开发时需要考虑到这个的最大值

2:每次调用select函数之前,都得将集合重新传给select,效率较慢;

3:每次调用完select函数,就算返回1,也会将集合全部遍历一遍,效率较慢

  

epoll机制TCP服务器
-------------

原理图大概为:  

![仙士可博客](/Upload/image/ueditor/20181105/1541428670200259.png "仙士可博客")

  

epoll机制提供了以下3个核心函数:

epoll_create() 创建epoll监听socket

epoll_ctl()注册,删除,修改监听

epoll_wait() 等待事件触发函数

  

在实现epoll机制前,我们得先了解下ET/LT模式

### LT(level-trigger) 水平触发  

epoll的默认工作方式,在这个模式下,只要监听的socket有可读/可写状态,都将返回该socket,例如:  

当客户端给tcp服务器发送一个数据时,这个client\_socket将会是可读的,调用epoll\_wait函数将会返回该client_socket,

如果服务器不做处理,这个client\_socket将会是一直可读的,下次调用epoll\_wait函数将会继续返回client_socket

实现代码:

```cpp
#include <stdio.h>
#include <arpa/inet.h>//inet\_addr() sockaddr\_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()
#include <sys/epoll.h> //epoll

#define BUFFER_SIZE 1024
#define CLIENT\_MAX\_SIZE 1024

int main() {
    char listen\_addr\_str\[\] = "0.0.0.0";
    size\_t listen\_addr = inet\_addr(listen\_addr_str);
    int port = 8080;
    int server\_socket, client\_socket;
    struct sockaddr\_in server\_addr, client_addr;
    socklen\_t addr\_size;
    char buffer\[BUFFER_SIZE\];//缓冲区大小
    int str_length;

    server\_socket = socket(PF\_INET, SOCK_STREAM, 0);//创建套接字

    bzero(&server\_addr, sizeof(server\_addr));//初始化
    server\_addr.sin\_family = INADDR_ANY;
    server\_addr.sin\_port = htons(port);
    server\_addr.sin\_addr.s\_addr = listen\_addr;

    if (bind(server\_socket, (struct sockaddr *) &server\_addr, sizeof(server_addr)) == -1) {
        printf("绑定失败\\n");
        exit(1);
    }
    if (listen(server_socket, 5) == -1) {
        printf("监听失败\\n");
        exit(1);
    }

    printf("创建tcp服务器成功\\n");


    struct epoll_event event;//监听事件
    struct epoll\_event wait\_event\_list\[CLIENT\_MAX_SIZE\];//监听结果
    int fd\[CLIENT\_MAX\_SIZE\];
    int j = 0;
    int epoll\_fd = epoll\_fd = epoll_create(10);//创建epoll句柄,里面的参数10没有意义
    if (epoll_fd == -1) {
        printf("创建epoll句柄失败\\n");
        exit(1);
    }
    event.events = EPOLLIN;//可读事件
    event.data.fd = server\_socket;//server\_socket

    int result = epoll\_ctl(epoll\_fd, EPOLL\_CTL\_ADD, server_socket, &event);

    if (result == -1) {
        printf("注册epoll 事件失败\\n");
        exit(1);
    }

    while (1) {
        result = epoll\_wait(epoll\_fd, wait\_event\_list, CLIENT\_MAX\_SIZE, -1);//阻塞
        if (result <= 0) {
            continue;
        }
        for (j = 0; j < result; j++) {
            printf("%d 触发事件 %d \\n", wait\_event\_list\[j\].data.fd, wait\_event\_list\[j\].events);
            //server_socket触发事件
            if (server\_socket == wait\_event\_list\[j\].data.fd && EPOLLIN == wait\_event_list\[j\].events & EPOLLIN) {
                addr\_size = sizeof(client\_addr);
                client\_socket = accept(server\_socket, (struct sockaddr *) &client\_addr, &addr\_size);
                printf("%d 连接成功\\n", client_socket);
                char msg\[\] = "恭喜你连接成功";
                write(client_socket, msg, sizeof(msg));

                event.data.fd = client_socket;
                event.events = EPOLLIN;//可读或错误
                result = epoll\_ctl(epoll\_fd, EPOLL\_CTL\_ADD, client_socket, &event);
                if (result == -1) {
                    printf("注册客户端 epoll 事件失败\\n");
                    exit(1);
                }
                continue;
            }

            //客户端触发事件
            if ((wait\_event\_list\[j\].events & EPOLLIN)
                ||(wait\_event\_list\[j\].events & EPOLLERR))//可读或发生错误
            {
                memset(&buffer, 0, sizeof(buffer));
                str\_length = read(wait\_event\_list\[j\].data.fd, buffer, BUFFER\_SIZE);
                if (str_length == 0)    //读取数据完毕关闭套接字
                {
                    close(wait\_event\_list\[j\].data.fd);
                    event.data.fd = wait\_event\_list\[j\].data.fd;
                    epoll\_ctl(epoll\_fd, EPOLL\_CTL\_DEL, wait\_event\_list\[j\].data.fd, &event);
                    printf("连接已经关闭: %d \\n", wait\_event\_list\[j\].data.fd);
                } else {
                    printf("客户端发送数据:%s \\n", buffer);
                    write(wait\_event\_list\[j\].data.fd, buffer, str_length);//执行回声服务  即echo
                }

            }
        }
    }

//    return 0;
}
```

lt模式下,也可以使用非阻塞模式,以上代码未使用

  

### ET(edge-trigger) 边缘触发

通过注册监听增加EPOLLET参数可将模式转换成边缘触发,

在et模式下,socket触发的多个事件只会返回一次,必须一次性全部处理,例如:

server\_socket 有10个待处理的新连接,在epoll\_wait函数返回后,必须循环读取accept直到没有数据可读,

由于必须一直循环读取,所以当accept没有数据可读时,必须是非阻塞模式,否则会阻塞

实现代码

```cpp
#include <stdio.h>
#include <arpa/inet.h>//inet\_addr() sockaddr\_in
#include <string.h>//bzero()
#include <sys/socket.h>//socket
#include <unistd.h>
#include <stdlib.h>//exit()
#include <sys/epoll.h> //epoll

#define BUFFER_SIZE 1024
#define CLIENT\_MAX\_SIZE 1024


int set\_non\_block(int socket) {
    int flags = fcntl(socket, F_GETFL, 0);
    flags |= O_NONBLOCK;
    return fcntl(socket, F_SETFL, flags);
}

int main() {
    char listen\_addr\_str\[\] = "0.0.0.0";
    size\_t listen\_addr = inet\_addr(listen\_addr_str);
    int port = 8080;
    int server\_socket, client\_socket;
    struct sockaddr\_in server\_addr, client_addr;
    socklen\_t addr\_size;
    char buffer\[BUFFER_SIZE\];//缓冲区大小
    int str_length;

    server\_socket = socket(PF\_INET, SOCK_STREAM, 0);//创建套接字

    bzero(&server\_addr, sizeof(server\_addr));//初始化
    server\_addr.sin\_family = INADDR_ANY;
    server\_addr.sin\_port = htons(port);
    server\_addr.sin\_addr.s\_addr = listen\_addr;

    if (bind(server\_socket, (struct sockaddr *) &server\_addr, sizeof(server_addr)) == -1) {
        printf("绑定失败\\n");
        exit(1);
    }
    if (listen(server_socket, 5) == -1) {
        printf("监听失败\\n");
        exit(1);
    }

    printf("创建tcp服务器成功\\n");


    set\_non\_block(server_socket);//设置非阻塞
    struct epoll_event event;//监听事件
    struct epoll\_event wait\_event\_list\[CLIENT\_MAX_SIZE\];//监听结果
    int fd\[CLIENT\_MAX\_SIZE\];
    int j = 0;
    int epoll\_fd = epoll\_fd = epoll_create(10);//创建epoll句柄,里面的参数10没有意义
    if (epoll_fd == -1) {
        printf("创建epoll句柄失败\\n");
        exit(1);
    }
    event.events = EPOLLIN|EPOLLET;//注册可读事件+et模式
    event.data.fd = server\_socket;//server\_socket

    int result = epoll\_ctl(epoll\_fd, EPOLL\_CTL\_ADD, server_socket, &event);

    if (result == -1) {
        printf("注册epoll 事件失败\\n");
        exit(1);
    }

    while (1) {
        result = epoll\_wait(epoll\_fd, wait\_event\_list, CLIENT\_MAX\_SIZE, -1);//阻塞
        if (result <= 0) {
            continue;
        }
        for (j = 0; j < result; j++) {
            printf("%d 触发事件 %d \\n", wait\_event\_list\[j\].data.fd, wait\_event\_list\[j\].events);
            //server_socket触发事件
            if (server\_socket == wait\_event\_list\[j\].data.fd && EPOLLIN == wait\_event_list\[j\].events & EPOLLIN) {
                addr\_size = sizeof(client\_addr);
                while(1) {
                    client\_socket = accept(server\_socket, (struct sockaddr *) &client\_addr, &addr\_size);

                    if(client_socket==-1){//没有数据可读
                        break;
                    }
                    printf("%d 连接成功\\n", client_socket);
                    char msg\[\] = "恭喜你连接成功";
                    write(client_socket, msg, sizeof(msg));
                    set\_non\_block(client_socket);//设置非阻塞

                    event.data.fd = client_socket;
                    event.events = EPOLLIN|EPOLLET;//可读+et模式
                    result = epoll\_ctl(epoll\_fd, EPOLL\_CTL\_ADD, client_socket, &event);
                    if (result == -1) {
                        printf("注册客户端 epoll 事件失败\\n");
                        exit(1);
                    }
                }
                continue;
            }

            //客户端触发事件
            if ((wait\_event\_list\[j\].events & EPOLLIN)
                ||(wait\_event\_list\[j\].events & EPOLLERR))//可读或发生错误
            {
                memset(&buffer, 0, sizeof(buffer));
                while(1){
                    str\_length = read(wait\_event\_list\[j\].data.fd, buffer, BUFFER\_SIZE);
                    //读取多次数据
                    if(str_length==-1){//没有数据返回
                        break;
                    }
                    if (str_length == 0)    //读取数据完毕关闭套接字
                    {
                        close(wait\_event\_list\[j\].data.fd);
                        event.data.fd = wait\_event\_list\[j\].data.fd;
                        epoll\_ctl(epoll\_fd, EPOLL\_CTL\_DEL, wait\_event\_list\[j\].data.fd, &event);
                        printf("连接已经关闭: %d \\n", wait\_event\_list\[j\].data.fd);
                    } else {
                        printf("客户端发送数据:%s \\n", buffer);
                        write(wait\_event\_list\[j\].data.fd, buffer, str_length);//执行回声服务  即echo
                    }
                }
            }
        }
    }
//    return 0;
}
```

以上说明,可看出:

1:epoll不需要遍历其他没有事件的socket,避免了select的性能浪费

2:epoll有两种工作模式,用于不同的场景,et和lt模式都可以用非阻塞,但et模式必须非阻塞,et模式编程难度较大,每次epoll_wait都得考虑必须处理掉所有事件

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。