zoukankan      html  css  js  c++  java
  • 对引擎收发包的一次思考

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

  • 相关阅读:
    什么是 FutureTask?使用 ExecutorService 启动任务?
    WeakHashMap 是怎么工作的?
    什么是 Executors 框架?
    什么是原子操作?在 Java Concurrency API 中有哪些原 子类(atomic classes)?
    Java 中是如何支持正则表达式操作的?
    JDBC 能否处理 Blob 和 Clob?
    Java 中你怎样唤醒一个阻塞的线程?
    Java Concurrency API 中的 Lock 接口(Lock interface) 是什么?对比同步它有什么优势?
    为什么我们调用 start()方法时会执行 run()方法,为什么 我们不能直接调用 run()方法?
    什么是线程组,为什么在 Java 中不推荐使用?
  • 原文地址:https://www.cnblogs.com/codestack/p/13892332.html
Copyright © 2011-2022 走看看