C10K问题:高并发模型设计

news2024/9/17 7:16:53

一、循环服务器模型

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h> //*******//
#include <netinet/in.h> //*******//
#include <arpa/inet.h>  //*******//

int sockfd;
void my_exit(int sig)
{
    shutdown(sockfd, SHUT_RDWR);
    close(sockfd);
    printf("shutdown socket done\n");
    exit(0);
}
void handle(int sig) // SIGPIPE的信号处理函数————以观察是否产生了SIGPIPE信号
{
    if (sig == SIGPIPE)
    {
        printf("SIGPIPE is going\n");
    }
}

int main(int argc, char **argv)
{
    signal(SIGINT, my_exit);
    signal(SIGPIPE, handle);
    signal(SIGPIPE, SIG_IGN);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socked is error");
        exit(-1);
    }
    printf("socket success\n");

    int i;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));

    struct sockaddr_in sockaddr_in1;
    sockaddr_in1.sin_family = AF_INET; 
    sockaddr_in1.sin_port = htons(4443);                         
    // 正确的做法是使用htons函数将主机字节序转换为网络字节序————htons而非htonl因为端口号是16位
    sockaddr_in1.sin_addr.s_addr = inet_addr("192.168.106.128"); //*****//
    if (bind(sockfd, (struct sockaddr *)&sockaddr_in1, sizeof(sockaddr_in1)) < 0)
    {
        perror("bind error");
        exit(-1);
    }
    printf("bind success\n");

    if (listen(sockfd, 20) < 0)
    {
        perror("listen error");
        exit(-1);
    }
    printf("listen success\n");

    struct sockaddr_in addr2;
    int len_addr2 = sizeof(addr2);

    while (1)
    {
        int sock_fd1 = accept(sockfd, (struct sockaddr *)&addr2, &len_addr2); // 每来一个客户端的连接请求,就会生成一个描述符,只要知道这个描述符,就能通过此通信
        if (sock_fd1 < 0)
        {
            perror("accept error");
            exit(-1);
        }
        printf("client ip = %s ,port = %d\n", inet_ntoa(addr2.sin_addr), ntohs(addr2.sin_port)); // 1.inet_ntoa把ip地址转换为字符————2.把网络的转换为主机的

        char buffer[1024] = {0};
        int recv_t = recv(sock_fd1, buffer, sizeof(buffer) - 1, 0);
        printf("recv_t : %d   ", recv_t);
        if (recv_t < 0)
        {
            perror("recv error");
            exit(-1);
        }
        else if (recv_t == 0) // recv的返回值为零的时候,证明客户端关闭了!
        {
            printf("client is closed\n");
        }
        else
        {
            printf("recv :%s\n", buffer);

            memset(buffer, 0, sizeof(buffer));
            scanf("%s", buffer);

            // int w_t = send(sock_fd1, buffer, strlen(buffer), 0);
            int w_t = send(sock_fd1, buffer, strlen(buffer), MSG_NOSIGNAL); // MSG_NOSIGNAL:表示此操作不愿被SIGPIPE信号断开;或注册信号处理函数
            if (w_t < 0)
            {
                perror("send data error");
                exit(-1);
            }
        }
        shutdown(sock_fd1, SHUT_RDWR);
    }
    return 0;
}

循环服务器模型:

阻塞:

同一时间只能处理一客户端的请求(读写请求或者连接请求),accept和recv会阻塞

非阻塞:

1.accept非阻塞:

// flag标志位,置为sock_nonblock;
    int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);

就可以将accept自动设置为非阻塞 

2.recv非阻塞:

recv函数的flag位置为MSG_DONTWAIT;

int ret3 = recv(ret2, buffer, sizeof(buffer), MSG_DONTWAIT); // msg_dontwait非阻塞接收

3.read非阻塞:

fcntl设置文件描述符的flags位;

// read:
            int flags = fcntl(ret2, F_GETFL);
            flags = flags | O_NONBLOCK;
            fcntl(ret2, F_SETFL,flags);

4.例子: 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>

