美文网首页
网络相关

网络相关

作者: 万福来 | 来源:发表于2020-05-12 11:06 被阅读0次

    网络相关

    Linux内核将所有的外部设备都看作一个文件来操作,对一个文件的读写操作都会调用内核提供的一个系统命令,返回一个file descriptor (fd,文件描述符)。而对一个socket的读写也会有相应的描述符,称为socket描述符。

    UNIX提供了5种I/O模型

    • 阻塞型I/O:最常用的模型,默认情况下,所有文件操作都是阻塞的,套接字在进程空间中调用recvfrom函数,系统调用直到数据包到达且被复制到应用进程的缓冲区中或者发生错误时才返回,在此期间会一直等待。
    • 非阻塞型I/O:revcfrom用应用层到内核的时候,如果该缓冲区没有数据,就直接返回一个错误码,一般都对非阻塞I/O模型进行轮询检查这个状态,来判断内核是不是有数据到来。
    • I/O复用模型:linux提供select/poll,进程通过将一个或者多个文件描述符传递给select或poll系统调用,阻塞在select操作上,这样select/poll可以帮助我们监听多个文件描述符是否处于就绪状态,select/poll是顺序扫描文件描述符链表,而且支持的文件描述符数量有限。所以linux后期又提供了epoll系统调用,epoll使用基于事件驱动方式代替顺序遍历链表,时间复杂度由O(N)优化为O(1),因此性能更高。当有文件描述符就绪时,通过回调函数进行通知用户进程。
    • 信号驱动I/O模型:首先开启套接口信号驱动I/O功能,并通过系统调用执行信号处理函数,当数据就绪后,就为该进程生成一个sigio信号,通过信号回调通知应用程序。
    • 异步I/O模型:告知内核启动某个操作,并让内核在整个操作完成后通知我们。与信号驱动模型的区别是,信号驱动I/O由内核通知我们何时开始一个I/O操作;异步I/O模型由内核通知我们I/O操作何时已经完成。

    I/O多路复用技术原理

    I/O多路复用技术通过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。
    目前支持I/O多路复用的系统调用有select、pselect、poll、epoll;

    select/poll 与 epoll 对比

    • select支持的socket描述符数量受限制,可以通过FD_SETSIZE设置,默认值1024;epoll没有这个FD的数量限制。
    • select/poll每次调用都会线性遍历扫描整个链表,时间复杂度是O(N); 而epoll使用基于事件驱动方式代替顺序遍历链表,时间复杂度由O(N)优化为O(1),因此性能更高。
    • epoll可以通过内核和用户空间mmap同一块内存实现,加速内核和用户空间的数据传递。
    • epoll的api更加简单,包括创建epoll描述符,添加监听事件,阻塞等待所监听的事件发生,关闭epoll描述符。

    epoll内部实现大概如下:

    • epoll初始化时,会向内核注册一个文件系统,用于存储被监控的句柄文件,调用epoll_create时,会在这个文件系统中创建一个file节点。同时epoll会开辟自己的内核高速缓存区,以红黑树的结构保存句柄,以支持快速的查找、插入、删除。还会再建立一个list链表,用于存储准备就绪的事件。

    • 当执行epoll_ctl时,除了把socket句柄放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后,就把socket插入到就绪链表里。

    • 当epoll_wait调用时,仅仅观察就绪链表里有没有数据,如果有数据就返回,否则就sleep,超时时立刻返回。

    • BIO 就是传统的 java.io包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者写入输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。它的优点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。

    • NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的对象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。

    • AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非阻塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

    基于BIO实现的伪异步I/O模型

    采用单线程循环处理accept事件,当有客户端接入时,将客户端的socket封装成一个实现Runnable的Task放到后端的线程池中进行,线程池中维护一个消息队列N个活跃线程对队列中的任务进行异步处理。队列大小和线程数量可以固定大小,资源占用也就可控的,不用为每个客户端创建一个线程。

    • 伪异步I/O的优点:
      将accept事件和socke的读写事件进行了线程隔离,读写事件采用线程池进行了异步处理,不用为每个客户端创建一个单独的线程。
    • 伪异步I/O的缺点:
      socket的输入流和输出流的读写操作都是阻塞的,网络传输较慢时,读取输入流的线程会一直阻塞,如果线程池中的线程都处于读写阻塞中,其他消息只能在消息队列中排队,甚至将队列打满。所以无法从根本上解决同步I/O导致的通信线程阻塞问题。

    基于I/O多路复用实现NIO

    NIO 是利用了单线程轮询事件的机制,通过高效地遍历就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。

    1. 首先,通过 Selector.open() 创建一个 Selector,作为类似调度员的角色;
    2. 然后,创建一个 ServerSocketChannel,并且向 Selector 注册,通过指定 SelectionKey.OP_ACCEPT,告诉调度员,它关注的是新的连接请求;
    3. 为什么我们要明确配置非阻塞模式呢?这是因为阻塞模式下,注册操作是不允许的,会抛出 IllegalBlockingModeException 异常;
    4. Selector 阻塞在 select 操作,当有 Channel 发生接入请求,就会被唤醒;
    5. 然后根据selectionKey与客户端建立链接,链接成功后,获取ServerSocketChannel通道,
      并将该通道向Selector注册,开始关注通道可读状态;
    6. 当有客户端写入数据,通道状态为可读状态时,selector线程会通知开始处理读取客户端数据;
    7. 主要通过缓冲区ByteBuffer来读取数据,读是非阻塞的。
    8. 读完客户端数据,开始写处理,写也是非阻塞的。

    缓冲区 Buffer

    在NIO类库中,所有数据都是通过缓冲区处理的,在读取数据时,直接读到缓冲区中,写入数据时,写到缓冲区中。任何时候访问NIO中的数据都是通过缓冲区进行操作。
    缓冲区本质是数组,常用的实现类有


    image.png

    通道 Channel

    Channel 与InputStream/OutputStream区别
    Channel 是一个通道,可以用于读、写或者同时读写,是全双工的,可以双向流通,不阻塞;
    Channel 主要有两类,分别用于网络读写的和文件读写操作;
    InputSteam 是输入流,只能用于读数据,而且没有数据时会一直阻塞;
    OutputStream 是输出流,只能用于写入输出数据;

    多路复用器 Selector

    Selector可以通过一个线程轮询注册在其上的Channel,如果某个channel上有了新的TCP连接接入、读和写事件,这个channel就处于就绪状态,会被selector轮询出来,然后通过selectionKey可以获取就绪channel的集合,进行后续的I/O操作。
    一个Selector可以同时轮询多个Channel,在jdk5update10版本中进行了优化,使用epoll代替了原来的select/poll实现,没有了最大连接接句柄的限制。

    NIO2.0 又叫AIO

    NIO2.0引入了新的异步通道的概念,提供了异步文件通道和异步套接字通道的实现,异步通道提供两种方式获取操作结果。

    • 通过java.util.concurrent.Feture类表示异步操作的结果。
    • 在执行操作的时候传入一个java.nio.channels。
      在异步I/O操作的时候可以传递信号变量,当操作完成之后会回调相关方法。它不需要通过多路复用器( Selector) 对注册的通道进行轮询操作即可实 现异步读写,从而简化了NIO 的编程模型。

    Reactor单线程模型

    由于Reactor模式使用的是异步非阻塞IO,所有的IO操作都不会被阻塞,理论上一个线程可以独立处理所有的IO操作。这时Reactor线程是个多面手,负责多路分离套接字,Accept新连接,并分发请求到处理链中。


    image.png

    对于一些小容量应用场景,可以使用到单线程模型。但对于高负载,大并发的应用却不合适,主要原因如下:
    当一个NIO线程同时处理成百上千的链路,性能上无法支撑,即使NIO线程的CPU负荷达到100%,也无法完全处理消息
    当NIO线程负载过重后,处理速度会变慢,会导致大量客户端连接超时,超时之后往往会重发,更加重了NIO线程的负载。
    可靠性低,一个线程意外死循环,会导致整个通信系统不可用
    为了解决这些问题,出现了Reactor多线程模型。

    Reactor多线程模型

    image.png

    相比上一种模式,该模型在处理链部分采用了多线程(线程池)。
    在绝大多数场景下,该模型都能满足性能需求。但是,在一些特殊的应用场景下,如服务器会对客户端的握手消息进行安全认证。这类场景下,单独的一个Acceptor线程可能会存在性能不足的问题。为了解决这些问题,产生了第三种Reactor线程模型

    Reactor主从模型

    image.png

    该模型相比第二种模型,是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接;并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,读写网络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同。

    Netty其实本质上就是Reactor模式的实现,Selector作为多路复用器,EventLoop作为转发器,Pipeline作为事件处理器。但是和一般的Reactor不同的是,Netty使用串行化实现,并在Pipeline中使用了责任链模式。Netty可以同时支持Reactor单线程模型、多线程模型和主从模型。默认使用主从模型的变种,
    将Reactor分成两部分,mainReactor负责监听server socket,accept新连接;并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,主要负责读写网络数据。
    当系统在运行过程中,如果频繁的进行线程上下文切换,会带来额外的性能损耗。多线程并发执行某个业务流程,业务开发者还需要时刻对线程安全保持警惕,哪些数据可能会被并发修改,如何保护?这不仅降低了开发效率,也会带来额外的性能损耗。
    为了解决上述问题,Netty采用了串行化设计理念,从消息的读取、编码以及后续Handler的执行,始终都由IO线程EventLoop负责,这就意外着整个流程不会进行线程上下文的切换,数据也不会面临被并发修改的风险。这也解释了为什么Netty线程模型去掉了Reactor主从模型中线程池。
    Netty提供的经过扩展的Buffer相对NIO中的有个许多优势,作为数据存取非常重要的一块,我们来看看Netty中的Buffer有什么特点。

    1.ByteBuf读写指针

    在ByteBuffer中,读写指针都是position,而在ByteBuf中,读写指针分别为readerIndex和writerIndex,直观看上去ByteBuffer仅用了一个指针就实现了两个指针的功能,节省了变量,但是当对于ByteBuffer的读写状态切换的时候必须要调用flip方法,而当下一次写之前,必须要将Buffe中的内容读完,再调用clear方法。每次读之前调用flip,写之前调用clear,这样无疑给开发带来了繁琐的步骤,而且内容没有读完是不能写的,这样非常不灵活。相比之下我们看看ByteBuf,读的时候仅仅依赖readerIndex指针,写的时候仅仅依赖writerIndex指针,不需每次读写之前调用对应的方法,而且没有必须一次读完的限制。

    2.零拷贝

    Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
    Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。
    Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。

    3.引用计数与池化技术

    在Netty中,每个被申请的Buffer对于Netty来说都可能是很宝贵的资源,因此为了获得对于内存的申请与回收更多的控制权,Netty自己根据引用计数法去实现了内存的管理。Netty对于Buffer的使用都是基于直接内存(DirectBuffer)实现的,大大提高I/O操作的效率,然而DirectBuffer和HeapBuffer相比之下除了I/O操作效率高之外还有一个天生的缺点,即对于DirectBuffer的申请相比HeapBuffer效率更低,因此Netty结合引用计数实现了PolledBuffer,即池化的用法,当引用计数等于0的时候,Netty将Buffer回收致池中,在下一次申请Buffer的没某个时刻会被复用。

    Netty中的关键概念

    EventLoopGroup:一个 Netty 程序启动时, 至少要指定一个 EventLoopGroup对象,EventLoopGroup内有很多个EventLoop,负责处理事件;
    EventLoop: 负责处理各种事件,相当于NIO的一个selector;
    Channel:即通讯的通道,发送接受数据
    ChannelPipeline:Channel的数据管道
    ChannelHandler:可看作是处理ChannelPipeline中传输的数据的工具
    ChannelHandlerContext:使得ChannelHandler可以和ChannelPipeline或其他handler进行交互等。
    netty默认采用的是主从Reactor模型,一个是bossGroup,一个是workerGroup;
    每个分组都可以分别指定里边的线程数量或者是EventLoop数量。
    boosGroup主要负责处理连接事件,连接成功后,会将该连接重新注册到workerGroup,该分组不涉及具体业务逻辑,一般设置一个线程即可,而workerGroup主要用来处理连接成功后的数据读写事件,每个线程都会负责该连接下的所有读写事件,不会进行线程切换,所以该分组线程池设置多些。在Netty中,当有连接请求时,会从EventLoopGroup中拿到一个EventLoop,EventLoop会绑定一个Channel,随后做出具体的处理。每一个请求都有一个EventLoop去处理。EventLoop在这里扮演的角色就相当于一个线程用来监听selector事件,而EventLoopGroup就相当于线程池,负责管理调度EventLoop。

    Netty的数据读写流程

    那么在完成绑定Channel的操作后,具体怎么处理数据呢?Netty中,将Channel的数据管道抽象为ChannelPipeline,消息数据会在ChannelPipeline中流动和传递。ChannelPipeline是ChannelHandler的容器,持有I/O事件拦截器ChannelHandler的链表,负责对ChannelHandler的管理和调度。ChannelHandler可以看成处理ChannelPipeline中数据的工具,可以处理I/O事件或者拦截I/O操作, 并转发给它所在ChannelPipeline中的下一个handler。我们可以方便地新增和删除ChannelHandler来实现不同业务逻辑的处理。但是ChannelPipeline不是直接管理ChannelHandler的,而是通过ChannelHandlerContext来间接管理。Channel处理框图如下:


    image.png

    Netty中根据事件源头的不同将handler分为两种:InBound和OutBound。InBound事件通常由I/O线程触发,例如TCP连接建立和关闭、读事件等等,分别会触发相应的handler的方法。而OutBound事件则一般由用户主动发起的网络I/O操作,例如用户发起的连接操作,绑定操作和消息发送操作等,也会分别触发相应的方法。
    可以从上图看到,Handler由headHandler到tailHandler组成了一条双向链表,Handler链使用了责任链的设计模式(类似在web开发中的filter和拦截器),当事件传入ChannelPipeline中后,会经过一个个handler的处理。当触发ChannelRead事件的时候,消息将从headHandler至tailHandler依次处理;当调用ChannelHandlerContext的write方法发送消息进行通讯时,消息先从tailHandler开始,经过一系列handler处理后传递至headHandler,最终被添加到消息发送缓冲区后刷新输出。

    网络体系结构分层

    image.png

    TCP/IP基础

    TCP/IP 的具体含义

    从字面意义上讲,有人可能会认为 TCP/IP 是指 TCP 和 IP 两种协议。实际生活当中有时也确实就是指这两种协议。然而在很多情况下,它只是利用 IP 进行通信时所必须用到的协议群的统称。具体来说,IP 或 ICMP、TCP 或 UDP、TELNET 或 FTP、以及 HTTP 等都属于 TCP/IP 协议。他们与 TCP 或 IP 的关系紧密,是互联网必不可少的组成部分。TCP/IP 一词泛指这些协议,因此,有时也称 TCP/IP 为网际协议群。
    互联网进行通信时,需要相应的网络协议,TCP/IP 原本就是为使用互联网而开发制定的协议族。因此,互联网的协议就是 TCP/IP,TCP/IP 就是互联网的协议。

    数据包

    包、帧、数据包、段、消息
    以上五个术语都用来表述数据的单位,大致区分如下:
    包可以说是全能性术语;
    帧用于表示数据链路层中包的单位;
    数据包是 IP 和 UDP 等网络层以上的分层中包的单位;
    段则表示 TCP 数据流中的信息;
    消息是指应用协议中数据的单位。
    每个分层中,都会对所发送的数据附加一个首部,在这个首部中包含了该层必要的信息,如发送的目标地址以及协议相关信息。通常,为协议提供的信息为包首部,所要发送的内容为数据。在下一层的角度看,从上一层收到的包全部都被认为是本层的数据。

    传输层中的 TCP 和 UDP

    TCP/IP 中有两个具有代表性的传输层协议,分别是 TCP 和 UDP。

    TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,当应用程序采用 TCP 发送消息时,虽然可以保证发送的顺序,但还是犹如没有任何间隔的数据流发送给接收端。TCP 为提供可靠性传输,实行“顺序控制”或“重发控制”机制。此外还具备“流控制(流量控制)”、“拥塞控制”、提高网络利用率等众多功能。
    UDP 是不具有可靠性的数据报协议。细微的处理它会交给上层的应用去完成。在 UDP 的情况下,虽然可以确保发送消息的大小,却不能保证消息一定会到达。因此,应用有时会根据自己的需要进行重发处理。
    TCP 和 UDP 的优缺点无法简单地、绝对地去做比较:TCP 用于在传输层有必要实现可靠传输的情况;而在一方面,UDP 主要用于那些对高速传输和实时性有较高要求的通信或广播通信。TCP 和 UDP 应该根据应用的目的按需使用。

    端口号

    数据链路和 IP 中的地址,分别指的是 MAC 地址和 IP 地址。前者用来识别同一链路中不同的计算机,后者用来识别 TCP/IP 网络中互连的主机和路由器。在传输层也有这种类似于地址的概念,那就是端口号。端口号用来识别同一台计算机中进行通信的不同应用程序。因此,它也被称为程序地址。

    三次握手建立连接

    1. 第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
    2. 第二次握手:服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
    3. 第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。

    四次握手断开连接

    中断连接端可以是客户端,也可以是服务器端。

    1. 第一次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态。意思是说"我客户端没有数据要发给你了",但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据。
    2. 第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端,你的请求我收到了,但是我还没准备好,请继续你等我的消息。这个时候客户端就进入FIN_WAIT_2 状态,继续等待服务器端的FIN报文。
    3. 第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端,好了,我这边数据发完了,准备好关闭连接了。服务器端进入LAST_ACK状态。
    4. 第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。服务器端收到ACK后,就知道可以断开连接了。客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。最终完成了四次握手。

    通过序列号与确认应答提高可靠性

    • 在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个已收到消息的通知。这个消息叫做确认应答(ACK)。当发送端将数据发出之后会等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。反之,则数据丢失的可能性很大。
    • 在一定时间内没有等待到确认应答,发送端就可以认为数据已经丢失,并进行重发。由此,即使产生了丢包,仍然能够保证数据能够到达对端,实现可靠传输。
    • 未收到确认应答并不意味着数据一定丢失。也有可能是数据对方已经收到,只是返回的确认应答在途中丢失。这种情况也会导致发送端误以为数据没有到达目的地而重发数据。
    • 此外,也有可能因为一些其他原因导致确认应答延迟到达,在源主机重发数据以后才到达的情况也屡见不鲜。此时,源主机只要按照机制重发数据即可。
    • 对于目标主机来说,反复收到相同的数据是不可取的。为了对上层应用提供可靠的传输,目标主机必须放弃重复的数据包。为此我们引入了序列号。
    • 序列号是按照顺序给发送数据的每一个字节(8位字节)都标上号码的编号。接收端查询接收数据 TCP 首部中的序列号和数据的长度,将自己下一步应该接收的序列号作为确认应答返送回去。通过序列号和确认应答号,TCP 能够识别是否已经接收数据,又能够判断是否需要接收,从而实现可靠传输。

    超时重发机制(TCP重传机制)

    • 超时重发是指在重发数据之前,等待确认应答到来的那个特定时间间隔。如果超过这个时间仍未收到确认应答,发送端将进行数据重发。最理想的是,找到一个最小时间,它能保证“确认应答一定能在这个时间内返回”。
    • TCP 要求不论处在何种网络环境下都要提供高性能通信,并且无论网络拥堵情况发生何种变化,都必须保持这一特性。为此,它在每次发包时都会计算往返时间及其偏差。将这个往返时间和偏差时间相加,重发超时的时间就是比这个总和要稍大一点的值。
    • 在 BSD 的 Unix 以及 Windows 系统中,超时都以0.5秒为单位进行控制,因此重发超时都是0.5秒的整数倍。不过,最初其重发超时的默认值一般设置为6秒左右。
    • 数据被重发之后若还是收不到确认应答,则进行再次发送。此时,等待确认应答的时间将会以2倍、4倍的指数函数延长。
    • 此外,数据也不会被无限、反复地重发。达到一定重发次数之后,如果仍没有任何确认应答返回,就会判断为网络或对端主机发生了异常,强制关闭连接。并且通知应用通信异常强行终止。

    以段为单位发送数据

    • 在建立 TCP 连接的同时,也可以确定发送数据包的单位,我们也可以称其为“最大消息长度”(MSS)。最理想的情况是,最大消息长度正好是 IP 中不会被分片处理的最大数据长度。
    • TCP 在传送大量数据时,是以 MSS 的大小将数据进行分割发送。进行重发时也是以 MSS 为单位。
    • MSS 在三次握手的时候,在两端主机之间被计算得出。两端的主机在发出建立连接的请求时,会在 TCP 首部中写入 MSS 选项,告诉对方自己的接口能够适应的 MSS 的大小。然后会在两者之间选择一个较小的值投入使用

    利用窗口控制提高速度

    • TCP 以1个段为单位,每发送一个段进行一次确认应答的处理。这样的传输方式有一个缺点,就是包的往返时间越长通信性能就越低。
    • 为解决这个问题,TCP 引入了窗口这个概念。确认应答不再是以每个分段,而是以更大的单位进行确认,转发时间将会被大幅地缩短。也就是说,发送端主机,在发送了一个段以后不必要一直等待确认应答,而是继续发送。
    • 窗口大小就是指无需等待确认应答而可以继续发送数据的最大值。上图中窗口大小为4个段。这个机制实现了使用大量的缓冲区,通过对多个段同时进行确认应答的功能。

    相关文章

      网友评论

          本文标题:网络相关

          本文链接:https://www.haomeiwen.com/subject/jrzvnhtx.html