tcp之IO模型

2023-05-16

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服务器的创建过程,就开始实现吧:

#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服务器

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

#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服务器

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

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

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

}

仙士可博客

 

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

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

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

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

 

非阻塞式TCP服务器

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

#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的数量

原理图大概为:

仙士可博客

 

实现代码:

#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服务器

原理图大概为:

仙士可博客

 

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

实现代码:

#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没有数据可读时,必须是非阻塞模式,否则会阻塞

实现代码

#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都得考虑必须处理掉所有事件

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

tcp之IO模型 的相关文章

  • 套接字术语 - “阻塞”是什么意思?

    当谈论 C 中的套接字编程时 术语 阻塞 是什么意思 我需要构建一个服务器组件 可能是 Windows 服务 来接收数据 进行一些处理并将数据返回给调用者 呼叫者可以等待回复 但我需要确保多个客户端可以同时呼叫 如果客户端 1 连接并且我花
  • 指示远程主机已关闭连接的 NetworkStream.Read 的替代方案?

    关于使用以下命令处理 TCP IP 连接TcpClient类 除了等待之外 还有其他方法可以检查远程主机是否已关闭连接吗 NetworkStream Read返回0的方法 您可以使用IOControlCode KeepAliveValues
  • WebSocket 和纯 TCP 之间的根本区别是什么?

    我读过关于WebSockets http en wikipedia org wiki Web Sockets我想知道为什么浏览器不能像任何其他桌面应用程序一样简单地打开简单的 TCP 连接并与服务器通信 为什么可以通过 websocket
  • WCF TCP 客户端 - 如何使用它们的基本指南?

    我有一个 WCF 服务并希望使用 TCP 绑定连接到它 这一切都很好 但是你应该如何处理客户呢 我注意到 如果您为每个调用创建一个新客户端 它不会重新使用该通道 并会留下一堆 TCP 连接 直到超时 创建客户端 调用其方法 然后关闭它是正常
  • Python。从 6 字节字符串中打印 mac 地址

    我有 6 字节字符串的 mac 地址 您将如何以 人类 可读的格式打印它 Thanks import struct x x x x x x struct unpack BBBBBB your variable with mac
  • wireshark 和 tcpdump -r:奇怪的 tcp 窗口大小

    我正在使用 tcpdump 捕获 http 流量 并且对 TCP 慢启动以及窗口大小如何增加感兴趣 sudo tcpdump i eth1 w wget tcpdump tcp and port 80 当我使用 Wireshark 查看转储
  • 通过 TCP 客户端套接字接收数据时出现问题

    我正在尝试用 C 语言编写一个 TCP 客户端程序 客户端将在其中启动 连接到服务器 然后它会发送一些信息 然后监听它收到的信息并做出相应的反应 我遇到麻烦的部分是持续聆听 这是我所拥有的 while 1 numbytes recv soc
  • 我的代码中某处存在无限循环

    我有这个 Java 游戏服务器 最多可处理 3 000 个 tcp 连接 每个玩家或每个 tcp 连接都有自己的线程 每个线程的运行情况如下 public void run try String packet char charCur ne
  • 是否可以通过 TCP 连接到正在侦听 3G 网络端口的 iPhone?

    我正在开发一个严重依赖 P2P 的应用程序 但我目前没有任何 SIM 卡可供实验 因此我正在 wifi 网络上进行测试 我想知道 3G 网络上的 iPhone 是否可以连接以及是否需要穿越 NAT 设备 您位于提供商的路由器后面 您的 IP
  • net.TCPConn 允许在 FIN 数据包后写入

    我正在尝试为一些服务器端代码编写单元测试 但我在确定关闭测试用例时遇到了困难 环回 TCP 连接似乎无法正确处理干净关闭 我在一个示例应用程序中重现了这一点 该应用程序按顺序执行以下操作 创建客户端和服务器连接 通过从客户端向服务器成功发送
  • Web 服务器可以处理多少个套接字连接?

    假设我要获得共享 虚拟或专用托管 我在某处读到服务器 计算机一次只能处理 64 000 个 TCP 连接 这是真的吗 无论带宽如何 任何类型的托管可以处理多少个 我假设 HTTP 通过 TCP 工作 这是否意味着只有 64 000 个用户可
  • 如何在java应用程序中检测FIN - tcp标志?

    我在两台计算机之间有持久的 TCP 连接 第二台计算机不受我的控制 第二台计算机可以随时发送FIN标志 并且首先必须关闭当前连接 将FIN标志发送回第二台计算机 我如何知道第二台计算机正在发送 FIN 标志 以及何时必须调用 Java 应用
  • 构建多线程 TCP/IP 服务器

    我想构建一个可供最多 100 个并发客户端使用的 TCP IP 服务器 但仍不确定如何开始 至少我需要服务器 监听客户端 并将它们全部存储在数组或列表中 对于每个客户端 它需要根据其客户端状态接收和发送数据 当有人连接或断开连接时 服务器应
  • 如何在Linux中打开端口[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我已经安装了 Web 应用程序 该应用程序在 RHEL centOS 上的端口 8080 上运行 我只能通过命令行访问该机器 我尝试从我的
  • 为什么turn服务器不支持tcp连接?

    我是 WebRTC 新手 我需要为我的 webrtc 应用程序配置我自己的 Turn 服务器 我使用以下命令安装了我的转弯服务器 apt get install coturn 我只需要通过 tcp 运行转变服务器 它不必使用 UDP 进行任
  • 是否可以找到哪个用户位于 localhost TCP 连接的另一端?

    这是一个编程问题 但它是 Linux Unix 特定的 如果我从本地主机获得 TCP 连接 是否有一种简单的方法可以告诉哪个用户在 C 程序内建立了连接而无需 shell 我知道这对于 Unix 域套接字来说并不太难 我已经知道远程 IP
  • 我应该害怕使用 UDP 进行客户端/服务器广播通话吗?

    我在过去的两天里阅读了每一篇StackOverflow问题和答案 以及googling当然 关于印地TCP and UDP协议 以便决定在我的用户应用程序和 Windows 服务之间的通信方法中应该使用哪一种 从我目前所看到的来看 UDP是
  • iOS 上的多个 HTTP 请求与单个 TCP 连接

    我正在开发一个 iPhone 应用程序 它使用我控制的基于 Web 的 API 连接到持续打开的 TCP 端口并通过 TCP API 发出请求 或者为我想要获取的所有数据发出新的 HTTP 请求 会更快或更高效吗 我认为差异可以忽略不计 但
  • TcpClient 在异步读取期间断开连接

    我有几个关于完成 tcp 连接的问题 客户端使用 Tcp 连接到我的服务器 在接受客户端后listener BeginAcceptTcpClient ConnectionEstabilishedCallback null 我开始阅读netw
  • 触发“对等方重置连接”

    我想测试当发生 对等方重置连接 错误时我们的应用程序 嵌入式 ftp 服务器 中发生的日志记录 这个帖子 https stackoverflow com questions 1434451 connection reset by peer很

随机推荐

  • 线程池BUG复现和解决

    逻辑很简单 xff0c 线程池执行了一个带结果的异步任务 但是最近有偶发的报错 xff1a java util concurrent RejectedExecutionException Task java util concurrent
  • 移动端开发——APP端上H5容器化建设

    1 背景 当前移动端和前端的结合愈加紧密 xff0c 尤其是在偏重活动运营的电商App中 xff0c 受制于App版本审核 xff0c 具备研发成本低 可灵活发布等特点的H5页面受到青睐 xff0c 使其在APP端上承接了越来越多的业务 然
  • C++时间与字符串转换

    1 常用的时间存储方式 1 xff09 time t类型 xff0c 这本质上是一个长整数 xff0c 表示从1970 01 01 00 00 00到目前计时时间的秒数 xff0c 如果需要更精确一点的 xff0c 可以使用timeval精
  • 解决linux环境下nohup: redirecting stderr to stdout问题

    在生产环境下启动Weblogic时 xff0c 发现原来好好的nohup信息输出到指定文件中的功能 xff0c 突然出问题了 现象是控制台输出的信息一部分输出到了我指定的文件 xff0c 另一部分却输出到了nohup out xff0c 而
  • [转]Redis作为消息队列与RabbitMQ的性能对比

    周末测试了一下RabbitMQ的性能 xff0c RabbitMQ是使用Erlang编写的一个开源的消息队列 xff0c 本身支持很多的协议 xff1a AMQP xff0c XMPP SMTP STOMP xff0c 也正是如此 xff0
  • Python3.7 实现TCP通信

    TCP 连接程序分为服务端和客户端两部分 服务端步骤如下 xff1a 1 创建套接字Socket 什么是套接字 应用层通过传输层进行数据通信时 xff0c TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题 为了区别不同的应用程序
  • CentOS、Ubuntu、Debian三个linux比较异同

    Linux有非常多的发行版本 xff0c 从性质上划分 xff0c 大体分为由商业公司维护的商业版本与由开源社区维护的免费发行版本 商业版本以Redhat为代表 xff0c 开源社区版本则以debian为代表 这些版本各有不同的特点 xff
  • LDAP 中 CN, OU, DC 的含义

    1 LDAP的存储规则 区分名 xff08 DN xff0c Distinguished Name xff09 和自然界中的树不同 xff0c 文件系统 LDAP 电话号码簿目录的每一片枝叶都至少有一个独一无二的属性 xff0c 这一属性可
  • bat修改hosts文件

    attrib R C WINDOWS system32 drivers etc hosts 64 echo 64 echo 127 0 0 1 aaaa bbb com gt gt C WINDOWS system32 drivers et
  • 使用org.apache.tools.zip实现zip压缩和解压

    import java io import org apache tools zip import java util Enumeration 功能 zip压缩 解压 支持中文文件名 说明 本程序通过使用Apache Ant里提供的zip工
  • freeModbus代码解读及移植笔记

    freeModbus的代码库还是很好用的 xff0c 本人在wince和C8051F410下均移植成功 xff08 只用到RTU模式 xff09 但freeModbus提供的文档比较少 xff0c 只能对照着Modbus协议一点点试着读懂源
  • MySQL变量:local_infile

    local infile服务器变量指示能否使用load data local infile命令 该变量默认为ON 该变量为OFF时 xff0c 禁用客户端的load data local infile命令 Sql代码 mysql gt sh
  • strcpy函数实现

    C语言标准库函数strcpy的一种典型的工业级的最简实现 返回值 xff1a 返回目标串的地址 对于出现异常的情况ANSI C99标准并未定义 xff0c 故由实现者决定返回值 xff0c 通常为NULL 参数 xff1a strDesti
  • C++库介绍

    1 C 43 43 标准库 xff08 STL xff09 STL六大组件 容器 算法 迭代器 仿函数 适配器 配接器 空间配置器 1 容器 各种数据结构 xff0c 如vector list deque set map等 xff0c 用来
  • 【C++】extern “C“ 用法详解

    前言 前面简单了解了C 43 43 中的extern 34 C 34 之后 xff0c 可能很多小伙伴对这个陌生的词非常困惑 xff0c 不能理解他的使用场景 所以本章内容就来详细了解extern 34 C 34 的用法 xff0c 这里使
  • FreeRTOS学习第三篇——FreeRTOS任务创建(下)

    声明 xff1a 本文为博主的学习篇章 xff0c 欢迎大家指错 xff0c 共同学习 在解决一下上篇遗留下来的问题之前 xff0c 还得提前做些功课 xff0c 了解一些FreeRTOS的全局变量 PRIVILEGED DATA stat
  • printf用法之打印二进制,八进制,十进制,十六进制

    printf用法之打印2进制 xff0c 八进制 xff0c 十进制 xff0c 十六进制 printf是格式化输出函数 xff0c 它可以直接打印十进制 xff0c 八进制 xff0c 十六进制 xff0c 输出控制符分别为 d o x
  • 【飞控开发基础教程7】疯壳·开源编队无人机-SPI(气压计数据获取)

    COCOFLY教程 疯壳 无人机 系列 SPI xff08 气压计数据获取 xff09 图1 一 SPL06 简介 SPL06 是歌尔公司最新推出新款气压传感器 xff0c 最新推出新款气压传感器SPL06 001 xff0c 歌尔是全球领
  • 【遥控器开发基础教程5】疯壳·开源编队无人机-SPI(2.4G 双机通信)

    COCOFLY教程 疯壳无人机 系列 SPI 2 4G 双机通信 图1 一 NRF24L01 1 1 NRF24L01 简介 NRF24L01 是由NORDIC 生产的工作在 2 4GHz 2 5GHz 的ISM 频段的单片无线收发器芯片
  • tcp之IO模型

    5种io模型 tcp服务器分为了5种io复用模型 分别是 阻塞io模型 非阻塞io模型 io复用 信号驱动io 异步io 本文会讲前面3种io模型的tcp服务器实现 本文只做tcp服务器实现 客户端逻辑处理 接收数据等缓冲区不做深入说明 简