zoukankan      html  css  js  c++  java
  • TCP/UDP丢包

    一、丢包
    这个丢包不是网卡级别的丢包,在每个网卡中也会显示丢失的包的数据。这个一般是由于网卡在中断处理中需要通过skbuff来存储新来的包。此时是直接通过内存管理接口申请结构,此时这个地方并没有办法做限制,因为此时的中断处理程序并不理解上层的协议,更不用说进程或者是socket这些逻辑概念。所以当网卡收到数据之后就分配一个包结构,此时分配失败就认为是丢掉一个包,计入网卡的报文统计中。
    TCP和UDP是传输层协议,所以它们的丢包一般是主动丢弃,并且它参考了更加详细的控制信息。这里我们最为关注的有两个,对于udp来说,一般是由于一个UDP能够占有的系统资源达到限量;不能让一个服务耗光系统中所有的资源;对于TCP来说,它除了有socket限制之外,对于侦听的套接口,它同样还需要有一个运行多少个连接存在的问题,不可能让一个套接口三次握手过多的连接而用户态不执行accept。
    二、UDP丢包
    UDP丢包主要存在于接收端无法及时处理对方发送的数据,这些数据以报文的形式在系统中暂时存储,但是如果这些未接受的报文太多,操作系统就会将新到来的报文丢掉,从而避免一个套接口对整个系统资源耗光。
    这个逻辑和思路都比较简单,也是因为UDP本身是一个相对比较简单的传输控制协议。这里大致看一下相关代码
    __udp4_lib_rcv--->>>udp_queue_rcv_skb
        if ((rc = sock_queue_rcv_skb(sk,skb)) < 0) {
            /* Note that an ENOMEM error is charged twice */
            if (rc == -ENOMEM)
                UDP_INC_STATS_BH(UDP_MIB_RCVBUFERRORS, up->pcflag);
            goto drop;
        }
    而接收函数中处理为
    int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
    {
        int err = 0;
        int skb_len;

        /* Cast skb->rcvbuf to unsigned... It's pointless, but reduces
           number of warnings when compiling with -W --ANK
         */
        if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >=
            (unsigned)sk->sk_rcvbuf) {
            err = -ENOMEM;
            goto out;
        }
    这里如果达到一个套接口的限量,则返回错误,上层记录到UDP丢包状态中,这个状态可以通过/proc/net/snmp文件查看,例如我的系统
    Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors
    Udp: 672 0 0 670 0 0
    这个功能在2.6.17--2.6.21之间的一个版本添加,小于2.6.17的版本一定没有。
    三、TCP丢包
    1、三次握手时丢包
    这个主要是由用户的listen的backlog参数决定的一个信息。其中的backlog表示可以有多少个连接完成三次握手而不执行accept,如果大于该值,则三次握手不能完成,这是一个准确值。相对来说还有个大概值,这个值也是根据backlog参数计算得到,只是按照2的幂数取整了,例如backlog为5,该值可能为8.它用来控制一个套接口可以同时最多接收多少个连接请求,这个请求准确的说是第一次握手,这个数值其实是和accept的限量是独立的。极端情况下,以listen参数为5说明,第一次握手可以有8个完成,而三次握手可以有5个。
    ①、listen之backlog参数处理
    inet_listen -->>>sk->sk_max_ack_backlog = backlog;这里的数值是对于完成三次握手而没有被accept的连接的限制。
    inet_csk_listen_start--->>reqsk_queue_alloc
        for (lopt->max_qlen_log = 3;
             (1 << lopt->max_qlen_log) < nr_table_entries;
             lopt->max_qlen_log++);
    该数值限制的是第一次握手的回应数量。
    ②、第一次握手处理
    int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
        /* TW buckets are converted to open requests without
         * limitations, they conserve resources and peer is
         * evidently real one.
         */
        if (inet_csk_reqsk_queue_is_full(sk) && !isn) {这里判断listen中可以完成第一次握手的数量,如果大于限量,丢掉报文
    #ifdef CONFIG_SYN_COOKIES
            if (sysctl_tcp_syncookies) {
                want_cookie = 1;
            } else
    #endif
            goto drop;
        }

        /* Accept backlog is full. If we have already queued enough
         * of warm entries in syn queue, drop request. It is better than
         * clogging syn queue with openreqs with exponentially increasing
         * timeout.
         */
        if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)这里的young表示系统中还没有被重传的sync回应套接口
            goto drop;
    ……
    drop:
        return 0;
    这里的连接丢掉并没有记录任何信息,所以我们并不知道系统拒绝了多少三次握手的第一次请求。
    ③、第三次握手回应时丢包
    struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
                      struct request_sock *req,
                      struct dst_entry *dst)
    {
        struct inet_request_sock *ireq;
        struct inet_sock *newinet;
        struct tcp_sock *newtp;
        struct sock *newsk;
    #ifdef CONFIG_TCP_MD5SIG
        struct tcp_md5sig_key *key;
    #endif

        if (sk_acceptq_is_full(sk))
            goto exit_overflow;
    ……
    exit_overflow:
        NET_INC_STATS_BH(LINUX_MIB_LISTENOVERFLOWS);
    exit:
        NET_INC_STATS_BH(LINUX_MIB_LISTENDROPS);
    此时该信息有记录,可以通过/proc/net/snmp查看该信息。
    2、缓冲区满时丢包
    tcp_data_queue
            if (eaten <= 0) {
    queue_and_out:
                if (eaten < 0 &&
                    (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||  同样是检测了一个套接口的接收缓存数量,超过限量尝试整理出存储空间,如果失败丢弃报文
                     !sk_stream_rmem_schedule(sk, skb))) {
                    if (tcp_prune_queue(sk) < 0 ||
                        !sk_stream_rmem_schedule(sk, skb))
                        goto drop;
                }
                sk_stream_set_owner_r(skb, sk);
                __skb_queue_tail(&sk->sk_receive_queue, skb);
            }
    ……
    drop:
            __kfree_skb(skb);
            return;
    这里并没有记录,所以超过缓冲区的丢包不会记录。但是TCP有自己的流量控制和重传机制,所以丢包并不是问题。
    3、限量的设置
    这些限制可以通过set_sockopt接口来修改。
        {
            .ctl_name    = NET_CORE_WMEM_DEFAULT,
            .procname    = "wmem_default",
            .data        = &sysctl_wmem_default,
            .maxlen        = sizeof(int),
            .mode        = 0644,
            .proc_handler    = &proc_dointvec
        },
        {
            .ctl_name    = NET_CORE_RMEM_DEFAULT,
            .procname    = "rmem_default",
            .data        = &sysctl_rmem_default,
            .maxlen        = sizeof(int),
            .mode        = 0644,
            .proc_handler    = &proc_dointvec
        },
    proc文件系统中也可以修改这些接口
    [tsecer@Harry template]$ cat /proc/sys/net/core/rmem_default 
    114688
    [tsecer@Harry template]$ cat /proc/sys/net/core/wmem_default 
    114688
    [tsecer@Harry template]$

  • 相关阅读:
    JS使用readAsDataURL读取图像文件
    python20个骚操作
    HTML标签的for属性
    进程、线程、协程理解
    mysql 深度解析auto-increment自增列"Duliplicate key"问题
    2020年MySQL数据库面试题总结(50道题含答案解析)
    如何用Redis统计独立用户访问量
    Redis中的布隆过滤器及其应用
    redis系列教程以及面试题
    大厂面试爱问的「调度算法」,20 张图一举拿下
  • 原文地址:https://www.cnblogs.com/tsecer/p/10487420.html
Copyright © 2011-2022 走看看