2.1.1网络io与io多路复用select/poll/epoll

简介: 2.1.1网络io与io多路复用select/poll/epoll

关于网络io,我们可以通过一个服务端-客户端的示例来了解:

这是一段TCP服务端的代码:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
  //open
  //创建网络io
  int sockfd = socket(AF_INET, SOCK_STREAM, 0); // io
  struct sockaddr_in servaddr;
  memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123
  servaddr.sin_family = AF_INET;
    //INADDR_ANY绑定任意网卡,接收任意网卡的数据
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
    servaddr.sin_port = htons(9999);
    if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
    printf("bind failed: %s", strerror(errno));
    return -1;
    }
    listen(sockfd, 10); 
}

值得注意的是htonlhtons都是将主机字节序转换为网络字节序

htonl表示转换四字节的无符号整数,htons表示转换两字节的无符号整数

htonl,  htons,  ntohl, ntohs - convert values between host and network byte order

运行这段程序,可以发现程序没有任何效果,直接退出,而当我们在listen后面加上

getchar();后,程序阻塞,这时通过命令netstat -anop | grep 9999查看端口状态:

发现该端口正处于listen状态,这时通过网络调试助手(充当客户端)连接192.168.209.130:9999发现连接成功。

服务端其实一直都处于listen状态,之后还需要通过accept接受连接

listen(sockfd, 10); 
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(struct sockaddr_in);
    int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
    getchar();

accept接受客户端的连接,并通过传出参数返回客户端的信息,以及函数返回clientfd,后续该客户端的数据收发都通过该clientfd进行。这也就反应了一个问题,每个客户端连接的都会对应一个clientfd。这时运行程序,程序阻塞等待客户端的连接,但这次是阻塞在accept系统调用上。创建的sockfd默认是阻塞的

而关于阻塞和非阻塞的概念,简单总结就是阻塞会等待有事件发生,非阻塞则是不管有无事件都会立即返回。

我们将sockfd设为非阻塞形式,并将getchar()注释掉再看看效果:

#include <fcntl.h>
  ...
  listen(sockfd, 10);
  //设为非阻塞
    int flags = fcntl(sockfd, F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl(sockfd, F_SETFL, flags);
    struct sockaddr_in clientaddr;
    ...
    //getchar();

调用程序发现,程序立即返回,不再阻塞!

现在思考一个问题,连接成功是在listen完成,还是在accept完成呢?

我们可以在listen后加上一个sleep(10);,在accept返回后打印一下返回值

listen(sockfd, 10);
    sleep(10);
    ...
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(struct sockaddr_in);
    int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
    printf("clientfd: %d\n", clientfd);

程序运行后,立马连接,发现可以连接成功,并且10秒后,accept返回4

说明在listen连接就已经建立成功了,而clientfd为4则是因为,标准输入、标准输出、标准错误、以及sockfd已经占用了0、1、2、3再分配的文件描述符就是4。

接下来进行数据的收发(使用阻塞模式),accept之后调用recvsend

char buffer[BUFFER_LENGTH] = {0};
    int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
    printf("ret: %d, buffer: %s\n", ret, buffer);
    send(clientfd, buffer, ret, 0);

这时收发数据只能进行一次,我们可以加上while循环实现循环收发。若想实现多个客户端连接,并支持收发数据,也把accept放入while循环??形如这样?

while (1) {
        int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
        char buffer[BUFFER_LENGTH] = {0};
        int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
        printf("ret: %d, buffer: %s\n", ret, buffer);
        send(clientfd, buffer, ret, 0);
}

我们开多个客户端连接发现确实能连接上服务端,但连接后仍然只能进行一次数据收发。

因为一直阻塞在accept上,服务端只会服务新来的连接的一次数据收发。

那要支持多个客户端连接,并且都能进行多次数据收发该如何做呢?

我们可以将数据收发的工作放在一个线程中循环做:

#include <pthread.h>
void *client_thread(void *arg) {
    int clientfd = *(int*)arg;
    //线程中循环数据收发
    while (1) {
        char buffer[BUFFER_LENGTH] = {0};
        int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);
        //recv返回0说明对端关闭连接
        if (ret == 0) {
      close(clientfd);
      break;
    }
        printf("ret: %d, buffer: %s\n", ret, buffer);
        send(clientfd, buffer, ret, 0);
    }
}
...
  while (1) {
        int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
        pthread_t threadid;
        pthread_create(&threadid, NULL, client_thread, &clientfd);
    }
...

来一个连接,创建一个线程,该线程循环负责该客户端的数据收发。

但是这种模式存在一个弊端,成千上万个客户端连接,难道要创建对应个数的线程吗?有没有更好的解决办法?有,那便是IO多路复用!

Linux中有三种IO多路复用:select、poll、epoll

下面介绍使用selectpoll的方式:

#include <sys/select.h>
#define BUFFER_LENGTH 1024
listen(sockfd, 10); 
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(struct sockaddr_in);
    fd_set rfds, rset;
    FD_ZERO(&rfds);
    FD_SET(sockfd, &rfds);
    int maxfd = sockfd;
    int clientfd = 0;
    while (1) {
        rset = rfds;
        //这里传入文件描述符最大值加1
        //判断时是形如for(; i < maxfd; i++)所以要加一
        int nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
        if (FD_ISSET(sockfd, &rset)) {
            clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
            printf("accept: %d\n", clientfd);
            FD_SET(clientfd, &rfds);
            if (clientfd > maxfd) maxfd = clientfd;
            if(--nready == 0) continue;
        }
        int i = 0;
        for (i = sockfd + 1; i <= maxfd; i++) {
            if (FD_ISSET(i, &rset)) {
                    char buffer[BUFFER_LENGTH] = {0};
                    int ret = recv(i, buffer, BUFFER_LENGTH, 0);
                    if (ret == 0) {
                        close(i);
                        break;
                    }
                    printf("ret: %d, buffer: %s\n", ret, buffer);
                    send(i, buffer, ret, 0);
            }
        }
    }
    getchar();

