免费的网站有哪些,godaddy 网站上传,麒麟seo软件,常州专业网站建设文章目录 传输层在网络通信中扮演的角色认识TCP协议TCP协议的多种机制确认应答(ACK)机制超时重传机制连接管理机制#x1f53a;滑动窗口流量控制拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常处理 总结 传输层在网络通信中扮演的角色 上图是网络通信中五个模块#xff… 文章目录 传输层在网络通信中扮演的角色认识TCP协议TCP协议的多种机制确认应答(ACK)机制超时重传机制连接管理机制滑动窗口流量控制拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常处理 总结 传输层在网络通信中扮演的角色 上图是网络通信中五个模块今天所谈的传输层介于应用层和网络层之间应用层是程序运行所在的地方而网络层是定位主机从而发送信息的地方因此传输层的基本任务就是完成信息在程序与发送之间的数据缓冲和异常处理。
有了这个大致的功能认知这对于学习传输层的协议非常有帮助。
认识TCP协议
传输层协议其实包括UDP协议和TCP协议这个在之前写的一篇应用层协议文章中提到过应用层的代码实现是要基于传输层协议的。话说回来由于UDP是面向数据报的一种协议因此可靠性不如面向连接和字节流的TCP协议。因此TCP被广泛应用于各种网络领域的传输层所以TCP是本篇博客的主角。
TCP协议作为一种协议理所应当的具有其独特的数据规划下面是抽象出来的数据分化图 关于上图的具体数据的解释部分内容后面着重讲解
图中前5行前20个字节是TCP首部的标准长度选项部分忽略最后的数据部分就是应用层交付或需要接收的数据。也就是说前20个字节是报头下面的数据就是报文有效载荷。
一、端口的意义解决分用 协议需要解决的两个问题 1.如何进行封装和解包 TCP的数据格式已经写死了就如上面的结构图一样只需要按照顺序就可以拿取或者填充数据。 2.如何进行分用 首行的端口号可以解决其中目的端口表明了数据送往应用层的哪一个程序。 二、32位序号和32位确定序号的作用应答部分讲解
三、4位首部长度的意义
报头有标准长度20字节也就意味着报头是变长的具体的大小由报头中的4位首部长度体现。4位bit取值范围为[0000-1111]单位是4字节意味着报头的理论长度范围是[0-60]字节。但是由于最少是20字节因此实际长度范围是[20-60]字节。
四、序号实现消息的应答机制
TCP作为一种可靠的传输手段必须能够应对丢包的问题解决这个问题的首要任务就是要能够检测出来数据丢失问题。32位序号是发送端每次发送数据的标识号唯一表明一条数据。32位确认序号是接收端对于发送端消息的应答序号该序号表明所有该序号之前的信息都被接收。具体细节单独讲解。
五、6位标志位 URG: 紧急指针是否有效 ACK: 确认号是否有效 PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走 RST: 对方要求重新建立连接; 把携带RST标识的称为复位报文段 SYN: 请求建立连接; 把携带SYN标识的称为同步报文段 FIN: 通知对方, 本端要关闭了, 称携带FIN标识的为结束报文段 六、16位窗口大小
表明缓冲区的大小标识着一个接收端的接收能力。
七、16位检验和
发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题。 此处的检验和不光包含TCP首部, 也包含TCP数据部分。
八、16位紧急指针
标识哪部分数据是紧急数据。
TCP协议的多种机制
TCP协议作为一种可靠的传输协议大佬们对其进行了多方面的考量和完善出现了一系列的机制来保证安全、高效、尽量贴近现实需求。这些机制基本全是围绕TCP的报头内容设计的因此接下来会对报头的部分数据进行详细解析。 确认应答(ACK)机制
一个消息有没有被对方接收到在网络中如何确定这点借助应答来解决。只要发送的消息有一条对应的应答被发送回来了就认为消息成功被对方接收到了。这种机制就是确认应答机制但是这种机制总会有一条最新的消息没有应答这是不可避免的只要保证部分百分百应答就可以。
确认应答机制主要是解决丢包的问题毕竟数据在网络中传输不可能保证每次都是成功的甚至即使传输成功也会由于种种原因而没有被客户端正确收到。这时就需要需要检测是哪些数据丢失并对这些数据进行重传操作直到数据传输成功。 其中[1-1000]、[1001-2000]、[2001-3000]这种数据的含义是[序号 - 数据大小序号-1]数据大小的单位是字节对于数据[1-1000]该数据序号是1数据大小为1000-11则确认序号是1001即确认序号序号数据大小。一旦出现丢包问题确认序号将会保持不变数值是最后一个确认收到的数据的确认序号 事实上序号还能保证数据能够一定的顺序被接收端处理。
超时重传机制
主机A作为发送端主机B作为接收端如果主机A给B发送了一条消息在一定时间内收不到应答就认为消息发送失败了。
可能的失败原因有三种
1.主机A发送的消息丢包了。
2.主机B的应答丢包了。
3.网络拥塞消息一直在路上而无法被接收。
无论哪一种情况最终的结果都是主机A作为发送方重发数据直到接收到主机B的应答。但是第二种情况由于是应答丢失实际上主机B已经接收到了信息因此主机A重发数据会导致数据重复因此得依靠序号来实现去重直接丢弃重复的数据。
那么这个超时怎么定义多久算是超时呢
最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”。但是这个时间的长短, 随着网络环境的不同, 是有差异的。如果超时时间设的太长, 会影响整体的重传效率如果超时时间设的太短, 有可能会频繁发送重复的包TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间。 Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍。 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传。 如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增。 累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接。 连接管理机制
在正常情况下, TCP要经过三次握手建立连接, 四次挥手断开连接。这就是著名的三次握手和四次挥手原则。 客户端与服务端建立连接之后连接的属性是由双方共同维护的只要有一方进行了某种改变连接的操作连接属性都会发生变化。
三次握手
服务端 • [CLOSED - LISTEN] 服务器端调用listen后进入LISTEN状态, 等待客户端连接。 • [LISTEN - SYN_RCVD] 一旦监听到连接请求(同步报文段), 就将该连接放入内核等待队列中, 并向客户端发送SYN确认报文。 • [SYN_RCVD - ESTABLISHED] 服务端一旦收到客户端的确认报文, 就进入ESTABLISHED状态, 可以进行读写数据了。 客户端 • [CLOSED - SYN_SENT] 客户端调用connect, 发送同步报文段。 • [SYN_SENT - ESTABLISHED] connect调用成功, 则进入ESTABLISHED状态, 开始读写数据。 四次挥手
服务端 • [ESTABLISHED - CLOSE_WAIT] 当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器返回确认报文段并进CLOSE_WAIT。 • [CLOSE_WAIT - LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据); 当服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)。 • [LAST_ACK - CLOSED] 服务器收到了对FIN的ACK, 彻底关闭连接。 客户端 • [ESTABLISHED - FIN_WAIT_1] 客户端主动调用close时, 向服务器发送结束报文段, 同时进入FIN_WAIT_1。 • [FIN_WAIT_1 - FIN_WAIT_2] 客户端收到服务器对结束报文段的确认, 则进入FIN_WAIT_2, 开始等待服务器的结束报文段。 • [FIN_WAIT_2 - TIME_WAIT] 客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出LAST_ACK。 • [TIME_WAIT - CLOSED] 客户端要等待一个2倍的MSL(Max Segment Life, 报文最大生存时间)的时间, 才会进入CLOSED状态。 为什么是TIME_WAIT的时间是2*MSL?
MSL是TCP报文的最大生存时间, 因此TIME_WAIT持续存在2*MSL的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的)。同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重发一个FIN。 这时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK)。
TIME_WAIT状态引起的服务端bind失败
• 原因
TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待两个MSL的时间后才能回到CLOSED状态。使用CtrlC终止了server, 所以server是主动关闭连接的一方, 在TIME_WAIT期间仍然不能再次监听同样的server端口。MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在Centos7上默认配置的值是60s。
• 但是在某些情况下是不合理的
服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短, 但是每秒都有很大数量的客户端来请求)。这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃, 就需要被服务器端主动清理掉), 就会产生大量TIME_WAIT连接。由于我们的请求量很大, 就可能导致TIME_WAIT的连接数很多, 每个连接都会占用一个通信五元组(源ip,源端口, 目的ip, 目的端口, 协议)。其中服务器的ip和端口和协议是固定的。如果新来的客户端连接的ip和端口号和TIME_WAIT占用的链接重复了, 就会出现问题。
• 解决办法
使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符。
为什么要进行三次握手而不进行两次握手甚至是一次握手
• 一次握手
客户端发送SYN给服务端因为网络延迟的原因客户端没等到连接完成就走了。但服务端接收到了请求就认为连接建立成功了一直等待客户端发送消息白白浪费了服务端的资源。
• 二次握手
客户端发送SYN给服务端因为网络延迟的原因客户端没等到连接完成就走了。服务端接收到了迟来的SYN并发送SYNACK给客户端并认为连接建立成功。但是此时客户端早就走了那么服务端的资源就又白白浪费了。
以上两个情况在一般情况下影响也不是特别大但是一旦有恶意的攻击者故意利用这点漏洞快速地向一个服务端同时发送多次连接请求就会导致服务端在短时间内资源被大量的无效连接资源占用最终导致服务器崩溃。
首先三次握手会避免因为网络延迟问题而建立无效的连接因为最后一次的ACK表明客户端一直在等着服务端的应答。三次握手虽然也没办法避免被攻击的情况发生但是会大大加重攻击的代价因为三次握手有客户端向服务端发送ACK注定会导致客户端也会建立连接资源导致攻击者与被攻击者都承担资源占用的后果。而一次握手和两次握手则不会产生这种效果客户端往往只是发送请求而不真正建立连接资源这是TCP协议的特性不能看作是漏洞。
为什么要进行四次挥手
这里我们得清楚一个概念客户端关闭连接的最开始的潜台词是客户端不再发送任何信息了即关闭接收缓冲区。但这并不意味着服务端就不给客户端发送信息因此想要断开连接的客户端不能想走就走不能直接关闭自己的发送和接收缓冲区必须还得等到服务端完成所有的任务才会允许双方断开连接。
• 最开始的client-server的FIN意味着client不再发送信息。
• 接着server-client的ACK表示server已经知晓了client的状况将server自己的接收缓冲区内的剩余信息全部取出来之后就可以关闭server的接收缓冲区了。
• server-client的FIN表示server也不再给client发送信息了意味着已经没有剩余的事情可以做了可以断开连接了。双方都将自己的IO关闭。
• client-server的ACK表示client已经知晓最后的断开连接通牒了完成断开动作。注意最后的应答不属于IO操作不属于信息的发送
现实生活中形象点的例子恋爱煲电话粥 男子张三和女子王丽谈恋爱正值热恋晚上打电话。[张三client]、[王丽server] 张三和王丽你侬我侬说了半夜的情话最后张三瞌睡了实在是顶不住了。 张三宝宝我瞌睡了咱不聊了睡觉吧 王丽可以啊但是刚刚的话我还没讲完欸你先听我说完。接着王丽将剩余的话讲完张三一直听着不敢直接挂断电话 王丽好啦我讲完了时间确实不早了咱们睡觉吧我也不讲哩。王丽等着张三回应自己 故事到这里就有了分叉点。 1.若是张三直接回应了王丽则有如下情况 张三好嘞睡觉咯 王丽听到后知晓挂电话与分别的最终时刻到了最终双方和和美美地挂断了电话。 2.此时若是张三很长时间没有回答王丽则有如下情况直到张三回应王丽 王丽你睡着了嘛回我一声话啊我好知道挂不挂电话去睡觉啊 知识点补充 listen函数作为服务端的监听函数其中有第二个参数backlog在学习完连接管理机制之后可以做如下解释
当服务端没有调用accept函数时底层所能建立的最多的连接数是有限的连接状态为ESTABLISHED并且与backlog这个参数有关具体最大数量为backlog1超过这个数目建立的连接状态就不是完全连接了连接状态是SYN_RCVD。
这样就会有一个类似队列一样的存在去管理那些没来的及被accept的连接一旦服务器空闲起来了就会从队列中获取连接执行对应的任务。这大大减缓了服务器在满载的时候的处理压力避免了重复建立连接的情况提高了效率。但是队列的长度不宜过长因为这个队列存在的意义就在于缓解压力太长的队列维护起来本身就很浪费资源因此均衡一点的长度是被期望的。
滑动窗口
确认应答策略, 对每一个发送的数据段, 都要给一个ACK确认应答.。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点, 就是性能较差。尤其是数据往返的时间较长的时候。
如果一次性发送多条信息使得他们的等待应答时间重合那么信息发送的效率就会大大提升。但是发送多条信息的前提是接收端得有相应的接收能力对应的就是接收端的接收缓冲区的大小在TCP的报头中通过16位窗口大小体现。因此就在发送端的发送缓冲区中引入了滑动窗口的概念。 发送端发送缓冲区的抽象视角 关于发送缓冲区的各部分数据含义 • 滑动窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。
• 滑动窗口内的信息, 不需要等待任何ACK, 直接发送。
• 收到ACK后, 滑动窗口向后移动, 调整窗口位置依旧认为窗口内的数据无需等待即可发送接着发送尚未被发送的信息; 依次类推……
• 窗口越大, 则网络的吞吐率就越高。
• 在尚未收到ACK之前滑动窗口是不会移动位置的一旦需要重发某条信息并完成信息的重发后窗口会立即进行调整。
流量控制
接收端处理数据的速度是有限的。如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度。 这个机制就叫做流量控制 (Flow Control)。
• 窗口的大小是随着应答报头中的16位窗口大小改变而改变接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端。16位表示的数据范围[0-64]KB但这并不意味着最多只能发送 64 KB大小的数据在选项中可以对窗口大小进行扩充但这一般不是编程猿所负责的了所以不做深入解析了。 • 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;发送端接受到这个窗口之后, 就会减慢自己的发送速度。
• 如果接收端缓冲区满了, 就会将窗口置为0。这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端。
可能存在的情况 拥塞控制
拥塞控制是针对网络阻塞和网络异常这种情况所设计的当网络比较拥堵时如果上来就发送大量的信息时就会恶化拥堵的情况。因此发送端最开始会先进入慢启动阶段发送少量的数据进行一种试探再决定用多大的传输速度。
拥塞窗口在这种情况下被引入进来最开始大小是1。
• 每收到一个ACK应答阻塞窗口就会增大。
• 每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口。
• 拥塞窗口增长速度, 是指数级别的。“慢启动” 只是指初使时慢, 但是增长速度非常快。为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍。此处引入一个叫做慢启动的阈值当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长。
• 当TCP开始启动的时候, 慢启动阈值等于窗口最大值在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1。 少量的丢包, 仅仅是触发超时重传; 大量的丢包, 就认为是网络拥塞。 当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降。 拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案。 延迟应答
如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小。如果接收缓冲区接收了一部分数据但是很快就被接收端给拿走了空间很快就会被腾出来。这意味着接收者的处理能力较强应答晚一点的话应答中所携带的窗口大小就会更大。这意味着接收者向外界表示它的网络吞吐量较大也就使得发送者一次能够发送更多信息适当的延迟有助于挖掘接收者的处理潜能。
那么所有的包都可以延迟应答么? 也不是的。
数量限制: 每隔N个包就应答一次。
时间限制: 超过最大延迟时间就应答一次。
具体的数量和超时时间, 依操作系统不同也有差异。 捎带应答
在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 “一发一收” 的。也就是说一条信息不仅仅只有单纯的应答或是信息任务而是二者兼备。 面向字节流
TCP协议下使用socket函数创建网络套接字实际上就是创建一个文件描述符具有全双工性质。
全双工就是可以对同一个文件描述符同时进行读和写的操作而不发生读写冲突这就意味着读写双方的信息肯定不在一块空间里而是单独的两块空间接收缓冲区和发送缓冲区。 调用write时数据会先写入发送缓冲区中。如果发送的字节数太长, 会被拆分成多个TCP的数据包发出。如果发送的字节数太短, 就会先在缓冲区里等待等到缓冲区长度差不多了或者其他合适的时机发送出去。
接收数据的时候数据也是从网卡驱动程序到达内核的接收缓冲区。然后应用程序可以调用read从接收缓冲区拿数据。
另一方面, TCP的一个连接既有发送缓冲区也有接收缓冲区那么对于这一个连接既可以读数据也可以写数据。
由于缓冲区的存在TCP程序的读和写不需要一一匹配即可以一次性读取或写入完所有的信息也可以分批次多次读取或写入信息。
粘包问题
站在传输层的角度TCP是一个一个报文过来的。 按照序号排好序放在缓冲区中。站在应用层的角度看到的只是一串连续的字节数据。那么应用程序看到了这么一连串的字节数据就不知道从哪个部分开始到哪个部分是一个完整的应用层数据包。上述问题就是粘包问题了很类似于一屉包子挨得近了就容易粘在一起分界线就模糊了。
解决的思路当然就是明确两个包之间的分界线。
对于定长的包保证每次都按固定大小读取即可。
对于变长的包可以在包头的位置约定一个包总长度的字段从而就知道了包的结束位置。对于变长的包还可以在包和包之间使用明确的分隔符(应用层协议是程序猿自己来定的只要保证分隔符不和正文冲突即可)。但是这些都是TCP所要面临的问题对于UDP来说 UDP是一个一个把数据交付给应用层。就有很明确的数据边界。站在应用层的站在应用层的角度使用UDP的时候要么收到完整的UDP报文要么不收。不会出现半个的情况。
TCP异常处理
• 进程关闭
进程关闭的最后会自动关闭文件描述符也就是会发送FIN信号那么四次挥手就会正常完成。
• 机器重启
和进程终止的情况相同
• 异常断电
接收端认为连接还在一旦接收端有写入操作接收端发现连接已经不在了就会进行reset。即使没有写入操作TCP自己也内置了一个保活定时器会定期询问对方是否还在。如果对方不在也会把连接释放。
总结
TCP设计得这么麻烦就是为了保证通信的可靠性并且尽量提高通信的效率。
可靠性校验和、序列号(按序到达)、确认应答、超时重发、连接管理、流量控制、拥塞控制
提高效率滑动窗口、快速重传、延迟应答、捎带应答
那么如果用UDP实现类似于TCP的可靠传输要根据具体的情景要求加入对应的机制。