结论先行
- 对于面向连接的工作模式,可以节省系统调用API的次数
- 可以让报文缓冲区分配和管理更具弹性
- 适合的场景将节省一次用户空间内、不必要的拷贝
前言
很久以前就知道网络Socket套接字API存在iovec接口,曾以为它能够实现零拷贝,但是,简单分析后,发现iovec指向的也是用户空间的地址,所以,认为它没什么太大价值,不能够实现用户空间到内核空间的零拷贝,就没有继续深入学习研究下去。
直到最近研究recvmmsg
接口,以及伴随一直思考的的性能优化任务,突然对iovec
产生了理解的电火花,用网络套接字iovec
类接口,可以对适用的场景节省一次不必要的数据拷贝工作!
下面列举一个比较容易理解的例子 😃
不知道IOVEC接口前
简而言之,例子就是组件API对上层提供发送网络报文接口,但对于接口参数传递的报文数据,又必须在入参数据前加上扩展头后,才能发送出去,而且不得不做!
- 不使用
iovec
- 组件API实现将不得不通过构造一个完整的报文数据区,构造新包头,拷贝入参报文数据后,将此新报文数据区发送出去
- 与上层API存在工作协议,在入参报文数据区前能够提供前缀的工作空间,以便于组件API实现可以在不拷贝入参报文缓冲区的情况下,添加了新包头后进行发送
但是,在知道了iovec
类接口后,就不需要这么复杂了!!!
scatter/gather操作
- 流模式,按iovec次序分散和收集
- 报文模式,将一次收发dgram,按iovec次序分散和收集
以前笔记积累过网络操作报文缓冲区预留前后缀空间的最佳实践
具体例子
反例
extern struct T_GTPUHeader;
int sendMessageToPeer(..., const char* data, size_t len, int flags, struct sockaddr *dest_addr, socklen_t addrlen)
{
unsigned char abEncodeBuf[MAX_PKG];
struct T_GTPUHeader* ptGTPUHeader = (T_GTPUHeader*)abEncodeBuf;
// Use the input paras or some global info
fillGTPUHeader(ptGTPUHeader, ...);
// 多一次用户空间内的拷贝
memcpy(abEncodeBuf + sizeof(struct T_GTPUHeader), data, len);
// get sockfd by some mean
return sendto(sockfd, abEncodeBuf, sizeof(struct T_GTPUHeader) + len, flags, dest_addr, addrlen);
}
正例
extern struct T_GTPUHeader;
int sendMessageToPeer_iovec(..., const char* data, size_t len, int flags, struct sockaddr *dest_addr, socklen_t addrlen)
{
struct T_GTPUHeader tGTPUHeader = {};
// Use the input paras or some global info
fillGTPUHeader(&tGTPUHeader, ...);
// Use iovec api
struct iovec vec[2];
vec[0].iov_base = &tGTPUHeader;
vec[0].iov_len = sizeof(tGTPUHeader);
vec[1].iov_base = data;
vec[1].iov_len = len;
struct msghdr msghdr = {};
msghdr.msg_name = dest_addr;
msghdr.msg_namelen = addrlen;
msghdr.msg_iov = &vec; /* scatter/gather array */
msghdr.msg_iovlen = sizeof(vec)/sizeof(vec[0]); /* # elements in msg_iov */
// get sockfd by some mean
return sendmsg(sockfd, &msghdr, flags);
}
图解
发送
接收
iovec可支持更自由的内存管理