计算机网络 | Linux | 解析TCP协议——进阶篇

目录

进阶篇

TCP%E6%8A%A5%E6%96%87%E6%AE%B5%E7%9A%84%E9%A6%96%E9%83%A8%E6%A0%BC%E5%BC%8F-toc" style="margin-left:40px;">一.TCP报文段的首部格式

TCP%E7%9A%84%E8%BF%9E%E6%8E%A5%E5%BB%BA%E7%AB%8B-toc" style="margin-left:40px;">二.TCP的连接建立

1.三次握手

2.连接过程中出现的几种状态

3.为什么是三次握手

4.SYN flood攻击

5.在三次握手过程中,如果服务器一直收不到客户端的ack会发生什么?

6.初始序列号Seq为什么要随机初始化?

TCP%E7%9A%84%E8%BF%9E%E6%8E%A5%E9%87%8A%E6%94%BE-toc" style="margin-left:40px;">三.TCP的连接释放

1.四次挥手

2.为什么建立连接是三次握手,而关闭连接却是四次挥手

3.socket中的close是一次就关闭的吗?

4.如果已经建立了连接, 但是客户端突发故障了怎么办?

5.服务端不调用close会发生什么?

TIME_WAIT%E7%8A%B6%E6%80%81-toc" style="margin-left:40px;">四.TIME_WAIT状态

TIME_WAIT%E7%8A%B6%E6%80%81%E4%BB%A5%E5%8F%8A%E4%B8%BA%E4%BB%80%E4%B9%88%20TIME_WAIT%20%E7%8A%B6%E6%80%81%E8%BF%98%E9%9C%80%E8%A6%81%E7%AD%89%202MSL-toc" style="margin-left:80px;">1.为什么需要TIME_WAIT状态以及为什么 TIME_WAIT 状态还需要等 2MSL

2.为什么是2MSL?

TIME_WAIT%E4%BC%9A%E5%B8%A6%E6%9D%A5%E5%93%AA%E4%BA%9B%E9%97%AE%E9%A2%98%EF%BC%9F-toc" style="margin-left:80px;">3、TIME_WAIT会带来哪些问题?

TIME_WAIT%E7%8A%B6%E6%80%81%E5%A6%82%E4%BD%95%E4%BA%A7%E7%94%9F-toc" style="margin-left:80px;">4.TIME_WAIT状态如何产生

五.connect()、listen()和accept()三者之间的关系

1.connect()函数分析

2.listen()函数分析

3.accept()函数分析

六.拥塞控制

1.什么是拥塞控制

TCP%E6%8B%A5%E5%A1%9E%E6%8E%A7%E5%88%B6%E7%9A%84%E7%AE%97%E6%B3%95-toc" style="margin-left:80px;">2.TCP拥塞控制的算法

3.慢开始和拥塞避免

4.快重传和快恢复

七.粘包问题

TCP%E7%9A%84%E7%B2%98%E5%8C%85%E9%97%AE%E9%A2%98-toc" style="margin-left:80px;">1.TCP的粘包问题

2.UDP的粘包问题


进阶篇

TCP%E6%8A%A5%E6%96%87%E6%AE%B5%E7%9A%84%E9%A6%96%E9%83%A8%E6%A0%BC%E5%BC%8F">一.TCP报文段的首部格式

首部固定部分各字段的意义如下:

  1. 源端口和目的端口各占2个字节,分别写入源端口号和目的端口号。
  2. 序号占4字节。在一个TCP连接中传送的字节流中的每一个字节都按顺序编号。
  3. 确认号占4字节,是期望收到对方下一个报文段的第一个数据字节的序号。
  4. 数据偏移占4位,它指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。这个字段实际上是指出TCP报文段的首部长度。
  5. 保留占6位,保留为今后使用,但目前应置为0。
  6. 紧急URG,当URG=1时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据),而不要按原来的排队顺序来传送。
  7. 确认ACK,仅当ACK=1时确认号字段才有效。当ACK=0时,确认号无效。TCP规定,在连接建立后所有传送的报文段都必须把ACK置1。
  8. 推送PSH很少使用。
  9. 复位RST,当RST=1时,表明TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。
  10. 同步SYN,在连接建立时用来同步序号。
  11. 终止FIN用来释放一个连接。当FN=1时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接。
  12. 窗口占2个字节。窗口大小标志着TCP缓冲区内部剩余空间的大小,起到一个流量控制的作用。如果窗口满了,那么这个时候是不允许数据接收的。后面到达的数据会被丢失。
  13. 校验和占2个字节,这里的校验和由发送端填充,CRC校验。接收端校验数据的时候如果校验不通过,那么认为数据有问题。此处的校验和不仅仅校验TCP首部,还校验数据部分。
  14. 紧急指针占2字节它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据)。因此,紧急指针指出了紧急数据的末尾在报文段中的位置。

