美文网首页TCP
高性能网络编程系列

高性能网络编程系列

作者: YDDMAX_Y | 来源:发表于2018-05-07 10:56 被阅读0次

    本系列转自陶辉大牛的博客。

    高性能网络编程(一)----accept建立连接

    高性能网络编程2----TCP消息的发送

    1. SO_SNDTIMEO
      发送超时时间,可以简单的认为是把用户态数据copy到TCP发送缓冲区的超时时间。
      JVM中该参数就是0。

    3. 高性能网络编程3----TCP消息的接收

    3.1 四种队列

    为了功能区分和减小并发加锁竞争,所以组织了多种队列
    1.1 receive(数据是已去除TCP协议之后的可以直接被copy到用户态的数据)
    1.2 out-of-order
    1.3 prequeue
    1.4 backlog(socket被加锁时放入)

    3.2 prequeue与tcp_low_latency

    当有socket正在睡眠以等待更多的数据时,新到的包根据tcp_low_latency的配置可能到prequeue队列,也可能到receive或者out-of-order队列。
    tcp_low_latency默认为0,即关闭,此时新到的包进入tcp_low_latency

    3.2.1 prequeue

    1. tcp_low_latency打开时
      在TCP中断时需要处理ACK响应等TCP协议,去除TCP头等信息(放入receive队列的需要去除),甚至还可能把数据直接copy到用户态buffer。
      所以,这种模式下用户进程能快速的得到数据,但是软中断的时间长,造成TCP吞吐量下降。
    2. tcp_low_latency关闭时
      与普通机制的主要区别在于,在进程没有收取到足够的数据而睡眠等待时,prequeue机制会将skb放入prequeue队列中再唤醒进程,再由进程对skb进行TCP协议处理,再copy数据;而普通模式下skb会在软中断上下文处理,在放入sk->sk_receive_queue队列中后再唤醒进程,进程被唤醒后只是copy数据。对比普通模式,prequeue机制下使得skb的TCP协议处理延迟,延迟的时间为从skb被放入prequeue队列并唤醒进程开始,到进程被调度到时调用tcp_prequeue_process函数处理skb时截止。对于收数据的进程而言在一次数据接收过程中其实并没有延迟,因为普通模式下进程也会经历睡眠-唤醒的过程。但由于TCP协议处理被延迟,导致ACK的发送延迟,从而使数据发送端的数据发送延迟,最终会使得整个通信过程延迟增大。现在我们知道prequeue机制延迟大的原因了:skb的TCP协议处理不是在软中断中进行,而是推迟到应用进程调用收包系统调用时。

    3.2.2 为什么有用户进程因等待数据睡眠时才有tcp_low_latency机制

    因为有进程在等待,所以可以让新到的包的TCP协议完成由等待的进程完成。

    3.3 一些参数

    1. SO_RCVTIMEO
      JAVA不不支持设置该参数,该参数JVM设的为0
    2. SO_RCVLOWAT
      《UNIX网络编程中》描述该参数用于select/epoll,和本位描述不符。
    3. TP_LOW_LATENCY

    4. 网络编程4--TCP连接的关闭

    4.1 监听句柄的关闭

    半连接直接发RST

    4.2 关闭ESTABLISH状态的连接

    1. 如果还有数据未读取
      发RST
    2. 如果还有待发送的数据
      发送,在最后一个报文加上FIN
    3. so_linger
      so_linger是close(无论socket是否工作在阻塞模式,都是阻塞的)的超时时间,用来尽量保证对方收到了close时发出的消息,即,至少需要对方通过发送ACK且到达本机。

    5. 高性能网络编程5--IO复用与并发编程

    1. select和epoll
      select每次调用都需要把所有欲监控的socket传入内核态
    2. epoll提供的2种玩法ET和LT
      LT是每次满足期待状态的连接,都得在epoll_wait中返回,所以它一视同仁,都在一条水平线上。ET则不然,它倾向更精确的返回连接。在上面的例子中,连接第一次变为可写后,若是程序未向连接上写入任何数据,那么下一次epoll_wait是不会返回这个连接的。ET叫做 边缘触发,就是指,只有连接从一个状态转到另一个状态时,才会触发epoll_wait返回它。可见,ET的编程要复杂不少,至少应用程序要小心的防止epoll_wait的返回的连接出现:可写时未写数据后却期待下一次“可写”、可读时未读尽数据却期待下一次“可读”。

    6. 高性能网络编程6--reactor反应堆与定时器管理

    7. 高性能网络编程7--tcp连接的内存使用

    net.ipv4.tcp_rmem = 8192 87380 16777216  
    net.ipv4.tcp_wmem = 8192 65536 16777216  
    net.ipv4.tcp_mem = 8388608 12582912 16777216  
    net.core.rmem_default = 262144  
    net.core.wmem_default = 262144  
    net.core.rmem_max = 16777216  
    net.core.wmem_max = 16777216  
    

    7.1 net.ipv4.tcp_adv_win_scale = 2

    读取缓冲包含两部分:

    1. 于应用程序的延时报文读取
    2. 接收窗口

    tcp_adv_win_scale意味着,将要拿出1/(2^tcp_adv_win_scale)缓存出来做应用缓存。即,默认tcp_adv_win_scale配置为2时,就是拿出至少1/4的内存用于应用读缓存,那么,最大的接收滑动窗口的大小只能到达读缓存的3/4。

    7.2 初始的拥塞窗口

    以广为使用的linux2.6.18内核为例,在以太网里,MSS大小为1460,此时初始窗口大小为4倍的MSS。有些网络中,会在TCP的可选头部里,使用12字节作为时间戳使用,这样,有效数据就是MSS再减去12,初始窗口就是(1460-12)4=5792*,这与窗口想表达的含义是一致的,即:我能够处理的有效数据长度。

    在linux3以后的版本中,初始窗口调整到了10个MSS大小,这主要来自于GOOGLE的建议。原因是这样的,接收窗口虽然常以指数方式来快速增加窗口大小(拥塞阀值以下是指数增长的,阀值以上进入拥塞避免阶段则为线性增长,而且,拥塞阀值自身在收到128以上数据报文时也有机会快速增加),若是传输视频这样的大数据,那么随着窗口增加到(接近)最大读缓存后,就会“开足马力”传输数据,但若是通常都是几十KB的网页,那么过小的初始窗口还没有增加到合适的窗口时,连接就结束了。这样相比较大的初始窗口,就使得用户需要更多的时间(RTT)才能传输完数据,体验不好。

    7.3 接收窗口应该设置多大?

    image.png

    所以:接收buffer大小=BDP*4/3

    7.4 内存

    7.4.1 TCP缓存上限自动调整策略关闭

    1. SO_SNDBUF和SO_RCVBUF
      应用程序可以为某个连接设置的参数,分别代表写缓冲和读缓冲的最大值。
      在内核中会把这个值翻一倍再作为写缓存上限使用。
    2. net.core.wmem_max和net.core.rmem_max
      操作系统级别的定义的参数。当SO_SNDBUF和SO_RCVBUF大于系统级的参数时,以系统级的为准。
      在内核中也会把这个值翻一倍再作为写缓存上限使用。
    3. net.core.rmem_default和net.core.wmem_default
      定义了读写缓冲区大小的默认值

    7.4.2 TCP缓存上限自动调整策略

    在并发连接比较少时,把缓存限制放大一些,让每一个TCP连接开足马力工作;当并发连接很多时,此时系统内存资源不足,那么就把缓存限制缩小一些,使每一个TCP连接的缓存尽量的小一些,以容纳更多的连接。
    net.ipv4.tcp_moderate_rcvbuf = 1
    默认tcp_moderate_rcvbuf配置为1,表示打开了TCP内存自动调整功能。若配置为0,这个功能将不会生效(慎用)。

    相关文章

      网友评论

        本文标题:高性能网络编程系列

        本文链接:https://www.haomeiwen.com/subject/ezjmrftx.html