Linux传输层TCP,UDP相关内容
传输层协议在TCP/IP协议中用“源IP”“目的IP”“源端口”“目的端口”“协议号”这样的五元组来表示一个通信udp和tcp都是全双工的在接收信息的时候可以同时发送消息TCP协议有连接可靠传输面向字节流但维护成本高因为在通信途中没有确认传输成功之前TCP就需要把数据存在传输层维护起来。在tcp传输层中有MSS这个是在tcp三次握手的时候进行协商的因为数据链路层最多可以发送1500字节的数据而这个数据是有包含tcp报头和网络层报头的所以最大的可传输的数据长度为MSS所以tcp原本是可以直接把数据一次性拷贝过去的但tcp在滑动窗口做了分段就是这个原因。发送数据实际上和把数据刷新到磁盘一模一样在用户层都有缓冲区然后用户层缓冲区把数据交给操作系统内核的缓冲区操作系统决定把数据交给底层的磁盘就叫刷新到磁盘如果交给底层的网卡就叫做网络通信但本质都是拷贝。TCP的文件描述符只有一个但这一个文件描述符既可以读也可以写因为TCP的两端的文件描述符对应的struct file都有两个缓冲区一个用于读一个用于写所以TCP是可以控制发送的等操作系统觉得什么时候可以发送了数据才能发送而udp做不到这一点因为udp没有发送缓冲区可靠性报文格式前20字节属于tcp的标准报头数据偏移首部长度表示报头长度选项长度是多少如果没有选项那么这个值就是20但首部长度只有4位表示范围是0-15但计算的单位是4字节所以表示的范围是0-60字节而标准报头是20字节所以选项最多是40字节然后通过固定长度分离16位窗口大小基于确认应答机制当服务器给客户端确认应答的时候会发送完整报文或者报头服务器会在窗口填上服务器接收缓冲区的剩余大小同理当客户端确认应答的时候也可以使用同样的方法序号为了解决数据报乱序问题可以使用序号保证数据的按序到达http等应用层协议在传输层看来也只是一个大字符串而已放在传输层的一个char数组数组天然就有下标每一次传输层都会发送一批数据就会把这一批数据里数组下标最大的数组下标作为序号填到字段里序号刚开始是随机的这是为了避免新连接接收到老数据确认序号是用于应答的里面填充的是收到报文的序号1表示确认序号之前的数据已经全部收到了下一次被应答方发送就要从确认序号作为开始用于填充序号通过这样的规定表示我们允许应答有少量的丢失比如说确认序号已经收到301了但101和201都没有收到那么我们依旧认为301之前的报文全部收到了关于为什么需要两个序号客户端给服务器发信息服务器也可能给客户端发消息如果服务器使用捎带应答那么服务器既要应答也就是使用确认序号这里的确认序号等于收到的报文的序号1而服务器同时也要发信息也就是使用序号序号就是收到的报文的确认序号所以在这种条件下只使用一个序号就会出现冲突。六个标记位tcp建立连接正常的数据通信和断开连接都需要tcp报文的服务端收到不同类型的tcp报文就会做出不同的动作ACK判断确认序号是否有效当三次握手建立成功后绝大部分报文的ACK默认都置为1的表示当前报头具有应答属性至于有没有数据需要看有没有有效载荷为0就表示当前报文不包含确认信息确认序号就被忽略SYN请求建立连接我们把携带SYN标识的称为同步报文段FIN通知对方连接要关闭了和close很像PSH当置为1的时候提示接收端应用程序立刻从TCP缓冲区把数据读走比如说服务端一直不读数据而接收缓冲区数据越来越多数据就放不下了就可以通过这个位置提醒服务器提取数据RST对方请求重新连接我们把携带RST标识的称为复位报文段有时候会有特殊情况比如说客户端认为连接建立成功了而服务端认为连接没建立成功当客户端请求服务器信息的时候服务器会在报文里添加RST字段请求重连客户端在第三次ACK发送出去后就认为连接建立完成但如果最后一个报文丢失的话服务器就认为连接没建立成功但客户端就认为连接建立好了URG紧急指针是否有效如果是0标识紧急指针无效如果是1则标识这个报文含有紧急数据代表16位紧急指针有效需要高优先级处理紧急指针如果我们需要让一些数据优先那么就设置URG标志位紧急指针里面的数字表示紧急数据在正文中的偏移量这个数据就可以被高优先级处理关于为什么没有指定紧急数据的大小是因为TCP这个协议里紧急数据只能携带一个字节可以用于机器卡顿无法处理外来数据的时候对机器发起询问读取软件状态编号就可以进行修改优化如果我们想发送紧急数据可以使用sendto在flags这个参数传递MSG_OOB参数就可以发送紧急数据要接收紧急数据的话在recv的flags设置MSG_OOB即可流量控制服务器是有接收缓冲区和发送缓冲区的当客户端不断向服务器发送消息但服务器来不及处理的时候会选择让客户端发慢一点从而有时间处理接收缓冲区里的内容这就是流量控制否则会导致大面积丢包。而tcp是可以重传的即使不进行流量控制丢包之后也可以让客户端重新发送报文但这样会消耗大量的网络带宽资源造成低效率的问题。其实流量控制是一种类似于生产者消费者模型的机制用于将生产者消费者的速度匹配当服务器的接收缓冲区快满了的时候TCP报头里的PSH会置为1提醒服务器的应用程序把数据读走如果一直不取走客户端会一直往发送缓冲区写数据直到缓冲区写满了就阻塞了。在三次握手期间就已经协商了双方缓冲区的接收能力等内容第三次握手发送ACK的报头的报文是可以携带数据的如果发送方一直发送数据把接收方的缓冲区打满后接收方发送后来的响应报文说窗口大小为0发送方就不能再发送发送方就开始等待接收方的窗口更新报文但发送方并不会一直等而是在一段时间后发送一个窗口探测报文窗口探测报文并没有携带数据因为我们不知道对方缓冲区是否已经满了无法处理数据只携带报头就不会使用到缓冲区。接收方接收到窗口探测报文就必须响应把窗口大小通过报头带回去。一端通过16位窗口字段把窗口大小告诉另外一端2的16次方就是65535也就是说缓冲区的最大是60KB但tcp报头里还有选项字段包含了一个窗口扩大因子M实际窗口大小是窗口字段的值左移M位确认应答ACK机制tcp是通过确认应答机制保证数据的可靠性的也就是说客户端在给服务器发送消息后服务器确认接收到正确的消息然后就会确认应答而发送消息和确认应答实际上都会携带完整的TCP报文或者报头应答发送TCP数据捎带应答客户端需要收到应答才能确定数据没有丢失所以客户端有一个定时机制当一段时间后没有收到应答客户端就认为数据丢失进行重传。但每一次都只发一条消息应答的话效率太低所以现实中的TCP客户端会并行发送一批消息但这会出现数据报乱序问题这就需要序号的作用超时重传机制主机A把数据发给主机B的时候可能数据还没有到达主机B也可能数据包丢失也有可能是应答丢失了这种情况会让主机B收到重复的报文但报文具有序号可以进行去重无论如何主机A在一定时间没有收到主机B的确认应答之后都会把数据报重发。关于超时的时间间隔如果设置的时间太长出现丢包就会影响我们重传的效率如果设置的时间太短可能会发送大量重复的包所以这个时间间隔必须是动态的而且要和网络状况是强相关的。策略Linux里每次都以500ms为一个单位进行控制如果重发一次收不到应答就等待2500ms再次重发如果依旧收不到应答就等到4500ms再次重发以此类推直到积累到一定的次数之后TCP认为网络或者对端主机出现问题就会强制关闭连接连接管理机制在三次握手的时候会建立连接协商起始序号协商双方的接收缓冲区的大小netstat-ntp#可以用来查看连接情况connect函数只负责发起三次握手实际上就是客户端对服务器发起一次SYN接下来就是一直阻塞直到三次握手完成后connect才返回。accept本身不参与三次握手只有在三次握手后连接建立好accept把连接拿上去用如果底层一直没有建立连接accept就会一直阻塞。四次挥手其实也可以看作是三次挥手因为服务端发送的FINACK是可以合在一起变成捎带应答三次握手也可以看作是四次握手和四次挥手也是一样的道理缺任何一个环节就会导致连接的可靠性出问题。三次握手其实是保证客户端和服务端都有一次发送消息和收到消息的经历首先这是为了验证客户端和服务端的全双工通路是否通畅也就是能不能正常的收和发消息其次如果只有两次握手服务器并不知道自己发出的消息客户端有没有收到也就是不知道服务器发送消息的功能是否通畅所以需要第三次握手收到客户端发送的应答才能确定发送消息通畅。还有一个原因建立连接是有消耗的如果只有一次握手同一个客户端一直发送SYN信号而服务端一直建立连接就会导致浪费如果只有两次握手服务器在接收到客户端第一次发送的SYN信号后就建立一个连接结构体客户端在收到服务器的ACK也会建立一个结构体如果这个时候客户端崩掉了没有第三次ACK的话那么服务器会一直不知道客户端的情况直到很久不使用这个连接才正常关闭这种时间周期太长非常占据资源第三次握手其实可以把这种风险嫁接到客户端上保证服务器的稳定性。如果服务端的全连接队列已经满了最后会导致服务器或者被建立连接的一方处于SYN_RECV状态无法变成ESTABLISH状态但客户端已经处于ESTABLISH状态了说明客户端有发送ACK报头但服务端会把这个报头丢弃这样的连接就称为半连接这种连接不会长时间保存但半连接也是有限制长度的如果半连接的队列满了那么正常的客户端是连接不上服务器的这就是SYN洪水。在半连接情况下如果客户端能够发送消息的话服务器会发送RST报头重新建立连接。当客户端发送FIN报头调用close的时候服务器会接收到然后变为CLOSE_WAIT状态客户端变为FIN_WAIT2状态接下来向服务器发送2号信号服务器在底层会自动完成剩下两次挥手客户端就变为TIME_WAIT状态如果反过来服务器变为TIME_WAIT状态IP和端口依旧被使用中所以有时候我们关闭服务器后立刻重启并且使用相同的端口就会绑定失败我们可以用setsockopt来设置套接字属性让套接字可以复用端口如果服务器还在正常使用那么会重启失败如果发现服务器是处于TIME_WAIT状态就让服务器立即启动客户端没有这个烦恼因为客户端绑定的是随机端口号。TCP协议规定主动断开连接的一方要先处于TIME_WAIT状态等待两个MSL(一个报文在网络中存在的最大的时间)的时间才能到达CLOSED状态一般要持续60-120s的时间这里有两个原因第一是需要让通信双方历史数据得以消散大部分是丢弃历史报文否则再次重连的时候容易让新的连接受到历史数据的影响第二个是如果最后一个ACK报文丢失处在LASK_ACK状态下的另一方会重新发送FIN报头所以不能退出只能一直处于TIME_WAIT状态正常完成四次挥手拥塞控制其他机制是用于两端机器的而拥塞控制机制是用于网络的如果发送数据出了问题不一定是主机出现了问题也有可能是网络出了问题。如果通信的时候出现了少量丢包这是tcp的常规情况如果发送方发现大量数据超时出现了大量的丢包这就是网络拥塞那么就很有可能是网络的问题了硬件设备出问题/数据量太大引起阻塞在这种情况下我们是不能大面积重发丢失的报文的。因为网络是共用的我们让识别出网络拥塞的主机减少发的包的数量而其他主机可以正常通信就能大大降低网络的压力。这里需要引入一个拥塞窗口拥塞窗口的初始大小为1每次收到一个ACK应答拥塞窗口1每次发送数据包的时候把拥塞窗口和接收端主机反馈的窗口大小作比较取较小的值作为滑动窗口的大小这被我们称为慢启动为了不增长那么快我们会引入一个称为慢启动的阈值当拥塞窗口超过这个阈值的时候就不会继续指数级增长刚开始的时候是很慢的而是变成线性增长这样当网络出现阻塞的时候发送少量的报文如果都能够发送就说明网络已经趋于健康我们就应该去关注另外一个窗口的大小了。提高性能滑动窗口已经发送出去但还没受到响应的报文可能在发送方中存在多个这些要被tcp保存在发送缓冲区里我们只需要对缓冲区做划分区域即可。已发送未应答区域是滑动窗口因为滑动窗口的存在我们才能一次性给对方发送一批数据滑动窗口的最大的大小是对方接收缓冲区中剩余空间的大小。如果丢包/未收到ACK的时候发送了100020003000的数据1000和3000的报文都发送成功但2000这个报文没有收到ACK我们并不会把滑动窗口往前移动因为我们有序号和确认序号的概念这里的确认序号最少都到3001代表3001之前的数据我们都收到了下一个数据序号应该从3001开始无论是1000丢失还是3000丢失都是一样的我们都会通过确认序号继续发送接下来的数据这里说明tcp是允许少量的ACK丢失的。但数据并不会丢失因为我们有一种快重传策略确认序号的定义是序号之前的报文都收到了能够保证我们收到的是丢的序号最小的报文假设1000收到了但2000的数据丢失那么确认序号一直都是1001当发送方收到了3次同样的确认序号后就会重新发送丢的包。但快重传是有条件的也就是说在发送数据的末期的时候需要发送的数据很少可能很难触发三次确认序号相同的条件所以我们依旧需要超时重传。滑动窗口不会向左移动只会不动或者向右移动窗口可能扩大可能缩小可能为0也可能不变start就是确认序号end就是确认序号窗口大小如果数据没有那么多那end就是数据尾只有窗口为0双方才会探测。捎带应答在接收方向发送方发送数据时捎带着确认应答告知发送方接收到的数据。这种方式可以减少数据传输中的往返次数从而提高整体的传输效率。例如在TCP协议中捎带应答与延迟应答结合使用能够有效降低通信成本。延迟应答如果我们需要发送的效率提高可以让接收方给发送方通告一个更大的窗口一次发送越多的数据发送的效率越高所以接收方可以晚一些应答因为这样上层就有足够的时间把缓冲区数据取走就能腾出更大的缓冲区空间。面向字节流由于缓冲区的存在TCP的读和写不需要一一匹配对方发一百个字节的数据接收方可以读一百次每次读一个字节也可以一次性读100个字节相应的发送方也可以一次性发一百个字节也可以一个字节发一百次内核只认识有多少个字节而不会对接收到的报文做区分分析报文的任务交给用户层去做。粘报问题因为接收方会把接收缓冲区所有的数据一次性读到用户层导致发送方发送的多个数据包在接收方接收时被粘合在一起用户层没有对接收到的数据进行处理导致无法正确区分每个数据包的边界。如果想要解决粘报问题可以在用户层定制协议规定定长报文或者使用特殊字符来明确报文和报文的边界或者使用定长报头自描述字段或者使用自描述字段特殊字符异常进程终止连接是和文件相关的文件的生命周期是随进程的所以连接的生命周期也是随进程的而在操作系统层面上进程正常终止和异常终止是没区别的所以连接会正常四次挥手正常断开连接。机器重启机器关机之前先要做的就是杀掉所有的进程所以其实和上一个进程终止一样正常断开连接正常释放资源机器掉电/网线断开假设客户端网线断开客户端就无法对另外一端发送消息客户端重新联网后与服务器重新建立连接对于服务器来说连接还是旧的而客户端的连接是新的这就是连接认知不一致的问题如果客户端一直没有重新连接服务器发送出去的数据就不会有应答时间一长服务器就会把连接断开。UDP协议无连接不可靠传输面向数据报UDP报头有效载荷16位UDP长度指的是整个报头的长度16位有效载荷的长度指的是数据的长度也就是UDP长度-8不可靠传输如果UDP检验失败会直接把报文丢弃并不会通知对方再发送一次也就是没有重传机制面向数据报如果需要传输一个10kB的数据sendto传一次那么recvfrom也只能接收一次而不能循环调用10次recvfrom每次1kBUDP是没有发送缓冲区的调用sendto直接交给链路层只有接收缓冲区如果接收缓冲区满了后来的数据报会直接被丢弃而且UDP不保证可靠性可能传输来的数据报顺序是乱的UDP的长度最多是2^16B,也就是64KB不能通过UDP发送超过64KB的数据比较常用于直播视频等内容structudp_header{uint16_tsrc_port;uint16_tdest_port;uint16_tudp_len;uint16_tcheck;};udp的报文是用一个称为sk_buff的结构体描述的structsk_buff{//struct udp_header | 需要发送的数据 | 其他char*start;//指向结构体的开头char*pos;//指向报文的有效部分char*end;//指向结构体的结尾.....structsk_buff*next;//指向下一个报文};DNS等应用层协议底层就是基于UDP协议的浏览器里内置了DNS服务器的IP地址浏览器会把域名交给DNS服务器然后DNS服务器会给浏览器返回域名对应的IP地址这样浏览器才能去访问对应的网址