值得注意的是select是通过判断fd_set中的某些位,从而判断是否发生事件,因此,select所能处理的文件描述符个数是有限的,只有1024个。

下面是poll的使用方式:

#include <poll.h>
#define POLL_SIZE     1024
  listen(sockfd, 10); 
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(struct sockaddr_in);
    struct pollfd fds[POLL_SIZE] = {0};
    fds[sockfd].fd = sockfd;
    fds[sockfd].events = POLLIN;
    int maxfd = sockfd;
    int clientfd = 0;
    while (1) {
      int nready = poll(fds, maxfd + 1, -1);
      if (fds[sockfd].revents & POLLIN) {
              clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
              printf("accept: %d\n", clientfd);
              fds[clientfd].fd = clientfd;
              fds[clientfd].events = POLLIN;
              if (clientfd > maxfd) maxfd = clientfd;
              if(--nready == 0) continue;
        }
        int i = 0;
        for (i = 0; i <= maxfd; i++) {
          if (fds[i].revents & POLLIN) {
             char buffer[BUFFER_LENGTH] = {0};
                    int ret = recv(i, buffer, BUFFER_LENGTH, 0);
                    if (ret == 0) {
                        fds[i].fd = -1;
                        fds[i].events = 0;
                        close(i);
                        break;
                    }
                    printf("ret: %d, buffer: %s\n", ret, buffer);
                    send(i, buffer, ret, 0);
          }
        }
    }
    getchar();

poll相较select,支持的文件描述符数量不受限制,并且每次调用无需重新设置事件,因为内核不会修改,而是通过revent返回。但是他们都有性能瓶颈,他们返回就绪的文件描述符个数,但仍需我们自己去遍历到底是哪个文件描述符上有事件,而epoll解决了这种问题。

文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:https://ke.qq.com/course/417774?flowToken=1020253

相关文章
|
3月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
46 0
Linux C/C++之IO多路复用(poll,epoll)
|
6月前
|
安全 Java Linux
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
195 1
|
6月前
|
存储 Java Unix
(八)Java网络编程之IO模型篇-内核Select、Poll、Epoll多路复用函数源码深度历险!
select/poll、epoll这些词汇相信诸位都不陌生,因为在Redis/Nginx/Netty等一些高性能技术栈的底层原理中,大家应该都见过它们的身影,接下来重点讲解这块内容。
103 0
|
17天前
|
SQL 安全 网络安全
网络安全与信息安全:知识分享####
【10月更文挑战第21天】 随着数字化时代的快速发展,网络安全和信息安全已成为个人和企业不可忽视的关键问题。本文将探讨网络安全漏洞、加密技术以及安全意识的重要性,并提供一些实用的建议,帮助读者提高自身的网络安全防护能力。 ####
58 17
|
27天前
|
存储 SQL 安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将介绍网络安全的重要性,分析常见的网络安全漏洞及其危害,探讨加密技术在保障网络安全中的作用,并强调提高安全意识的必要性。通过本文的学习,读者将了解网络安全的基本概念和应对策略,提升个人和组织的网络安全防护能力。
|
28天前
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将从网络安全漏洞、加密技术和安全意识三个方面进行探讨,旨在提高读者对网络安全的认识和防范能力。通过分析常见的网络安全漏洞,介绍加密技术的基本原理和应用,以及强调安全意识的重要性,帮助读者更好地保护自己的网络信息安全。
47 10
|
30天前
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在数字化时代,网络安全和信息安全已成为我们生活中不可或缺的一部分。本文将介绍网络安全漏洞、加密技术和安全意识等方面的内容,并提供一些实用的代码示例。通过阅读本文,您将了解到如何保护自己的网络安全,以及如何提高自己的信息安全意识。
59 10
|
30天前
|
存储 监控 安全
云计算与网络安全:云服务、网络安全、信息安全等技术领域的融合与挑战
本文将探讨云计算与网络安全之间的关系,以及它们在云服务、网络安全和信息安全等技术领域中的融合与挑战。我们将分析云计算的优势和风险,以及如何通过网络安全措施来保护数据和应用程序。我们还将讨论如何确保云服务的可用性和可靠性,以及如何处理网络攻击和数据泄露等问题。最后,我们将提供一些关于如何在云计算环境中实现网络安全的建议和最佳实践。
|
1月前
|
监控 安全 网络安全
网络安全与信息安全:漏洞、加密与意识的交织
在数字时代的浪潮中,网络安全与信息安全成为维护数据完整性、保密性和可用性的关键。本文深入探讨了网络安全中的漏洞概念、加密技术的应用以及提升安全意识的重要性。通过实际案例分析,揭示了网络攻击的常见模式和防御策略,强调了教育和技术并重的安全理念。旨在为读者提供一套全面的网络安全知识框架,从而在日益复杂的网络环境中保护个人和组织的资产安全。
|
28天前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在数字化时代,网络安全和信息安全已成为我们日常生活中不可或缺的一部分。本文将深入探讨网络安全漏洞、加密技术和安全意识等方面的问题,并提供一些实用的建议和解决方案。我们将通过分析网络攻击的常见形式,揭示网络安全的脆弱性,并介绍如何利用加密技术来保护数据。此外,我们还将强调提高个人和企业的安全意识的重要性,以应对日益复杂的网络威胁。无论你是普通用户还是IT专业人士,这篇文章都将为你提供有价值的见解和指导。

热门文章

最新文章