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

本文共 4266 字,大约阅读时间需要 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 6666void 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 6666void 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/

    你可能感兴趣的文章
    NLog类库使用探索——详解配置
    查看>>
    NLP 基于kashgari和BERT实现中文命名实体识别(NER)
    查看>>
    NLP 项目:维基百科文章爬虫和分类【01】 - 语料库阅读器
    查看>>
    NLP_什么是统计语言模型_条件概率的链式法则_n元统计语言模型_马尔科夫链_数据稀疏(出现了词库中没有的词)_统计语言模型的平滑策略---人工智能工作笔记0035
    查看>>
    NLP学习笔记:使用 Python 进行NLTK
    查看>>
    NLP的神经网络训练的新模式
    查看>>
    NLP采用Bert进行简单文本情感分类
    查看>>
    NLP问答系统:使用 Deepset SQUAD 和 SQuAD v2 度量评估
    查看>>
    NLP:使用 SciKit Learn 的文本矢量化方法
    查看>>
    Nmap扫描教程之Nmap基础知识
    查看>>
    Nmap端口扫描工具Windows安装和命令大全(非常详细)零基础入门到精通,收藏这篇就够了
    查看>>
    NMAP网络扫描工具的安装与使用
    查看>>
    NMF(非负矩阵分解)
    查看>>
    nmon_x86_64_centos7工具如何使用
    查看>>
    NN&DL4.1 Deep L-layer neural network简介
    查看>>
    NN&DL4.3 Getting your matrix dimensions right
    查看>>
    NN&DL4.8 What does this have to do with the brain?
    查看>>
    nnU-Net 终极指南
    查看>>
    No 'Access-Control-Allow-Origin' header is present on the requested resource.
    查看>>
    NO 157 去掉禅道访问地址中的zentao
    查看>>