关于使用writev()/readv()替代write()/read()传输及获取文件大小的咨询
先给你拆解成两个问题逐一解答,帮你理清思路:
一、客户端:write() vs writev(),以及你写的writev写法问题
首先得敲个重点:你写的writev(sockfd, buffer, strlen(buffer))完全不对!writev()的函数签名和write()差异很大,它的第二个参数是一个struct iovec数组,每个元素对应一个要发送的缓冲区的地址和长度;第三个参数是这个数组里的元素个数,可不是单个缓冲区的长度哦。
那什么时候该用writev?它是为分散写场景设计的:比如你手里有好几个不连续的缓冲区(比如一个消息头、一段正文、一个结尾标记),想一次性按顺序把它们发出去,又不想先把这些内容拷贝到一块连续内存里,这时候用writev能省掉内存拷贝的开销,效率更高。如果你的数据本来就是一块连续的buffer,那用write()就足够了,简单直接,性能也没差别。
给你贴个writev的正确用法例子:
#include <sys/uio.h> struct iovec iov[2]; char header[] = "File: example.txt\n"; char content[] = "This is the file content."; // 填充第一个缓冲区(消息头) iov[0].iov_base = header; iov[0].iov_len = strlen(header); // 填充第二个缓冲区(文件内容) iov[1].iov_base = content; iov[1].iov_len = strlen(content); // 一次性发送两个缓冲区的内容 writev(sockfd, iov, 2);
二、服务器端:read() vs readv(),以及获取文件大小的问题
先纠正一个误区:不管是read()还是readv(),都没办法直接获取“文件大小”——TCP是流式协议,数据是连续字节流,没有天然的“消息边界”,你没法通过一次read/readv调用就知道客户端发的整个文件有多大。要拿到文件大小,必须让客户端在发送文件内容之前,先把文件的大小作为“头部信息”发过来(比如先传4字节的整数,代表文件总字节数,注意转成网络字节序)。
那readv的作用是什么?和writev对应,它是分散读:把读取到的字节流自动拆分到多个预先分配好的缓冲区里。比如你已经知道客户端会先发4字节的长度,再发文件内容,就可以用readv把前4字节读到一个小缓冲区,后面的内容直接读到一个大缓冲区,不用先读一块内存再手动拆分,省点代码量。
回到你的需求:如果要获取客户端发送的文件大小,正确流程是这样的:
- 客户端先把文件大小转成网络字节序,用write()发送给服务器;
- 服务器用read()读取这4字节,转成主机字节序,得到文件总大小;
- 然后分配足够大的缓冲区(或者循环读取),用read()/readv()读取数据,直到读取到的总字节数等于预先拿到的文件大小。
那服务器要不要改用readv?同样看场景:如果你的读取逻辑需要把数据分到多个独立缓冲区,就用readv;如果只是要读到一块连续内存里,用read()更简单直接,完全够用。
最后总结一下
- 客户端:单块连续数据用
write()就好;只有多块分散数据需要一次性发送时,再考虑writev(),而且一定要注意它的参数格式。 - 服务器端:
read()/readv()都不能直接拿文件大小,必须靠客户端先发长度信息;是否用readv取决于你是否需要分散存储数据,否则用read()更省心。
内容的提问来源于stack exchange,提问作者Ace.McCloud

