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实际是,在用户态创建一个内核态数据的映射
-
用户态操作映射,实际上是直接操作内核态中拷贝的文件数据(页缓存)
-
内核态的文件数据(页缓存): 内核维护一个叫做页缓存(page cache)的数据结构,它是内存中的一块区域,用来缓存文件的数据。读文件时,内核把文件数据加载到页缓存中,以加快对文件内容的访问速度。页缓存中的数据就代表了文件在内核中的“拷贝”。
-
mmap 和页缓存的关系: 当使用
mmap
在用户态创建一个映射时,操作系统会将文件的一部分或全部数据映射到内存中。用户态的这个映射实际上指向的是内核的页缓存。因此,内核态中的文件数据(页缓存)和mmap
映射到用户态的地址空间,是同一个物理内存区域。
-
-
-
mmap仍需将数据拷贝到socket缓冲区
- 磁盘文件->内核文件缓冲区(页缓存)->(用户空间虚拟地址 ->)用户缓冲区->socket缓冲区->网卡
- 实际上是从用户态拷贝到socket缓冲区
sendfile可以在fd与socket_fd之间直接传输数据
- 磁盘文件->内核文件缓冲区(页缓存)->网卡, 不再经过socket的发送缓冲区