TCP%E7%9A%84%E8%BF%9E%E6%8E%A5%E5%BB%BA%E7%AB%8B">二.TCP的连接建立

1.三次握手

TCP建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个TCP报文段,因此我们又将TCP的连接建立过程称为三次握手。

假定主机A运行的是TCP客户程序,而B运行TCP服务器程序。注意,在本例中,A主动打开连接,而B被动打开连接。

开始,A的TCP客户进程也是首先创建传输控制模块TCB,在打算建立TCP连接时,才向B发出连接请求报文段。B的TCP服务器进程先创建传输控制块TCB,准备接受客户进程的连接请求。然后服务器进程就处于 LISTEN(收听)状态,等待客户的连接请求。如有,即作出响应。

  • 第一次握手:客户端A打算建立TCP连接,于是向B发出连接请求报文段。这时请求报文段首部中的同步位SYN=1,同时选择一个初始序号seq=x。这时,TCP客户进程进入 SYN-SENT(同步已发送)状态。
  • 第二次握手:B收到连接请求报文段后,如同意建立连接,则向A发送确认。在确认报文段中应把SYN位和ACK位都置1,确认号是ack=x+1,同时也为自己选择一个初始序号seq=y。这时TCP服务器进程进入 SYN-RCVD(同步收到)状态。
  • 第三次握手:A收到B的确认后,还要向B给出确认。确认报文段的ACK置1,确认号ack=y+1,而自己的序号seq=x+1。这
    时,TCP连接已经建立,A进入 ESTABLISHED(已建立连接)状态,当B收到A的确认后,也进入 ESTABLISHED状态。

通过这样的三次握手,客户端与服务端建立起可靠的双工的连接,开始传送数据。 三次握手的最主要目的是保证连接是双工的,可靠更多的是通过重传机制来保证的。 

2.连接过程中出现的几种状态

  • 半连接状态:发生在TCP三次握手过程中,客户端向服务器发起连接,服务器也进行了回应,但是客户端却不进行第3次握手。
  • 半打开状态:在TCP连接中,如果某一端关闭了连接或者是异常关闭,则该连接处于半打开状态。解决半打开问题:引入心跳机制就可以察觉半打开状态。
  • 半关闭状态:当TCP链接中客户端向服务器发送 FIN 请求关闭,服务端回应ACK之后,并没有立即发送 FIN 给客户端,客户端就处于半关闭状态,此时客户端可以接收服务器发送的数据,但是客户端已经不能再向服务器发送数据。

3.为什么是三次握手

为什么A最后还要发送一次确认呢?这主要是为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误。

例如,现假定出现一种异常情况,即A发出的第一个连接请求报文段在某些网络结点长时间滞留了,以致延误到连接释放以后的某个时间才到达B.本来这是一个早已失效的报文段。但B收到此失效的连接请求报文段后,就误认为是A又发出一次新的连接请求。于是就向A发出确认报文段,同意建立连接。假定不采用三次握手,那么只要B发出确认,新的连接就建立了。由于现在A并没有发出建立连接的请求,因此不会理睬B的确认,也不会向B发送数据。但B却以为新的运输连接已经建立了,并一直等待A发来数据。B的许多资源就这样白白浪费了。

采用三报文握手的办法,可以防止上述现象的发生。例如在刚才的异常情况下,A不会向B的确认发出确认。B由于收不到确认,就知道A并没有要求建立连接。

4.SYN flood攻击

Syn攻击就是攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送syn包(也就是进行第一次握手),服务器回复确认包,并等待客户的确认(进行第二次握手),但由于源地址是不存在的,服务器需要不断的重发直至超时,由于一台服务器可用的TCP连接是有限的,如果恶意攻击方快速连续地发送此类连接请求,则服务器可用TCP连接队列很快将会阻塞,系统资源和可用带宽急剧下降,无法提供正常的网络服务,从而造成拒绝服务。

5.在三次握手过程中,如果服务器一直收不到客户端的ack会发生什么?

