尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

网络基础2(四)

网络基础2(四) 1.流量控制接收端处理数据的速度是有限的如果发送端发的太快导致接收端的缓冲区被打满这个时候如果发送端继续发送就会造成丢包继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力来决定发送端的发送速度这个机制就叫做流量控制。双方3次握手成功就进入正常的通信过程了那第一次通信时怎么保证发送的数据量是合理的(第一次发不知道对方接收能力)很好解决不要理解三次握手只是三次握手期间双方也交换了报文。因此在这期间已经协商了双方的接收能力。三次握手成功后才能进行消息的发送第三次的时候我们可以携带数据意味着协商这件事在前两个报文就协商了。我们发个报文对方ACK再连续发3个报文对方接收窗口变0了返回去根据流量控制A 主机就不发了就等。这时不能一直去等A等的时候超过了重传时间后还没收到窗口更新通知 A 会发送窗口探测的报文B收到应答后携带16位窗口A 就得知窗口有没有更新了。那主机A发窗口探测时会不会携带数据呢不会只单纯发个TCP报头这样不存在缓冲区来不及接收的丢包问题。主机B也是有策略的窗口更新了会向主机A推送窗口更新通知。哪个时间短就以哪个为主这样提高了容错性防止一方出问题(双方都出异常确认了双方不连接会主动关闭链接)。回忆我们的TCP首部中, 有一个16位窗口字段就是存放了窗口大小信息。那么问题来了16位数字最大表示65535那么TCP窗口最大就是65535字节么实际上TCP首部40字节选项中还包含了一个窗口扩大因子M实际窗口大小是窗口字段的值左移M位。2.滑动窗口下面说一下滑动窗口正常情况下TCP在通信时是发送数据确认应答发送数据确认应答。实际上通信时已经知道对方的接收能力就允许TC 一次给对方发送很多报文再由接收方对每个报文进行应答。因为TCP会批量化的把数据全发出去最后收到应答。已经发出去但是暂时没有收到应答的报文要被TCP暂时保存起来(支持重传)。已经发出去但是暂时没有收到应答可能会在发送方存在多个这样报文。那这样多个报文会被保存到哪里呢TCP发送的数据本来就在发送缓冲区里发送是把发送缓冲区的数据拷贝到底层让底层去发。也就是数据即便发了但其实还在缓冲区里所以我们要做的是把缓冲区做一个简单的区域划分就行了。可以把缓冲区划分为三部分第一个区间数据是已发送已确认中间是已发送未确认最后是待发送未来第一部分数据可被覆盖也就是叫做从TCP缓冲区移除它了。第二个窗口是可以发/已经发的数据但是尚未收到应答的区域。没收到应答不让这个区域变化就叫做把数据暂时保存只要收到应答把收到应答的报文纳入前一个区域这个报文就被清了。我们把已发送但未收到确认的区域称为滑动窗口滑动窗口是发送缓冲区的一部分。那已发送或可以发送的这部分区域最大那是多少呢因为有滑动窗口区域我们才可以一次向对方发送大量的TCP报文。所以认为滑动窗口的范围大小是对方的接收窗口。比如接收方剩余空间更新给对方(发送方)对方把窗口调成合适的大小就可以同步发大量数据并且不超过接收方范围。上面的区域划分怎么理解我们把缓冲区当做一个数组来看待数组里存放的是一个个字节。未来想衡量一部分区域只需要有数组下标就可以了也就是在双方的TCP协议里维护几个整数限定开始和结束区域范围就出来了。开始之前是已发送已确认结束后是待发送指针之间是已发送未确认。当start指向部分确认了start向后移动对方接收能力变大了就把end向待发送区扩展。所谓的滑动窗口本质上是指针右移。为了能够有效的解决TCP发数据串行化的发、确认、发、确认我们把更改数据方式更改为批量发数据。这样是把所有数据放在发送缓冲区中通过限定区域凡是在窗口内的是可以立即发送的而暂时不用收到应答的报文。操作系统为了维护这个窗口因TCP本来就有发送缓冲区发送缓冲区中数据被划分成区域已发送/可直接发送暂时不用应答的数据所在的区域称为滑动窗口只要对某些报文做ACK了比如ACK2001所以窗口左侧数据对发送方来讲对方已经收到了此时将左侧下标进行右移到2001位置左侧1000字节数据自然的被归类到确认收到应答的范围了。目前认为至少滑动窗口的大小不能超过对方接收缓冲区的剩余空间的大小即应答报文的窗口大小。下面来看第一个问题如果丢包了怎么理解滑动窗口比如连续发了很多报文现在序号为3000的报文丢了其余三个对方收到也有应答那此时窗口是否向右滑动再看看我们对于确认序号的定义确认序号是X表示X之前的报文我们全部收到了。现在比如主机B把所有报文都收到了只是把中间报文的的应答丢了。主机B在填确认序号时填的是5001代表5001之前所有的报文我们都收到了所以滑动窗口直接更新到5001位置了因此体现了允许少量的ACK丢失。前三个应答收到了最后一个应答丢了也不担心之前应答收到后窗口更新到4001的位置然后剩的报文等着超时补发就可以了。通过上述看到由于确认序号意义ACK丢了我们不怕。那如果是数据丢了呢假设序号是3000的报文丢了其余三个都收到了同时ACK也给了对方应答那4001和5001的ACK里确认序号应该填几由于确认序号定义所以ACK里确认序号填2001所以窗口更新到2001位置。因此确认序号保证了滑动窗口线性的连续的向后更新不会出现跳跃的情况。那后续如何补发呢如图2000的数据丢了其余没丢回复。如果主机A等待应答时收到三个同样确认序号的确认应答时则会立即进行重发这种策略称为快重传。那已经有了快重传为什么还有超超时重传呢因为收到三个同样的确认应答才会快重传快重传一定概率可帮我们提高效率但万一没有三个回应就无法触发所以超时重传是兜底的。再看下一个问题滑动窗口会向左移动吗不会因为左侧是已经发送并且已经收到确认的不会向左移动所以左右指针只会一味地。滑动窗口会向右移动吗会的(一会详谈)。移动的时候大小会变化吗假设A在给B发消息B主机有自己的接收缓冲区。现在B主机不把数据取走现在剩余空间还有4000字节。主机A继续发了4千字节B缓冲区被打满了这时窗口大小是0因为目前理解是对应应答报文的窗口大小。所以这个窗口大小是会动态变化的动态变化无非是变大、变小、不变。因此针对性提出向右移动的三种方式1.现在A和B互相通信B上层不取数据A发个数据B接一个这样通告给A时窗口大小是一直在变小的收一个ACK就向右移动最后到0因此向右移动有个情况是右不变左移动。2.现在接收方上层把缓冲区里的数据都取走了接收方窗口大小是16KB。4个ACK都被收到了此时左指针(最初在1001)移动到最右侧右指针向右移动16KB也就是对方给我通告个更大的窗口我就让窗口整体向右滑动的同时变大。这就是左右都移动范围扩大向右移动范围也可能缩小。3.在1的基础上对方开始取了取一个ACK右指针向右移动。计算机语言可这样描述上述每次收到应答这样进行调整通过上面可理解流量控制是通过滑动窗口实现的(控制可往大也可往小控制)。再补充一下我们在谈滑动窗口划分时前提是缓冲区有数据有可能后面有些地方还没有数据因此窗口大小是窗口大小和有效数据最小的那个比如数据到后面没多少就完了但对方更新了很大窗口end这时更新到数据结尾就行。再看个问题滑动窗口会在发送缓冲区中越界吗 TCP采用了类似环状算法因此不用担心(如上是滑动窗口内容)。3.序号下面再细说一下序号双方进行三次握手时如果抓包会发现通行双方的序号并不是从0开始的。网络中我们可能面临各种各样的情况比如刚连接建好通了会信有些数据还在网络中置留着呢。已经超时重传了但依旧置留我把连接断了断了后又重新进行连接服务器对新链接可能收到老数据这样会有问题。所以双方对应的序号刚开始基本上是随机的比如我的起始序号是1234你的起始序号4321双方前两次握手协商时拿到了彼此都认可的序号。双方默认以随机的最小值作为起始序号比如双方协商出是1234这样和历史数据出现序号冲突概率又变小了很难出现新链接获取老数链接的情况。如图在缓冲区把数据拷过来每个数据都有数组下标最终序号可理解为1234数组下标。对方收到后再回应再拿确认序号-1234就是下一次发送数据在缓冲区的位置。这样关了链接又重新链接随机号一变发生和老数据序号冲突概率就很小了老数据一来比对发现和新链接序号差别很大就丢了。4.延迟应答下面来说说延迟应答客户端给服务端发消息时服务器要给客户端进行应答。理论上发送方一次发送更多的数据发送的效率就越高。发送方一次发送更多的数据取决于对方告诉我他能接收更多的数据。如果接收方给发送方通告一个更大的窗口大小(tcp报头)意味着接收方能接收更大数据意味着发送方发送效率就越高。那么如何让接收方给对方通告一个更大的窗口呢我们服务器收到一个报文本来要立即应答现在等一下再应答。因为在等的期间上层有较大的概率把数据取走这样缓冲区剩余空间变大了这样在通告对方时窗口大小就增大了。不是说延迟应答就一定提高效率也是在博概率。延迟是TCP控制的我们可以控制给对方更新一个最大窗口我们自己写TCP服务比较推荐做法是每次都通过read、recv尽快的把数据全部从内核中拿上来。5.拥塞控制总结一下几乎所有的策略起作用的都是在两端机器上都由客户端和服务器两台机器相互配合。可是客户端和服务器在进行通信时只考虑了双方机器的可靠性和效率问题。可网络中的数据包大部分事件是在网上的所以不仅要考虑左右两端机器用什么策略也要对网络信道有所评估因此TCP其实还替我们考虑了网络。要明白网络和双方主机没任何关系网络中出任何问题双方主机是无能为力的但可以做一些策略。下面来谈一下拥塞控制如果发送数据出现问题不仅仅是对方主机出现问题了也可能是网络出现了问题。1.如果客户端和服务器通信时出现了少量的丢包2.如果通信的时候出现了大量的丢包下面说个故事比如班里有30个人考试有2个人挂科了看到后我们会认为自己没学好的问题如果是28人挂科了我们会认为是校方的问题。所以出现少量丢包是常规情况出现大量丢包会认为是网络出现了问题可能有硬件设备问题、数据量太大引起阻塞。因此如果通仅双方出现了大量的数据丢包问题TCP会判断是网络出问题了这时的问题称为网络拥塞了。发送方怎么知道大量数据包丢了滑动窗口内大量数据都超时了。那丢了大量报文我们发送方应该怎么办我们不能立即对报文进行超时重发因为会加重网络的拥塞(已经可能是数据量大引起阻塞)。我一个人不重发就起作用说个题外话网络资源是被很多人共享的每台机器都有TCP。当网络出现拥塞的时候影响的是大家每个人都减少发数量网络中拥塞就能快一些消散然后陆续恢复大家这是用TCP协议实现了多主机面对网络出现拥塞时的共识(阻塞程度不同共识不同)。下面来说拥塞控制的策略(前提要知道每台识别到主机拥塞的机器都要做)TCP在识别到发生了网络拥塞时引入了慢启动机制它是先发送少量的数据探探路探清网络拥堵状态若发的少量数据丢了就进行超时重传这时超时重传只是传一个报文。如果发一个报文得到了应答第二次发两个报文再得到应答发4个报文再得到应答发8个报文。下面引入一个概念叫拥塞窗口刚开始识别到网络拥塞了我们设置拥塞窗口大小为1。每收到一个应答拥塞窗口加1。每次发送数据包的时候将拥塞窗口和接收方的接收能力大小做比较取较小的值作为实际发送窗口大小。现在更正一下理解滑动窗口大小是min(窗口大小, 拥塞窗口)。这样TCP考虑了对方主机接收能力还动态考虑了网络的接收能力。主机判断网络健康程度的指标是数据量超过拥塞窗口会引发网络拥塞否则不会。网络是动态的拥塞窗口本身肯定不能是静态的。慢启动非常厉害如果网络出现拥塞前期发送少量报文若每个报文都有应答此时判定网络已经趋于健康了此时发送策略应尽快恢复正常通信。这样前期慢先试探成功了后期增长幅度高让网络尽快恢复了解上述看个问题实际机器的发送量会一直指数增长吗不会因为滑动窗口取得是最小值一直让拥塞窗口增长很大也没必要。拥塞窗口指数增长时有个阈值由指数增长切换为线性增长真正拥塞窗口算法是当前出现了网络拥塞此时发送方执行慢启动随着传输轮次和时间推移发到一定程度切位线性不断调整让拥塞窗口增大(没考虑接收窗口)。大到一定程度又引起了网络拥塞以这次发生网络拥塞的窗口大小进行乘法减小这个值是下一次慢启动指数变线性的临界值。慢启动阈值最近一次发生网络拥塞时拥塞窗口大小/2。上面是真正触发了网络拥塞才执行这样算法比如按对方接收能力触发拥塞限定没啥意义。6.面向字节流下面谈谈面向字节流这个读写次数不需要完全匹配的叫面向字节流。读和写不需要非常一致的匹配次数这种叫做面向字节流。UDP是发几次必须收几次这就是面向数据包。日常生活中UDP特别像寄快递寄了几个包裹对方就得收几次。UDP本身报头里有16位长度UDP没有发送缓冲区上层交的报文直接添加报头发出去。接收方收到报文后根据UDP长度把报文区分开交给上层就有完整的UDP报文了这是面向数据报。下面聊TCP用户层构建的请求里面保存的数据不是直接给对方的接收缓冲区或交付给上层而是把用户缓冲区的数据拷贝到发送缓冲区数据按字节方式在缓冲区里放着后续什么时候发、发多少、出错怎么办完全由TCP决定。比如要发一小段数据就把那一小段数据单独拿出来然后拼接上TCP报头向下交付最终到接收方TCP层。然后做报头和有效载荷的分离将数据放接收方的接收缓冲区。以前发送缓冲区是什么内容现在对方接收缓冲区也就是什么内容。可能接收方上层在忙此时接收缓冲区已经有很多的数据了。当上层读取时本着尽快把数据读走的原则可能读1个1个半等。反正内核中收到的数据一次取多少由用户决定。这时发送方从用户层到内核层可能拷贝了四五次这些数据以字节方式呈现在缓冲区TCP根据网络、对方接收能力状况综合评估应该发多少字节。用户以为在缓冲区写了四个请求在TCP内核看来只认识多个字节数据它的任务是安全把这些字节数据发给对面对方TCP层也只认为今天收到了这么多TCP字节。上层在读取时对读的这些字节进行解析分离整个工作由用户层自主决定。也就是TCP这层不关心上层协议不关心上层报文格式TCP只有字节概念。TCP层不断把数据从发送方的发送缓冲区拷贝发送至接收方这样双方缓冲区有了字节流动的概念这就叫面向字节流。相当于如发送方缓冲区有一个报文40字节对方给发送方通告窗口是20字节TCP发时把1/2内容发给对方。对方上层会定义用户层缓冲区数据流依次拼接到用户缓冲区对用户层缓冲区在应用层进行解析。下面继续说用户层要发4个请求4个请求通过write写到了tcp缓冲区中。假设开始并没有发出去后来对方接收能力好了后把4个报文打包和报头发了过去经过解析报头把剩下数据流放到接收缓冲区。用户层将来对报文进行处理必须一个一个的处理所以要求用户层将读的字节流变成一个一个完整的请求。但上层读上去的数据可能有时候不是完整的报文如果用户层不对报文进行一个一个报文的分离(如没保存可能丢了还会影响后续报文)在处理报文时可能会多处理或少处理对应请求这种情况我们称为数据报粘包问题。也就是比如把缓冲区数据都读到应用层在应用层对数据进行分析(客户端和服务端没定过协议)可能没办法区分一个一个的报文可能把一个报文最后数据(第二个报文中前几个字节)整体当成一个报文来处理了这就是数据报粘包问题。下面说个故事来理解一下比如过年家里蒸了包子蒸好后包子都是粘在一起的我们直接拿可能想吃一个包子但总是拿的时候多出些包子多的这部分就叫做粘包问题。粘包问题是相对于用户层的概念要解决粘包问题要定协议(我们之前用Encode和Decode的原因在这里)。在应用层通过协议明确报文和报文之间的边界。用户层可通过以下解决粘包问题1.定长报文。2.使用特殊字符。3.使用自描述字段定长报头。4.使用自描述字段特殊字符。有了分离后的数据然后才到了反序列化变成结构化数据此时用户才是真正得到请求。7.连接异常下面来谈谈TCP连接异常的问题通信双方正常通信要先三次握手把连接建立好那如果进程终止了曾经建立的连接怎么办连接本身和进程其实没有直接关系连接本身和文件是直接相关的。因为每建个套节字在系统中要新增一个文件描述符意味着每打开了文件是有对应连接的。我们之前说过文件的生命周期是随进程的意味着连接本身随进程。因此进程结束了连接进行正常的四次挥手即可正常自动断开。那机器重启了连接怎么办关机前系统先要杀掉所有的进程所以连接也是自动断开。那机器掉电或网线断开呢如果客户端把网线拔了客户端没有机会向服务器进行4次挥手。服务器此时不清楚客户端挂了服务器还保持连接。网断了客户端能识别到客户端就把链接关了。此时重联网再发消息会有链接认知不一致的问题所以服务端会给客户端发reset重新建立链接服务端关了以前的链接。若重连不发消息TCP内其实有保活机制服务端发现客户端长时间不发消息给客户端定期发保活消息若客户端都没有应答服务端还是会把连接关了。8.文件和socket的关系下面打通一下文件和socket的关系我们当前写的所有网络服务本质都是一个进程内核中叫task_struct。我们以前说过每个被打开的文件都有文件描述符数据结构是struct files_struct该数据结构中包含了一个fd数组称为struct_file*fd_array[]。当我们打开一个文件的时候PCB指向的是struct files打开文件要给我们创建struct filesstructfiles里有函数指针的操作方法还有文件缓冲区struct files内部有个指针叫private_data它的类型是void*。当我们创建网络套接字时操作系统中为我们创建很多的数据结构其中有一个数据结构叫struct socket。private_data这个指针会指向struct_socket这样的结构(若是网络文件)struct_socket内部有个指针是struct file会回指向文件网络里想找到文件也可以找到最终整个网络文件挂接到struct file之下应用层通过文件描述符就可以找到struct socket。struct socket里有个wait_queue_head_t它里面有个list_head。之前说过进程在阻塞等待的时候实际上要把自己的PCB连入到指定的数据结构里所以网络里读取网络数据不就绪时当前进程挂接到wait_queue里就行了。struct files里还有struct sock指针strcut sock里有接收和发送队列操作系统中走到这已经到网络了udp_sock和tcp_sock第一个字段都是struct sock当我们创建一个套接字时我们都会创建tcpsocket或udpsocket创建套节字时会传入SOCK_STREAM或SOCK_DGRAM。这个struct sock指针并没有直接指向struck sock我们创建 tcpsocket时操作系统中创建的是struct tcp_socket创建udpsocket时操作系统中创建的是struct udp_sock。只不过它们开头的地方都叫做sock。所以sk然后指向第一部分socket里面有些字段可以区分socket类型未来想访问某个套接字类型直接如(struct tcp_sock*)sk-这样就能强转访问了它的本质就是多态。无论在struct file文件中还是在struct socket网络套接字里面还是在udp_sock/tcp_sock里面它们各自会提供各自层的方法的通过函数指针方式提供。tcp或udp不会send数据到网络里而是把数据包给下一层
返回列表