面向报文(UDP)和面向字节流(TCP)

原文:https://blog.csdn.net/bobozai86/article/details/80530428 

一、简介

        面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率。若太短,会是IP太小。UDP对应用层交下来的报文,即不合并,也不拆分,而是保留这些报文的边界。这就是说,应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。

        面向字节流的话,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。

二、缓冲区

1.发送缓冲区问题:
TCP:每个TCP套接字都有一个发送缓冲区,可以用SO_SNDBUF套接口选项来改变这一缓冲区的大小。当某个应用进程调用write往套接字写数据时,内核从应用进程缓冲区中拷贝所有数据到套接口的发送缓冲区,如果套接口发送缓冲区容不下应用程序的所有数据,或者是应用进程的缓冲区大于套接口的发送缓冲区,或者是套接口的发送缓冲区中有别的数据,应用进程将被挂起。内核将不从write返回。直到应用进程缓冲区中的所有数据都拷贝到套接口发送缓冲区。所以,从写一个 TCP套接口的write调用成功返回仅仅表示我们可以重新使用应用进程缓冲区,它并不是告诉我们对方收到数据。TCP发给对方的数据,对方在收到数据时 必须给矛确认,只有在收到对方的确认时,本方TCP才会把TCP发送缓冲区中的数据删除。

UDP:UDP因为是不可靠连接,不必保存应用进程的数据拷贝,应用进程中的数据在沿协议栈向下传递时,以某种形式拷贝到内核缓冲区,当数据链路层把数据传出后就把内核缓冲区中数据拷贝删除。因此它不需要一个真正意义上的发送缓冲区,但是任何UDP套接字还是有发送缓冲区的,并且我们可以使用SO_SNDBUF套接字选项更改它,不过它仅仅是可写到该套接字的UDP数据包的大小上限。如果一个应用进程写一个大于套接字发送缓冲区大小的数据包,内核将返回该进程一个EMSGSIZE错误。

2.接受缓冲区
TCP:对于TCP来说,套接字接收缓冲区中可用空间的大小限定了TCP通告对端的窗口大小,TCP套接字的接收缓冲区不可能溢出,因为不允许对端发出超过本端所通告窗口大小的数据,这就是TCP的流量控制。如果对端无视窗口大小而发出超过该窗口大小的数据,本端TCP将丢弃它们。

UDP:当收到的数据包装不进套接字接收缓冲区时,该数据包就被丢弃。UDP是没有流量控制的,较快的发送端可以很容易地淹没较慢的接收端,导致接收端的UDP丢弃数据包。

具体参考:

https://www.cnblogs.com/balaamwe/archive/2012/01/12/2320983.html

三、TCP粘包

1、TCP粘包现象

        TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

PS:UDP不存在粘包问题,是由于UDP发送的时候,没有经过Negal算法优化,不会将多个小包合并因此发送出去。另外,在UDP协议的接收端,采用了链式结构来记录每一个达到UDP包,这样接收端应用程序一次recv只能从socket接收缓冲区中读出一个数据包。也就是说,发送端send了几次,接收端必须recv几次(无论recv时执行了多大的缓冲区)。

2、为什么出现粘包现象

(1)发送方原因

  我们知道,TCP默认会使用Nagle算法。而Nagle算法主要做两件事:1)只有上一个分组得到确认,才会发送下一个分组;2)收集多个小分组,在一个确认到来时一起发送。所以,正是Nagle算法造成了发送方有可能造成粘包现象。

(2)接收方原因

  TCP接收到分组时,并不会立刻送至应用层处理,或者说,应用层并不一定会立即处理;实际上,TCP将收到的分组保存至接收缓存里,然后应用程序主动从缓存里读收到的分组。这样一来,如果TCP接收分组的速度大于应用程序读分组的速度,多个包就会被存至缓存,应用程序读时,就会读到多个首尾相接粘到一起的包。

3、如何处理粘包

(1)发送方

对于发送方造成的粘包现象,我们可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭Nagle算法。

(2)接收方

遗憾的是TCP并没有处理接收方粘包现象的机制,我们只能在应用层进行处理。

(3)应用层处理

        应用层的处理简单易行!并且不仅可以解决接收方造成的粘包问题,还能解决发送发造成的粘包问题。解决方法就是循环处理:应用程序在处理从缓存读来的分组时,读完一条数据时,就应该循环读下一条数据,直到所有的数据都被处理;但是如何判断每条数据的长度呢?

两种途径:

1)格式化数据:每条数据有固定的格式(开始符、结束符),这种方法简单易行,但选择开始符和结束符的时候一定要注意每条数据的内部一定不能出现开始符和结束符。

2)发送长度:发送每条数据的时候,将数据的长度一并发送,比如可以选择每条数据的前4位是数据的长度,应用层处理时可以根据长度来判断每条数据的开始和结束。

考虑到大量流量情况下,开始符和结束符不好确定,很难保证不会出现在数据内部,为了做到万无一失,推荐还是采用发送长度的方式比较好。

参考:

https://blog.csdn.net/scythe666/article/details/51996268

你可能感兴趣的:(网络编程)