linux学习笔记linux下i/o接口的演变之路(代码片段)

坐望云起 坐望云起     2022-12-16     510

关键词:

一、概述

        Linux(实际上是 Unix)的一个基本概念是 Unix/Linux 中的一切都是文件的规则。每个进程都有一个指向文件、套接字、设备和其他操作系统对象的文件描述符表。

        与许多 IO 源一起工作的典型系统有一个初始化阶段,然后进入某种待机模式——等待任何客户端发送请求并响应它。

1、用户和内核模式

        在任何现代操作系统中,CPU 实际上都在两种截然不同的模式下花费时间:

        内核模式

        在内核模式下,执行代码可以完全且不受限制地访问底层硬件。它可以执行任何 CPU 指令并引用任何内存地址。内核模式通常保留给操作系统的最低级别、最受信任的功能。内核模式下的崩溃是灾难性的;他们将停止整个 PC。

        用户模式

        ​​在用户模式下,执行代码无法直接访问硬件或引用内存。在用户模式下运行的代码必须委托给系统 API 来访问硬件或内存。由于这种隔离提供的保护,用户模式下的崩溃总是可以恢复的。在您的计算机上运行的大部分代码都将在用户模式下执行。

2、进程切换

        进程切换是操作系统调度程序从一个正在运行的程序更改为另一个。这需要保存当前执行程序的所有状态,包括它的寄存器状态、相关的内核状态以及它的所有虚拟内存配置。

        一个进程切换将通过以下更改:

        保存处理器上下文,包括程序计数器和其他寄存器
        更新过程控制块 (PCB) 信息
        将PCB的进程移动到相应的队列中,如就绪队列、事件块队列等队列
        选择另一个进程执行并更新其 PCB
        更新内存中的数据结构
        恢复 PCB 上下文

3、阻塞进程

        阻塞进程通常在等待一个事件,例如释放信号量或消息到达其消息队列。在多任务系统中,此类进程被期望通过系统调用通知调度程序它要等待,以便可以将它们从活动调度队列中删除,直到事件发生。在等待期间继续运行的进程(即,在紧密循环中不断轮询事件)被称为忙等待,这是不可取的,因为它浪费了可用于其他进程的时钟周期。当进程进入阻塞状态时,不会占用CPU资源。

4、缓冲 I/O

        缓冲的输出流会将写入结果累积到一个中间缓冲区中,仅当累积了足够的数据(或请求了 flush())时才将其发送到 OS 文件系统。这减少了文件系统调用的数量。由于在大多数平台上文件系统调用可能很昂贵(与短 memcpy 相比),因此在执行大量小型写入时,缓冲输出是一种净赢。当您已经有大缓冲区要发送时,无缓冲输出通常会更好——复制到中间缓冲区不会进一步减少操作系统调用的数量,并且会引入额外的工作。数据在传输过程中需要进行数据复制操作,操作操作带来的CPU内存开销非常高。

5、文件描述符 (FD)

        在 Unix 和相关计算机操作系统中,文件描述符(FD,不太常见的 fildes)是用于访问文件或其他输入/输出资源(例如管道或网络套接字)的抽象指示符(句柄)。文件描述符构成 POSIX 应用程序编程接口的一部分。它是一个非负的索引值,很多底层程序经常基于它。

        当发生读取操作时,数据会经历两个阶段:

        1、等待数据准备好

        2、将数据从内核复制到进程

        因为这两个阶段,Linux系统产生了以下5种网络模型:

        阻塞 I/O

        非阻塞 I/O

        I/O 多路复用

        信号驱动I/O

        异步 I/O

        其中I/O 多路复用貌似是最常用的方式。

二、使用 fork()

        Fork()创建一个与其父进程同步运行的新子进程,Fork 比多线程效率低。在使用fork的服务器中,一个进程对应一个客户端,即使有性能空间,内存也会耗尽,响应性能明显下降。最终导致著名的 C10K 问题。

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

#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define INF_TIME -1
#define DISABLE -1

int listen_fd;