int main(int argc, char **argv)
{
    //*************************************************************//
    // flag标志位,置为sock_nonblock;
    int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    //*************************************************************//
    if (sockfd < 0)
    {
        perror("socket error");
        exit(-1);
    }

    int j = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));

    struct sockaddr_in addr1;
    addr1.sin_family = AF_INET;
    addr1.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr1.sin_port = htons(5555);
    int ret1 = bind(sockfd, (struct sockaddr *)(&addr1), sizeof(addr1));
    if (ret1 < 0)
    {
        perror("bind error");
        exit(-1);
    }

    if (listen(sockfd, 20) < 0)
    {
        perror("listen error");
        exit(-1);
    }

    struct sockaddr_in addr2;
    int len;
    memset(&addr2, 0, sizeof(addr2));
    char buffer[1024] = {0};
    while (1)
    {
        int ret2 = accept(sockfd, (struct sockaddr *)&addr2, &len);
        if (ret2 < 0)
        {
            //-----------------------------------------------------------------//
            if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
            //-----------------------------------------------------------------//
            {
                perror("accept error");
                exit(-1);
            }
            else
            {
                sleep(1);
                printf("accept going\n");
                continue;
            }
        }
        while (1)
        {
            // read:
            int flags = fcntl(ret2, F_GETFD);
            flags = flags | O_NONBLOCK;
            fcntl(ret2, F_SETFD,flags);

            memset(buffer, 0, sizeof(buffer));
            //-----------------------           ret2!!!       ----------------------//
            // int ret3 = recv(ret2, buffer, sizeof(buffer), MSG_DONTWAIT); // msg_dontwait非阻塞接收
            //----------------------------------------------------------------------//

            int ret3 = read(ret2, buffer, sizeof(buffer));
            if (ret3 < 0)
            {
                if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
                {
                    perror("recv error");
                    exit(-1);
                }
                else
                {
                    continue;
                }
            }
            else if (ret3 == 0)
            {
                printf("client closed\n");
                break;
            }
            else
            {
                printf("receive message is : %s\n", buffer);
                memset(buffer, 0, sizeof(buffer));
                scanf("%s", buffer);
                sendto(ret2, buffer, sizeof(buffer), 0, (struct sockaddr *)&addr2, sizeof(addr2));
            }
        }
    }

    return 0;
}

二、阻塞IO+多进程

例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void handler(int sig)
{
    if(sig==SIGCHLD)
    {
        printf("child process is exit\n");
        waitpid(-1,NULL,0);
    }
}
int main(int argc, char **argv)
{
    signal(SIGCHLD,handler);

    int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd < 0)
    {
        perror("socket error");
        exit(-1);
    }

    int j = 1;
    setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));

    struct sockaddr_in addr1;
    addr1.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr1.sin_family = AF_INET;
    addr1.sin_port = htons(5555);

    int ret1 = bind(socket_fd, (struct sockaddr *)&addr1, sizeof(addr1));
    if (ret1 < 0)
    {
        perror("bind error");
        exit(-1);
    }
    if (listen(socket_fd, 20) < 0)
    {
        perror("listen error");
        exit(-1);
    }
    struct sockaddr_in addr2;
    int len = sizeof(addr2);
    while (1)
    {
        char message[1024] = {0};
        memset(&addr2, 0, sizeof(addr2));
        int fd1 = accept(socket_fd, (struct sockaddr *)&addr2, &len);

        if (fd1 < 0)
        {
            perror("accept error");
            exit(-1);
        }
        pid_t pid = fork();
        if (pid == 0)
        {
            while (1)
            {
                read(fd1, message, sizeof(message));
                printf("%s\n", message);
                char *p = message;
                for (; *p != '\0'; ++p)
                {
                    *p = *p - 'a' + 'A';
                }
                write(fd1, message, strlen(message) + 1);
                memset(message, 0, sizeof(message));
            }
        }
        if (pid > 0)
        {
            close(fd1);
            continue;
        }
    }
    return 0;
}

waitpid 函数

是一个用于等待子进程终止并回收其资源的系统调用,它的函数原型如下:
#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);
waitpid 函数用于等待指定的子进程终止,并且提供了更灵活的选项来控制等待的行为。下面是参数的含义:

pid:要等待的子进程的进程ID。可以使用以下值:

<-1:等待任何进程组ID等于pid的子进程。
-1:等待任何子进程,类似于 wait 函数。
0:等待与调用进程在同一个进程组的任何子进程。
> 0:等待指定进程ID等于pid的子进程。
status:用于存储子进程终止状态的整数指针。status 中将包含子进程的退出状态信息。

options:用于指定等待选项的整数。常用的选项包括:

WNOHANG:如果没有终止的子进程可用,则立即返回,不阻塞。
WUNTRACED:也等待已经停止但尚未报告的子进程。
WCONTINUED:等待已继续但尚未报告的子进程。
waitpid 函数之所以可以回收子进程资源,是因为它在等待子进程终止时会挂起当前进程,直到指定的子进程终止。一旦子进程终止,waitpid 会将子进程的退出状态信息(如退出码)存储在 status 参数中,然后返回子进程的进程ID。通过检查 status 中的信息,您可以确定子进程的终止状态,并根据需要采取相应的操作。

void handler(int sig)
{
    if(sig==SIGCHLD)
    {
        printf("child process is exit\n");
        waitpid(-1,NULL,0);
    }
}

优缺点:

优点:

编码简单,不用考虑进程间的数据同步、服务器健壮;

