Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

应用层对UDP进行分段的计算规则? #6

Closed
ghost opened this issue Mar 23, 2016 · 4 comments
Closed

应用层对UDP进行分段的计算规则? #6

ghost opened this issue Mar 23, 2016 · 4 comments

Comments

@ghost
Copy link

ghost commented Mar 23, 2016

@xtaci 您好,我看了下Write函数的代码,对分段的处理有点疑问:

func (s *UDPSession) Write(b []byte) (n int, err error) {
    s.mu.Lock()
    defer s.mu.Unlock()
    if s.is_closed {
        return 0, ERR_BROKEN_PIPE
    }

    max := int(s.kcp.mss * 255)
    if s.kcp.snd_wnd < 255 {
        max = int(s.kcp.mss * s.kcp.snd_wnd)
    }
    for {
        if len(b) <= max { // in most cases
            s.kcp.Send(b)
            break
        } else {
            s.kcp.Send(b[:max])
            b = b[max:]
        }
    }
    s.need_update = true
    return
}

我看了下UDP协议头结构体的定义如下:

/*UDP头定义,共8个字节*/

typedef struct _UDP_HEADER 
{
 unsigned short m_usSourPort;       // 源端口号16bit
 unsigned short m_usDestPort;       // 目的端口号16bit
 unsigned short m_usLength;        // 数据包长度16bit
 unsigned short m_usCheckSum;      // 校验和16bit
}__attribute__((packed))UDP_HEADER, *PUDP_HEADER;

其中,数据包长度用2个字节来表示,所以UDP最大载荷为65535。

kcp.mss在

func NewKCP(conv uint32, output Output) *KCP {
    kcp := new(KCP)
    kcp.conv = conv
    kcp.snd_wnd = IKCP_WND_SND
    kcp.rcv_wnd = IKCP_WND_RCV
    kcp.rmt_wnd = IKCP_WND_RCV
    kcp.mtu = IKCP_MTU_DEF
    kcp.mss = kcp.mtu - IKCP_OVERHEAD
    kcp.buffer = make([]byte, (kcp.mtu+IKCP_OVERHEAD)*3)
    kcp.rx_rto = IKCP_RTO_DEF
    kcp.rx_minrto = IKCP_RTO_MIN
    kcp.interval = IKCP_INTERVAL
    kcp.ts_flush = IKCP_INTERVAL
    kcp.ssthresh = IKCP_THRESH_INIT
    kcp.dead_link = IKCP_DEADLINK
    kcp.output = output
    return kcp
}

中看到mss的值=IKCP_MTU_DEF-IKCP_OVERHEAD=1400-24=1376。

从网上看到这样的信息:

当我们发送的UDP数据大于1472的时候会怎样呢?
这也就是说IP数据报大于1500字节,大于MTU.这个时候发送方IP层就需要分片(fragmentation).
把数据报分成若干片,使每一片都小于MTU.而接收方IP层则需要进行数据报的重组.
这样就会多做许多事情,而更严重的是,由于UDP的特性,当某一片数据传送中丢失时,接收方便
无法重组数据报.将导致丢弃整个UDP数据报。
因此,在普通的局域网环境下,我建议将UDP的数据控制在1472字节以下为好.

进行Internet编程时则不同,因为Internet上的路由器可能会将MTU设为不同的值.
如果我们假定MTU为1500来发送数据的,而途经的某个网络的MTU值小于1500字节,那么系统将会使用一系列的机
制来调整MTU值,使数据报能够顺利到达目的地,这样就会做许多不必要的操作.

鉴于Internet上的标准MTU值为576字节,所以我建议在进行Internet的UDP编程时.
最好将UDP的数据长度控件在548字节(576-8-20)以内.

我的疑问:

  1. socket的缓存和TCP/UDP(网上说内核对UDP没有缓存)和窗口个数之间是什么关系?