服务端会给每个待完成的半连接都设一个定时器,如果超过时间还没有收到客户端的ACK消息,则重新发送一次SYN-ACK消息给客户端,直到重试超过一定次数时才会放弃。这个时候服务器需要分配内核资源维护半连接。

6.初始序列号Seq为什么要随机初始化?

这样做主要是为了保证网络安全,如果不是随机产生初始序列号,黑客将会以很容易的方式获取到你与其他主机之间通信的初始化序列号,并且伪造序列号进行攻击,这已经成为一种很常见的网络攻击手段。

TCP%E7%9A%84%E8%BF%9E%E6%8E%A5%E9%87%8A%E6%94%BE">三.TCP的连接释放

1.四次挥手

  • 第一次挥手:数据传输结束后,通信的双方都可释放连接。现在A和B都处于 ESTABLISHED状态。A的应用进程先向其TCP发出连接释放报文段,并停止再发送数据,主动关闭TCP连接。A把连接释放报文段首部的终止控制位FIN置1,其序号seq=u这时A进入FⅠNWAI-1(终止等待1)状态,等待B的确认。
  • 第二次挥手:B收到连接释放报文段后即发出确认,确认号是ack=u+1,而这个报文段自己的序号是seq=ⅴ。然后B就进入 CLOSEWAT(关闭等待)状态。TCP服务器进程这时应通知高层应用进程,因而从A到B这个方向的连接就释放了,这时的TCP连接处于半关闭状态,即A已经没有数据要发送了,但B若发送数据,A仍要接收。也就是说,从B到A这个方向的连接并未关闭。
  • A收到来自B的确认后,就进入 FIN-WAIT2(终止等待2)状态,等待B发出的连接释放报文段。
  • 第三次挥手:若B已经没有要向A发送的数据,其应用进程就通知TCP释放连接。这时B发出的连接释放报文段必须使FIN=1。现假定B的序号seq=w。B还必须重复上次已发送过的确认号ack=u+1.这时B就进入 LAST-ACK(最后确认)状态,等待A的确认。
  • 第四次挥手:A在收到B的连接释放报文段后,必须对此发出确认。在确认报文段中把ACK置1,确认号ack=w+1,而自己的序号是seq=u+1。然后进入到 TIME-WAIT(时间等待)状态。

请注意,现在TCP连接还没有释放掉。必须经过时间等待计时器设置的时间2MSL后,A才进入到 CLOSED状态。时间MSL叫做最长报文段寿命。B只要收到了A发出的确认,就进入 CLOSED状态。同样,B在撤销相应的传输控制块TCB后,就结束了这次的TCP连接。我们注意到,B结束TCP连接的时间要比A早一些。

2.为什么建立连接是三次握手,而关闭连接却是四次挥手

在三次握手时,服务端可以把 ACK 和 SYN(ACK 起应答作用,而 SYN 起同步作用)放在一个报文里来发送。但是四次挥手时,服务端发送的FIN与ACK是分开发送的。

原因在于:首先FIN信号是由于调用close所以才发送的,而ACK是由内核发送的,所以ACK报文和FIN报文在发送的时间上都是分开的,不一定能同时发送。但是三次握手的时候发送SYN是由内核直接完成的,所以这就可以达到一个同步发送的情况。

3.socket中的close是一次就关闭的吗?

使用close中止一个连接,但它只是减少文件描述符的引用计数,并不直接关闭连接,只有当描述符的引用计数为0时才关闭连接。

4.如果已经建立了连接, 但是客户端突发故障了怎么办?

TCP设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

5.服务端不调用close会发生什么?

如果服务器的代码没有调用close,那么意味着并没有发送FIN结束报文段。那么也就是说,此连接的服务器长期保持在CLOSE_WAIT状态,这会有什么影响?

服务器长期保持在CLOSE_WAIT状态,也就是说分配的文件描述符并没有关闭并归还。那么大量的CLOSE_WAIT存在的话,就会导致一种资源的泄漏,可能到最后就没有可分配的文件描述符了,那么就会使一些客户端无法连接,从而造成不可估量的影响。

TIME_WAIT%E7%8A%B6%E6%80%81">四.TIME_WAIT状态

