码迷,mamicode.com
首页 > 其他好文 > 详细

对引擎收发包的一次思考

时间:2020-10-29 10:18:25      阅读:23      评论:0      收藏:0      [点我收藏+]

标签:缓冲区   参数设置   根据   连接   buffer   recvfrom   data   code   include   

技术图片

 

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

 

对引擎收发包的一次思考

标签:缓冲区   参数设置   根据   连接   buffer   recvfrom   data   code   include   

原文地址:https://www.cnblogs.com/codestack/p/13892332.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!