void int_handle(int n) 
  close(listen_fd);
  exit(EXIT_SUCCESS);


// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n) 
  ssize_t n_left = n, n_written;

  while (n_left > 0) 
    if ((n_written = write(fd, ptr, n_left)) <= 0) 
      return n_written;
    
    n_left -= n_written;
    ptr += n_written;
  

  return EXIT_SUCCESS;


int main(int argc, char **argv) 
  // Create listen socket
  if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    fprintf(stderr, "Error: socket\\n");
    return EXIT_FAILURE;
  

  // TCP port number
  int port = 8080;

  // Initialize server socket address
  struct sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(port);

  // Bind socket to an address
  if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
      0) 
    fprintf(stderr, "Error: bind\\n");
    return EXIT_FAILURE;
  

  // Listen
  if (listen(listen_fd, BACKLOG_SIZE) < 0) 
    fprintf(stderr, "Error: listen\\n");
    return EXIT_FAILURE;
  

  // Set INT signal handler
  signal(SIGINT, int_handle);

  fprintf(stderr, "listen on port %d\\n", port);

  while (1) 
    // Check new connection
    struct sockaddr_in client_addr;
    socklen_t len_client = sizeof(client_addr);

    int conn_fd;
    if ((conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr,
                          &len_client)) < 0) 
      fprintf(stderr, "Error: accept\\n");
      return EXIT_FAILURE;
    

    printf("Accept socket %d (%s : %hu)\\n", conn_fd,
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

    pid_t pid = fork();

    if (pid < 0) 
      fprintf(stderr, "Error: fork\\n");
      return EXIT_FAILURE;
    

    if (pid == 0) 
      // child
      char buf[BUF_SIZE];
      close(listen_fd);
      while (1) 
        ssize_t n = read(conn_fd, buf, BUF_SIZE);

        if (n < 0) 
          fprintf(stderr, "Error: read from socket %d\\n", conn_fd);
          close(conn_fd);
          exit(-1);
         else if (n == 0)   // connection closed by client
          printf("Close socket %d\\n", conn_fd);
          close(conn_fd);
          exit(0);
         else 
          printf("Read %zu bytes from socket %d\\n", n, conn_fd);
          write_n(conn_fd, buf, n);
        
      
     else 
      // parent
      close(conn_fd);
    
  

  close(listen_fd);
  return EXIT_SUCCESS;

三、I/O多路复用

1、select 

        计算复杂度为 O(n),因为必须线性搜索描述符(select函数仅仅知道有几个I/O事件发生了,但并不知道具体是哪几个socket连接有I/O事件,还需要轮询去查找,时间复杂度为O(n),处理的请求数越多,所消耗的时间越长。)。特点是可以管理的描述符数量有上限。

        select() 只能监视小于 FD_SETSIZE (1024) 的文件描述符数量——对于许多现代应用程序来说,这是一个不合理的下限——并且这个限制不会改变。所有现代应用程序都应该使用 poll(2) 或 epoll(7 ),不受此限制。

        参考代码

#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>

#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define INF_TIME -1
#define DISABLE -1

int listen_fd;

void int_handle(int n) 
  close(listen_fd);
  exit(EXIT_SUCCESS);


// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n) 
  ssize_t n_left = n, n_written;

  while (n_left > 0) 
    if ((n_written = write(fd, ptr, n_left)) <= 0) 
      return n_written;
    
    n_left -= n_written;
    ptr += n_written;
  

  return EXIT_SUCCESS;


int main(int argc, char **argv) 
  char buf[BUF_SIZE];
  fd_set fds;
  FD_ZERO(&fds);
  int clients[N_CLIENT];
  for (int i = 0; i < N_CLIENT; i++) 
    clients[i] = DISABLE;
  
  memset(&fds, 0, sizeof(fds));

  // Create listen socket
  if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    fprintf(stderr, "Error: socket\\n");
    return EXIT_FAILURE;
  

  // Set INT signal handler
  signal(SIGINT, int_handle);

  // TCP port number
  int port = 8080;

  // Initialize server socket address
  struct sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(port);

  // Bind socket to an address
  if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
      0) 
    fprintf(stderr, "Error: bind\\n");
    return EXIT_FAILURE;
  

  // Listen
  if (listen(listen_fd, BACKLOG_SIZE) < 0) 
    fprintf(stderr, "Error: listen\\n");
    return EXIT_FAILURE;
  

  fprintf(stderr, "listen on port %d\\n", port);

  FD_SET(listen_fd, &fds);

  int max_fd = listen_fd;  // max fd
  int max_i = 0;           // max client into clients[] array

  while (1) 
    FD_ZERO(&fds);
    FD_SET(listen_fd, &fds);
    for (int i = 0; i < N_CLIENT; i++) 
      if (clients[i] != DISABLE) 
        FD_SET(clients[i], &fds);
      
    

    int res_select = select(max_fd + 1, &fds, NULL, NULL, NULL);

    if (res_select < 0) 
      fprintf(stderr, "Error: select");
      return EXIT_FAILURE;
    

    // Check new connection
    if (FD_ISSET(listen_fd, &fds)) 
      struct sockaddr_in client_addr;
      socklen_t len_client = sizeof(client_addr);

      int connfd;
      if ((connfd = accept(listen_fd, (struct sockaddr *)&client_addr,
                           &len_client)) < 0) 
        fprintf(stderr, "Error: accept\\n");
        return EXIT_FAILURE;
      

      printf("Accept socket %d (%s : %hu)\\n", connfd,
             inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

      // Save client socket into clients array
      int i;
      for (i = 0; i < N_CLIENT; i++) 
        if (clients[i] == DISABLE) 
          clients[i] = connfd;
          break;
        
      

      // No enough space in clients array
      if (i == N_CLIENT) 
        fprintf(stderr, "Error: too many clients\\n");
        close(connfd);
      

      if (i > max_i) 
        max_i = i;
      
      if (connfd > max_fd) 
        max_fd = connfd;
      
    

    // Check all clients to read data
    for (int i = 0; i <= max_i; i++) 
      int sock_fd;
      if ((sock_fd = clients[i]) == DISABLE) 
        continue;
      

      // If the client is readable or errors occur
      ssize_t n = read(sock_fd, buf, BUF_SIZE);
      if (n < 0) 
        fprintf(stderr, "Error: read from socket %d\\n", sock_fd);
        close(sock_fd);
        clients[i] = DISABLE;
       else if (n == 0)   // connection closed by client
        printf("Close socket %d\\n", sock_fd);
        close(sock_fd);
        clients[i] = DISABLE;
       else 
        printf("Read %zu bytes from socket %d\\n", n, sock_fd);
        write_n(sock_fd, buf, n);
        write_n(1, buf, n);
      
    
  

  close(listen_fd);
  return EXIT_SUCCESS;

2、poll

        它和select的功能几乎一样,同样的计算量只需要O(n)。但是,它可以被认为是向上兼容的,因为对要管理的描述符没有限制。

        参考代码

#include <arpa/inet.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define INF_TIME -1
#define DISABLE -1

int listen_fd;

void int_handle(int n) 
  close(listen_fd);
  exit(EXIT_SUCCESS);


// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n) 
  ssize_t n_left = n, n_written;

  while (n_left > 0) 
    if ((n_written = write(fd, ptr, n_left)) <= 0) 
      return n_written;
    
    n_left -= n_written;
    ptr += n_written;
  

  return EXIT_SUCCESS;


int main(int argc, char **argv) 
  char buf[BUF_SIZE];
  struct pollfd clients[N_CLIENT];

  // Create listen socket
  if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    fprintf(stderr, "Error: socket\\n");
    return EXIT_FAILURE;
  

  // TCP port number
  int port = 8080;

  // Initialize server socket address
  struct sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(port);

  // Bind socket to an address
  if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
      0) 
    fprintf(stderr, "Error: bind\\n");
    return EXIT_FAILURE;
  

  // Listen
  if (listen(listen_fd, BACKLOG_SIZE) < 0) 
    fprintf(stderr, "Error: listen\\n");
    return EXIT_FAILURE;
  

  // Set INT signal handler
  signal(SIGINT, int_handle);

  fprintf(stderr, "listen on port %d\\n", port);

  clients[0].fd = listen_fd;
  clients[0].events = POLLIN;

  for (int i = 1; i < N_CLIENT; i++) 
    clients[i].fd = DISABLE;
  
  int max_i = 0;  // max index into clients[] array

  while (1) 
    int n_ready = poll(clients, max_i + 1, INF_TIME);

    // Time out
    if (n_ready == 0) 
      continue;
    

    // Error poll
    if (n_ready < 0) 
      fprintf(stderr, "Error: poll %d\\n", errno);
      return errno;
    

    // Check new connection
    if (clients[0].revents & POLLIN) 
      struct sockaddr_in client_addr;
      socklen_t len_client = sizeof(client_addr);

      int connfd;
      if ((connfd = accept(listen_fd, (struct sockaddr *)&client_addr,
                           &len_client)) < 0) 
        fprintf(stderr, "Error: accept\\n");
        return EXIT_FAILURE;
      

      printf("Accept socket %d (%s : %hu)\\n", connfd,
             inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

      // Save client socket into clients array
      int i;
      for (i = 0; i < N_CLIENT; i++) 
        if (clients[i].fd == DISABLE) 
          clients[i].fd = connfd;
          break;
        
      

      // No enough space in clients array
      if (i == N_CLIENT) 
        fprintf(stderr, "Error: too many clients\\n");
        close(connfd);
      

      clients[i].events = POLLIN;

      if (i > max_i) 
        max_i = i;
      
    

    // Check all clients to read data
    for (int i = 1; i <= max_i; i++) 
      int sock_fd;
      if ((sock_fd = clients[i].fd) == DISABLE) 
        continue;
      

      // If the client is readable or errors occur
      if (clients[i].revents & (POLLIN | POLLERR)) 
        ssize_t n = read(sock_fd, buf, BUF_SIZE);

        if (n < 0) 
          fprintf(stderr, "Error: read from socket %d\\n", sock_fd);
          close(sock_fd);
          clients[i].fd = DISABLE;
         else if (n == 0)   // connection closed by client
          printf("Close socket %d\\n", sock_fd);
          close(sock_fd);
          clients[i].fd = DISABLE;
         else 
          printf("Read %zu bytes from socket %d\\n", n, sock_fd);
          write_n(sock_fd, buf, n);
          write_n(1, buf, n);
        
      
    
  

  close(listen_fd);
  return EXIT_SUCCESS;

3、epoll

        select低效的另一个原因在于程序不知道哪些socket收到数据,只能一个个遍历。如果内核维护一个“就绪列表”,引用收到数据的socket,就能避免遍历。

        由于描述符的状态是在内核空间监控的,所以直接返回修改后的描述符,所以计算量为O(1),可以高速处理。

        系统调用帮助我们在内核中创建和管理上下文。我们将任务分为 3 个步骤:

#include <sys/epoll.h>

# 调用epoll_create()创建一个ep对象,即红黑树的根节点,返回一个文件句柄
int epoll_create(int size);

# 调用epoll_ctl()向这个ep对象(红黑树)中添加、删除、修改感兴趣的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

# 调用epoll_wait()等待,当有事件发生时网卡驱动会调用fd上注册的函数并将该fd添加到rdlist中,解除阻塞
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

        参考代码

#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>

#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define INF_TIME -1
#define DISABLE -1

int listen_fd;

void int_handle(int n) 
  close(listen_fd);
  exit(EXIT_SUCCESS);


// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n) 
  ssize_t n_left = n, n_written;

  while (n_left > 0) 
    if ((n_written = write(fd, ptr, n_left)) <= 0) 
      return n_written;
    
    n_left -= n_written;
    ptr += n_written;
  

  return EXIT_SUCCESS;


int main(int argc, char **argv) 
  char buf[BUF_SIZE];

  // Create listen socket
  if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    fprintf(stderr, "Error: socket\\n");
    return EXIT_FAILURE;
  

  // TCP port number
  int port = 8080;

  // Initialize server socket address
  struct sockaddr_in server_addr;
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(port);

  // Bind socket to an address
  if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
      0) 
    fprintf(stderr, "Error: bind\\n");
    return EXIT_FAILURE;
  

  // Listen
  if (listen(listen_fd, BACKLOG_SIZE) < 0) 
    fprintf(stderr, "Error: listen\\n");
    return EXIT_FAILURE;
  

  // Set INT signal handler
  signal(SIGINT, int_handle);

  fprintf(stderr, "listen on port %d\\n", port);

  // Create epoll
  int epfd = epoll_create1(0);
  if (epfd < 0) 
    fprintf(stderr, "Error: epoll create\\n");
    close(listen_fd);
    return EXIT_FAILURE;
  

  struct epoll_event listen_ev;
  memset(&listen_ev, 0, sizeof(listen_ev));
  listen_ev.events = EPOLLIN;
  listen_ev.data.fd = listen_fd;
  if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &listen_ev) < 0) 
    fprintf(stderr, "Error: epoll ctl add listen\\n");
    close(listen_fd);
    return EXIT_FAILURE;
  

  struct epoll_event evs[N_CLIENT];
  while (1) 
    // Wait epoll listener
    int n_fds = epoll_wait(epfd, evs, N_CLIENT, -1);

    // Error epoll
    if (n_fds < 0) 
      fprintf(stderr, "Error: epoll wait\\n");
      close(listen_fd);
      return EXIT_FAILURE;
    

    for (int i = 0; i < n_fds; i++) 
      if (evs[i].data.fd == listen_fd)   // Add epoll listener
        struct sockaddr_in client_addr;
        socklen_t len_client = sizeof(client_addr);

        int conn_fd;
        if ((conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr,
                              &len_client)) < 0) 
          fprintf(stderr, "Error: accept\\n");
          return EXIT_FAILURE;
        

        printf("Accept socket %d (%s : %hu)\\n", conn_fd,
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        struct epoll_event conn_ev;
        memset(&conn_ev, 0, sizeof(listen_ev));
        conn_ev.events = EPOLLIN;
        conn_ev.data.fd = conn_fd;
        if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &conn_ev) < 0) 
          fprintf(stderr, "Error: epoll ctl add listen\\n");
          close(listen_fd);
          return EXIT_FAILURE;
        
       else if (evs[i].events & EPOLLIN)   // Read data from client
        int sock_fd = evs[i].data.fd;
        ssize_t n = read(sock_fd, buf, BUF_SIZE);

        if (n < 0) 
          fprintf(stderr, "Error: read from socket %d\\n", sock_fd);
          close(sock_fd);
         else if (n == 0)   // connection closed by client
          printf("Close socket %d\\n", sock_fd);
          struct epoll_event sock_ev;
          memset(&sock_ev, 0, sizeof(listen_ev));
          sock_ev.events = EPOLLIN;
          sock_ev.data.fd = sock_fd;
          if (epoll_ctl(epfd, EPOLL_CTL_DEL, sock_fd, &sock_ev) < 0) 
            fprintf(stderr, "Error: epoll ctl dell\\n");
            close(listen_fd);
            return EXIT_FAILURE;
          
          close(sock_fd);
         else 
          printf("Read %zu bytes from socket %d\\n", n, sock_fd);
          write_n(sock_fd, buf, n);
          write_n(1, buf, n);
        
      
    
  

  close(listen_fd);
  return EXIT_SUCCESS;

四、io_uring

        io_uring是由 Facebook 的 Jens Axboe 创建的用于 Linux 的新异步 I/O API。它旨在提供一个不受当前select、poll、epoll或aio系列系统调用限制的 API。

        从linux kernel 5.1 开始提供一个新的异步 I/O API。处理是通过操纵两个环形缓冲区,提交队列(SQ)和完成队列(CQ )来执行的。例如,要执行从套接字接收消息,通过将操作码提交队列条目 (SQE) 注入 SQ 来异步执行处理。这些进程的完成通知也可以通过从CQ接收完成队列条目 (CQE) 来实现。

        io_uring 的用户态 API

io_uring_setup(2)

io_uring_enter(2)

io_uring_register(2)

        一个名为 liburing 的库为 io_uring 提供了一个简单的接口。

#include <arpa/inet.h>
#include <errno.h>
#include <liburing.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define BACKLOG_SIZE 5
#define BUF_SIZE 1024
#define N_CLIENT 256
#define N_ENTRY 2048
#define GID 1

int listen_fd;

enum 
  ACCEPT,
  READ,
  WRITE,
;

typedef struct UserData 
  __u32 fd;
  __u16 type;
 UserData;

void int_handle(int n) 
  close(listen_fd);
  exit(EXIT_SUCCESS);


// wirte n byte
ssize_t write_n(int fd, char *ptr, size_t n) 
  ssize_t n_left = n, n_written;

  while (n_left > 0) 
    if ((n_written = write(fd, ptr, n_left)) <= 0) 
      return n_written;
    
    n_left -= n_written;
    ptr += n_written;
  

  return EXIT_SUCCESS;


int main(int argc, char **argv) 
  char buf[BUF_SIZE] = 0;

  // Create listen socket
  if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    fprintf(stderr, "Error: socket\\n");
    return EXIT_FAILURE;
  

  // TCP port number
  int port = 8080;

  // Initialize server socket address
  struct sockaddr_in server_addr, client_addr;
  socklen_t client_len = sizeof(client_addr);
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(port);

  // Bind socket to an address
  if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
      0) 
    fprintf(stderr, "Error: bind\\n");
    return EXIT_FAILURE;
  

  // Listen
  if (listen(listen_fd, BACKLOG_SIZE) < 0) 
    fprintf(stderr, "Error: listen\\n");
    return EXIT_FAILURE;
  

  // Set INT signal handler
  signal(SIGINT, int_handle);

  fprintf(stderr, "listen on port %d\\n", port);

  // Initialize io_uring
  struct io_uring ring;
  struct io_uring_sqe *sqe;
  struct io_uring_cqe *cqe;
  int init_ret = io_uring_queue_init(N_ENTRY, &ring, 0);
  if (init_ret < 0) 
    fprintf(stderr, "Error: init io_uring queue %d\\n", init_ret);
    close(listen_fd);
    return EXIT_FAILURE;
  

  // Setup first accept
  sqe = io_uring_get_sqe(&ring);
  io_uring_prep_accept(sqe, listen_fd, (struct sockaddr *)&client_addr,
                       &client_len, 0);
  io_uring_sqe_set_flags(sqe, 0);
  UserData conn_info = 
      .fd = listen_fd,
      .type = ACCEPT,
  ;
  memcpy(&sqe->user_data, &conn_info, sizeof(conn_info));

  while (1) 
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    struct UserData conn_info;
    memcpy(&conn_info, &cqe->user_data, sizeof(conn_info));
    int type = conn_info.type;

    if (cqe->res == -ENOBUFS) 
      fprintf(stderr, "Error: no buffer %d\\n", cqe->res);
      close(listen_fd);
      return EXIT_FAILURE;
     else if (type == ACCEPT) 
      int conn_fd = cqe->res;
      printf("Accept socket %d \\n", conn_fd);
      if (conn_fd >= 0)   // no error
        // Read from client
        sqe = io_uring_get_sqe(&ring);
        io_uring_prep_recv(sqe, conn_fd, buf, BUF_SIZE, 0);

        UserData read_info = 
            .fd = conn_fd,
            .type = READ,
        ;
        memcpy(&sqe->user_data, &read_info, sizeof(read_info));
      

      // Add new client
      sqe = io_uring_get_sqe(&ring);
      io_uring_prep_accept(sqe, listen_fd, (struct sockaddr *)&client_addr,
                           &client_len, 0);
      io_uring_sqe_set_flags(sqe, 0);
      UserData conn_info = 
          .fd = listen_fd,
          .type = ACCEPT,
      ;
      memcpy(&sqe->user_data, &conn_info, sizeof(conn_info));
     else if (type == READ) 
      int n_byte = cqe->res;
      if (cqe->res <= 0)   // connection closed by client
        printf("Close socket %d\\n", conn_info.fd);
        close(conn_info.fd);
       else 
        // Add Write
        printf("Read %d bytes from socket %d\\n", n_byte, conn_info.fd);
        sqe = io_uring_get_sqe(&ring);
        io_uring_prep_send(sqe, conn_info.fd, buf, n_byte, 0);
        write_n(1, buf, n_byte);  // output stdout
        io_uring_sqe_set_flags(sqe, 0);
        UserData write_info = 
            .fd = conn_info.fd,
            .type = WRITE,
        ;
        memcpy(&sqe->user_data, &write_info, sizeof(write_info));
      
     else if (type == WRITE) 
      // Add read
      sqe = io_uring_get_sqe(&ring);
      io_uring_prep_recv(sqe, conn_info.fd, buf, BUF_SIZE, 0);

      UserData read_info = 
          .fd = conn_info.fd,
          .type = READ,
      ;
      memcpy(&sqe->user_data, &read_info, sizeof(read_info));
    

    io_uring_cqe_seen(&ring, cqe);
  

  close(listen_fd);
  return EXIT_SUCCESS;