TIME_WAIT%E7%8A%B6%E6%80%81%E4%BB%A5%E5%8F%8A%E4%B8%BA%E4%BB%80%E4%B9%88%20TIME_WAIT%20%E7%8A%B6%E6%80%81%E8%BF%98%E9%9C%80%E8%A6%81%E7%AD%89%202MSL">1.为什么需要TIME_WAIT状态以及为什么 TIME_WAIT 状态还需要等 2MSL

  1. 第一,为了保证A发送的最后一个ACK报文段能够到达B。这个ACK报文段有可能丢失,因而使处在 LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认。B会超时重传这个FN+ACK报文段,而A就能在2MSL时间内收到这个重传FIN+ACK报文段。接着A重传一次确认,重新启动2MSL计时器。最后,A和B都正常进入到CLOSED状态。如果A在 TIME- WAIT状态不等待一段时间,而是在发送完ACK报文段后立即释放连接,那么就无法收到B重传的FIN+ACK报文段,因而也不会再发送一次确认报文段。这样,B就无法按照正常步骤进入 CLOSED状态。
  2. 第二,防止“已失效的连接请求报文段”出现在本连接中。A在发送完最后一个ACK报文段后,再经过时间2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。

2.为什么是2MSL?

我们知道服务端收到ACK,关闭连接。但是客户端无法知道ACK是否已经到达服务端,于是开始等待?等待什么呢?假如ACK没有到达服务端,服务端会为FIN这个消息超时重传 timeout retransmit ,那如果客户端等待时间足够,又收到FIN消息,说明ACK没有到达服务端,于是再发送ACK,直到在足够的时间内没有收到FIN,说明ACK成功到达。这个等待时间至少是:服务端的timeout + FIN的传输时间,为了保证可靠,采用更加保守的等待时间2MSL。

客户端发出ACK,等待ACK到达对方的超时重传时间 MSL(最大报文生存时间),等待FIN的超时重传,也是MSL,所以如果2MSL时间内没有收到FIN,说明对方安全收到ACK。

TIME_WAIT%E4%BC%9A%E5%B8%A6%E6%9D%A5%E5%93%AA%E4%BA%9B%E9%97%AE%E9%A2%98%EF%BC%9F">3、TIME_WAIT会带来哪些问题?

  • 作为服务器,短时间内关闭了大量的Client连接,就会造成服务器上出现大量的TIME_WAIT连接,占据大量的tuple,严重消耗着服务器的资源。

  • 作为客户端,短时间内大量的短连接,会大量消耗Client机器的端口,毕竟端口只有65535个,端口被耗尽了,后续就无法再发起新的连接了。

TIME_WAIT%E7%8A%B6%E6%80%81%E5%A6%82%E4%BD%95%E4%BA%A7%E7%94%9F">4.TIME_WAIT状态如何产生

主动发起关闭连接(调用close)的一方最后才会出现TIME_WAIT状态。

五.connect()、listen()和accept()三者之间的关系

1.connect()函数分析

对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三次握手,而这个连接的过程是由内核完成,不是这个函数完成的,这个函数的作用仅仅是通知 Linux 内核,让 Linux 内核自动完成 TCP 三次握手连接,最后把连接的结果返回给这个函数的返回值(成功连接为0, 失败为-1)。通常的情况,客户端的 connect() 函数默认会一直阻塞(可以使用fcntl()函数或者ioctl()函数把connect()变为非阻塞),直到三次握手成功或超时失败才返回(正常的情况,这个过程很快完成)。

在流式套接字中,connect()函数的调用会触发操作系统完成一系列客户准备建立连接的过程,该过程主要包括:

  1. 对于一个未绑定的套接字,请求操作系统分配尚未使用的本地端口号,分配本地Internet地址,形成唯一的端点地址与套接字绑定。
  2. 向套接字注册name参数中声明的服务器地址。
  3. 触发协议栈向函数指明的目标地址发送SYN请求,完成TCP的三次握手。在正常情况下这个过程很快,不过,Internet是一种“尽力而为”的网络,客户的初始消息或服务的响应都有可能会丢失。出于这个原因,系统的TCP实现会逐渐增大时间间隔并多次重传握手消息,如果客户TCP在一段时间后还没有接收到来自服务器的响应,它就会超时并放弃。
  4. 等待服务器的响应,由于网络传输时延和服务器处理过程中的延迟,connect()函数要等待协议栈的操作,可能不能立刻返回。在等待服务器响应的过程中,工作在阻塞模式下的套接字会一直等待,直到返回连接建立成功与否;而工作在非阻塞模式下的套接字,无论连接是否建立好都会立即返回。从以上分析来看,在流式套接字的编程过程中,connect()函数成功返回意味着已确认服务器是存在的,且从客户到达服务器的路径是可达的。注意,在数据报套接字编程中,connect()函数也可以被调用,但是调用后的结果与流式套接字并不相同。