缺点:

1.资源消耗大,启动一个进程消耗相对比启动一个线程消耗大得多;处理多个连接的时候,需要启动多个进程去处理

2.系统的进程数是有限制的

3.查看系统最多支持的进程数——ulimit -u命令;也可以修改

4.TCP服务器进程同时打开文件数的限制——ulimit -n命令;每一次accept就会产生一个文件描述符,数量限制在0-1024

参考:

Linux系统打开文件最大数量限制(进程打开的最大文件句柄数设置) - 废物大师兄 - 博客园 (cnblogs.com)icon-default.png?t=N7T8https://www.cnblogs.com/cjsblog/p/9367043.html

三、阻塞IO+多线程

例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>

void change(char *p) // 字符大写
{
    for (; *p != '\0'; ++p)
    {
        *p = *p - 'a' + 'A';
    }
}
void *pthread_cs(void *arg)
{
    int fd = (int)arg;
    char message[1024] = {0};

    pthread_detach(pthread_self()); // 获取当前线程的id号,然后设置为分离态(结束后可以自动回收资源)
    while (1)
    {
        memset(message, 0, sizeof(message));
        if (read(fd, message, sizeof(message)) == 0)
        // 如果读取结果是0,则证明客户端已经关闭;需要主动退出线程,否则服务器主程序也会异常退出
        {
            printf("client cloesd\n");
            pthread_exit(NULL);
        }
        printf("%s\n", message);
        change(message);
        write(fd, message, sizeof(message));
    }
}

int main(int argc, char **argv)
{
    pthread_t id;
    int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket < 0)
    {
        perror("socket error");
        exit(-1);
    }
    int j = 1;
    setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));

    struct sockaddr_in addr0;
    addr0.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr0.sin_family = AF_INET;
    addr0.sin_port = htons(5555);

    int len_addr0 = sizeof(addr0);
    int ret1 = bind(socket_fd, (struct sockaddr *)&addr0, len_addr0);
    if (ret1 < 0)
    {
        perror("bind error");
        exit(-1);
    }

    int ret2 = listen(socket_fd, 20);
    if (ret2 < 0)
    {
        perror("listen error");
        exit(-1);
    }

    while (1)
    {
        struct sockaddr_in addr1;
        int len_addr1 = sizeof(addr1);
        int fd = accept(socket_fd, (struct sockaddr *)&addr1, &len_addr1);
        if (fd < 0)
        {
            perror("accept error");
            exit(-1);
        }
        pthread_create(&id, NULL, pthread_cs, (void *)fd); //------------(void *)fd----------//
    }
    return 0;
}

注意事项:

1:

if (read(fd, message, sizeof(message)) == 0)
        // 如果读取结果是0,则证明客户端已经关闭;需要主动退出线程,否则服务器主程序也会异常退出
        {
            printf("client cloesd\n");
            pthread_exit(NULL);
        }

2:

pthread_detach(pthread_self()); // 获取当前线程的id号,然后设置为分离态(结束后可以自动回收资源)

3:

//------------(void *)fd----------//这里需要传值而不是传地址(每次循环,地址上的值会变化)
        pthread_create(&id, NULL, pthread_cs, (void *)fd);

优缺点:

优点:相对多进程,会节约一些资源,会更加高效一点

缺点:

1.相对于多进程,增加coding复杂度,因为需要考虑数据同步锁保护【锁也是很浪费资源的】

2.一个进程中不能启动太多的线程——【当前进程的资源有限,线程数量受到进程栈空间的限制:线程函数压栈】

3.ulimit -s查看栈的大小,算出最大线程的个数

总结:线程是进程的优化,优先选择线程——

【解决线程数量限制——线程池

【解决锁的开销——减小锁的颗粒度(上锁的代码越少越好)】

四、池化技术(线程池)

1.实现线程池

---------------------------------------------------------(抽空写)---------------------------------------------

2.利用线程池来实现创建多进程响应多个客户端的连接

五、I/O多路转接(复用)(单Reactor )

select

例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
#include <sys/select.h>
#include <errno.h>

int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{
    int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    if (socket_fd < 0)
    {
        perror("socket creat error");
        exit(-1);
    }
    int j = 1;
    setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));

    struct sockaddr_in s_addr;
    int len_s_addr = sizeof(s_addr);
    s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(5555);
    if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0)
    {
        perror("bind error");
        exit(-1);
    }
    listen(socket_fd, 20);
    return socket_fd;
}

void change(char *p) // 字符大写
{
    for (; *p != '\0'; ++p)
    {
        *p = *p - 'a' + 'A';
    }
}