UDP因为是不可靠连接,不必保存应用进程的数据拷贝,应用进程中的数据在沿协议栈向下传递时,以某种形式拷贝到内核缓冲区,当数据链路层把数据传出后就把内核缓冲区中数据拷贝删除。因此它不需要一个发送缓冲区。写UDP套接口的write返回表示应用程序的数据或数据分片已经进入链路层的输出队列,如果输出队列没有足够的空间存放数据,将返回错误ENOBUFS.

  1. 如果UDP数据报文长度超过MTU,导致在IP层进行分段,而网络丢包率比较高的情况下为什么不应用层对UDP的分段设置成s.kcp.mss 而是乘以窗口数?(觉得跟上个问题有关)
    max := int(s.kcp.mss * 255)
    if s.kcp.snd_wnd < 255 {
        max = int(s.kcp.mss * s.kcp.snd_wnd)
    }

期待您的回复,非常感谢!

@xtaci
Copy link
Owner

xtaci commented Mar 23, 2016

我的疑问:

  1. socket的缓存和TCP/UDP(网上说内核对UDP没有缓存)和窗口个数之间是什么关系?

UDP没有缓存的说法是错误的,man 7 udp有明确的说明:

udp_mem (since Linux 2.6.25)
This is a vector of three integers governing the number of pages allowed for queueing by all UDP sockets.

通过sysctl -a |grep udp, 可以观察到系统的udp缓存分配

窗口是TCP的概念,接收方用于告诉发送方可以一次性发送多少数据,而不等ACK,用于消除RTT带来的等待耗时。

KCP的默认MTU是1400,即单个UDP包最大为1400+8,不是你说的s.kcp.mss乘以窗口数,这里的分隔值只是确保单个数据包大小不超过整个发送缓冲区。

576这个值是个经验值,此文的时间可能比较久远,旧式设备可能有这个限制,但新设备基本都支持1400,你可以观察一下你的家用路由器的MTU默认值。 如果1400这个值真的导致了IP fragment,其实是很容易观察到降速问题的,按需调整也就行了。

@ghost
Copy link
Author

ghost commented Mar 24, 2016

谢谢 @xtaci

[ec2-user@ip-172-31-10-243 ~]$ sudo sysctl -a |grep udp
net.ipv4.udp_mem = 174852   233137  349704    (单位page个数)
net.ipv4.udp_rmem_min = 4096                             (单位字节)
net.ipv4.udp_wmem_min = 4096                            (单位字节)

麻烦帮看下我的理解的对吗?

  1. 设置窗口数主要参考上行和下行带宽,配合平均发送的数据大小,算出窗口值;
  2. 保证窗口值和kcp.mss的乘积接近但不大于udp_mem*PAGE_SIZE的最大值;

关于rmem_min 和 wmen_min设置有个疑问:
设置成udp_mem max / 使用udp的进程数是否是最好的?(但udp_mem max 的设置要注意不能占用全部剩余可用内存)

@xtaci
Copy link
Owner

xtaci commented Mar 24, 2016

麻烦帮看下我的理解的对吗?

  1. 设置窗口数主要参考上行和下行带宽,配合平均发送的数据大小,算出窗口值;

静态窗口控制来说,这个是正确的。

  1. 保证窗口值和kcp.mss的乘积接近但不大于udp_mem*PAGE_SIZE的最大值;

两者没有关系,MTU已经控制了。建议你复习下TCP。

关于rmem_min 和 wmen_min设置有个疑问:
设置成udp_mem max / 使用udp的进程数是否是最好的?(但udp_mem max 的设置要注意不能占用全部剩余可用内存)

udp_mem : 全局控制
udp_rmem_min : per socket控制 ( Each UDP socket is able to use the size for receiving data, even if total pages of UDP sockets exceed udp_mem pressure. )
udp_wmem_min : per socket控制

如果处理的够快,基本不需要调整这些参数,默认系统给的buffer就很大了,你可以计算下。

@ghost
Copy link
Author

ghost commented Mar 24, 2016

谢谢 @xtaci, 我买本tcp协议学习下。

@ghost ghost closed this as completed Mar 24, 2016
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant