对引擎收发包的一次思考
Posted codestack
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对引擎收发包的一次思考相关的知识,希望对你有一定的参考价值。
这是对引擎strace 的结果,可以看到引擎在回复报文的时, 频繁的使用write 系统调用,报文内容可以看到就是一个http响应报文,
正常情况应该是只会调用一次write回复报文,但是实际情况是调用了多次write回复报文,根据http报文的响应头、响应行等依次回复报文。
所以优化方式: 将多次回复合并成一次
情况1:将多次的buf 组装成一个buf,然后write出去,所以需要提前使用memcpy 拷贝报文
那是不是就没有别的办法吗??
还记得 APUE中有一章节高级I/O讲到readv writev吗!! scatter read /gather write
现在来看看socket 收发包的接口和他们的区别:
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);
可以看到 read write实现的功能就是向fd 中读取数据/写入数据,但是可以看到内核和用户态进程没有任何的交互;同时要求fd 是已经建立连接
于是就有了recv send,但是flag在设计上存在一个基本问题:它是按值传递的,而不是值-结果参数,因此它只能从进程向内核传递标志,内核不能向进程传递标志。
send/recv与write/read的作用基本相同,只是多了一个flag参数,当flag参数设置为0时,它们的功能一致了!
为了解决要求fd 是connected状态问题,于是就有了recvfrom sendto等问题,可以给无连接状态fd 收发包,比如udp socket
sendto/recvfrom函数地址指针为NULL且地址长度为0时,其作用于send/recv一致;
sendto用于向socket中写入(读取)数据,如果用在已经建立连接的socket上,需要忽略其地址和地址长度参数,即地址指针设置为NULL,地址长度设置为0;如udp,如果不调用connec建立连接,则需要指定地址参数,如果调用connect建立了连接,则省略地址参数
为了解决读写缓冲区单一问题;于是就有了readv和writev(分散读,集中写),但是有个缺点就是 内核和进程无法交互相关信息
recvmsg sendmsg解决了内核和进程之间传递辅助数据的问题
sendmsg用于向socket文件描述符中写入多个缓冲区的数据,recvmsg用于向多个缓冲区读取socket文件描述符中的数据,发送(接收)前需要构造msghdr消息头
#include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); /*--------------------------------*/ ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
msghdr参数就比较复杂
struct msghdr { void *msg_name; /* protocol address */ socklen_t msg_namelen; /* sieze of protocol address */ struct iovec *msg_iov; /* scatter/gather array */ int msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data ( cmsghdr struct) */ socklen_t msg_conntrollen; /* length of ancillary data */ int msg_flags; /* flags returned by recvmsg() */ }
msg_name和msg_namelen用于套接字未连接的时候(主要是未连接的UDP套接字),用来指定接收来源或者发送目的的地址。两个成员分别是套接字地址及其大小,类似recvfrom和sendto的第二和第三个参数。对于已连接套接字,则可直接将两个参数设置为NULL和0。而对于recvmsg,msg_name是一个值-结果参数,会返回发送端的套接字地址。
msg_iov和msg_iovlen两个成员用于指定数据缓冲区数组,即iovec结构数组。iovec结构如下
#include <sys/uio.h> struct iovec { void *iov_base; /* starting address of buffer */ size_t iov_len; /* size of buffer */ }
其中iov_base就是一个缓冲区元素,事实上也是一个数组,而iov_len则是指定该数据的大小。也就是说,缓冲区是一个二维数组,并且每一维长度不是固定的
所以:为了解决引擎多次系统调用的问题,目前最简单的办法是使用writev 接口发送数据,但是需要考虑writev 发送1000bytes的数据,实际上只发送了500, 剩下500bytes在下次发送问题,
也就是writev的异常逻辑处理,也就每次writev后需要找出那部分数据还没有发送,然后重新监听fd到writeable 然后继续发送,已经发送完毕的数据其缓存可以释放掉
以上是关于对引擎收发包的一次思考的主要内容,如果未能解决你的问题,请参考以下文章