对引擎收发包的一次思考

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 然后继续发送,已经发送完毕的数据其缓存可以释放掉

 


以上是关于对引擎收发包的一次思考的主要内容,如果未能解决你的问题,请参考以下文章

对扩展openflow协议的一点思考

Eclipse OSGi 包需要另一个包的片段

你不好奇 Linux 是如何收发网络包的?

finalize()和四种引用的一点思考

基于FPAG的UDP数据包的收发

对消费主义的陷阱以及脱离人的动物性本能的一点思考