计算机网络(传输层)

应用层的数据 并不是直接发送到网络而是交给了传输层,比如通过write拷贝到tcp的缓冲区,接下来数据什么时候发、怎么发、出错了怎么办完全由传输层去做决定。 端口号是包含在输层的概念,上层进程绑定某些端口号之后,传输层收到指定端口号的报文,有传输层上交给与该端口号绑定的指定进程。一个进程可以bind多个端口号,但是一个端口号一般只能bind一个进程。

 报头就是结构体,添加报头就是定义结构体对象,并填充对象里面的字段,而所谓的封装就是把数据和报头拷贝到一块,

UDP

有效载荷就是应用层给我的请求报文,通过write系统调用拷贝给我的。udp是通过固定报头长度和16为udp长度将实现有效载荷和报头的分离。由于报头包括了目的端口号,所以udp可以根据该端口号向上交付给绑定了该端口号的应用层进程。

所谓的udp就在Linux内核里面,内核是用c语言实现的,所谓的报头(协议)就是结构化数据,c语言表示结构化数据采用的就是struct,而strcut分为两种一种是结构体一种是位段。添加报头就是在定义结构体或位段对象,在里面填数据,或者申请空间对空间对强转,得到报头之后向里面填充字段。所谓封装就是将有效荷载和报头拷贝在一起。解包就是将报头和载荷区分开来,然后根据报头的字段来解析后面的内容。以后网络里面的所有报头理解称为结构体就可以了。 定义协议就是结构化数据。

双方在通信的时候不是不建议是用结构体吗?因为结构体在不同的平台下会有内存对齐的问题、变量大小的问题。因为当前属于操作系统层,操作系统是需要保持一致的,我不管你内存对齐的问题,udp的报头必须8个字节,并按照要求进行相应的的字节划分,也就是不管说明平台争对udp等报头的结构体编译出来的大小和布局必须一样,这是协议强制规定的。应用层上面我们不直接用结构体,在应用层上有一个序列化和反序列化,因为我们应用层的协议是随时随地都可能发生变化的,tcp/ip协议一旦订好了就不太会变化,在设计上由于应用层一直再变化,所以我们上层为了提供开发效率高的方案,需要自定义协议结合序列化和反序列的方案来实现,而内核的所有协议采用的是结构体字段来构建报头的。

UDP的socket既能读也能写所以叫做全双工。应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并,这就是所谓的面向数据报; 用UDP传输100个字节的数据:如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个 字节; 而不能循环调用10次recvfrom, 每次接收10个字节。udp发的次数和对方接受的次数在没有网络丢包的情况下是一样的额,这样每一个报文都是一个独立的报文,不需要上层再去额外解释如何获得一个完整的报文了。udp比较简单,不用建立连接也就是意味着服务器不用维护连接。udp由于其不保证可靠性,所以并不需要具有发送缓冲区, sendto将数据交给操作系统,操作系统把数据经过网络协议栈由udp协议封装报头之后直接交给下层,不保证可靠性就不需要为了维护可靠性做各种各样的策略和机制。但是接收缓冲区是需要具有的,是为了尽可能的保证不要出现过高的丢包率,如果接收缓冲区满了,后续的数据只能直接丢弃了。udp接收缓冲区不能保证接收到的udp报文顺序和发送的顺序是一致的,这又是其不可靠的表现之一。16为的udp长度决定了有效荷载最大时64kb,如果要传更大的数据就必须对数据做拆分处理。

TCP

4位首部长度是tcp报头的长度,其中包含了选项长度,单位是4个字节,取值范围是[0,15]也就是0到60个字节,也说明选项至多40个字节。tcp没有有效载荷的长度, 但是tcp我们无法得知有效载荷的长度,确实无法得知,这是和tcp面向字节流有关。 不可靠的现象:丢包、乱序、重复、校验失败、发送太快、发送太慢、网络出问题。解决丢包问题的前提是我们需要知道我们丢包了。

为了解决丢包问题,我们首先需要知道自己丢包了,产生了tcp的确认应答机制:c要是等待了一定的时间没有收到ack应答就判定报文丢失,对于ack是不需要进行再次应答的。无法 保证所有的报文都是可靠送达的(主要是最新的报文),但是我们能够局部保证可靠性。

 

