show me the code

https://gitee.com/shotlink/process-pool

文档中进程池代码bug

注意send

https://gitee.com/shotlink/testaa的都要recv,不然会一直留在socket的接收缓冲区中,若存在epoll_wait监听,会一直将其加入就绪队列,会有逻辑错误。

  • 其实这是默认的水平触发的特性,只要被监视的文件描述符上有待处理的事件,epoll_wait 就会通知应用程序
  • 可以用边缘触发解决,此时只有缓冲区的数据增多的时候,才能使epoll_wait就绪,一直存在但没变化则不会。
    • 在设置EPOLLIN的地方加上EPOLLET,即ev.events = EPOLLIN EPOLLET

粘包问题

  • 原错误示例中,调用了send向client发送文件名,再调用一次send来发送从文件读取的数据,但发送的数据却没有设定的数据边界(因为TCP的接收缓冲区将多个发送的数据序列,视为连续的字节流)
  • 所以当客户端读取的时候, 有可能一次recv,读取了两次发送的内容
  • 并且把文件名+文件内容作为文件名创建一个文件. 这就是说为的粘包问题
  • UDP不会在TCP层/传输层拆分重组,不会粘包,他是在是IP层/网络层进行的,UDP层/传输层只会觉得每一个UDP报文段都是完整的
  • 文档中解决方法就是先传文件长度,client接收该长度后,划定文件名界限来接收文件名,再接收文件内容。

传输大文件可能的问题

  • 半包问题
    • send函数本身只是把要发送的数据, 交给操作系统
    • 所以可能会出现发送截断文件的情况
    • 可将recv函数的flags参数设为MSG_WAITALL解决(等待所有请求的数据才返回)

进度条

void progress_bar(double hundred) {
  int barWidth = 70;
  int pos = barWidth * (hundred / 100.0);
  char bar[barWidth + 1];
  memset(bar, ' ', sizeof(bar)); // 初始化填充空格
  for (int i = 0; i < pos; ++i) {
    bar[i] = '='; // 填充已完成部分
  }
  if (pos < barWidth) {
    bar[pos] = '>'; // 当前进度指示器
  }
  bar[barWidth] = '\0'; // 确保字符串结束符

  printf("\033[0;34m[%s] [%.0f%%]\033[0m\r", bar, hundred); // 输出进度条
  fflush(stdout); // 确保输出实时刷新
}

//调用,需有bytes_recv与file_size:
  off_t curSize = 0;
  off_t lastSize = 0;
  while (1) {
    curSize += bytes_recv;
    double percentage = (double)curSize * 100 / file_size;
    if (percentage - (double)lastSize * 100 / file_size >= 1 ||curSize == file_size) {
      lastSize = curSize;
      if (curSize == file_size) {//完成后保证进度为100
        percentage = 100.0;
      }
      progress_bar(percentage);
    }
  }
  printf("\n");

零拷贝

常规情况下

  • 服务端读文件到内核态
  • 再把内核态的文件数据拷到用户态
  • 再从用户态拷回内核态,让系统决定发送

零拷贝则是直接从内核态发出,不用来回拷贝。

mmap 并非零拷贝

  • mmap实际是,在用户态创建一个内核态数据的映射

    • 用户态操作映射,实际上是直接操作内核态中拷贝的文件数据(页缓存)

      1. 内核态的文件数据(页缓存): 内核维护一个叫做页缓存(page cache)的数据结构,它是内存中的一块区域,用来缓存文件的数据。读文件时,内核把文件数据加载到页缓存中,以加快对文件内容的访问速度。页缓存中的数据就代表了文件在内核中的“拷贝”。

      2. mmap 和页缓存的关系: 当使用 mmap 在用户态创建一个映射时,操作系统会将文件的一部分或全部数据映射到内存中。用户态的这个映射实际上指向的是内核的页缓存。因此,内核态中的文件数据(页缓存)mmap 映射到用户态的地址空间,是同一个物理内存区域

  • mmap仍需将数据拷贝到socket缓冲区

    • 磁盘文件->内核文件缓冲区(页缓存)->(用户空间虚拟地址 ->)用户缓冲区->socket缓冲区->网卡
    • 实际上是从用户态拷贝到socket缓冲区

sendfile可以在fd与socket_fd之间直接传输数据

  • 磁盘文件->内核文件缓冲区(页缓存)->网卡, 不再经过socket的发送缓冲区

更新时间: