0
点赞
收藏
分享

微信扫一扫

C++后端开发(2.1.1)——网络io模型中的阻塞与非阻塞

_阿瑶 2022-03-12 阅读 28

C++后端开发(2.1.1)——网络io模型中的阻塞与非阻塞


本节提纲

0.简介

1.基本代码框架

int main(int argc, char **argv)
{
    int listenfd, connfd, n;
    struct sockaddr_in servaddr;
    char buff[MAXLNE];
    //使用socket套接字创建listenfd
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    //设置sockaddr结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);

    //绑定地址
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
    {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    //开启listen
    if (listen(listenfd, 10) == -1)
    {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
#if
// 在这个区域分别使用阻塞,多线程,select,poll,epoll等多种方式实现连接
#endif
    close(listenfd);
    return 0;
}

2.阻塞——单线程

2.1 accept位于whlie循环之前:只能连接一个客户端

  //首先是最基本的方案,在while循环之前accept
    //只能实现单个客户端的连接
    //定义客户端地址
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1)
    {
        printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    printf("=================wait for client===================\n");
    while (1)
    {
        n = recv(connfd, buff, MAXLNE, 0);
        if (n > 0)
        {
            //加上字符串的尾部,以便显示和转发
            buff[n] = '\0';
            printf("recv msg from clientL %s \n", buff);
            send(connfd, buff, n, 0);
        }
        else if (n == 0)
        {
            close(connfd);
        }
    }

2.2 accept位于whlie循环之中:能连接多个客户端,但只能接收一条消息

    //将accept放入while循环,可以实现多个客户端连接,
    //但是每次只能进行对话一次
    printf("========waiting for client's request========\n");
    while (1)
    {

        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1)
        {
            printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }

        n = recv(connfd, buff, MAXLNE, 0);
        if (n > 0)
        {
            buff[n] = '\0';
            printf("recv msg from client: %s\n", buff);

            send(connfd, buff, n, 0);
        }
        else if (n == 0)
        {
            close(connfd);
        }

        // close(connfd);
    }

3.阻塞——thread 多线程

3.1 多线程实现方法

3.2 多线程优缺点

// 4G / 8m = 512 假设运行内存只有4g,每个客户端连接占8m,只能支持512个连接
// C10K
void *client_routine(void *arg)
{
    int connfd = *(int *)arg;

    char buff[MAXLNE];

    while (1)
    {
        int n = recv(connfd, buff, MAXLNE, 0);
        if (n > 0)
        {
            buff[n] = '\0';
            printf("recv msg from client: %s\n", buff);
            send(connfd, buff, n, 0);
        }
        else if (n == 0)
        {
            close(connfd);
            break;
        }
    }
    return NULL;
}

    //开线程,可以实现多个客户端连接,
    //但是数量有限,开销大
    while (1)
    {

        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1)
        {
            printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
            return 0;
        }

        pthread_t threadid;
        pthread_create(&threadid, NULL, client_routine, (void *)&connfd);
    }

4 多路复用——select

4.1 select 执行流程

4.2 select 优缺点

4.2.1优点

4.2.2 缺点

在这里插入图片描述

  //使用select实现多路复用
    //分别声明select中的读集合,写集合,读操作,写操作
    fd_set rset, wset, rfds, wfds;

    //全都置0
    FD_ZERO(&rfds);
    //将listen_fd加入读集合
    FD_SET(listenfd, &rfds);

    FD_ZERO(&wfds);

    int max_fd = listenfd;

    while (1)
    {
        //将新的读写操作更新读写集合
        rset = rfds;
        wset = wfds;
        //登记读写集合
        int nready = select(max_fd + 1, &rset, &wset, NULL, NULL);

        //监听到新的套接字
        if (FD_ISSET(listenfd, &rset))
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1)
            {
                printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                return 0;
            }
            //加入到写集合中
            FD_SET(connfd, &rfds);
            //更新max_fd
            if (connfd > max_fd)
                max_fd = connfd;
            //返回的套接字里面啥也没有
            if (--nready == 0)
                continue;
        }

        //循环读操作里面的
        int i = 0;
        for (i = listenfd + 1; i <= max_fd; i++)
        {
            //在读操作里
            if (FD_ISSET(i, &rset))
            {
                n = recv(i, buff, MAXLNE, 0);
                if (n > 0)
                {
                    buff[n] = '\0';
                    printf("recv msg from client: %s\n", buff);
                    //设置为可写
                    FD_SET(i, &wfds);
                    // reactor
                    // send(i, buff, n, 0);
                }
                else if (n == 0)
                {
                    FD_CLR(i, &rfds);
                    close(i);
                }
                if (--nready == 0)
                    break;
            }

            //在写操作里
            else if (FD_ISSET(i, &wset))
            {
                send(i, buff, n, 0);
                FD_CLR(i, &wfds);
                //设置为可读
                FD_SET(i, &rfds);
            }
        }
    }

3 多路复用——poll

3.1 pollfd结构体