2.listen()函数分析

对于服务器,它是被动连接的。这里需要注意的是,listen()函数不会阻塞,它主要做的事情为,将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后,listen()函数就结束。

所以,只要 TCP 服务器调用了 listen(),客户端就可以通过connect() 和服务器建立连接,而这个连接的过程是由内核完成。

在被动状态的socket有两个队列,一个是正在进行三次握手的socket队列,一个是完成三次握手的socket队列。在握手完成后会从正在握手队列移到握手完成的队列,此时已经建立连接。

3.accept()函数分析

accept()函数功能是,从连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。TCP 的连接队列满后,会延时连接。

accept()函数的返回值是另一个用于数据传输的套接字,被称为已连接套接字,负责与本次连接的客户通信。区分这两个套接字非常重要,一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命期中一直存在。操作系统为每个由服务器进程接受的客户连接创建一个已连接套接字,当服务器完成对某个给定客户的服务时,相应的已连接套接字就被关闭。accept()函数的调用实际上是一个出队的操作,如果此时没有客户连接请求,则客户连接请求队列为空,在阻塞模式下该函数会持续等待,直到一个连接请求到达。当成功返回时,由addr指向的sockaddr结构中填充了连接另一端客户的地址和端口号,如果调用者对此不关心,可以将参数addr和addrlen设置为NULL。

六.拥塞控制

1.什么是拥塞控制

在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫做拥塞。所谓拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。

TCP连接中,两个端点只要迟迟不能收到对方的确认信息,就猜想在当前网络中的某处很可能发生了拥塞,但这时却无法知道拥塞到底发生在网络的何处,也无法知道发生拥塞的具体原因。因此才有了TCP拥塞控制

TCP%E6%8B%A5%E5%A1%9E%E6%8E%A7%E5%88%B6%E7%9A%84%E7%AE%97%E6%B3%95">2.TCP拥塞控制的算法

TCP进行拥塞控制的算法有四种

  1. 慢开始
  2. 拥塞避免
  3. 快重传
  4. 快恢复

3.慢开始和拥塞避免

发送方维持一个叫做拥塞窗口的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口。发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就可以再增大些,以便把更多的分组发送出去,这样就可以提高网络的利用率。但只要网络出现拥塞或有可能出现拥塞,就必须把拥塞窗口减小一些,以减少注入到网络中的分组数,以便缓解网络出现的拥塞。

慢开始算法的思路是这样的:当主机开始发送数据时,由于并不清楚网络的负荷情况,所以如果立即把大量数据字节注入到网络,那么就有可能引起网络发生拥塞。经验证明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。使用慢开始算法后,每经过一个传输轮次,拥塞窗口cwnd就加倍。

拥塞避免算法的思路是让拥塞窗口cwnd缓慢地增大,即每经过一个传输轮次就把发送方的拥塞窗口cwnd加1,而不是像慢开始阶段那样加倍增长。这表明在拥塞避免阶段,拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。但请注意,“拥塞避免”并非完全能够避免了拥塞。“拥塞避免”是说把拥塞窗口控制为按线性规律增长,使网络比较不容易出现拥塞。

4.快重传和快恢复

有时,个别报文段会在网络中丢失,但实际上网络并未发生拥塞。如果发送方迟迟收不到确认,就会产生超时,就会误认为网络发生了拥塞。这就导致发送方错误地启动慢开始,把拥塞窗口cwnd又设置为1,因而降低了传输效率。

采用快重传算法可以让发送方尽早知道发生了个别报文段的丢失。快重传算法首先要求接收方不要等待自己发送数据时才进行捎带确认,而是要立即发送确认,即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认。

如上图所示,接收方收到了M1和M2后都分别及时发出了确认。现假定接收方没有收到M3但却收到了M4本来接收方可以什么都不做。但按照快重传算法,接收方必须立即发送对M2的重复确认,以便让发送方及早知道接收方没有收到报文段M3。发送方接着发送M5和M6.接收方收到后也仍要再次分别发出对M2的重复确认。这样,发送方共收到了接收方的4个对M2的确认,其中后3个
都是重复确认。快重传算法规定,发送方只要一连收到3个重复确认,就知道接收方确实没有收到报文段M3,因而应当立即进行重传(即“快重传”),这样就不会出现超时,发送方也不就会误认为出现了网络拥塞。