int main(int argc, char **argv)
{
    int n_r;
    int cfd;
    int sockfd;
    int maxfd;

    fd_set r_set;
    fd_set all_set;

    int client[FD_SETSIZE]; // 保存cfd——FD——SETSIZE = 1024;

    struct sockaddr_in c_addr;
    memset(&c_addr, 0, sizeof(c_addr));
    socklen_t c_size = sizeof(struct sockaddr_in);

    sockfd = socket_creat();
    printf("socketfd is %d\n", sockfd);

    for (int i = 0; i < FD_SETSIZE; i++) // 文件描述符集合全置为-1;
    {
        client[i] = -1;
    }

    FD_SET(sockfd, &all_set);
    maxfd = sockfd;
    char message[1024];
    while (1)
    {
        r_set = all_set;
        int rnum = select(maxfd + 1, &r_set, NULL, NULL, NULL);
        if (FD_ISSET(sockfd, &r_set))
        {
            c_size = sizeof(struct sockaddr_in);

            printf("socketfd is %d\n", sockfd);

            cfd = accept(sockfd, (struct sockaddr *)&c_addr, &c_size);
            if (cfd < 0)
            {
                if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
                {
                    perror("accept error! ");
                    exit(1);
                }
                continue;
            }
            printf("info client: ip = %s port = %d\n",
                   inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
            FD_SET(cfd, &all_set);
            printf("%d\n", __LINE__);
            if (maxfd < cfd)
            {
                maxfd = cfd;
            }
        }
        for (int i = 0; i < FD_SETSIZE; i++)
        {
            if (client[i] == -1)
            {
                client[i] = cfd;
                printf("%d\n", __LINE__);
                break;
            }
        }
        if (--rnum > 0)
        {
            printf("%d\n", __LINE__);
            continue;
        }
        for (int i = 0; i < FD_SETSIZE; i++)
        {

            if ((cfd = client[i]) != -1)
            {
                printf("%d\n", __LINE__);
                if (FD_ISSET(cfd, &r_set))
                {
                    printf("%d\n", __LINE__);
                    memset(message, 0, sizeof(message));
                    n_r = read(cfd, message, sizeof(message));
                    if (n_r < 0)
                    {
                        perror("read cfd data error! ");
                        FD_CLR(cfd, &all_set);
                        client[i] = -1;
                    }
                    if (n_r == 0)
                    {
                        printf("client is close! \n");
                        FD_CLR(cfd, &all_set);
                        client[i] = -1;
                    }
                    printf("%d\n", __LINE__);
                    printf("recv data:%s\n", message);
                    change(message);
                    write(cfd, message, strlen(message) + 1);
                    if (--rnum == 0)
                    {
                        break;
                    }
                }
            }
        }
    }
    return 0;
}

my_select_server:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <pthread.h>
#include <sys/select.h>
#include <errno.h>

int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{
    int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    if (socket_fd < 0)
    {
        perror("socket creat error");
        exit(-1);
    }
    int j = 1;
    setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));

    struct sockaddr_in s_addr;
    int len_s_addr = sizeof(s_addr);
    s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(5555);
    if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0)
    {
        perror("bind error");
        exit(-1);
    }
    listen(socket_fd, 20);
    return socket_fd;
}

void change(char *p) // 字符大写
{
    for (; *p != '\0'; ++p)
    {
        *p = *p - 'a' + 'A';
    }
}

