从字面意思上我们可能会以为Tcp/IP协议就是单纯的TCP、IP协议。然而在很多情况下,它只是利用IP进行通信所必须用到的协议群的统称。
预备知识之传输层和端口号
传输层:
负责数据能够从发送端传输接收端,它向它上面的应用层提供通信服务,属于面向通信部分高层,同时也是用户功能中最低层。
端口号
以包裹为例,邮寄员(IP)根据收件人地址(目的IP地址)向目的地(计算机)投递包裹(ip数据报),包裹到达目的地后由对方(传输层协议)根据包裹信息判断最终的接收人(接收端应用程序),如果快递单上只写了地址和名字,而假如该地址有多个同名同姓的人,那么邮寄员就不知道该交给谁,所以现实中我们是不是还通过追加电话号码区分。而TCP/IP通信中也是这样,需要指定类似于电话号码的端口号进行数据传输。
端口号(port)是传输层协议的内容.
- 端口号是一个32位的整数;
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
- IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用
一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定
源端口号与目的端口号
描述的是数据是谁发的,要发给谁
端口号划分
- 0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的
- 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的
UDP协议
用户数据报协议,不提供复杂的控制机制,利用ip提供无连接的通信服务。
协议端格式
主要特点
- 无连接
发送数据前不需要建立连接,故发送数据结束后也没有连接可释放,因此减少了开销和发送数据之前的时延。
- 尽最大努力交付
不保证可靠交互,因此主机不需要维持复杂的连接状态表
- 面向报文的
发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层,UDP对应用层交下来的报文既不合并也不拆分,保留这些报文的边界。也就是说应用层交给UDP多长的报文,UDP就照样发送。(所以要注意选择合适的报文,避免太长IP层进行分片,而太短IP数据报首部相对长度太大,都会降低IP层效率)
- 无拥塞控制
网络出现拥塞也不会使源主机的发送速率降低(比如视频会议要求源主机以恒定速率发送数据,并允许在网络拥塞时丢失一些数据,但不允许有太大的时延,UDP正好符合要求)
- 首部开销小
首部8字节,比TCP20字节的首部短
- 支持一对一,一对多,多对一,多对多的交互通信
应用场景
我们注意到, UDP协议首部中有一个16位的最大长度. 也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部).然而64K在当今的互联网环境下, 是一个非常小的数字.如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装。
基于UDP的应用层协议:
NFS: 网络文件系统
TFTP: 简单文件传输协议
DHCP: 动态主机配置协议
BOOTP: 启动协议(用于无盘设备启动)
DNS: 域名解析协议
TCP协议
TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制
- 源端口号/目的端口号:表示数据从哪个进程来到哪个进程去
- 32位序号:也叫报文段序号,由于TCP是面向字节流的,故传送的字节流的起始序号必须在建立连接时设置。首部序号字段值指本报文段所发送的第一个字节的序号。(例如一个报文段序号字段值是301,而携带100个字节的数据,这表示本报文段第一个字节序号301,最后一个字节序号是400,如果还有报文段下一个报文段应该从401开始)。
- 确认号(32位):期望收到对方下一个报文段第一个数据字节的序号。若确认号等于n,则表示到序号n-1的所有数据都已正确收到
- TCP报头长度:最大60个字节,最小20字节
- 标志位
紧急URG:,紧急指针是否有效。URG=1,表明紧急指针字段有效,告诉系统此时有紧急数据需要尽快传送(相当于由高优先级的数据)
**ACK:**确认号是否有效。仅当ACK=1时确认字段才有效,TCP规定在建立连接后,所有的传送的报文段都必须把ACK置为1。
PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走。有时两个应用程序进行交互式通信时,有一端想键入一个命令立即收到对方的响应,此时TCp可以使用推送(push)操作,发送方TCP把PSH置1,并立刻创建一个报文段发送出去。接收端就尽快接受,不必等到整个缓存填满后再向上交付
RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段.当RST=1代表TCP连接出现严重差错(主机崩溃或其他),必须释放连接然后再重新建立连接。
SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段.当SYN=1,ACK=0,表示这是一个连接请求报文段若对方同意连接则在响应报文段中将SYN=1,ACK=1.因此SYN=1表示这是一个连接请求或连接接受报文
FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段,FIN=1表示数据发送完毕并要求释放传输连接。
- 窗口:允许对方发送的数据量,动态变化着。
- 16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也
包含TCP数据部分 - 16位紧急指针: 标识哪部分数据是紧急数据;
TCP可靠传输依赖的机制
确认应答(ACK)机制
生活中两个人对话,是否完全理解对方的说的内容要靠双方的反应。比如听到了就回复嗯,这里的嗯相当于返回了一个确认应答ACK.
比如上图屌丝男和女神的对话,说我爱你时一段时间后女神并没有回复他,说明此时数据已丢失,需要重发。如果没有确认应答,那么屌丝男又给女神发了一条消息,女神这回复了他,那么他就以为女神回复的是第一句,沾沾自喜,哈哈哈哈。
通过序列号和确认号提高可靠性
①、正常的数据传输
②、数据包丢失的情况
③、确认应答的丢失
超时重传
在确认应答机制中我们看到主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B;如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发。主机A未收到B发来的确认应答, 也可能是因为ACK丢失了因此主机B会收到很多重复数据. 那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉.这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果.
如何确定超时重传时间?
时间太长影响整体传输,太短有可能频繁发送重复的包。
所以系统基于TCP协议实现,动态计算报文最大生存时间(MSL),超时时间设置为2MSL
**作用:**超过超时时间表示丢包,需要重新发数据。
注意数据不会被反复,无限重发,达到一定次数强制关闭连接
连接管理机制
正常情况下需要3次握手建立连接,4次挥手断开连接。为什么呢,请看下面讲解
建立连接的过程:三次握手
LISTEN:收听状态,等待客户建立连接,
SYN-SEND:同步已发送
SYN-RCVD:同步收到
ESTAB-LISHED:已建立连接
上图也就是TCP三次握手(三报文握手):总体来说就是呼叫,应答,回应。一般来说主动发起连接的叫客户,被动等待建立连接的应用进程叫服务器。假设A端运行的是TCP客户程序,B运行的是TCP服务器程序。最初的TCP进程都处于CLOSED状态。
- 第一步:A向B发送送个数据包并将SYN(用于建立连接时的同步信号)置为一,表示希望建立连接,这个包中的序号假设是X.
- 第二步:B机器收到A机器发过来的数据包后,通过SYN得知这是一个建立连接的请求,于是发送一个响应包,并将SYN,ACK都置为一。(此时A收到SYN=1,ACK=1,代表A知道B同意建立连接)假设这个包中的序号是Y,而确认号必须是X+1,表示收到发过来的SYN,TCP中SYN被当作数据部分的第一个字节
- 第三步:A收到响应包后进行确认,确认包中将ACK置1,并将确认序列号设置为Y+1,表示收到了B的SYN
经过这三部后,两台服务器就建立连接了,
为什么需要三次握手呢?
- 保证两台机器信息对等,确保两台机器都没有什么问题(各自具备收发报服务)。
- 防止请求超时导致脏连接
为什么会出现脏连接?因为网络报文生存时间可能会超过TCP请求超时时间,假如两次握手就可以建立连接,传输数据并释放连接后第一个超时连接请求到达B机器的话,B机器以为是A机器创建连接的新请求,然后确认同一创建连接,因为A的状态不是SYI_SEND,所以直接丢弃了B的确认数据,导致只是B单方面建立了连接。并一直等待A发送数据,B的资源也就浪费了。
为什么不是4次或2次握手?
先看下面场景:
两次:,不能。为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误
四次:可以采取4次甚至N次握手,但是有必要吗?建立连接的时间太长,效果也会大打折扣。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
连接释放的过程:四次挥手
先看下面的场景:假如A,B在玩游戏。
A:B啊,我不想玩了
B:好吧,你不想玩了,我知道了。(此时还只是A不想玩了,即A不会再发送数据,但是B能不能直接关闭尼,很有可能是A发完了自己的数据就准备不玩了,而B还没做完自己的事,所以它还具备发数据的能力,此时处于半关闭状态,此时A可以选择不在接收数据或在接受一次数据,等待B也主动关闭)
B:那好吧A,我也不玩了
A:好的拜拜。
- 1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
- 2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
- 3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
- 4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
- 5)客户端收到服务器的连接释放报文后,必须发出确ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
- 6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
**CLOSED-WAIT状态:**服务器端程序没有调用close方法,导致出现大量的CLOSE-WAIT状态,代表半关闭(男女朋友分手),是一种bug.
第三次数据传输,服务器端发送FIN数据到客户端,客户端处于TIME-WAIT,为什么不能设置为CLOSED?
此时只是单方面的关闭,客户端还要等待服务器发送最后的数据。
为什么TIME-WAIT时间是2MSL?(关于msl头条面试问过:2msl的时间,英文名字,大小是多少?)
MSL(Maximum Segment Lifetime)报文最大生存时间
Windows : MSL = 2 min
linux(Ubuntu, CentOs) : MSL = 60s
Unix : MSL = 30s
返回的是ACK传输时间和服务器重新发送FIN的时间
- MSL是TCP报文的最大生存时间, 因此TIME_WAIT持续存在2MSL的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的);
- 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重发一个FIN. 这时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK)如果直到2MSL,客户端都没有再次收到FIN,那么客户端推断ACK已经被成功接收,则结束TCP连接。
为什么4次挥手
确保数据能够完成传输。
但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
能否五次挥手?能否三次挥手?
可以五次挥手,让用户程序再关闭连接前处理需要的任务如释放资源
不能三次挥手,第二次的ACK是系统内核实现的ACK,而FIN是用户手动关闭的。
滑动窗口
对于确认应答策略一发一收当数据较长时性能太低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了).
- 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值. 上图的窗口大小就是4000个字节(四个段).
- 发送前四个段的时候, 不需要等待任何ACK, 直接发送;
- 收到第一个ACK后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推;
- 操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉;窗口越大, 则网络的吞吐率就越高;
那么如果出现了丢包, 如何进行重传?
- 情况一:、数据包已经抵达, ACK被丢了
这种情况下, 部分ACK丢了并不要紧, 因为可以通过后续的ACK进行确认;
- 情况二: 数据包就直接丢了
- 当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 "我想要的是 1001"一样;
- 如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
- 这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;
这种机制被称为 “高速重发控制”(也叫 “快重传”)
流量控制
接收端通过TCP协议头中”窗口大小“字段,告知发送端发送数据的大小。
目的:接收端能力有限,为了防止接收缓冲区被打满,再接收数据就直接丢弃,接收端主机无法处理。
- 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;
- 窗口大小字段越大, 说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
- 发送端接受到这个窗口之后, 就会减慢自己的发送速度;
- 如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端
- 接收端如何把窗口大小告诉发送端呢? 回忆我们的TCP首部中, 有一个16位窗口字段, 就是存放了窗口大小信息;那么问题来了, 16位数字最大表示65535, 那么TCP窗口最大就是65535字节么?实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是 窗口字段的值左移 M 位
拥塞控制
虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.
因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的.
TCP引入慢启动机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据
像上面这样的拥塞窗口增长速度, 是指数级别的. “慢启动” 只是指初使时慢, 但是增长速度非常快.为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.此处引入一个叫做慢启动的阈值当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长
当TCP开始启动的时候, 慢启动阈值等于窗口最大值;
- 拥塞避免是指把拥塞窗口设置为线性规律增长,使网络较不容易出现拥塞
- 在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1;
- 少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;
- 当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;
- 拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.
- TCP拥塞控制这样的过程, 就好像热恋的感觉
延迟应答机制
如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小.
- 假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
- 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
- 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
- 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M
一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
那么所有的包都可以延迟应答么? 肯定也不是;
数量限制: 每隔N个包就应答一次;
时间限制: 超过最大延迟时间就应答一次; 具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2,超时时间取200ms;
捎带应答
在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 “一发一收” 的. 意味着客户端给服务器说了"How are you", 服务器也会给客户端回一个 “Fine, thank you”;
那么这个时候ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起回给客户端
沾包问题
- 首先要明确, 粘包问题中的 “包” , 是指的应用层的数据包.
- 在TCP的协议头中, 没有如同UDP一样的 “报文长度” 这样的字段, 但是有一个序号这样的字段.
- 站在传输层的角度, TCP是一个一个报文过来的. 按照序号排好序放在缓冲区中.
- 站在应用层的角度, 看到的只是一串连续的字节数据.那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包.
那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界
- 对于定长的包, 保证每次都按固定大小读取即可; 例如上面的Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
- 对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;
- 对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可
UDP不存在沾包问题
对于UDP, 如果还没有上层交付数据, UDP的报文长度仍然在. 同时, UDP是一个一个把数据交付给应用层.就有很明确的数据边界.
站在应用层的站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收. 不会出现"半个"的情况
TCP异常的情况
- 进程终止: 进程终止会释放文件描述符, 仍然可以发送FIN. 和正常关闭没有什么区别.
- 机器重启: 和进程终止的情况相同.
- 机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset. 即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.
- 另外, 应用层的某些协议, 也有一些这样的检测机制. 例如HTTP长连接中, 也会定期检测对方的状态. 例如QQ, 在QQ断线之后, 也会定期尝试重新连接.
TCP总结:
可靠性:
- 校验和
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
提高性能:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
其他:
定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等
基于TCP应用层协议
HTTP
HTTPS
SSH(安全外壳协议)
Telnet( 远程终端协议)
FTP(文件传输协议)
SMTP(可靠且有效的电子邮件传输的协议)
当然, 也包括你自己写TCP程序时自定义的应用层协议;
TCP与UDP对比
○ UDP 是无连接的,即发送数据之前不需要建立连接。
○ UDP 使用尽最大努力交付,即不保证可靠交付,同时也不使用拥塞控制。
○ UDP 是面向报文的。UDP 没有拥塞控制,很适合多媒体通信的要求。
○ UDP 支持一对一、一对多、多对一和多对多的交互通信。
○ UDP 的首部开销小,只有 8 个字节。
○ TCP 是面向连接的运输层协议。
○ 每一条 TCP 连接只能有两个端点(endpoint),每一条 TCP 连接只能是点对点的(一对一)。
○ TCP 提供可靠交付的服务。2017/12/19 OneNote Online
○ TCP 提供全双工通信(允许数据在两个方向上同时传输)。
○ TCP是面向字节流。
○ 首部最低20个字节。
用UDP实现可靠传输(经典面试题)
参考TCP的可靠性机制, 在应用层实现类似的逻辑;
例如:
引入序列号, 保证数据顺序;
引入确认应答, 确保对端收到了数据;
引入超时重传, 如果隔一段时间没有应答, 就重发数据
转载:https://blog.csdn.net/qq_41552331/article/details/105646991