此时数据段只是单纯的丢失,而不是因为网络拥塞导致,所以此时不需要拥塞窗口更新为最小值进行慢启动,此时需要设置拥塞窗口大小为:门限值大小+3,更新之后按照拥塞避免算法继续进行。也就是下图的④~⑤的过程。

七.粘包问题

TCP%E7%9A%84%E7%B2%98%E5%8C%85%E9%97%AE%E9%A2%98">1.TCP的粘包问题

TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的。这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的。TCP的发送方无法保证对等方每次接收到的是一个完整的数据包。

粘包问题本质上要在应用层维护消息与消息的边界。解决方案如下:

  • 在接收端接收的时候采用定长的方式接收;

  • 在数据包尾添加一些分隔符;

  • 在数据包头部加上数据包长度;

  • 更复杂的应用层协议。

关于粘包问题的相关博客: https://blog.csdn.net/zhangxinrun/article/details/6721495

2.UDP的粘包问题

为什么udp不会粘包

  • TCP协议是面向流的协议,UDP是面向消息的协议
  • UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据。

UDP具有保护消息边界,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样对于接收端来说就容易进行区分处理了。传输协议把数据当作一条独立的消息在网上传输,接收端只能接收独立的消息。接收端一次只能接收发送端发出的一个数据包,如果一次接受数据的大小小于发送端一次发送的数据大小,就会丢失一部分数据,即使丢失,接受端也不会分两次去接收


http://www.niftyadmin.cn/n/1629319.html

相关文章

linux sticky bit 目录权限 rwt权限

今天看到有个目录的权限是rwxrwxrwt 很惊讶这个t是什么,怎么不是x或者-呢?搜了下发现: 这个t代表是所谓的sticky bit。 sticky bit: 该位可以理解为防删除位. 一个文件是否可以被某用户删除, 主要取决于该文件所属的组是否对该用户具有写权…

Python02 标准输入输出、数据类型、变量、随记数的生成、turtle模块详解

1 标准输出 python3利用 print() 来实现标准输出 def print(self, *args, sep , end\n, fileNone): # known special case of print"""print(value, ..., sep , end\n, filesys.stdout, flushFalse)Prints the values to a stream, or to sys.stdout by default…

Windows下 Win+R 快速运行程序设定方法

基本原理通过WinR键,键入快捷方式名即可,设定步骤如下: 1、 首先在本地路径下建立一个文件夹存放专门的快捷键,我是放在d盘的path目录下,如下图 其中的快捷键名就是我要在命令窗口运行的程序名称,说明一下…

微信小程序flex布局

Flex布局相对于以前我们经常所用到的布局方式要好的很多,在做微信小程序的时候要既能符合微信小程序的文档开发要求,又能使用不同以往的居中方式并减少css的相关样式声明。先来看看关于flex的一张图:从上面可以看到一些flexbox的相关信息&…

软件工程第一个个人小程序

这个学期刚刚开始,我们有很多专业课,其中就有一门非常重要的科目——软件工程,这次是软件工程第一个个人小程序,是分析一个英文文章各个单词出现的频率,然后把其中频率最高的10个单词输出,我的编程能力不是…

xp下virtualbox安装ubuntu系统共享目录设置

virtualbox安装版本4.1.8 下载地址 http://dl.pconline.com.cn/html_2/1/59/id46462&pn0.html ubuntu使用最新版本的11.10 下载地址: http://www.ubuntu.com/download/ubuntu/download 下载和安装就不用介绍了,直奔主题, 在安装好ubunt…

Window vagrant 安装部署【转】

回想以前,想要安装个虚拟机是多么的麻烦。先要费尽心机找到想要的操作系统镜像文件,然后安装虚拟化软件,按照其提供的GUI界面操作一步步创建,整个过程费时费力。但是,自从使用了Vagrant以后,咱腰不酸了,腿不…

java加载properties文件的几种方式

大致分为两种方式,一是获取文件流,然后通过工具类加载,二是直接通过工具类加载;一下是几种加载方法的具体示例代码: 1 package com;2 3 import java.io.BufferedInputStream;4 import java.io.File;5 import java.io.F…