int main(int agrc, char **argv)
{
    int socket_fd = socket_creat();
    struct sockaddr_in c_addr;
    socklen_t c_len;
    memset(&c_addr, 0, sizeof(c_addr));

    fd_set set_1;
    fd_set set_all;
    FD_SET(socket_fd, &set_all);
    int maxfd = socket_fd;

    int fd_ary[FD_SETSIZE];
    for (int i = 0; i < FD_SETSIZE; ++i)
    {
        fd_ary[i] = -1;
    }
    char message[1024];
    while (1)
    {
        set_1 = set_all;
        int ret = select(maxfd + 1, &set_1, NULL, NULL, NULL);

        if (FD_ISSET(socket_fd, &set_1))
        {
            int cfd = accept(socket_fd, (struct sockaddr *)&c_addr, &c_len);
            if (cfd < 0)
            {
                if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
                {
                    perror("accept error");
                    exit(-1);
                }
                printf("%d\n", __LINE__);
                continue;
            }
            if (cfd > 0)
            {
                printf("client id : %s , client opt is %d\n",
                       inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
                FD_SET(cfd, &set_all);
                if (maxfd < cfd)
                {
                    maxfd = cfd;
                    printf("%d\n", __LINE__);
                }
                for (int i = 0; i < FD_SETSIZE; ++i)
                {
                    if (fd_ary[i] == -1)
                    {
                        fd_ary[i] = cfd;
                        printf("%d\n", __LINE__);
                        break;
                    }
                }
            }
        }
        for (int i = 0; i < FD_SETSIZE; ++i)
        {
            if (FD_ISSET(fd_ary[i], &set_1))
            {
                printf("%d\n", __LINE__);
                memset(message, 0, sizeof(message));
                int ret_r = read(fd_ary[i], message, sizeof(message));
                if (ret_r == 0)
                {
                    printf("client is closed\n");
                    fd_ary[i] = -1;
                    FD_CLR(fd_ary[i], &set_all);
                }
                if (ret_r < 0)
                {
                    perror("read error");
                    exit(-1);
                }
                printf("%s\n", message);
                change(message);
                write(fd_ary[i], message, sizeof(message));
            }
        }
    }
    return 0;
}

select优缺点:

缺点:

1.select用到的文件描述符集合,包含的数量有限(宏定义:FD_SETSIZE为1024,虽然可以修改);

2.select是一种轮询全盘扫描(时间复杂度是O(n) ),只知道多少个描述符发生变化——遍历文件描述符集合,看哪个发生变化,再采取操作

优点:

1.可以跨平台,几乎所有平台

2.可以监听所有的文件描述符:普通文件、套接字和设备文件等等

poll

基本原理与select一致,也是轮询+遍历;唯一的区别就是poll没有最大文件描述符限制(使用链表的方式存储fd)

函数原型
#include cpoll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd fds[POLL_SIZE];
    fds[0].fd = sock_fd;
    fds[0].events = POLLIN;
    fds[0].revents = 0;
    for (int i = 1; i < POLL_SIZE; ++i)
    {
        fds[i].fd = -1;
        fds[i].events = POLLIN;
        fds[i].revents = 0;
    }

revents字段表示实际发生的事件,可能包括POLLIN(可读事件)、POLLOUT(可写事件)

功能
监听集合有没有动静,如果没有动静就阻塞
如果有动静就成功返回,返回值为集合中有动静的fd的数里。
参数

2) nfds :数组的元素个数。
3)timeout:超时时间,如果写的是

-1:不设置超时,如果集合没有动静就一直阻塞下去,直到pol函数被信号中断〈唤醒)或者集合有动静为止
非-1值:比如3000 ( 3000徵妙),表示将超时时间设置为3秒
也就是说poll超时时间的单位时微妙s

返回值
-1:说明函数调失败,errno被设置。
如果是被信号中断从而导致出错返回-1时.errno被设置为EINTR,·如果不想o中断,要么重启poll的调用:要么忽略或者屏蔽下这些信号。
0:超时时间到,而且没有文件描述符有动静。
>0:返回有响应的文件描述符的数量。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>
#include <fcntl.h>
#define POLL_SIZE 1024

int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{
    int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    if (socket_fd < 0)
    {
        perror("socket creat error");
        exit(-1);
    }
    int j = 1;
    setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));

    struct sockaddr_in s_addr;
    int len_s_addr = sizeof(s_addr);
    s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(5555);
    if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0)
    {
        perror("bind error");
        exit(-1);
    }
    listen(socket_fd, 20);
    return socket_fd;
}

void change(char *p) // 字符大写
{
    for (; *p != '\0'; ++p)
    {
        *p = *p - 'a' + 'A';
    }
}