我们不用发送一次报文要等到接收到了应答再发送下一次的报文,这样效率太低了,真实情况下一般是客户端一次发送多条报文,服务器对每条报文都要进行ack应答。但现在的问题是多个报文经过网络,发送顺序不一定是接收顺序。当client收到多个确认的时候,client如何知道哪个ack应答对应的是哪一个报文?这就注定了我们要给报文编号。

c向s发的报文中报头部分的32位序号指的是自身报文的编号,s给的应答必须要有确认序号(确认序号的含义X是X-1之前的报文已经全部收到了,下次请从客户端从X编号开始发送)。为什么要这样进行规定呢?  以上图为例,即便客户端没有收到ack11,只要我们收到ack12也可以确定我们的request10并没有丢只不过是对应的ack11丢了,这种规定可以允许少量的ack丢失,从而可以更加细粒度的确认丢包原因。

为什么32位序号和32位确认序号不能整合在同一个字段内? 一套s->c的报文可以是对c->s的ack(需要确认序号)以及s->c发送数据(需要序号)的结合,叫做捎带应答,可以增加通信的效率。

应用层的工作是将数据通过send系统接口将数据从应用层缓冲区拷贝到传输层tcp的发送缓冲区就直接返回了,而从本端缓冲区拷贝到对端缓冲区的过程是由tcp协议来负责的,对端调用read接口也不是直接从网络里面读数据而是从tcp的接收缓冲区读取数据到上层。如果发送数据太快了,对端接收缓冲区当被填充满了之后后面的报文都会直接丢弃,这样会造成资源的浪费,虽然有应答机制不会丢包,但是浪费了很多不必要的资源。16位窗口大小会填上接收缓冲区剩余空间的大小,本端接收到之后可以决定后续还可以向对端发送多少数据,这叫做流量控制,当缓冲区空间剩余很大时,流量控制也是可以提速信息传输的效率的。校验和如果校验失败,该报头和以及有效荷载全部都会被丢弃,这叫做真正意义的丢包。校验和可以对tcp的报头和有效载荷做校验,一旦校验失败该报头以及报文数据会被全部丢弃。保留6位是目前并未使用的字段,将在再做打算。

标志位

服务器在任何一个时刻会收到来自不多客户端的各种各样的报文请求,不同的报文请求对应不同的处理动作,这也从侧面应证了报文是有不同的类型。标志位的本质是用来表示不同类型的报文,虽然这些报文都是以tcp请求的方式来进行数据的传输。每一个标志位只占用一个比特位, ACK是确认应答报文,当接收到该报文的时候,请关注一下我的确认序号,当然该报文可能会携带数据,这就是捎带应答。SYN是一个连接请求的报文,3次握手,1、SYN;2、SYN+ACK;3、ACK。FIN:是一个连接断开的请求报文,4次挥手。

PSH(push) :催促接收端应用程序尽快从TCP缓冲区把数据读走。

RST(reset):是对连接进行重置,双方建立连接的时候可能因为一些原因连接建立不成功,或者连接建立成功之后因为一些原理双方连接不一致的情况(比如说有服务器网络断了,导致连接失败,但是另客户端端认为连接依旧保持正常,当服务器网络恢复之后,客户端一致维护者旧的连接并还是向服务器发起请求,而恢复网络的服务器接收到这个请求之后会觉得非常奇怪,不是说好的通信之前要三次握手的吗,为什么客户端直接跟我通信发数据呢?服务器这时候会意识到客户端的这个连接是异常的),这时候接服务器发送RST标志位置为1的tcp报文,客户端接收之后需要将老的程序断掉了,然后重新建立连接。

URG(urgent):告诉接收端我们报文携带的有效载荷里面是有部分紧急数据的,需要插队提前处理,并告知我的16位紧急指针的位置是有效的(说明平常这个字段是无效的)。只有具有定位功能的数据,比如说下标,在宏观上我们都可以叫做指针。16位紧急指针是紧急数据在有效载荷部分的偏移量,紧急指针指向的数据是包含在有效荷载里面的 。常规数据入队列,紧急数据直接插队通过特殊接口交给上层。注意紧急数据只有一个字节的大小,可以在正常传递数据的途中新开辟一条通信链路(用的还是之前的连接),但可以只发一个字节的数据,通过状态码的方式给对方发送控制指令来控制数据传输的相关细节,或者说通过紧急指针实现对传送数据的过程进行控制,所以紧急指针可以叫做带外数据。 

标志位的本质是报文的类型,因为不同类型的报文我们对应不同的动作。 

应答机制:

可靠性并不是说数据必须每次成功拿到,数据没有拿到但是我知晓了这也是可靠性的一种表现。tcp的可靠性在于当我们给对端发数据的时候,如果我们接收到了ack应答,说明上次数据已经成功发送,同时如果超时没有收到ack,也就是我们判断上次数发送失败这也是可靠性的体现。在通过send/write函数接口将数据从应用层拷贝到tcp的发送缓冲区的时候,tcp并不需要关注这些数据是什么,只把它当成二进制数据流。tcp发送缓冲区可以看成一个char sendBuffer[],这并不是说我们要把数据当成字符来看,而是说将数据以8个比特位也就是1个字节为一个最基本的单位进行传输,而char sendBuffer[]这个数组的下标就是报头中的序号信息。如果主机a将数据d发送给主机b,但是主机a在特定的时间内没有收到该数据d的ack,主机a对该数据d进行超时重传,那么主机b就可能会收到重复的数据d,所以主机b还要根据序号进行报文去重(序号去重也是可靠性的表现之一) 。由于超时重传的存在,主机a是不能发送完数据d之后就将数据d从tcp的发送缓冲区给清掉的,还需要维护一段时间,直到收到数据d的ack或者进行超时重传。超时重传的时间是和当前的网络环境强相关的,所以只能动态计算的。累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接.

三次连接:

我们要进行三次握手目的是为了建立连接。服务端accept并没有参与三次握手,而是一直在阻塞等待直到三次握手成功把连接拿上来。客户端的connect也是需要在三次连接建立成功之后才可以返回。 过程中第三次连接如果在发送过程中丢失,那么客户端会认为三次连接建立成功、而服务端认为三次连接未重新建立,这时候双方不一致,客户端接下来会给服务端发送数据(因为它认为三次连接已经建立好了),此时服务端如果收到该数据报文之后就明白客户端误认为连接建立成功了,服务端就可以给客户端发送连接重置RST,这时客户端可以把连接关掉进行重新连接。所以三次连接是在赌,赌第三次ACK是否会被收到。但这是小概率事件,大多数情况,客户端发送第三次ACK,服务端不给客户端发送RST,就说明当前连接成功建立了。虽然存在失败的机率,但是大不了重新连接就是的了。如果两次握手的话对于tcp来说,存在重大的漏洞,让服务器非常容易受到SYN洪水攻击,也就是客户端向服务器发送海量的SYN只要服务器收到SYN就认为连接建立成功了,但是客户端不用对第二次连接SYN+ACK做任何处理。如果是四次握手或者说是偶数次握手,由于连接有一定概率异常,尤其是服务器面对多个客户端,连接异常的情况一定是时常发生,奇数次握手一定是客户端先把连接建立好,偶数次握手一定时服务器先建立好连接,但是连接是否能成功建立还要考虑客户端是否能接收到最后依次ACK,这样承担最后一次握手连接异常的成本会嫁接到服务器端,那服务器端面多多个客户端,那么会导致服务器维护这种异常连接称为必然,不利于服务器。三次握手的过程是由双方操作系统自动完成的。

四次挥手的原因是因为需要征得客户端和服务器双发方的同意,双方都认为互不来往了这才叫做连接断开,四次挥手是可靠的建立这种共识的最小成本。建立连接的过程中三次握手也可以看成四次握手,也就是第二次握手将ACK和SYN分层两次发送,但是建立连接的连接的共识双方是比较容易建立的,所以合并到了一块,但是断开连接的过程客户端和服务器端很难快速建立共识,比如说客户端想要断开连接,服务器说好啊但是服务器目前不想断开连接因为服务器还有数据要发送给客户端,所以就就有了四次挥手,而不是三次。客户端在第四次ACK完成之后会进入到time_wait状态一段时间,才会close是为了,因为第四次ACK是没有应答的,所以为了保证它发送的最后一次ack尽量要被服务器收到。如果客户端close fd但是服务器不close fd,客户端会进入到FIN_WAIT_2状态,等待服务器发送FIN或者发送数据,但是如果这段时间服务器一直不给客户端发送数据,客户端也不会等你太久就直接close退掉,但是服务器会比较长的时间处于CLOSE_W AIT的状态,此时客户端已经close了,所以服务器维系这种状态不仅浪费资源而且没有实际意义,所以服务器在通信的时候一定不要忘记关闭fd。一段时间之后当服务器再想close fd也就是给客户端发送FIN的时候,由于客户端早已经关闭了,一定不会对该FIN进行ACK,所以服务器会重发几次FIN然后没有收到ACK自己就close掉了。上述也可指,最害怕的是CLOSE_WAIT状态,因为他会长时间的维系该状态并且会无效的浪费许多的资源,但是对于FIN_WAIT2和LAST_ACK这两种状态还好,因为他们是不稳定的,维系一段时间之后会自动退出close掉。文件描述符的生命周期是随进程的,

正常四次挥手退出的时候,服务端和客户端任意一端,做为主动断开的一端会维护一段时间的TIME_WAIT临时状态,但是被动断开的一方会立马close退出。如果是客户端主动发起的FIN,那么客户端会维持一段时间的TIME_WAIT状态,而服务器端做为被动断开的一方  在接收到第四次回收ACK之后可以立马close。

TIME_WAIT状态虽然处于四次挥手已经完成的状态,但是此时连接状态还在,说明维护连接状态的相关数据结构还存在,如果我们此时打开服务绑定端口号并且主动断开连接再迅速的绑定相同的端口号,会报错bind error,因为主动断开连接的一方在进入TIME_WAIT状态时,其实连接还没有被彻底关掉,socket和端口号依旧是相关的,所以会吧当我们启动服务器并关闭的时候是不能立马再次启动服务器的,否则端口号bind会失效,必须要归还端口号才能立马重新启动,这是因为服务器做为主动发起FIN的一发会保持一端时间的TIME_WAIT的状态(端口号依旧在被使用),在这个状态下服务器的端口号和ip地址是依旧被使用的过程,虽然此时服务器的进程的以及被终止掉了的。解决方法:

    void Socket()
    {
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            logMessage(Fatal, "socket error, code: %d, errstring: %s", errno, strerror(errno));
            exit(SOCKET_ERR);
        }
        // 设置地址是复用的
        int opt = 1;
        setsockopt(_sock, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt));//对当前套接字在进行修饰一下
        //int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);对sockfd该套接字,在level层(tcp层、ip层、还是那一层),optname通常会写成SO_REUSEADDR即无视time_wait状态实现ip地址复用,SO_REUSEPORT端口号复用,opt为1设置进去,说明optname设置为真。
    }

为什么我们要TIME_WAIT状态,TIME_WAIT要等待2MSL的时间,MSL是TCP报文的最大生存时间,就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失,大概率保证最后一次ACK报文可靠到达,从而正常断开连接。

四次挥手四次的原因是因为要争得双方的统一,所谓的挥手是要征得对方的同意的,也就是要包含确认的。

流量控制:第一次发送报文的时候我们是不知道对端接收缓冲区的大小,如何保证第一次发送数据不是太大也不是太小。 双发在进行tcp通信的时候并不是双方进行tcp报文第一次交换的时候,双方在进行三次握手的时候就至少进行过一次报文的交换的,客户端在进行连接建立请求的时候,除了SYN标志位被置为1,客户端也一定要将16位的窗口大小通告给服务器,服务器就知道客户端的承载能力,而第二次握手的时候客户端就可以得知服务器的承载能力。所以一开始进行通信的时候我们就不用担心发快或者发慢的问题。如果我们通过16位窗口得知对端承载能力已经到了极限的时候,我们就需要停止发送了,但是接下来我该如何决定下一次发送消息的时间呢,这时发送方会进行定期的窗口探测(也就是发送tcp报头不携带数据再通过对方的ACK来获知对方的当前的承载能力),对端也可以给我主动发送窗口更新的通知。

  滑动窗口: 

tcp通信是发送报文在返回一个应答的过程,在发送报文和确认应答的过程中,可以是上面左边的串行工作模式,但是也可以是右边的工作模式,一次发送多个报文,对方再给客户端进行多并发的响应。这样就把报文的发送的时间进行重叠了,从而提高了发送的效率。