i/o模型演变

文章目录1BIO2NIO3select4epoll1BIO  在Linux系统早期,由于socket是阻塞的,用户进程需要阻塞等待请求到达,所以需要为每个socket开启一个进程。这时缺点显而易见,开启越多的进程则需要越多的内存,同时CPU的... 查看详情

i/o模型演变

文章目录1BIO2NIO3select4epoll1BIO  在Linux系统早期,由于socket是阻塞的,用户进程需要阻塞等待请求到达,所以需要为每个socket开启一个进程。这时缺点显而易见,开启越多的进程则需要越多的内存,同时CPU的... 查看详情

pile读书笔记_标准i/o

在学习和分析标准I/O库的同时,可以重点与Linux的I/O系统调用进行比较。stdin、stdout和stderr都是FILE类型的文件指针,是由C库静态定义的,直接与文件描述符0、1和2相关联,所以应用程序可以直接使用它们。其中,stdin是不可写的... 查看详情

网络编程笔记linux系统常见的网络编程i/o模型简述

1.典型的I/O模型根据”UnixNetworkProgrammingVolume1”一书第6.2节的说明,Linux系统支持的典型I/O模型包含下面5种:阻塞I/O(blockingI/O)非阻塞I/O(nonblockingI/O)I/O多路复用(I/Omultiplexing,e.g.selec 查看详情

linux操作系统原理—i/o处理流程

目录文章目录目录LinuxI/O接口read()和write()处理流程I/O性能优化机制I/Obuff/cachePageCacheBufferedI/O零拷贝技术LinuxI/O接口LinuxI/O接口可以分为以下几种类型:文件I/O接口:用于对文件进行读写操作的接口,包括open()、read()、write()、close()... 查看详情

linux运维进阶之路

...越摸不着头脑。到最后不了了之。在我看来,这些同学的学习方法和学习过程都是盲目的,没有目标,没有目的性,只是在随便的翻阅一些文档和笔记,没有制定自己的学习计划。以至于基础本身就没学完全,没搞透彻基础知识... 查看详情

linux运维进阶之路

...。到最后不了了之。   在我看来,这些同学的学习方法和学习过程都是盲目的,没有目标,没有目的性,只是在随便的翻阅一些文档和笔记,没有制定自己的学习计划。以至于基础本身就没学完全,没搞透彻基础知识... 查看详情

关于linux性能调优中网络i/o的一些笔记(代码片段)

写在前面和小伙伴分享一些Linux网络优化的笔记,内容很浅,可以用作入门博文内容结合《Linux性能优化》读书笔记整理涉及内容包括常用的优化工具(mii-tool,ethtool,ifconfig,ip,sar,iptraf,netstat)使用Demo及对应的输出解释具体的调优策... 查看详情

关于linux性能调优中网络i/o的一些笔记(代码片段)

写在前面和小伙伴分享一些Linux网络优化的笔记,内容很浅,可以用作入门博文内容结合《Linux性能优化》读书笔记整理涉及内容包括常用的优化工具(mii-tool,ethtool,ifconfig,ip,sar,iptraf,netstat)使用Demo及对应的输出解释具体的调优策... 查看详情

开发成长之路(13)--linux网络服务端编程(通识篇)(代码片段)

文章目录文件I/O进程线程SOCKET网络编程epoll线程池数据库专区文件I/O引用一句经典的话:“UNIX下一切皆文件”。文件是一种抽象机制,它提供了一种方式用来存储信息以及在后面进行读取。在创建一个文件后,它会给... 查看详情

linux学习笔记linux内核6.0.2目录结构(代码片段)

一、linux内核目录arch包含和硬件体系结构相关的代码,每种平台占一个相应的目录,如i386、arm、arm64、powerpc、mips等。Linux内核目前已经支持30种左右的体系结构。在arch目录下,存放的是各个平台以及各个平台的芯片... 查看详情

linux磁盘i/o是怎么工作的(下)

掌握了磁盘I/O的工作原理,怎么才能衡量磁盘的I/O性能?一、磁盘性能指标​说到磁盘性能的衡量标准,必须要提到五个常见指标,也就是我们经常用到的,使用率、饱和度、IOPS、吞吐量以及响应时间等。这五个指标,是衡量... 查看详情

linux开发之libaio源码分析及应用(代码片段)

...at公司基于Linux内核的符号表封装了一套异步I/O(简称aio)的接口,并提供了一些新的接口用来简化上下文配置,开成一个库,命名为libaio。glibc后来有实现一套异步I/O,其实现是用多线程来封装同步的I/O以达到异步... 查看详情

linux学习笔记(第一周)

Linux笔记基本概念UNIX体系结构操作系统(内核):一种软件,控制计算机硬件资源,提供程序运行环境。​内核的接口被称为系统调用(systemcall,图中的阴影部分)。公用函数库构建在系统调用接口之上,应用程序既可使用公用函... 查看详情

linux性能优化实战:linux磁盘i/o是怎么工作的(上)(24)

...态磁盘3、相同磁盘随机I/O比连续I/O慢很多4、最小单位5、接口6、RAID陈列卡7、网路存储二、通用块层1、概念2、第一功能3、第二功能4、I/O调度算法三、I/O栈1、Linux存储系统I/O栈全景图 2、全景图详解1、文件系统层2、通用块... 查看详情

java学习之路--i/o流

 java基础学习总结——流一、JAVA流式输入/输出原理    流是用来读写数据的,java有一个类叫File,它封装的是文件的文件名,只是内存里面的一个对象,真正的文件是在硬盘上的一块空间,在这个文件里面存放着各种各... 查看详情

linux系统编程:基础io上简单复习c语言文件接口|学习系统文件接口|认识文件描述符|linux下,一切皆文件|重定向原理(代码片段)

...inux下,一切皆文件”二、系统文件I/O💦为什么要学习文件系统接口💦测试用例一💦测试用例二💦测试用例三💦标志位💦open的返回值💦重定向 查看详情

linux系统编程:基础io上简单复习c语言文件接口|学习系统文件接口|认识文件描述符|linux下,一切皆文件|重定向原理(代码片段)

...inux下,一切皆文件”二、系统文件I/O💦为什么要学习文件系统接口💦测试用例一💦测试用例二💦测试用例三💦标志位💦open的返回值💦重定向 查看详情