int main(int argc, char **argv)
{
    int sock_fd = socket_creat();

    struct sockaddr_in c_addr;
    socklen_t len_c_addr = sizeof(c_addr);
    memset(&c_addr, 0, sizeof(c_addr));

    struct pollfd fds[POLL_SIZE];
    fds[0].fd = sock_fd;
    fds[0].events = POLLIN;
    fds[0].revents = 0;
    for (int i = 1; i < POLL_SIZE; ++i)
    {
        fds[i].fd = -1;
        fds[i].events = POLLIN;
        fds[i].revents = 0;
    }

    char message[1024] = "0";
    while (1)
    {
        int ret_p = poll(fds, POLL_SIZE, -1);
        if (ret_p < 0)
        {
            perror("poll error");
            exit(-1);
        }
        if (ret_p > 0)
        {
            if (fds[0].events == fds[0].revents)
            {
                fds[0].revents = 0;
                int c_fd = accept(sock_fd, (struct sockaddr *)&c_addr, &len_c_addr);
                if (c_fd < 0)
                {
                    perror("accept error");
                    exit(-1);
                }
                if (c_fd > 0)
                {
                    for (int i = 1; i < POLL_SIZE; ++i)
                    {
                        if (fds[i].fd == -1)
                        {
                            fds[i].fd = c_fd;
                            break;//---------------       注意---------------//
                        }
                    }
                    printf("cilent ip is %s , cilent port is %d\n",
                           inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
                }
            }
            for (int i = 1; i < POLL_SIZE; ++i)
            {
                if (fds[i].events == fds[i].revents)
                {
                    int ret_r = read(fds[i].fd, message, sizeof(message));
                    if (ret_r < 0)
                    {
                        perror("read error");
                        fds[i].fd = -1;
                    }
                    if (ret_r == 0)
                    {
                        printf("client is closed\n");
                        fds[i].fd = -1;
                    }
                    if (ret_r > 0)
                    {
                        printf("receive message is %s\n", message);
                        change(message);
                        write(fds[i].fd, message, sizeof(message));
                        memset(message, 0, sizeof(message));
                    }
                    fds[i].revents = 0;
                }
            }
        }
    }

    return 0;
}

epoll—重点

使用步骤

1.epoll_create

size:

epoll上能关注的最大描述符数量。(真正用的时候随便设一个就行,epoll在容里不够的时候会做自动扩展)

返回值:返回红黑树的描述符epollfd ;出错时返回-1,错误代码置在ernno

epoll在内部是一个红黑制结构。文件描述符默认0—2被终端占用,每次有一个新的文件描述符就创建一个树节点。epoll认设置的是树上最多的节点数是2000:但是如果敌里超过2000:则大小会被自动扩展。

2.epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

3.epoll_wait 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>
#include <fcntl.h>
#include <sys/epoll.h>

#define POLL_SIZE 1024

int socket_creat() // 封装的套结字创建——————(socket、setsockopt、bind、listen)
{
    int socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    if (socket_fd < 0)
    {
        perror("socket creat error");
        exit(-1);
    }
    int j = 1;
    setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &j, sizeof(j));

    struct sockaddr_in s_addr;
    int len_s_addr = sizeof(s_addr);
    s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(5555);
    if (bind(socket_fd, (struct sockaddr *)&s_addr, len_s_addr) < 0)
    {
        perror("bind error");
        exit(-1);
    }
    listen(socket_fd, 20);
    return socket_fd;
}

void change(char *p) // 字符大写
{
    for (; *p != '\0'; ++p)
    {
        *p = *p - 'a' + 'A';
    }
}