滑动窗口大小是发送方可以并发发给对端的最大数据量。可以将缓冲区看成一个char sendbuffer []也就是一个字符数组,而滑动窗口就是两个数组下标维护的一段区间。滑动窗口是不能向左的,只能向右滑动。滑动窗口是可以变大的(也就是让windend变大一点容纳更多的发送区间),同样也可以变小(winend不动,winstart向右移动),变大或者变小取决于对端的接受能力。滑动窗口可以变成0,意思是对方不能再接收数据呢,这时候就进入到了窗口探测和窗口更新阶段了。滑动窗口的大小目前是根据对方的接收能力也就是响应报头中wind的大小。tcp为了可靠性必须要保证按序应答,应答序号seq就是说明seq之前的报文已经接受了,下次请从seq开始发送,也就是让winstart=seq,应答的win就是让winend=winstart+win,从而实现窗口的向右移动。

比如如果我们发送了1-5000个字节的数据,分成5个报文,每个报文的数据大小是1000个字节。当第一个包的ACK如果丢失了,由两种情况,情况1:第一个包已被服务器成功接收,但是其ACK再传输过程中丢失了,2000、3000、4000、5000这四个报文对应的ACK里面的序号里面2001、3001等,这个序号的意思不是说1000-2000或者2000-3000的报文我收到了,而是说2000之前的报文或者3000之前的报文我收到了,当我没有收到1001但是收到2001的ACK序号,我知道1-1000的包对方一定收到了,只不过是其ACK丢失了。应答丢失的情况并不用害怕。情况2:如果是数据真的丢失了,假如丢的是1000-2000的数据报文,那么接收方虽然收到了其余4000个字节的数据包,但是接受方发给客户端的ACK序号只能写1001也就,因为确认序号是告诉我们该序号之前的数据都接收到了,所以客户端接收的多条ACK的确认序号是重复的,这时候客户直到是存在丢包情况了,这时候滑动窗口左端winstart=1000然后也不会向右移动因为没有收到1000-2000这个数据包的ACK,滑动窗口这时候可以理解称为在进行等待,等待进行超时重传。当我们重传1000-2000报文并且对端手打了,返回的ACK确认讯号可以直接干到5001。快从传是当客户端收到3个确认应答的确认序号是相同的时候会进行重传,但是当我们传递报文只有一两个的时候,只能进行上述的超时重传。所以超时重传和快从传并不矛盾,快重传是一个强调效率的方式,而超时重传是对快重传进行兜底。

之前说的数据要支持重传,就必须被暂时保存起来,保存的位置就是滑动窗口中。滑动窗口里面的数据也是客户端并发发送的数据区间。发送缓冲区被设计称为环状结构,这也是历史数据可以被覆盖的原理,winstart和winend也就不存在越界的说法。响应当中的确认序号不是对你收到的报文做确认,而是对收到的报文以及之前的所有报文做确认。滑动窗口内的数据是准备发送的数据或者已经发送的数据但是没有收到对应的应答。

拥塞控制:

上述考虑的都是发送端和接收端两方的策略问题,并没有考虑网络的感受,在整个网络中有大量的接收方和发送方。 当存在少量丢包情况的时候,发送发可以采用重传策略。但是当存在大量丢包的时候,这时候很可能是网路出现了问题(不考虑网络瘫痪,这里仅仅考虑网络拥塞就是网络报文太多了,来不及传输处理),这时就不是客户端和服务器端可以控制解决的问题,那此时客户端应该采用什么样的发送策略呢?如果此时依旧采用快充穿或者超时重传的策略,只会加重网络拥塞的情况。

当发生网络拥塞的时候,发送端要基本得知网络拥塞的情况,必须要进行网络的探测。我们首先要减小发送量,但是不能什么也不发,称为慢启动机制,也就是先尝试发一个包、两个包、四个包等,不断的进行尝试。我们不能一直采用线性增长的方式来探测拥塞窗口的大小,因为后期单次增长的空间太大了以至于会对拥塞窗口的代下掌握不准确,所以前期采用指数增长后期采用线性增长,这中间通过慢启动的阈值来进行划分,慢启动的阈值也是会不断根据上次探测拥塞窗口的上限进行调整的。当数据量超过拥塞窗口的时候极有可能发生拥塞,但是当小于时拥塞发生的可能性不大。每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小值做为实际发送的窗口。拥塞窗口的大小不代表发送端发送数据量的大小,因为实际在进行数据发送的时候还需要考虑对端接受能力,二者的较小值才是我们实际可以发送的数据量。

延迟应答:

接收端在收到一个报文之后,在不超过超时重传的时间内多等待一会再进行应答,当然再等待的过程中接收端可能在继续接收数据,所以接收端也可以将多个报文的应答合成一个应答,在等待的期间可以然应用层有更多的时间进行读取。

tcp的工作基本模式主要时1:基于滑动窗口的多数据并发访问;2:每个报文基本上都是捎带应带的报文。校验和保证的交上去的数据一定时没有问题的。序列号是为了抱保证数据按需达到,并且通过该序列号进行去重。只有收到确认应答的报文才能保证百分百的被对方接收到。tcp是面向连接的,在通信的时候必须建立连接,双方在建立连接握手的时候本身就是就是在交换报文的过程,可以在这个期间交换通信的时候需要的属性数据。滑动窗口可以实现并发发送数据,是多个数据包的发送在时间上重叠,从而提高策略。用户只需要将数据拷贝到tcp的发送缓冲区,接下来这个数据什么时候发、如何发、出错了这么处理这些都是双方tcp协议内核操作系统自主决定的,所以tcp叫做传输控制协议。粘包问题再tcp层不存在,因为再tcp层是没有一个完整数据包的概念,这是应用层需要解决的问题。udp是否存在粘包问题呢?udp包头里面有udp定长报头,在加上udp长度字段,所以当一个udp报文被收取到的时候,udp是知道独立报文长度是多少的。我们只需要将一个完整的请求扔到udp的有效载荷里,在读取得时候是一定可以读到一个完整得请求的,然后直接可以向上交付,udp不用解决粘包问题,只需要收发数据,然后对收到的数据进行反序列化提取字段即可。tcp报头没有有效载荷的长度,因为不需要,这和tcp面向字节流是强相关的,当我们收到tcp的报文的时候,首先这个报文决定不会出错,因为有校验和,而且报文一定是有序的,所以收到报文之后只要将其有效荷载原封不懂得放到tcp的缓冲区里面就可以了,至于报文的边界如何处理由应用层取解决。 

进程一旦崩溃了,该进程曾经创建的连接、打开的文件、申请的空间会被os系统全部回收掉。对于不活跃的连接管理,tcp是很难进行处理的,这时候这些连接的管理需要依赖应用层。 

粘包问题?

如何将将一个完整的请求或者响应提取出来?udp是没有粘包问题的,因为udp是定长报头加上16位的总长度。为什么tcp没有有效载荷的长度,这是和tcp面向字节流的性质强相关的,因为它不需要。校验和保证了tcp报文没有出错,其次我们可以根据4位首部长度分离报头,tcp的可靠性保证了收到的报文的有序性,我们只需要将有效荷载放到接收缓冲区就可以了。

机器掉电/网线断开: 会出现双方连接状态认知不一致的问题,服务端认为连接还在, 一旦服务端有写入操作,服务端发现连接已经不在了, 就会进行reset。即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在。 如果对方不在, 也会把连接释放。如果立马恢复网络,客户端在接收到服务端的保活消息,客户端首先会疑问为什么和服务器没有建立连接就给我发消息了,客户端会意识到历史上出现了异常,此时客户端会给服务器应答并且进行reset连接重置 。对于不活跃的连接tcp很难处理,这就需要应用层来维护,tcp的保活机制很难满足实际的需求,只有应用层结合其需求才知道如何处理这些连接,比如qq可以检测到用户不活跃时主动断开连接头像变成灰色,当你移动鼠标就开始重新连接。

全连接队列

listen的第二个参数是全连接队列的长度,全连接是指三次握手建立成功的连接,这个连接可以不断accept,一旦accept读取到上层之后这个连接就会从这个全连接队列中移除。全连接队列指的时已经三次握手成功但是没来得及被上层accept读取的,这个队列不能没有但是也不能太长,目的是为了让服务器时刻保证自己是处于被100%使用的,但是队列不需要太长,因为这样会消耗os本身的资源,而这些资源如果直接给server使用更好,其次队列太长client其实是不会去等的。三次握手期间的叫做半连接队列,维护的时间非常的短。

滑动窗口既然涵盖了大量的字节流数据,对方的接受能力就是我窗口的大小,那为什么要把滑动窗口的一大段数据封装称为多个报文呢?拿什么做为一个报文进行发送呢?这样效率难道不是更高的吗?  

  • 10
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值