UDP,TCP
TCP和UDP作为运输层,用来将我们的主机接受到的报文,向上传递给我们的网络层,这个交付过程是怎么样的,TCP和UDP的报文格式,和其具体的应用和其各自的能够提供的服务有那些?TCP的连接又是怎样维持的的,这些问题接下来将要在本篇博客中要写的。
UDP,TCP在哪里服务
TCP和UDP发挥作用的地方是在我们的主机,也就是链路层接收到数据报之后,UDP和TCP开始发挥作用,根据协议的相应规定,将数据写入到相应的SOCKET缓冲区之中,然后应用层从中获取数据。可以将其想象成我们发送快递,我们每一个用户的快递包裹,被放在一个大盒子里,然后发送出去,这个盒子写着一个大的区域名,也就是我们的IP,到了目的地之后,打开大盒子,然后根据包裹上的姓名给每一个用户。
UDP,TCP报文格式
UDP报文格式,16位的源端口号,16位目的端口号,然后是数据段的长度,和数据段的一个校验和,然后是数据区,头部的大小为8个字节,相比于UDP,TCP的内容增加了很多,头部有20个字节,增加了序列号,确认号,一些用来做确认的保留字段,窗口长度,还有16为的紧急指针,但是,紧急指针现在并没有实际的应用,被认为是TCP设计的一个比较失败的地方。其原本的目的是想给一些数据报进行一个优先级的区分,下面逐一解释各字段所能够起到的作用。
大家可以结合我之前一篇Linux下进程通信的socket编程的来看,这样对于各个字段的具体意义和使用地方可能会更清晰。
报文共有字段
源端口号和目的端口号:用来找到目的地和用来得到响应之后,解包分给相应的socket缓冲区中。
数据长度:数据域中数据的总长度。
校验和:通过采用求和回卷然后取反的方式得到一个值,用来对于数据在传送的过程中,是否发生变化的一个检测和标记。
数据域:用来存放数据
TCP特有字段
序列号和确认号:在通过TCP发送数据的时候,很可能一个报文传送不了这么大的数据,因此需要对这些数据报进行切分成一个个的段,然后分别进行传送,为了确保可靠性传输,因此我们需要保证,我们的每一个分的段都已经到达,因此我们需要对这些段进行一个检测和确认,采用序列号和确认号来实现,如何通过序列号和确认号就实现数据的可靠性传输呢?讲完窗长再来回顾。
接收窗口:通过对于窗口长度的一个设定和控制来实现我们在传输过程中的流量控制。
**TCP首部长度:**TCP的首部的长度由于选项字段的存在使得其是可变化的,其存在的作用是什么呢?为什么要对首部长度记录下来呢?其目的就是在我们对数据解包,从中获取数据的时候要使用的,否则我们不知道我们的数据实际长度是多少。
标志字段:该字段中,通过对于的几个选项的标记来实现来用来在连接建立和拆除的时候,进行一个控制。
TCP连接建立和拆除
通过Socket通信,我们可以看到,服务器端,通过一个listen函数进行监听,监听我们制定的端口,然后轮询对接口进行连接请求监听,当有一个连接请求到达,就建立和它的连接,同时服务端分配出相应的socket同时fork出一个新的进程用来和其进行交互,建立连接之后,之间便可以发送消息了,连接建立的过程中发生了三次的握手,三次握手结束之后,连接建立,相互之间便可以发送消息了。
这个握手过程如下所示
握手开始的时候,客户机发送一个请求,其中SYN标记为1,同时自定义一个序列号,然后服务其接收后,返回以个自己的数据序列,然后返回一个ack数据,对其给予确认,然后,客户机接受到确认后,此时在给服务器端一个确认,同时将SYN标记为0,此时的数据报就可以携带数据过去了,也就是说在从服务器收到一个确认包之后,两者之间的连接已经建立起来了。三次数据传递完成,一个TCP连接就算是建立起来了。两者之间就建立了一个可靠性连接,可以进行数据的传递,同时也能够保证数据传递的完整性和有效性。
四次挥手过程如下所示
客户端发送一个数据报,数据报中的FIN标记为1,发送给服务器端之后,服务器端返回一个ACK,给予确认,然后等待一段时间之后,服务器端发送一个FIN标记为1的数据,这个时候,客户端接收到之后给予一个确认,服务器端收到确认,就会将为连接分配的socket对应的缓冲区清理掉,然后这个时候,客户端则等待一段时间之后,将连接关闭掉。
TCP的可靠性
在网络中数据传输的过程中出现丢失是经常的事情,很难再传递的路径中,进行避免,但是我们可以在端的运输层实现,通过在端中进行丢包重传,数据报出错重传等。具体是一个怎样的机制来实现的呢?
数据准确性
首先要保证数据是对的,通过我们前面说的校验和的方式,当检测到数据出错之后,我们就会给它丢一个NCK的报,我们的发送端接收到这个包之后就会根据将之前的包重新交付到网络层,再发送一次,
数据完整性
当我们在发送数据的时候,可能因为中途的路由阻塞等其它原因发生一些丢包现象,如何解决的这个丢包问题呢?首先从根源上考虑,我要如何确认这个包是丢失了的,需要我进行重传,然后就是我要如何重传。这个要从TCP最开始的数据分发开始了,对于一个大的数据,首先是将其切成一段,然后每一个端进行一个序号的标记,也就是序列号,对于这个序列号在选取上还是有一定问题的,下一个序列号也就是上一个序列号加上其数据的长度,这样来进行传输,当接收端接收到一个数据之后,就根据序列号对其进行一个整理,如果发现这一个没有怎么办呢??这又是一个问题了,首先想到的措施是对于每一个被接受到的数据报给予其一个确认,如果发送端没有接收到确认的话,就认为是丢掉了,然后重传,对的,TCP采取了这种思想,但并不完全这样,一个原因是采用的流水线发送,所以多个包同时到达了,如果每一个再都返回一个,是有点浪费带宽的,因此其采取的是累积确认,当多个到达的时候,如果前面的可以很好的排序,那么只是返回最后一个就好了,就确认为前面已经全部接收到了,如果前面有一个缺失的,那么再有数据包传递过来之后,我们收到的还是开始哪一个已经排好序的,如果确认,其将会不停的返回这个缺失的前面一个段进行确认,当我们的客户端,收到了三次冗余的数据确认之后,就会判定这个数据报已经是丢掉了,然后对其重新发送。还有如果数据报超过了一个时间之后,还没有返回数据,这个时候,我们也认为是发生了丢包,这个时间如何确定呢?这个时间并不是死的,而是根据当前的网络状况进行的一个自适应,动态变化的。现在还是没有讲之前说的序列号设计技巧,现在说一下,如果我们的每一个序列都是从0开始,如果我们的数据比较小,那么后续的数据报的序列号也就会比较小,如果紧跟着又是一个数据报,然后这个数据报也是比较小,同时窗口长度是比较长的,因此这两个数据都在里面了,然后这个时候给了我们一个确认,我们会认为是给哪一个数据进行确认呢??所以对于这个序列号进行设定的时候,都会随机生成一个比较大的数据作为基数,然后在其基础上进行序列号的确认。
TCP流控和拥塞控制
先说流控,为什么要用流控呢?什么时候需要流控?流控,顾名思义,流量控制,当有多个发送端,其发送速度较快的时候,而我们接收端处理能力不够了,因此会有一些包从内存中溢出,然后丢掉,而白白的浪费了带宽,同时也可能会让接收端崩溃掉,因此采取流量控制,如果流进的流量过大的时候,我们就采取某种方式,让其减慢速度,这个问题又是比较难解决的了,如何让发送方知道,我现在马上要被淹死了,回想上面对于TCP报格式的讲解中,其中有一个窗口长度字段,根据这个字段可以确认目的地的情况,从而决定,我要发送多少,如果窗口产犊变成了0,这个时候发送端就开始发送一个字节的数据报,类似于试探,直到窗口变大,然后再发送。‘
拥塞的发生不仅仅是在目的地,很可能目的地的窗口长度还是很长的,但是其在中间的路由中已经堵塞了,首先是对于这个堵塞的判断,我们如何判断这个堵塞发生了,有一个什么样的判定标准存在,当其过了这个标准,我们认为它发生了堵塞,如何根据拥塞情况来进行一个数据发送的适应,来减轻拥塞。这个涉及到慢启动,拥塞避免和快速恢复,慢启动是将开始时的发送窗口的长度设置的比较小,然后我们通过对这个窗口进行慢慢的增加,直到出现了拥塞,然后将其降到一半,然后再开始恢复,直到再次到达拥塞状态。
TCP和UDP的应用举例
UDP的非连接和无可靠性数据的保证,同时给它带来了优势,其可以有效的提升传输速度。因此根据其优势和不足,我们就可以很好的找到现实中那些是用了UDP,那些是用了TCP,对数据的可靠性要求不高的,即使丢弃几个包对它来说并没有什么影响,同时比较强调时效的,我们采取UDP,比如视频会议,语音通话,而对于数据传输的可靠性要求比较高的,我们就需要用TCP。
疑问
- socket和端口对应问题?
这个问题之前对我产生了困扰,说socket要进行一个端口的绑定,那我服务器上如果有多个连接,岂不是把端口全部都占用完了,socket确实要绑定端口号,因为进程要占用端口号,通过端口号,能够确定其属于哪一个进程,便于将数据交付给应用层,但是在建立连接的时候,我们的socket绑定的都是同一个端口号,如何区分呢?因为每一个socket对应一个连接,因为每一个连接的远程IP和端口号是不同的,因此通过这做为一个标记,来区分不同的socket。
- 一个浏览器打开了两个相同的页面,有几个缓冲区,建立了几个连接?(如果该浏览器不是多进程的,同时也没有本地缓存,远程连接的服务器也是相同的)
答案是取决于我们的浏览器和服务端的连接设置,Apache设置为了keep-alive,我们就建立一个长久的连接,然后从中取数据,否则的话,我们就需要每次连接开始三次握手,每次断开连接,四次挥手,由此来看,设置为Keep-alive,确实是有很多好处,明显提升访问速度,不考虑使用环境,而去谈某种方式的好坏都是耍流氓,任何一种方法,在不同的应用环境下,都能够展现出其独自的优势和特点,如果不设置keep-alive最明显的优势是可以提高并发量,但是如果设置了keep-alive明显可以提升单一用户的访问速度。