• TCP协议(全面总结)


    1.协议整体格式

    tcp协议关注的其实和udp是一样的,就是如何进行封装和解包,以及如何向上交付。传输层主要是进行决策的,在它的下层的网络层以及数据链路层等才是做执行的。
    在这里插入图片描述

    2. 4位首部长度

    TCP的标准报头长度是20个字节+选项的长度,四位首部长度表示的就是报头的长度,所以四位首部长度-20就是选项的长度。不过选项我们一般不考虑。因此首部长度通常就是20个字节。
    注意,四位首部长度的单位是4字节,因此如果表示的是20字节的话,它的值是5(5*4=20),即0101。
    通过4位首部长度可以得知报头长度,实现报头与有效载荷的分离,而通过16位目的端口号可以实现协议的向上传递。

    3.序号问题

    在tcp协议中有两行序号,分别是序号与确认序号,tcp的可靠性,最核心的机制就是基本序号的确认应答机制。我们分几层来理解这一机制。

    3.1 应答的作用

    在这里插入图片描述
    当客户端向服务端发送信息的时候,服务端收到信息之后会给客户端发送一条信息作为应答,表示自己收到客户端所发送的信息了。
    那么服务端又如何知道自己的确认信息被客户端收到了呢?客户端可以向服务端发送一条确认收到的信息作为应答,以此类推。
    本质来说其实就是:通过应答机制来保证,上一条信息被对方百分之百的收到了。它体现的是历史消息的可靠性。
    但是这种方式是有局限性的,从而使得TCP通信不是完全可靠的,因为最新的消息是没有应答的。

    3.2消息顺序

    我们发送消息是有顺序的,消息的顺序不同可能造成不同的后果,比如我们发送的顺序是1,2,3,4,5对方收到的消息的顺序有可能是5,4,3,2,1。那么如何保证发消息的顺序和收到消息的顺序是一致的呢?
    这就需要tcp协议中的32位序号来起作用了。在发送消息时,会根据发送顺序对每一条TCP的编辑序号:
    在这里插入图片描述
    接收端会将收到的5,4,3,2,1根据tcp中的序号来进行排序,从而保证按序接收。

    3.3确认信号

    当多条消息发送到接收端时,会产生多个确认序号,那么如何知道每一条确认序号确认的是哪一个消息呢?
    此时32位确认序号起作用,在tcp报头中确认序号的值是对历史中的消息的序号+1,比如确认消息中确认序号是2表示的是历史中序号是1的发送的消息被接收到了。
    在这里插入图片描述
    当确认序号为13,通常表示的是序号13之前的序号都收到了,告知发送方下一次请从13号报文开始发送。

    3.4全双工

    确认序号和序号为什么要分为两个独立的字段呢?一个字段似乎也可以完成正常确认应答机制。
    这是因为tcp是一个全双工的通信机制,即可以发送消息与确认消息同时进行。
    一个tcp的报文中,可以携带要发送的数据,也可以同时携带对历史报文的确认。

    4. tcp协议缓冲区

    4.1缓冲区原理

    TCP协议是自带发送缓冲区和接收缓冲区的。
    本质上其实是OS为TCPmalloc了两段空间,在应用层调用的系统调用接口write/send等,与其叫做发送接口,不如理解成拷贝函数,当应用层调用send的时候,并不是将数据发送到网络上,而是将数据拷贝到TCP的发送缓冲区。同理,当调用read或者recv时,其实是将接收缓冲区的数据拷贝到应用层。

    4.4缓冲区与序号

    我们可以把整个缓冲区想象成一个大数组,当应用层要发送数据的时候,会先将数据拷贝到发送缓冲区这个大数组中:
    在这里插入图片描述
    假设某一个数据占用了数组的前1000个模块,当发送该数据的时候,会将该数据占用的数组的最大下标作为序号来进行发送。即该数据的序号是1000。然后接收方返回的确认序号是1001,表示下一次从数组的1001号来进行数据的发送。

    4.3设置缓冲区的原因

    1.应用层只关心如何将数据发送到缓冲区中,或者如何在缓冲区中拿数据。
    2.TCP协议只需要关心如何发,什么时候发,发多少,出错了怎么办等发送细节问题。
    因为缓冲区的存在,可以做到应用层和TCP进行解耦。

    5. 16位窗口大小

    当发送方发送大量数据的时候,可能导致接收方来不及接收的问题,即接收方的接收缓冲区满了。此时多余的报文只能进行丢弃,虽然tcp有超时重传的机制,但是这样也会浪费大量的人力物力。
    因此在TCP协议中引入16位窗口大小,当接收方接收到消息之后,会将自己的接收缓冲区的剩余空间的大小通过16位窗口大小发送给发送方,从而使得发送方对发送的速度频率等进行控制。

    6. 6个标记位与三次握手,四次挥手原理

    6.1标志位的作用

    标志位,即tcp报文中的在这里插入图片描述
    tcp是面向链接的,在进行通信前需要先进行connect,建立连接的方式就是三次握手。三次握手的本质其实就是一共互发三次报文。
    在任何时刻都有可能有成百上千个链接,成百上千个报文的发送,而6个标志位的作用就是判断这些报文的属性,已达到更快对这些报文进行响应的目的。当报文具有某一属性的时候,就将这六个标志位中对应的属性位置为1。

    6.2ACK

    ACK置为1,表示的是该报文具有响应属性,几乎所有的tcp报文都有响应属性,它们的ack都是1。
    在这里插入图片描述

    6.3SYN

    表示报文具有建立链接属性,由于tcp通信之前要进行链接的,因此在没有正式传输数据之前需要互相传输具有SYN属性的报文。三次握手也由此而来,简单来说就是发送方先向接收方发送SYN属性的报文,接收方返回一个SYN属性和ACK属性的报文,发送方再发送一个ACK属性的报文,三次握手就建立成功:
    在这里插入图片描述
    其中SYN_SENT与SYN_RCVD表示同步发送状态和同步接收状态,ESTABLISHED表示链接建立完成状态,注意client端在发送ACK就认为链接建立完成了,而server只有收到了ACK才认为链接建立完成。
    注意,三次握手在我们所写的应用层代码是体现不出来的,它发生在客户端的connect和服务端的accept之间,是由TCP协议自动完成的,用户层全程不参与,用户层对三次握手没有任何影响,因为有缓冲区作为解耦。
    在这里插入图片描述

    6.4 RST

    RST表示的是复位,即断开链接并重置,在三次握手中我们发现,第一次握手与第二次握手都有响应,我们不关心报文是否丢失,但是第三次握手是没有响应的。不要认为三次握手一定会成功,它是一个大概率建立链接的过程。
    由于第三次握手没有响应,因此对于发送方client来说,当ACK发送出去(没有得到响应)的时候就认为链接建立好了,而对于接收方server来说,只有收到了第三次握手的ACK才认为链接建立成功。双方认为链接建立成功是有时差的。
    所以就有可能出现下面的情况:第三次握手ACK丢失,client端认为链接建立成功,而server没收到ACK认为链接没有建立成功。当client向server发送数据的时候,由于server认为链接没有建立成功,就会向客户端返回一个RST置为1的报文,此时client就会得知链接没有建立成功,从而断开链接并重置。
    在这里插入图片描述
    注意,以上只是发送RST的一种情况,任何链接异常的情况都可以使用RST来进行复位。

    建立链接的本质:
    server会收到大量的链接,因此需要对这些链接进行管理,采用先描述后组织的方式为这些链接建立响应的数据结构,因此双方维护链接是有成本的,需要花费大量时间和空间。

    6.5 PSH

    PSH即为PUSH,当接收方接收缓冲区快满的时候,会向发送方返回16位窗口大小让发送方控制发送速度,发送方会向接收方再发送带有PSH的报文,来催促对方尽快将接收缓冲区的数据向上交付。

    6.6 URG

    由于序号的引入,tcp的报文都是按序向上交付的,URG的作用是表示报文中携带了紧急数据,需要被优先处理。
    URG是配合16位紧急指针来使用的,该指针指向要发送报文的数据中的一个字节。即为紧急数据。
    一般当服务器没有任何响应的时候,客户端可以发送一个紧急数据,服务端回复一个紧急数据(通常是错误码)
    来判断哪里出了问题,这种方法并不常用。
    在应用层写send和recv的时候,向参数flag传入MSG_OOB即可。

    6.7FIN

    **FIN是断开链接的属性。**一般而言,建立链接的是client,断开链接时双方的事情,随时都可以发生。当客户端发出断开链接属性的报文时,由于是全双工的,此时server仍然可以向客户端发数据,一旦server收到了具有断开链接属性的报文,在对该报文做应答之后,双方彻底停止数据通信,然后server还会向客户端发送带有FIN的报文,client响应后表示链接彻底断开。以上即为四次挥手的过程,即关闭链接达成一致的过程:
    在这里插入图片描述

    6.8 为什么是三次握手,四次挥手

    6.8.1三次握手原因

    本质上是验证两者的收发能力。
    1.确认双方主机是否健康,只有主机健康才能传输信息。
    2.验证全双工,三次握手我们是能看到双方都有收发的最小次数。
    在这里插入图片描述
    第一次握手和第二次握手可以验证客户端的收发能力,因为在第二次握手时可以确定第一次握手成功,同时第二次握手时client来接收。
    对于server来说,第一次握手和第三次握手可以验证它的收发能力。

    3.还可以预防一些SYN攻击,如果是一次握手的话,就可能有被大量SYN链接请求攻击的影响,因为建立一个链接就需要消耗一些资源。三次握手可以让客户端也来进行链接管理,从而增加攻击成本。

    6.8.2四次挥手原因

    断开链接的本质是是双方达成链接都应该断开的共识,并互相通知,因此是四次握手。它是协商断开链接的最小次数。
    双方都要有一个提出链接断开,以及同意对方链接断开请求的过程。

    6.9 验证TIME_WAIT

    6.9.1 现象

    通过四次握手的图片我们可以看出,主动断开链接的一方会进入一个TIME_WAIT的状态,在进入该状态的时候,链接暂时不会被断开,因为最后的ACK不一定收到(没有响应)。断开链接不一定成功。在链接断开的一段时间内,我们可以观察到这一状态:
    我们可以使用服务端作为这一测试源,这里给出简单的服务端代码,可以完整Sock等部分代码可以参考我的博客http协议(协议发送接收格式)

    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            Usage(argv[0]);
            return -1;
        }
        uint16_t port = atoi(argv[1]);
        int listen_sock = Sock::Socket();
        Sock::Bind(listen_sock, port);
        Sock::Listen(listen_sock);
        while (true)
        {
            // int sock = Sock::Accept(listen_sock);
            // if (sock > 0)
            // {
            //     pthread_t tid;
            //     int *pram = new int(sock);
            //     pthread_create(&tid, nullptr, HandlerHttpRequest, (void *)pram);
            // }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    我们将accept部分注释掉,可以顺便验证一下accept的作用是将链接传到上层去,此时链接已经被建立完成了。
    首先将服务器启动:
    在这里插入图片描述
    此时可以看到服务器已经处于监听的状态了。
    此时我们再打开一个服务器,使用telnet来链接该服务器(telnet的底层也是使用tcp的):
    在这里插入图片描述
    此时使用netstat -ntp就可以看到链接建立成功了:
    在这里插入图片描述
    同时也可以看到链接我们的服务器的IP等信息。
    然后我们关闭服务器,就可以看到在服务器处查询到的链接状态即服务器的状态变成了TIME_WAIT
    要看到TIME_WAIT状态还需要将accept的代码放开:

            int sock = Sock::Accept(listen_sock);
            cout<<sock<<endl;
    
    • 1
    • 2

    在这里插入图片描述
    当将服务端关掉,就可以看到TIME_WAIT的状态了。注意,此时的链接依然是被连着的,端口还是被占用状态,因此当我们再次绑定相同端口会出错。当过一段时间再次查看的时候,发现链接断开了,端口号也可以重新进行链接了:
    在这里插入图片描述

    6.9.2 MSL

    通常来说,TIME_WAIT的时间是两个MSL,MSL表示的是报文最大的生存时间,即在统计在双方发送的报文发送的最长时间作为MSL。在Linux系统下通常为60s。
    TIME_WAIT是两个MSL的原因:

    1.尽量保证历史发送的网络数据在网络中消散。(有可能对方在收到断开请求时,刚刚发送了一个数据,数据一来一回刚好需要两个MSL作为保证)
    2.尽量的保证最后一个ACK被对方收到。

    6.9.3 解决bind error

    因为我们是断开链接的一方,TIME_WAIT的时候链接还在连着(四次挥手还没有完成,链接还没有释放,端口还是被占用着)
    使用setsockopt函数来解决这一问题,该函数可以用于地址复用。

    static int Socket()
    {
        int sock=socket(AF_INET,SOCK_STREAM,0);
        if(sock<0)
        {
            cerr<<"socket error"<<endl;
            return -1;
        }
        int opt=1;
        setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
        return sock;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    6.10 CLOSE_WAIT

    在应用层调用close关闭文件操作符的作用就是四次握手中的两次握手,即向对方发出FIN的信号。如果对方已经退出了而不关闭文件操作符呢?即我们让客户端关闭文件描述符,而服务端不去关闭,最终造成的结果是server进入CLOSE_WAIT的状态:
    在这里插入图片描述
    此时后两次挥手并没有成功,这也就给我们了一些启示:

    1.一个fd被用完一定别忘记释放
    2.fd是有限的,有一个就会少一个fd,最终可能造成fd泄漏问题。

    6.11 listen的第二个参数

    6.11.1 全连接与半连接

    在写TCP的代码的时候,将listen的第二个参数置为了5,。TCP需要对网络连接进行管理,因此就产生了两个队列,分别称为全连接队列和半连接队列。
    其中,半连接队列用来保存处于SYN_SENT和SYN_RECV状态的请求,而全连接队列用于保存处于establish状态,但是还没有被accept拿走的请求。全连接的队列长度等于listen第二个参数+1

    6.11.2验证

    我们将listen的第二个参数设为2,将accept部分的代码注释掉,使用多个客户端来链接服务端,观察链接状态:

    static void Listen(int sock)    
    {    
        if(listen(sock,1)<0)                                                                                                                               
        {    
            cerr<<"listen error"<<endl;    
            exit(-3);    
        }    
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    可以看到,当我们链接两个时,链接的状态都是established,但是当我们链接第三个的时候,可以看到链接的状态变成了SYN_RECV
    在这里插入图片描述
    这就表示三次握手没有完成,第三次链接处于半连接的状态,因为没有accept,全连接的队列长度为(1+1=2),因此后序链接只能是半连接。注意,listen的第二个参数加1表示的是,全连接队列的长度(没有被accept上去),而不是服务端所能维护的连接个数。

    6.11.3维护全连接的原因

    我们可以将其想象成一家火锅店,在火锅店的里面有很多座位,它代表内存和套接字资源,在火锅店的外面通常也会放几把椅子,当火锅店人满了的时候,再来客人可以在外面进行等待叫号,这排椅子代表全连接队列。
    如果没有这排椅子的话,客人看到火锅店满了可能就会直接选择离开,而当火锅店内的资源空出来的时候,由于没有人等待,有可能会空闲一段时间,这就造成了一些时间空间上的浪费。所以全连接的作用是:保证资源被充分的利用。
    注意,这个队列也不应该太长,因为维护队列也是有成本的,而且等待时间也不要太长,与其花费成本去维护全连接队列,不如提升服务器本身的性能。

    7.超时重传机制

    当我们发送对应的报文时,该报文没有回收到ACK,可能是报文丢了也可能是返回的ACK丢了,无论是哪一种情况,都会触发超时重传机制。
    如果响应的ACK丢了的话,就可能导致接收方收到两次报文(第一次+超时重传的),不过没关系,接收方会根据报文中的序号来识别重复报文,从而将重复报文进行丢弃。
    时间间隔:网络状态是不断变化的,网络通信的效率是变化的,发送数据得到ACK的时间也是浮动的,因此超时重传的时间间隔一定是浮动的。

    8.滑动窗口

    8.1滑动窗口的引入

    我们了解了TCP的确认应答机制,对每一次发送的数据段(将发送缓冲区看成一个大数组),都要给一个ACK确认应答,收到ACK之后再发送下一个数据段,这样做有一个比较大的缺点,就是性能较差,尤其是数据的往返之间较长的时候。
    其实我们发送数据的时候是可以一次发送多条数据的,有一部分数据是暂时不需要ACK来进行响应的,这样就可以大大提高效率了。
    在这里插入图片描述
    即当发送的数据数组下标到1000的时候,客户端在没有收到1001的确认信号的情况下,就从1001开始发送数据了。形成这种情况的是滑动窗口。

    8.2滑动窗口的位置

    在这里插入图片描述
    滑动窗口是存在于发送缓冲区中的,我们可以将发送缓冲区分为三个部分:
    1:已经发送,且已经确认的数据。
    2:可以/已经发送,但是还没有收到确认(可以暂时不要ACK)。
    3:还没有发送的数据。
    其中我们将2区域称为滑动窗口的区域。

    8.3滑动窗口的工作原理

    首先,我们要明确的是滑动窗口的大小是可变的。并且它的大小是与接收端的剩余接收缓冲区大小是有关的(即发回的TCP中的16位窗口大小)
    我们可以把发送缓冲区理解成一个大数组,并定义两个指针min_start和min_end分别指向滑动窗口的起始端和结束端。
    当发送端向接收端发送数据之后,接收端会返回一个确认序号,确认序号表示的是收到的报文的序号之前的数据都已经收到了,因此将min_start=确认序号。滑动窗口左端右移。
    接收端还会返回一个16位窗口大小,表示的是剩余的发送缓冲区的空间大小,此时只需要将min_end+=16位窗口大小,即可。
    那么如果中间的报文丢失的怎么办呢?

    8.4特殊情况处理

    8.4.1 数据包到达,ACK丢失

    当数据包到达,ACK丢失的时候,可以由后序的ACK来进行确认,因为确认信号的定义就是收到了确认序号之前的所有报文:
    在这里插入图片描述
    部分的ACK丢包了不要紧,因为可以通过后序的ACK来进行确认。比如1001的ACK丢包了,但是后序收到了6001的确认信息,这说明6001之前的所有数据包都被收到了,即1001也被收到了。不然服务端不会发送6001的确认序号。

    8.4.2数据包丢失

    当数据包丢失的时候,根据确认信号的性质,它代表的是确认序号之前的所有的字节序都已经被收到,但是1001到2000没有收到,如果发送回2001等就会表示已经收到了确认序号,因此它只会发送1001。
    在这里插入图片描述
    当客户端连续收到三次同样的确认应答序号的时候,就会意识到有数据包丢失了,因为一次没此时就会从确认序号的部分进行重发,重新发送之后收到的确认序号是8001,这代表8001之前的都已经被收到了,可以继续正常发送了。
    这种机制我们也称为快重传。

    如果只有两个报文呢?丢包的话就不会收到三个同样的确认应答,这就需要使用超时重传的机制了,快重传的效率比超时重传更高一些,因此我们引入了快重传的机制。

    9.流量控制

    9.1流量控制的概念

    接收端处理数据的速度是有限的,如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而因此丢包重传等一系列的连锁反应。
    因此TCP支持接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制。这个其实就是通过之前所讲的TCP的16位窗口大小来进行接收缓冲区剩余空间大小的传递的。

    9.2第一次流量控制

    我们发现,在第一次发送数据的时候,由于并不知道对方接收缓冲区的大小,因此无法控制发送数据的多少?
    其实在三次握手的时候,双发的窗口大小就已经交互了,而且三次握手的时候也不需要交换数据,期间协商窗口大小正好合适。然后根据对方的接收缓冲区的窗口大小来确认自己滑动窗口的初始值。

    9.3对方的接收缓冲区满了

    当对方的接收缓冲区满了的时候,发送端会停止发送数据,通常有两种策略:

    1.发送端进行定期的窗口探测,发送带有PSH属性的报文(不带任何数据)来检测对方发送缓冲区是否有空间,并催促对方赶紧向上层提交数据。
    2.接收端窗口更新之后,会通知发送端来发送数据。

    在TCP协议中,这两种方式是可以配合使用的。

    10.拥塞控制

    10.1拥塞控制的概念

    虽然TCP有了滑动窗口的概念,可以高效可靠地发送大量的数据,但是如果在开始阶段就发送大量的数据,仍然可能引发问题。因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵,在不清楚网络状态的情况下,贸然发送大量的数据有可能造成网络的拥堵。
    因此TCP还引入了慢启动的机制,先发送少量的数据,探探路,摸清当前网络拥堵的状态,再决定按照多大的速度传输数据。
    注意,网络是大家在共同使用的,因此所有的主机都会执行慢启动的策略。

    10.2拥塞窗口

    拥塞窗口用来表示当前网络最大承载该主机发送数据的大小。它与接收方发送的16位窗口大小共同限制发送方的滑动窗口的大小:
    滑动窗口大小等于拥塞窗口大小和16位窗口大小之间的最小值。
    拥塞窗口的大小是会进行增长的,在刚开始会从1开始呈现指数级别的增长,当达到一定限度的时候就会呈现线性程度的增长,该限度称为慢启动的阈值。
    在这里插入图片描述
    通过这个图可以看到,当数据发送还没达到阈值的时候,窗口大小是呈现指数级别增长的,当达到阈值之后就会呈现线性增长。直到遇到网络拥堵的状态停止增长,此时拥塞窗口变为1,阈值变为达到拥塞的一半,拥塞窗口继续以相同规则进行增长。

    11.延迟应答

    如果收到数据立刻返回ACK应答,到时候返回的窗口有可能比较小。
    比如说对方接收缓冲区还有100K的空间,收到100K的数据,如果立刻进行应答的话,返回的16位窗口大小就是0,发送方就不会再继续发送数据了。
    但是如果等了一会再去应答的话,这期间应用层就有可能将接收缓冲区的数据读到上层,返回的16位窗口大小就会变大。
    这种处理方法称为延迟应答。
    通常延迟的时间可以以数据包为基准,也可以以时间限制为基准,比如收到N个包就去应答一次,再比如超过最大延迟时间就应答一次。
    不过如果接收方上层处理数据的速度较慢的话,这种延迟应答的作用就不会很明显。

    12.捎带应答

    捎带应答指的是在发送的报文可以携带数据的同时也作为一个应答信号,比如我们的三次握手,在第二次握手中发送的报文既带有SYN的信息,又带有ACK的信息,是请求链接的同时,对对方的请求进行应答。

    13.面向字节流

    13.1概念

    与UDP的面向数据报不同,TCP协议是面向字节流的,其本质原因在于,使用TCP协议创建套接字的时候会同时建立一个发送缓冲区和一个接收缓冲区。这样做就相当于对应用层和传输层进行了解耦。
    比如在应用层有一个http协议需要传输,我们知道http协议是以行为单位的(即以\n为单位),但是这只是对于应用层而言,对于TCP来说无论是数据还是\n都表示的是字符,一视同仁,它不关心哪些数据组成了一个http协议,只关心如何将数据以字节的方式发送出去。
    在这里插入图片描述
    在TCP发送数据的时候,可能发送的是完整的http也可能是不完整的http,将每一个http区分开是上层的工作,TCP并不关心,在它看来它传输的是一个个的字节。这就叫做面向字节流。流表示如同流水一般。

    调用write时,数据会先写入发送缓冲区;
    如果发送的字节数太长,会被拆分成多个TCP的数据包发出。
    如果发送的字节数太短,就会先在缓冲区里等待,等待到缓冲区长度差不多了,或者其他合适的实际发送出去。
    接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区;
    然后应用程序可以调用read从接收缓冲区中拿数据。
    另一方面,TCP的一个链接,既有发送缓冲区,也有接收缓冲区,那么对于一个链接,既可以读数据,也可以写数据,这个概念叫做全双工。
    由于缓冲区的存在,当写100个字节的时候,可以调用一次write一次写100个字节,也可以调用100次write每次写一个字节。

    注意UDP是面向数据报的,因此数据要么不发送,要么就把整个数据完全发送,不存在发送一半数据这种情况。
    从他们的结构差异也可以看出:UDP协议中有总长度,因此可以进行拆分。而TCP协议中只有首部长度没有总长度,这也说明了TCP在发送的时候并不关心发送的信息是否完整。

    13.2粘包问题

    由于TCP的这种发送数据的形式,不能确定发送的http数据是否是完整的,如果htto协议中的Content-Length写错了,就会导致两个http协议粘在一起等情况,称这种情况为粘包问题。
    解决粘包问题的方式是**明确两个包之间的界限。**这是交给应用层来进行处理的。

    14.TCP异常问题

    进程终止:进程终止会释放文件描述符,仍然可以发送FIN,和正常关闭没有什么区别。
    机器重启:和进程终止的情况相同。
    机器掉电/网线断开:接收端认为链接还在,一旦接收端有写入的操作,接收端发现链接已经不在了,就会进行reset。即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在,如果对方不在,也会把链接释放。

    另外,应用层的某些协议,也有这样的检测机制,例如HTTP的长链接中,也会定期检测对方的状态,例如QQ,在QQ断线之后,也会定期尝试重新链接。

    14.TCP小结

    14.1可靠性

    校验和,序列号(按序到达),确认应答,超时重发,链接管理。流量控制,拥塞控制。

    14.2提高性能

    滑动窗口,快速重传,延迟应答,捎带应答

    14.3基于TCP的网络协议

    HTTP,HTTPS,SSH,Telnet,FTP,SMTP,当然也包括自己写的一些基于TCP的应用层协议。

    14.4TCP与UDP对比

    TCP是可靠连接,UDP是不可靠连接,注意可靠与不可靠是中性词。
    TCP用于可靠传输的情况,应用于文件传输,重要状态更新等场景;
    UDP主要用于高速传输和实时性要求较高的通信领域,比如直播,视频传输等。
    总之,TCP和UDP都是程序员的工具,什么时机使用还是需要具体情况具体分析。

    14.5UDP应用层实现可靠传输

    参考TCP可靠性机制,可以实现类似的逻辑:

    引入序列号,保证数据顺序。
    引入确认应答,确保对端收到了数据。
    引入超时重传,如果隔一段时间没有应答,就重发数据。

  • 相关阅读:
    【PCL库+ubuntu+C++】1. 点云界的hello world!
    数学问题:导函数的左右极限与函数的左右导数是一回事吗?
    模块与组件、模块化与组件化的理解以及react组件的创建
    图神经网络 | 混合神经网络模型GCTN地铁客流预测
    如何做红烧肉好吃又不腻 教你做红烧肉
    python应用(9)——将一个文件夹里的图片分配到多个文件夹内
    分库分表系列:分库分表的前世今生
    基于matlab的排队系统仿真
    前端开发语言有哪些
    图书管理系统
  • 原文地址:https://blog.csdn.net/qq_51492202/article/details/126566686