int main(int argc, char **argv)
{
    int sock_fd = socket_creat();
    struct sockaddr_in c_addr;
    socklen_t len_c_addr = sizeof(c_addr);
    memset(&c_addr, 0, sizeof(c_addr));

    int epfd = epoll_create(2000); // 初始化红黑书,返回值是红黑树的标识符 ; 红黑书大小——暂定输入参数为2000;
    //int epfd = epoll_create1(0);//也是一样的效果    
if (epfd < 0)
    {
        perror("epoll_creat error");
        exit(-1);
    }

    struct epoll_event event_0; // 先定义一个结构体,存放第一个sock_fd————以便于后面处理“新连接”的请求;
    event_0.events = EPOLLIN;   // 监听的是可读——也就是有新连接了
    event_0.data.fd = sock_fd;  // 存放sock_fd,以便于后面遍历的时候,通过event.data.fd找到sock_fd这个,而非处理新连接所新加的event

    struct epoll_event event_a[2000];    // 定义一个总的,而且空的struct epoll_event数组,作为epoll_wait的传出参数,用以保存所有变化了的文件描述符
    memset(event_a, 0, sizeof(event_a)); // 清空它

    int ret1 = epoll_ctl(epfd, EPOLL_CTL_ADD, sock_fd, &event_0); // 把第一个含有sock_fd的结构体加到红黑里;
    if (ret1 < 0)
    {
        perror("epoll add error");
        exit(-1);
    }
    char message[1024] = {0};
    while (1)
    {
        int ret = epoll_wait(epfd, event_a, 2000, -1); // 监听,-1代表阻塞,死等;
        for (int i = 0; i < 2000; ++i)                 // 遍历这个“包含所有变化的”的struct epoll_event数组,以处理每一个请求
        {
            if (event_a[i].data.fd == sock_fd) // 这个对应的是有新连接的请求
            {
                int c_fd = accept(sock_fd, (struct sockaddr *)&c_addr, &len_c_addr);
                if (c_fd < 0)
                {
                    perror("accept error");
                    exit(-1);
                }
                if (c_fd > 0)
                {
                    printf("cilent ip is %s , cilent port is %d\n",
                           inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
                    event_0.data.fd = c_fd; // 有新连接,就要把新的结构体(结点),挂到红黑树里 👇
                    epoll_ctl(epfd, EPOLL_CTL_ADD, c_fd, &event_0);
                }
            }
            else // 处理的是已经连接,有收发消息的请求
            {
                int fd = event_a[i].data.fd;
                int ret_r = read(fd, message, sizeof(message));
                if (ret_r < 0)
                {
                    perror("read error");
                    close(fd);// 需要关闭这个文件描述符;
                }
                if (ret_r == 0)
                {
                    close(fd);// 需要关闭这个文件描述符;
                    printf("client is closed\n");
                    int ret2 = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
//删除,第四个参数置为NULL;
                }
                if (ret_r > 0)
                {
                    printf("receive message is %s\n", message);
                    change(message);
                    write(fd, message, sizeof(message));
                    memset(message, 0, sizeof(message));
                }
            }
            if (--ret <= 0)
            {
                break;
            }
        }
    }
    return 0;
}

注意:上图中虽然套接字已经是非阻塞的了,但是可以不需要处理accept的errno,因为,如果不是新客户端连接的话,是不会进入accept语句里的

支持操作:

水平触发LT

        只要有数据都会触发,缓冲区剩余未读尽的数据会导致epoll_wait返回——(数据没读完就会一直发出请求给服务器,占用资源,降低效率)

边缘触发ET

        1.ET模式(边缘触发)只有数据到来才触发,不管缓存区中是否还有数据,缓冲区剩未读尽的数据不会导致epoll_wait返回

        2.边缘触发需要一次性的把缓冲区的数据读完为止,也就是一直读,直到读到EGAIN(EGAIN说明缓冲区已经空了)为止

        3.边缘触发需要设置文件句柄为非阻塞

                    // event_0.events = EPOLLIN | EPOLLET;//设置为边缘触发模式
                    //  int flags=fcntl(c_fd,F_GETFL);
                    //  flags=flags|O_NONBLOCK;
                    //  fcntl(cfd,F_SETFL,flags);

        4.epoll为什么要有EPOLLET触发模式?

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高,epoll工作在ET模式的时候。必须使用非阻塞接口;以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死

优缺点


select内部使用数组实现,poll是链表。他们需要做内核区到用户区的转换,还需要做数据拷贝,因此效率低。
epoll不需要做内核区到用户区的转换,因为数据存在共享内存中。epoll维护的树在共享内存中,内核区和用户区去操作共享内存
因此不需要区域转换,也不需要拷贝操作


六、Reactor模式

七、I/O多路复用进阶(单Reactor +线程/进程):线程及线程池使用

八、I/O多路复用进阶(多Reactor +线程/进程) ——主-从 Reactor模式

九、异步I/O探索

十、Proactor异步网络模型

十一、总结
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1061745.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DeckGL MVTLayer+Mapbox注入shader实现简单效果

1.数据获取与处理 可以从OSM下载相应的带高度的矢量建筑物轮廓数据&#xff0c;下载后进行数据处理&#xff0c;保留高度字段&#xff0c;其他字段根据需求选择&#xff08;比如说想实现选中建筑物弹出相应建筑物的信息&#xff0c;就把这些信息字段保留下来&#xff09;&…

腾讯云2023年双十一优惠活动整理

随着双十一的到来&#xff0c;各大电商平台纷纷推出优惠活动。作为国内领先的云计算服务提供商&#xff0c;腾讯云自然也不会错过。以下是对腾讯云2023年双十一优惠活动的整理&#xff0c;希望能够帮助大家轻松上云&#xff01; 一、腾讯云双十一活动入口 直达腾讯云双十一活动…

使用Pytorch从零实现Vision Transformer

在这篇文章中,我们将基于Pytorch框架从头实现Vision Transformer模型,并附录完整代码。 Vision Transformer(ViT)是一种基于Transformer架构的深度学习模型,用于处理计算机视觉任务。它将图像分割成小的图像块(patches),然后使用Transformer编码器来处理这些图像块。V…

基于Java的剧本杀预约系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

2023年CSP-J真题详解+分析数据

目录 亲身体验 江苏卷 选择题 阅读程序题 阅读程序(1&#xff09; 判断题 单选题 阅读程序(2) 判断题 单选题 阅读程序(3) 判断题 单选题 完善程序题 完善程序(1) 完善程序(2) 2023CSP-J江苏卷详解 小结 亲身体验 2023年的CSP-J是在9月16日9:30--11:30进行…

x64内核实验3-页机制的研究(1)

x64内核实验3-页机制的研究&#xff08;1&#xff09; CR4寄存器 在白皮书的第三卷2.5章对控制寄存器有详细的介绍&#xff0c;下面是白皮书中CR寄存器的结构图&#xff08;这里要说明一下cr4的第12位是由被使用的这个位在2.5章的后半部分有介绍是控制是否开启5级分页的位否则…

[Spring] Spring5——JdbcTemplate 简介

目录 一、JdbcTemplate 概述 1、什么是 JdbcTemplate 2、准备运行环境 二、添加功能 1、示例 三、修改和删除功能 1、示例 四、查询功能 1、查询记录数 2、查询返回 bean 对象 3、查询返回集合 4、测试 五、批量增删改功能 1、批量添加 2、批量修改 3、批量删除…

Context应用上下文理解

文章目录 一、Context相关类的继承关系Context类ContextIml.java类ContextWrapper类ContextThemeWrapper类 二、 什么时候创建Context实例创建Context实例的时机 小结 Context类 &#xff0c;说它熟悉&#xff0c;是应为我们在开发中时刻的在与它打交道&#xff0c;例如&#x…

全方位介绍工厂的MES质量检验管理系统

一、MES质量检验管理系统的定义&#xff1a; MES质量检验管理系统是基于制造执行系统的框架和功能&#xff0c;专注于产品质量的控制和管理。它通过整合和优化质量检验流程&#xff0c;提供实时的数据采集、分析和反馈&#xff0c;帮助工厂实现高效的质量管理。该系统涵盖了从…

解决高分屏DPI缩放PC端百度网盘界面模糊的问题

第一步 更新最新版本 首先&#xff0c;在百度网盘官网下载最新安装包&#xff1a; https://pan.baidu.com/download 进行覆盖安装 第二步 修改兼容性设置 右键百度网盘图标&#xff0c;点击属性&#xff0c;在兼容性选项卡中点击更改所有用户的设置 弹出的选项卡中选择更改高…

linux内核分析:虚拟化

三种虚拟化方式 1. 对于虚拟机内核来讲&#xff0c;只要将标志位设为虚拟机状态&#xff0c;我们就可以直接在 CPU 上执行大部分的指令&#xff0c;不需要虚拟化软件在中间转述&#xff0c;除非遇到特别敏感的指令&#xff0c;才需要将标志位设为物理机内核态运行&#xff0c…

【gitlab】本地项目上传gitlab

需求描述 解决方法 下面的截图是gitlab空项目的描述 上传一个本地项目按其中“Push an existing folder”命令即可。 以renren-fast项目为例 # 用git bash 下载renren-fast项目 git clone https://gitee.com/renrenio/renren-fast.git# 在renren-fast的所属目录 打开git ba…

详谈Spring

作者&#xff1a;爱塔居 专栏&#xff1a;JavaEE 目录 一、Spring是什么&#xff1f; 1.1 Spring框架的一些核心特点&#xff1a; 二、IoC&#xff08;控制反转&#xff09;是什么&#xff1f; 2.1 实现手段 2.2 依赖注入&#xff08;DI&#xff09;的实现原理 2.3 优点 三、AO…

时序分解 | Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间序列信号分解

时序分解 | Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间序列信号分解 目录 时序分解 | Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间…

连续爆轰发动机

0.什么是爆轰 其反应区前沿为一激波。反应区连同前驱激波称为爆轰波。爆轰波扫过后&#xff0c;反应区介质成为高温高压的爆轰产物。能够发生爆轰的系统可以是气相、液相、固相或气-液、气-固和液-固等混合相组成的系统。通常把液、固相的爆轰系统称为炸药。 19世纪80年代初&a…

子监督学习的知识点总结

监督学习 机器学习中最常见的方法是监督学习。在监督学习中&#xff0c;我们得到一组标记数据&#xff08;X&#xff0c;Y&#xff09;&#xff0c;即&#xff08;特征&#xff0c;标签&#xff09;&#xff0c;我们的任务是学习它们之间的关系。但是这种方法并不总是易于处理&…

基于微信小程序的付费自习室

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1 简介2 技术栈3 需求分析3.1用户需求分析3.1.1 学生用户3.1.3 管理员用户 4 数据库设计4.4.1 E…

定时器+按键控制LED流水灯模式+定时器时钟——“51单片机”

各位CSDN的uu们好呀&#xff0c;今天&#xff0c;小雅兰的内容是51单片机中的定时器以及按键控制LED流水灯模式&定时器时钟&#xff0c;下面&#xff0c;让我们进入51单片机的世界吧&#xff01;&#xff01;&#xff01; 定时器 按键控制LED流水灯模式 定时器时钟 源代…

Mac电脑BIM建模软件 Archicad 26 for Mac最新

ARCHICAD 软件特色 智能化 在2D CAD中&#xff0c;所有的建筑构件都由线条构成和表现&#xff0c;仅仅是一些线条的组合而已&#xff0c;当我们阅读图纸的时候是按照制图规范来读取这些信息。我们用一组线条表示平面中的窗&#xff0c;再用另一组不同的线条在立面中表示同一个…

C++11——神奇的右值引用与移动构造

文章目录 前言左值引用和右值引用右值引用的使用场景和意义右值引用引用左值万能引用右值引用的属性完美转发新的默认构造函数强制和禁止生成默认函数 总结 前言 本篇博客将主要讲述c11中新添的新特性——右值引用和移动构造等&#xff0c;从浅到深的了解这个新特性的用法&…