TCP和UDP都是位于传输层的协议,传输层负责将数据从发送端传输到接收端。
端口号(Port)标识了一台主机上正在进行通信的一个具体的应用程序,在TCP/IP协议中,使用源IP、源端口号、目的IP、目的端口号、协议号
的五元组来标识一个通信,即我们只要标注出这五个数据,就能唯一确定一条通信线路。
端口号的范围划分是:
其中知名端口号是为了方便使用,给某些服务器固定分配的端口号:
因此用户在写自己的程序使用端口号的时候,应该避免这些知名端口号。
一个进程可以和多个端口号绑定,即这个进程可以通过这些端口号分别建立连接,实现通信,但是一个端口号只能绑定一个进程,假如绑定多个进程,这个端口号在通信时无法判断应该将数据具体交给哪个进程!
netstat是一个用来查看网络状态的重要工具,如果在Linux服务器的命令行上输入netstat显示没有该命令,则需要通过yum进行安装。
netstat [-nlptua]
命令行参数解释:
前两行是UDP的首部,也可以叫做UDP的报头,报头总共有8个字节。UDP的报头是定长报头,固定为8个字节,因此只要取出前八个字节即可实现报头与有效载荷的分离。
第一行的前16个比特位表示UDP数据报的源端口号,即数据报是从哪里发出的,后16位则表示的是目的端口号,即数据报需要向哪里发送。在内核中是使用哈希的方式通过端口号找到相对应的目标进程。
其中第二行的前16比特位代表UDP长度,表示的是整个数据报(UDP首部加上UDP数据)的最大长度,通过这个UDP长度,我们可以计算出该UDP报文的结尾在哪里。
第二行的后16比特位代表UDP的检验和,即对整个UDP数据报进行检验,在UDP协议下,如果检验和出错,该数据报会直接被丢弃。
我们可以用寄信的例子来理解UDP的传输过程:每封信都有严格的界限(被信封包好),同时在写信或读信的时候都是以整封信为单位,并不能只写半封信或者只读半封信。而且在寄信过程中,信件有可能丢失,但是信件丢失后双方都不知道这件事情。
应用层给UDP交付多长的报文,UDP就会原样发送,既不会对报文进行拆分,也不会将它们合并,假如发送端调用一次sendto方法,发送了100个字节,那么接收端也必须对应只能调用一次recvfrom方法,并一次性接收100字节,而绝不能循环调用10次recvfrom方法,每次接收10字节。
UDP其实并没有真正意义上的发送缓冲区,调用了sendto之后会直接交给内核,由内核将数据传给网络层协议后进行后续的传输工作。
但是UDP是具有接收缓冲区的,只是这个接收缓冲区并不能保证收到的UDP数据报的顺序与发送端发送的顺序一致,因为在网络的发送的过程中,路由选择会影响数据的传输时间;同时这个缓冲区一旦满了,溢出的UDP数据报会被直接丢弃,并且根据UDP的机制,发送端不会再重新发送这些已经溢出的数据。
UDP的socket既可以读也可以写,是全双工的socket
UDP协议中UDP首部有一个16位的UDP长度,也就是说一个UDP数据报能传输的数据最大长度是64KB(包含了UDP首部),64KB在现在的网络环境下是一个非常小的容量,所以如果使用UDP要传输超过64KB的数据,就需要用户在应用层手动将数据分包,使用多个UDP数据报发送,并在接收端再手动将它们拼接起来。
TCP的全称是:传输控制协议(Transmission Control Protocol)
首先需要说明的是4位报头长度,它表示该TCP头部有多少个32位bit位(即有多少个4字节),因此TCP头部的最大长度应该是(2^4-1) * 4 = 15 * 4 = 60个字节,又由示意图可以知道报头占据了20个字节,因此选项字段最多可以有40个字节。选项字段和TCP报头共同组成了TCP的头部,在读取数据时应该先读取20字节,取出4位报头长,如果4位报头长度的值为5,那么说明TCP首部长度只有4*5 = 20字节,刚刚读取的就是所有的头部内容,下面接着的就是数据内容;如果报头长度的值大于5,则后面还要读取一部分选项字段。
通过对比我们发现,TCP数据包格式中并没有像UDP数据报中的16位UDP长度字段来标识数据的整体大小,这与两者的传输方式有关,UDP是面向数据报传输,每次要传输一个完整的数据报,所以必须标出每个数据报的具体大小,但是TCP是面向字节流传输的,可以一个字节一个字节传输,因此TCP可以一个字节一个字节放在缓冲区就行了,应用层想读多少根据应用层来自行决定。通过TCP的16位检验和,可以判断数据完整性,因此TCP不需要在数据格式中添加具体的数据长度了。
因为TCP是安全的传输协议,因此它采用了确认应答机制,这种机制的可靠性体现在:只要收到了对应的应答,就可以认为,之前的数据对方已经收到了!
现在的ACK其实是可以携带数据的,但是我们在这里方便理解,就先认为它并不携带数据,只是对收到数据做出应答。所以在计算机通信中并不存在绝对可靠,因为ACK只能保证ACK前的数据收到了,但是最新的一条通信数据永远无法被ACK。如上图中的ACK2,就没有人可以保证它的可靠性啦。
32位序号:因为TCP是可靠传输,所以发送的信息顺序也要保证接收方接受的顺序一致,但是如果有多条数据,在发送过程中因为路由选择不同,一定是会有先发出的后到达,后发出的先到达的现象。所以就需要给数据进行编号,这样接收方根据编号再将数据进行排列,这样就可以保证数据报文的顺序了,32位序号应该有发送方在发送数据报之前填写完毕。
32位确认序号:我们已经知道了TCP是确认应答机制,所以如果发送方发送了三份报文,分别编号5,6,7。接收方也会返回三条ACK确认信息,我们怎么知道这三条ACK分别确认的是哪条报文呢?这里就要用到确认序号,但是这里有一个重要的点:ACK所携带的确认序号是报文序号 + 1,即确认5号报文的确认序号是6;确认6号报文的确认序号是7;确认7号报文的确认序号是8。原因是确认序号告知的是发送方,前面的报文我已经收到了,下一份报文应该从确认序号开始发送!如果在接收方发送ACK过程中,7,8两个ACK丢失,发送方只收到了9号ACK,发送方还会去发送6,7两份报文吗?不会!9号ACK代表这前面所有的报文接收方都收到了,发送方只需要继续从9号报文开始发送就行了!所以确认序号的存在,允许TCP中出现少量的丢包现象。假如发送方给接收方发送了0到10000号报文,但是只收到了确认序号为5000的ACK,发送方会认为:0~4999号报文接收方一定收到了,但是后面的报文无法保证,所以会从5000开始重新发送。
原因是TCP也是全双工的通信协议,双方在建立通信后,都可以发送消息也都可以接收消息,当作为发送方时,自己要编写序号,同时要留意对方给自己返还的确认序号;但当自己作为接收方时,也要按对方填写的序号将收到的信息排序,并将自己确认收到的信息序号加1作为确认序号返还给对方。
16位窗口大小:发送方的接收缓冲区剩余空间的大小,通过这个可以实现流量控制。虽然TCP不像UDP那样,缓冲区溢出后,会导致丢包,因为TCP会有重传机制,但是传输过去的信息已经耗费了网络资源,再次传输会继续消耗网络资源,这对于网络通信是非常浪费的行为。因此我们设置了窗口大小,发送方可以在报文中告知对方,我的接收缓冲区剩余空间还有多少,这样等到接收方给发送方发送数据时,可以防止对方发送数据过多;而接收方在接收到消息后返还的ACK报文中也可以带上自己接受缓冲区剩余空间的大小,方便发送方控制自己的发送数据量。
TCP协议下,接收端会接收到大量形形色色的报文,怎么该辨别每个报文分别是干什么的呢?因此就需要六个标志位来帮助接收端识别自己接收的报文的身份。比如ACK报文的ACK标志位会被设置为1,当接收端发现之后,就知道这份报文是作为ACK确认发送过来的,就需要着重注意一下它的确认序号。
六个标志位:
这里SYN和ACK以及FIN我们应该都较为熟悉,重点介绍一下PSH、RST和URG三个标志位
PSH(Push):是在提示接收端,接收端的接收缓冲区快要满了。因为经过上面的窗口大小我们知道,接收端返回的ACK中也会标上自己缓冲区的大小,所以发送端是可以知道接收端的剩余缓冲区还有多少,当发送端发现接收端的接收缓冲区大小快要满了,就会在报文上携带PSH标志位,来提醒接收端的应用层程序立即将接收缓冲区中的数据读取完毕,这样不会延误自己继续发送数据的效率。
RST(Reset):即要求重新建立连接,我们举个例子来对它进行解释:
上图中①,②,③表示我们TCP建立连接的三次握手,其中①,②两份数据报丢失我们都不担心,因为此时并没有建立起来连接,所以只需要再次发送SYN重新建立连接就行了,但是如果③号报文,即客户端给服务器的ACK丢包,那问题就严重了。我们上文谈过ACK的确认应答机制并非绝对安全,永远有一条最新的数据是无法被确认的,③号的ACK就是这条数据。当客户端发出③号ACK后,就认为TCP连接已经建立,开始准备发送数据,但是如果发生了极小概率时间,该报文丢失了,那么就会产生一个信息差:客户端认为TCP连接已经建立,但是服务器没有收到客户端的ACK,认为三次握手失败,TCP连接建立失败!
此时客户端给服务器发送数据,服务器发现TCP并没有建立,对端还在给自己发送数据,就会意识到,在TCP连接建立过程中出现了问题,此时服务器就会给客户端发送一个报文,其中携带着RST标志位,告诉客户端,你要重新建立TCP连接!当客户端收到这份报文,就意识到TCP并没有建立成功,此时它就会停止发送数据,继续上面的三次握手,重新建立TCP连接了。同理,如果TCP传输过程中,客户端突然断开连接,并没有进行四次挥手,服务器还认为TCP连接存在,给客户端发了数据,客户端收到后,也会发送一个携带RST标志位的报文返给服务器,但是不同的是服务器看到这份报文后,就会断开连接了,因为建立连接永远是客户端向服务器请求,服务器是不会主动向客户端请求连接的。
URG(urgent):(不重要,已经很少使用)如果我们发送的某些信息,发出后就后悔了,不想让对方收到这些信息,所以需要发出通知让对方不要接收刚才的消息,但是因为TCP的有序性,对方收到不要接收信息的通知一定是晚于接收信息到达对方那里的,所以需要一种打破按序到达的机制。打个比方,我们在医院的挂号处排队,先来的排在前面,但是这时来了一位急诊病人,我们就会让他先去挂号。URG标志位被设置后,说明这份报文中有些数据是需要被优先处理的。即16位紧急指针。16位紧急指针是个偏移量,将起始地址加上偏移量后,就可以指向紧急数据。我们可以找到紧急数据的起始地址,但是它并没有标识结束地址的信息,因此每次的紧急数据只能发送一个字节。
16位检验和:对整个TCP报文进行检验,如果发生错误,直接丢弃该报文,但是TCP有重传机制,我们并不担心报文错误的问题,假如1,2,3,4,5报文中3号报文发生了错误,被TCP丢弃掉了,此时只需要将ACK3传给发送方即可,因为发送方会从ACK携带的序号开始,把后面的报文再发一遍。
至此需要介绍的内容结束了,以上为笔者自己整理的学习笔记,可能其中有部分内容不太正确,也不太完整,还请大家指正