/* Data structure describing a polling request.  */
struct pollfd
{
    int fd;			/* 文件描述符 */
    short int events;		/* poller所对应关心的事件,如果是读事件就是POLLIN,如果是写事件就是POLLOUT  */
    short int revents;		/* 对events的反馈,开始时为0,当有数据可读时就为POLLIN,类似select的rset */
};

3.2 poll使用实例

// poll 先把listenfd放进去,关注读事件
   struct pollfd fds[POLL_SIZE] = {0};
   fds[0].fd = listenfd;
   fds[0].events = POLLIN;

   int max_fd = listenfd;
   int i = 0;
   for (i = 1; i < POLL_SIZE; i++)
   {
       fds[i].fd = -1;
   }

   while (1)
   {
       int nready = poll(fds, max_fd + 1, -1);
       // listenfd发生读事件
       if (fds[0].revents & POLLIN)
       {
           struct sockaddr_in client;
           socklen_t len = sizeof(client);
           if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1)
           {
               printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
               return 0;
           }
           //接收此次收到的客户端文件描述符
           printf("accept \n");
           fds[connfd].fd = connfd;
           fds[connfd].events = POLLIN;

           if (connfd > max_fd)
               max_fd = connfd;

           if (--nready == 0)
               continue;
       }
       //遍历fds内部,读取revent确定是否收到数据
       for (i = listenfd + 1; i <= max_fd; i++)
       {
           if (fds[i].revents & POLLIN)
           {
               n = recv(i, buff, MAXLNE, 0);
               if (n > 0)
               {
                   buff[n] = '\0';
                   printf("recv msg from client: %s\n", buff);
                   //把刚刚收到的发出去
                   send(i, buff, n, 0);
               }
               //客户端断开连接,回收资源
               else if (n == 0)
               {
                   fds[i].fd = -1;
                   close(i);
               }
               if (--nready == 0)
                   break;
           }
       }
   }

3.3 poll执行流程

3.4 对于select提升的地方

3.5 poll弊端

4.多路复用——epoll

4.1 epoll 所使用的函数与解析

#include <sys/epoll.h>
int epoll_create(int size);
/*
epoll_create() 返回一个文件描述符,
该文件描述符“描述”的是内核中的一块内存区域,size现在不起任何作用。
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/*
epoll_ctl()用来操作内核事件表,用于向内核注册新的描述符或者是改变某个文件描述符的状态。
已注册的描述符在内核中会被维护在一棵红黑树上,
通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,
*/
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
/*
进程调用epoll_wait() 便可以得到事件完成的描述符。
*/

4.1.1 参数解析

4.2 epoll 关键结构体epoll_event *event

struct epoll_event{
    _uint32_t events; //epoll事件,读、写、异常三种
    epoll_data_t data; //用户数据
}

struct epoll_data{
    void* prt;
    int fd;
    _uint32_t u32;
    _uint64_t u64;
}epoll_data_t;

epoll_wait()该函数返回就绪文件描述符的个数
在这里插入图片描述

4.3 epoll优点

4.3 epoll 使用实例

   // poll/select -->
    //  epoll_create
    //  epoll_ctl(ADD, DEL, MOD)
    //  epoll_wait

    //创建一个空间,假设是一个大房子
    int epfd = epoll_create(1);
    // epoll针对的是事件,创建一个事件数组用来管理需要关注的事件
    struct epoll_event events[POLL_SIZE] = {0};
    //某次接收到的新事件
    struct epoll_event ev;
    //先把listenfd先塞进去
    ev.events = EPOLLIN;
    ev.data.fd = listenfd;
    // 添加listenfd,持续监听,添加到了内核管理的一棵红黑树上面
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

    while (1)
    {
        //非阻塞,这里是从内核中的链表中提取出来放进events数组里
        //查看events数组里面关注的事件描述符有没有置1,最后一个是超时时间
        //没有则返回-1,有则返回事件的数量
        int nready = epoll_wait(epfd, events, POLL_SIZE, 5);
        if (nready == -1)
        {
            continue;
        }

        int i = 0;
        for (i = 0; i < nready; i++)
        {
            //读取获取的fd
            int clientfd = events[i].data.fd;
            //如果是listenfd,则收到的是客户端的fd
            if (clientfd == listenfd)
            {
                struct sockaddr_in client;
                socklen_t len = sizeof(client);
                if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1)
                {
                    printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                    return 0;
                }
                printf("accept\n");
                //通过epoll_clt添加到内核的红黑树里
                ev.events = EPOLLIN;
                ev.data.fd = connfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            }
            // 获取到的消息
            else if (events[i].events & EPOLLIN)
            {
                n = recv(clientfd, buff, MAXLNE, 0);
                if (n > 0)
                {
                    buff[n] = '\0';
                    printf("recv msg from client: %s\n", buff);
                    //这里是直接发送回去,并不规范
                    send(clientfd, buff, n, 0);
                }
                else if (n == 0)
                {
                    //传输完成删除该fd
                    ev.events = EPOLLIN;
                    ev.data.fd = clientfd;
                    epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
                    close(clientfd);
                }
            }
        }
    }

举报

相关推荐

0 条评论