博客
关于我
网络编程之IO复用机制(多路IO转接)之select实现IO复用的代码实现03
阅读量:236 次
发布时间:2019-03-01

本文共 4276 字,大约阅读时间需要 14 分钟。

思路图与select机制

在学习网络编程时,select机制是一个非常重要的工具。它允许多个客户端同时与服务器通信,而不像传统的accept机制那样只能处理一个连接。这一机制通过IO复用(select、poll、epoll)与内核合作,显著提升了服务器的性能和处理能力。

select的核心原理

select函数的作用是监控多个文件描述符(套接字)的读写事件。它通过设置超时返回,避免阻塞,能够高效地处理大量客户端连接。以下是select的关键点:

  • 文件描述符管理:select监听读事件,只需处理读操作。写事件通常不需要特别处理,因为服务器端的写操作不受限制。
  • 事件类型:select返回四种事件类型,但在服务器端主要关注读事件。其他事件(如写事件)在服务器端通常不需要处理。
  • 异常事件:select会报告异常,但在服务器端不需要特别处理,因为异常通常由客户端触发。
  • select代码实战

    我们编写一个简单的select服务器示例,功能是接收客户端连接并转换大写字母。以下是代码的主要部分:

    #include 
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #define SERV_PORT 6666 void perr_exit(const char *s) { perror(s); exit(-1); } int accept(int fd, struct sockaddr *sa, socklen_t *salen) { int n; again: if ((n = accept(fd, sa, salen)) < 0) { if ((errno == ECONNABORTED) || (errno == EINTR)) goto again; else perr_exit("accept error"); } return n; } int main() { int nready, i, j, n; int maxfd, lfd, cfd; socklen_t clie_addr_len; char buf[BUFSIZ]; fd_set rset, allset; lfd = socket(AF_INET, SOCK_STREAM, 0); if (lfd == -1) { perr_exit("socket failed."); } struct sockaddr_in serv_addr; bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(SERV_PORT); if (bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { perr_exit("bind failed."); } if (listen(lfd, 128) == -1) { perr_exit("listen failed."); } FD_ZERO(&allset); FD_SET(lfd, &allset); maxfd = lfd; while (1) { rset = allset; nready = select(maxfd + 1, &rset, NULL, NULL, NULL); if (nready < 0) { perr_exit("select error."); } if (FD_ISSET(lfd, &rset)) { cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len); FD_SET(cfd, &allset); if (cfd > maxfd) { maxfd = cfd; } } if (nready == 1) { continue; } for (i = lfd + 1; i <= maxfd; i++) { if (FD_ISSET(i, &rset)) { if ((n = read(i, buf, sizeof(buf))) == 0) { close(i); FD_CLR(i, &allset); } else if (n > 0) { for (j = 0; j < n; j++) { buf[j] = toupper(buf[j]); } write(i, buf, n); write(STDOUT_FILENO, buf, n); } } } } close(lfd); return 0; }

    select、poll和epoll的对比

    select的优点:跨平台支持,兼容性好。缺点:监听文件描述符的数量受限(最大1024),处理效率较低,代码复杂度较高。

    poll的优点:支持更多文件描述符,事件分离更灵活。缺点:仅在Linux/Unix系统上支持,开发门槛较高。

    epoll的优点:效率更高,代码简洁。缺点:仅在Linux/Unix系统上支持,跨平台性差。

    select添加数组后的优化

    虽然select本身效率不错,但传统的实现方式存在一定局限性。通过引入文件描述符数组,可以优化事件处理逻辑,但实际效果有限。以下是优化后的代码示例:

    #include 
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #define SERV_PORT 6666 void perr_exit(const char *s) { perror(s); exit(-1); } int main() { int nready, i, j, n; int maxfd, lfd, cfd, tmpfd; int maxi; socklen_t clie_addr_len; char buf[BUFSIZ]; char *client = malloc(1024); FD_ZERO(&allset); FD_SET(lfd, &allset); maxfd = lfd; int opt = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in serv_addr; bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(SERV_PORT); if (bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { perr_exit("bind failed."); } if (listen(lfd, 128) == -1) { perr_exit("listen failed."); } while (1) { rset = allset; nready = select(maxfd + 1, &rset, NULL, NULL, NULL); if (nready < 0) { perr_exit("select error."); } if (FD_ISSET(lfd, &rset)) { clie_addr_len = sizeof(clie_addr); cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len); FD_SET(cfd, &allset); if (cfd > maxfd) { maxfd = cfd; } for (i = 0; i < 1024; i++) { if (client[i] == -1) { client[i] = cfd; break; } } maxi = i - 1; if (nready == 1) { continue; } } if (nready == 1) { continue; } for (i = 0; i <= maxi; i++) { if (client[i] == -1) { continue; } if (FD_ISSET(client[i], &rset)) { if ((n = read(client[i], buf, sizeof(buf))) == 0) { close(client[i]); FD_CLR(client[i], &allset); client[i] = -1; } else if (n > 0) { for (j = 0; j < n; j++) { buf[j] = toupper(buf[j]); } write(client[i], buf, n); write(STDOUT_FILENO, buf, n); } } if (--nready == 0) { break; } } } close(lfd); return 0; }

    测试结果

    通过测试可以发现,当使用select实现服务器时,能够支持多个客户端同时连接并通信。例如,使用nc模拟多个客户端连接到服务器,服务器可以正确接收和转换客户端发送的数据。这种实现方式有效地解决了传统accept机制的效率问题,展示了select机制的强大之处。

    转载地址:http://hpfv.baihongyu.com/

    你可能感兴趣的文章
    Netty遇到TCP发送缓冲区满了 写半包操作该如何处理
    查看>>
    Netty:ChannelPipeline和ChannelHandler为什么会鬼混在一起?
    查看>>
    Netty:原理架构解析
    查看>>
    Network Dissection:Quantifying Interpretability of Deep Visual Representations(深层视觉表征的量化解释)
    查看>>
    Network Sniffer and Connection Analyzer
    查看>>
    Network 灰鸽宝典【目录】
    查看>>
    NetworkX系列教程(11)-graph和其他数据格式转换
    查看>>
    Networkx读取军械调查-ITN综合传输网络?/读取GML文件
    查看>>
    network小学习
    查看>>
    Netwox网络工具使用详解
    查看>>
    Net与Flex入门
    查看>>
    net包之IPConn
    查看>>
    Net操作配置文件(Web.config|App.config)通用类
    查看>>
    Neutron系列 : Neutron OVS OpenFlow 流表 和 L2 Population(7)
    查看>>
    New Relic——手机应用app开发达人的福利立即就到啦!
    查看>>
    NFinal学习笔记 02—NFinalBuild
    查看>>
    NFS
    查看>>
    NFS Server及Client配置与挂载详解
    查看>>
    NFS共享文件系统搭建
    查看>>
    nfs复习
    查看>>