计算机网络
2022-04-13 17:08:19 62 举报
AI智能生成
计算机网络是一个复杂的系统,它由多个互联的计算机和通信设备组成,通过共享资源和服务来支持用户之间的信息交流和资源共享。计算机网络的主要组成部分包括服务器、客户端、传输介质和网络协议等。其中,服务器是提供共享资源的计算机,客户端则是使用这些资源的计算机。传输介质可以是电线、光纤、无线电波等,而网络协议则规定了数据在网络中的传输方式和格式。计算机网络的应用非常广泛,它可以用于数据传输、远程访问、电子邮件、文件共享、在线游戏等方面。随着互联网的发展,计算机网络已经成为现代社会不可或缺的一部分。
作者其他创作
大纲/内容
IO模型
基础知识
read系统调用
并不是直接从物理设备把数据读取到内存中,而是把数据从内核缓冲区复制到进程缓冲区
第一个阶段
等待数据从网络中到达网卡,当所等待的分组到达时,它被复制到内核中的某个缓冲区,这个工作系统自动完成,用户程序无感知
第二个阶段
是把数据从内核缓冲区复制到应用进程缓冲区
write系统调用
不是直接把数据写入物理设备,是把数据从进程缓冲区复制到内核缓冲区
缓冲区
用户程序的IO读写程序,在大多数情况下,并没有进行实际IO操作,而且在进程缓冲区和内核缓冲区之间直接进行数据交换。
底层操作会对内核缓冲区进行监控,等待缓冲区达到一定数量时,再进行IO设备的中断处理,集中执行物理设备的实际IO操作,这种机制提升了系统性能
系统调用read&write流程
上层程序的IO操作,实际上不是物理设备级别的读写,而是缓存的复制
网络交互过程
1.客户端请求,Linux通过网卡读取客户端的请求数据,将数据读取到内核缓冲区
2.获取请求数据,java服务器通过read系统调用,从Linux内核缓冲区中读取数据,再送入java进程缓冲区。
3.服务端业务处理,java服务器在自己的用户空间中处理客户端请求。
4.服务器端返回数据,java服务器完成处理之后,构建好响应数据,将这些从用户缓冲区写入内核缓冲区。
5.发送给客户端,Linux内核通过网络IO,将内核缓冲区中的数据写入网卡,网卡通过底层的通信协议,会将数据发送给目标客户端。
同步阻塞定义
同步
同步IO,是一种用户空间与内核空间的IO发起方式,同步IO是指用户空间的线程主动发起IO请求的一方,内核空间是被动接受方,异步IO则反过来,是指系统内核是主动发起IO请求的一方,用户空间线程时被动接受方。
阻塞
阻塞IO,指的是需要内核IO操作彻底完成后,才返回到用户空间执行用户的操作。阻塞指的是用户空间程序的执行状态。
非阻塞IO,指的是用户空间的程序不需要等待IO操作彻底完成,可以立即返回用户空间执行用户的操作,即处于非阻塞状态,与此同时内核会立即返回给用户一个状态值。
阻塞是指用户空间(调用线程)一直在等待,而不能干别的事情,非阻塞是指用户空间(调用线程)拿到内核返回的状态值就返回自己的空间,IO操作可以干就干,不可以干就去干别的事情。
BIO(阻塞IO)
同步阻塞IO的流程
在阻塞式IO中,java应用程序从IO系统调用开始,直到系统调用返回,在这段时间内,java进程是阻塞的。返回成功后,应用进程开始处理用户空间的缓存区数据。
1.从java启动IO读read系统调用开始,用户线程就进入阻塞状态,
2.当系统内核收到read系统调用,就开始准备数据,一开始,数据可能还没有到达内核缓冲区,例如,还没有收到一个完整的socket数据包,这个时候内核就要等待。
3.内核一直等到完整数据到达,就会将数据从内核缓冲区复制到用户缓冲区(用户内存),然后内核返回结果,(例如返回复制到用户缓冲区中的字节数)。
4.直到内核返回后,用户线程才会解除阻塞的状态,重新运行起来。
阻塞IO的特点是,在内核进行IO的两个阶段,用户线程都被阻塞了。
缺点
一般情况下,会为每个连接配备一个独立的线程,反过来说,就是一个线程维护一个连接的IO操作,在并发量小的情况下,这样做没有什么问题,但是,当在高并发的应用场景下,需要大量线程来维护大量的网络连接,内存,线程切换开销会非常大。
优化
伪异步I/O编程
NIO(同步非阻塞IO)
同步非阻塞IO流程
非阻塞socket的read读操作
1.在内核数据没有准备好的阶段,用户线程发起IO请求时,立即返回,所以为了读到最终数据,用户线程需要不断发起IO系统调用。
2.内核数据到达后,用户线程发起系统调用,用户线程阻塞。内核开始复制数据,它会将数据从内核缓冲区复制到用户缓冲区(用户空间的内存),然后内核返回结果(例如返回复制到用户缓冲区的字节数)
3.用户线程读到数据后,才会结束阻塞状态,重新运行起来。也就是说,用户进程需要经过多次尝试,才能保证最终读到数据,而后继续执行。
特点
1.在内核缓冲区中没有数据的情况下,系统调用会立即返回,返回一个调用失败的信息。
2.在内核缓冲区中有数据的情况下,是阻塞的,直到数据从内核缓冲复制到用户进程缓冲。复制完成后,系统调用返回成功,应用进程开始处理用户空间的缓存数据。
3.应用程序的线程需要不断进行IO系统调用,轮询数据是否已经准备好,如果没有准备好,就继续轮询,直到完成IO系统调用为止。
IO Multiplexing(IO多路复用)
经典的Reactor反应器设计模式,有时也称为异步阻塞IO, java中selector选择器和linux中epoll都是这种模型。
IO多路复用模型的流程
IO多路复用模型的流程
多路复用IO的read读操作
1.选择器注册,在这种模式中,首先,将需要read操作的目标socket网络连接,提前注册到select/poll选择器中,Java中对应的选择器类是Selector类,然后,才可以开启整个IO多路复用模型的轮询流程。
2.就绪状态的轮询,通过选择器的查询方法,查询注册过的所有socket连接的就绪状态,通过查询的系统调用,内核会返回一个就绪的socket列表,当任何一个注册过的socket中的数据准备好了,内核缓冲区有数据就绪了,内核就将该socket加入到就绪列表中。
4.复制完成后,内核返回结果,用户线程才会结束阻塞状态,用户线程读取到了数据,继续执行。
特点
IO多路复用模型的IO涉及两种系统调用SystemCall,另一种是select/epoll就绪查询,一种是IO操作
对于注册在选择器上的每一个可以查询到的socket连接,一般都设置为同步非阻塞模型
优点
和NIO模型相似,多路复用IO也需要轮询,负责select/epoll状态查询调用的线程,需要不断进行select/poll轮询,查找出达到IO操作就绪的socket连接。
缺点
使用select/epoll的最大优势在于,一个选择器查询线程可以同时处理成千上万个连接,系统不必创建大量线程,也不必维护这些线程,从而大大减小了系统开销。
AIO(异步IO)
异步IO模型的流程
异步IO的read操作
1.当用户线程发起了read系统调用,立刻就可以开始去做其他事情,用户线程不阻塞。
2.内核就开始了IO的第一阶段,准备数据。等待数据准备好了,内核就会将数据从内核缓冲区复制到用户缓冲区,用户空间的内存。
3.内核会给用户线程发送一个信号,或者回调用户线程注册的回调接口,告诉用户线程read操作完成了。
4.用户线程读取用户缓冲区的数据,完成后续业务
特点
异步IO,指的是用户空间与内核空间的调用方式反过来。用户空间线程变成被动接受者,而内核空间成了主动调用者。这有点类似于java中比较典型的回调模式。用户空间的线程向内核空间注册了各种IO事件的回调函数,由内核去主动调用。
在内核等待数据和复制数据的两个阶段,用户线程都不是阻塞的,用户线程需要接收内核的IO操作完成的回调函数,正因为如此,异步IO有的时候也被称为信号驱动IO
缺点
应用程序仅需要进行时间的注册与接收,其余工作都留给了操作系统,也就是说,需要底层内核提供支持。
总结
BIO和NIO对比图
Java NIO
缓冲区(Buffer)
MappedByteBuffer是专门用于内存映射的一种ByteBuffer类型
内部结构
一个Byte[]数组内存块,作为内存缓冲区
capacity(容量)
一旦写入对象超过了capacity容量,缓冲区就满了,不能再写入了。
position(读写位置)
position属性和缓冲区的读写模式有关,在不同模式下,position属性的值是不同的。当缓冲区进行读写的模式改变时,position会进行调整。
变化规则
写入模式
1.在刚进入到写模式时,position值为0,表示当前的写入位置从头开始
2.每当一个数据写到缓冲区之后,position会向后移动到下一个可写位置。
3.初始position的值为0,最大可写值position为limit-1
当position值达到limit时,缓冲区就已经无可写了。
读取模式
1.当缓冲区刚开始进入到读模式时,position会被重置为0.
2.当从缓冲区读取时,也是从position位置开始读,读取数据后,position向前移动到下一个可读位置。
3.position最大值为最大可读上线limit,当position达到limit时,表明缓冲区已经无数据可读。
模式切换
当新建一个缓冲区时,缓冲区处于写入模式,这时时可以写数据的。
使用即调用flip翻转方法,将缓冲区变成读取模式
flip翻转过程中,position会进行非常巨大的调整,具体的规则是,position由原来的写入位置,变成新的可读位置,也就是0,表示可以从头开始读,flip翻转的另外一半工作就是要调整limit属性。
limit(读写的限制)
写模式
写模式下,limit属性值含义为可以写入的数据最大上限
在刚进入到写模式时,limit的值会被设置为缓冲区capacity容量值,表示可以一直将缓冲区的容量写满。
读模式
读模式下,limit的值含义为最多能从缓冲区读取到多少数据。
flip翻转
将写模式下的position值,设置成读模式下的limit的值,也就是说,将之前写入的最大数量,作为可以读取的上限值。
mark(标记)
就当是一个暂存属性,暂时保存position的值,方便后面重复使用position
调用例子
首先,创建缓冲区,刚开始,缓冲区处于写模式,position为0,limit为最大容量。
然后,向缓冲区写数据,每写入一个数据向后面移动一个位置,也就是position的值加1,假定写入了5个数,当写入完成后,position的值为5
这时,使用即调用flip方法,将缓冲区切换到读模式,limit的值,先会被设置成写模式的position值。这里新的limit是5,表示可以读取的最大上限是5个数,同时新的position会被重置为0,表示可以从0开始读取。
allocate()创建缓冲区
缓冲区读写模式转换
通道(Channel)
主要类型
1.FileChannel文件通道,用于文件的数据读写
2.SocketChannel套接字通道,用于socket套接字tcp连接的数据读写
3.ServerSocketChannel服务器嵌套字通道(或者服务器监听通道),允许我们监听tcp连接请求,为每个监听到的请求,创建一个SocketChannel套接字通道。
4.DatagramChannel数据报通道,用于UDP协议的数据读写。
FileChannel
FileChannel为阻塞模式,不能设置为非阻塞模式
常用方法
int read(ByteBuffer dst) 从Channel到中读取数据到ByteBuffer
long read(ByteBuffer[] dsts) 将Channel到中的数据“分散”到ByteBuffer[]
int write(ByteBuffer src)将ByteBuffer 到中的数据写入到 Channel
long write(ByteBuffer[] srcs)将ByteBuffer[] 到中的数据“聚集”到 Channel
long position() 返回此通道的文件位置
FileChannel position(long p) 设置此通道的文件位置
long size() 返回此通道的文件的当前大小
FileChannel truncate(long s) 将此通道的文件截取为给定大小
void force(boolean metaData) 强制将所有对此通道的文件更新写入到存储设备中
其他特性
分散读取(Scatter ):是指把Channel通道的数据读入到多个缓冲区中去
聚集写入(Gathering )是指将多个 Buffer 中的数据“聚集”到 Channel。
transferFrom()
从目标通道中去复制原通道数据
transferTo()
把原通道数据复制到目标通道
ServerSocketChannel与SocketChannel
在NIO中,设计网络连接的通道有两个,一个是SocketChannel负责连接传输,一个是ServerSocketChannel负责连接的监听
ServerSocketChannel应用于服务器端,而SocketChannel同时处于服务器端和客户端,换句话说,对应一个连接,两端都有一个负责传输的SocketChannel传输通道。
非阻塞情况下,与服务器的连接还有可能没有真正建立,socketChannel.connect方法就返回了,因此需要不断的自旋
需要通过finishConnect()方法判断是否连接到服务端
ServerSocketChannel监听套接字的accept()方法,来获取新连接的套接字通道。
DatagramChannel
SctpChannel与NioSctpServerChannel
Selector选择器
定义
选择器的使命时完成IO的多路复用,一个通道代表一条连接通路,通过选择器可以同时监控多个通道的IO(输入输出状况)。选择器和通道的关系,是监控和被监控的
创建Selector
通过调用 Selector.open() 方法创建一个 Selector。
使用
通道和选择器之间的关系,通过register(注册)的方式完成。调用通道的Channel.register(Selector sel, int ops)方法,可以将通道实例注册到一个选择器中。
一条通道若能被选择,必须继承SelectableChannel类
IO事件
类型
1.可读: SelectionKey.OP_READ
一个数据可读的SocketChannel通道,处于读就绪状态。
2.可写:SelectionKey.OP_WRITE
一个等待写入数据的,处于写就绪状态
3.连接:SelectionKey.OP_CONNECT
某个SocketChannel通道,完成了和对端的握手连接,则处于连接就绪状态
4.接收:SelectionKey.OP_ACCEPT
某个ServerSocketChannel服务器通道,监听到一个新连接的到来则处于接收就绪状态
服务器监听通道ServerSocketChannel,仅仅支持Accept(接收到新连接)IO事件,而SocketChannel传输通道,则不支持Accept (接收到新连接) IO事件
SelectionKey选择键
通过select方法,选择器可以不断地选择通道中所发生操作的就绪状态,返回注册过的感兴趣的那些IO事件
话句话说,一旦在通道中发生了某些IO事件(就绪状态达成),并且是在选择器中注册过的IO事件,就会被选择器选中,并放入SelectionKey选择键的集合中。
SelectionKey选择键就是那些被选择器选中的IO事件
文件句柄数的修改
在生产环境Linux系统中,基本上都需要解除文件句柄数的限制,Linux的系统默认值为1024,也就是说,一个进程最多可以接收1024个socket连接,这远远是不够的。
ulimit -n
用来显示和修改当前用户进程一些基础限制的命令,-n命令选项用于引用或者设置当前文件句柄数量的限制值
永久设置
编辑/etc/rc.local开机启动文件,在文件中添加ulimt -shn 100000000内容
终极解除Linux系统的最大文件打开数量的限制,可以通过编辑Linux的极限配置文件/etc/securtiy/limts.conf来解决,修改此文件,加入如下内容
soft nofile 100000000
hard nofile 10000000
拓展知识-零拷贝
传统I/O
代码示例
File f = new File("helloword/data.txt");
RandomAccessFile file = new RandomAccessFile(file, "r");
byte[] buf = new byte[(int)f.length()];
file.read(buf);
Socket socket = ...;
socket.getOutputStream().write(buf);
RandomAccessFile file = new RandomAccessFile(file, "r");
byte[] buf = new byte[(int)f.length()];
file.read(buf);
Socket socket = ...;
socket.getOutputStream().write(buf);
过程详解
Java 本身并不具备 IO 读写能力,因此 read 方法调用后,要从 Java 程序的用户态切换至内核态,去调用操作系统(Kernel)的读能力,将数据读入内核缓冲区。这期间用户线程阻塞,操作系统使用 DMA(Direct Memory Access)来实现文件读,其间也不会使用 CPU。
DMA 也可以理解为硬件单元,用来解放 cpu 完成文件 IO
从内核态切换回用户态,将数据从内核缓冲区读入用户缓冲区(即 byte[] buf),这期间 CPU 会参与拷贝,无法利用 DMA
调用 write 方法,这时将数据从用户缓冲区(byte[] buf)写入 socket 缓冲区,CPU 会参与拷贝
接下来要向网卡写数据,这项能力 Java 又不具备,因此又得从用户态切换至内核态,调用操作系统的写能力,使用 DMA 将 socket 缓冲区的数据写入网卡,不会使用 CPU。
用户态与内核态的切换
可以看到中间环节较多,java 的 IO 实际不是物理设备级别的读写,而是缓存的复制,底层的真正读写是操作系统来完成的
用户态与内核态的切换发生了 3 次,这个操作比较重量级
数据拷贝了共 4 次
NIO优化
方法
ByteBuffer.allocate(10)
底层对应 HeapByteBuffer,使用的还是 Java 内存
ByteBuffer.allocateDirect(10)
底层对应DirectByteBuffer,使用的是操作系统内存
优化点
Java 可以使用 DirectByteBuffer 将堆外内存映射到 JVM 内存中来直接访问使用
内存回收过程
这块内存不受 JVM 垃圾回收的影响,因此内存地址固定,有助于 IO 读写
Java 中的 DirectByteBuf 对象仅维护了此内存的虚引用,内存回收分成两步
DirectByteBuffer 对象被垃圾回收,将虚引用加入引用队列
当引用的对象ByteBuffer被垃圾回收以后,虚引用对象Cleaner就会被放入引用队列中,然后调用Cleaner的clean方法来释放直接内存
DirectByteBuffer 的释放底层调用的是 Unsafe 的 freeMemory 方法
通过专门线程访问引用队列,根据虚引用释放堆外内存
减少了一次数据拷贝,用户态与内核态的切换次数没有减少
零拷贝
linux 2.1以后
详情
底层采用了 linux 2.1 后提供的 sendFile 方法,Java 中对应着两个 channel 调用 transferTo/transferFrom 方法拷贝数据
过程详解
Java 调用 transferTo 方法后,要从 Java 程序的用户态切换至内核态,使用 DMA将数据读入内核缓冲区,不会使用 CPU
数据从内核缓冲区传输到 socket 缓冲区,CPU 会参与拷贝
最后使用 DMA 将 socket 缓冲区的数据写入网卡,不会使用 CPU
方法
只发生了1次用户态与内核态的切换
数据拷贝了 3 次
linux 2.4以后
过程详解(linux 2.4)
Java 调用 transferTo 方法后,要从 Java 程序的用户态切换至内核态,使用 DMA将数据读入内核缓冲区,不会使用 CPU
只会将一些 offset 和 length 信息拷入 socket 缓冲区,几乎无消耗
使用 DMA 将 内核缓冲区的数据写入网卡,不会使用 CPU
用户态与内核态切换
整个过程仅只发生了1次用户态与内核态的切换,数据拷贝了 2 次
定义
零拷贝指的是数据无需拷贝到 JVM 内存中
更少的用户态与内核态的切换
不利用 cpu 计算,减少 cpu 缓存伪共享
零拷贝适合小文件传输
底层原理分析
图解DMA
read系统调用
read系统调用过程
UDP
特点
1.UDP是无连接的,即发送数据之前也不需要建立连接,当然,发送数据结束时,也没有连接可释放,因此减少了开销和发送数据之间的时延
2.UDP使用尽最大的努力交付,即不保证可靠交付,因此主机不需要维持复杂的连接状态表,这里有许多参数。
3.UDP是面向报文的。
图5-4 UDP是面向报文的
UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界
4.UDP没有拥塞控制,因此网络出现的拥塞不会使源主机的发送速率降低
5.UDP支持一对一,一对多,多对一和多对多的交互通信
6.UDP的首部开销小,只有8个字节,比tcp的20个字节首部要短
数据报格式
用户数据报UDP有两个字段,数据字段和首部字段
UDP首部
UDP用户数据报的首部和伪首部
首部字段很简单,只有8个字节,由四个字段组成,每个字段的长度都是两个字节。
数据校验
IP数据报的校验和只校验IP数据报的首部,但UDP的校验和是把首部和数据部分一起都校验。
TCP
特点
1.TCP是面向连接的运输层协议
应用程序在使用TCP协议之前,必须先建立TCP连接
在传输数据完毕后,必须释放已经建立的TCP连接
应用程序之间的通信好像在打电话,通话前要先拨号建立连接。通话结束后要挂机释放连接。
2.每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点的(一对一)
3.TCP提供可靠交付的服务
通过TCP连接传送的数据,无差错,不丢失,不重复,并且按序到达
4.TCP提供全双工通信
TCP允许通信双方的应用进程在任何时候都能发送数据
TCP连接的两端都设有发送缓存和接收缓存,用来临时存放双向通信的数据
在发送时,应用程序在数据传送给TCP的缓存后,就可以做自己的事情,而TCP在合适的时候把数据发送出去
在接收时,TCP把收到的数据放入缓存,上层的应用进程在合适的时候读取缓存中的数据
5.面向字节流
TCP面向流的概念
TCP中流stream是指流入到进程或者从进程中流出的字节序列
面向字节流的含义是:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但是TCP把应用程序交下来的数据堪称仅仅是一连串无结构的字节流
TCP报文段先要传送到IP层,加上IP首部后,再传送到数据链路层,再加上数据链路层的首部和尾部后,才离开主机发送到物理链路
TCP连接
每一条TCP连接唯一地被通信两端的两个端点,即两个套接字所确定
TCP连接的端点是个很抽象的套接字,即(IP地址:端口号)
可靠传输的工作原理
理想传输条件
传输信道不产生差错
不管发送方以多快的速度发送数据,接收方总是来得及处理收到的数据
实际的网络都不具备以上两个理想条件。但我们可以使用一些可靠的传输协议,当出现差错时,让发送方重传出现差错的数据,同时在接收方来不及处理所收到的数据时,及时告知发送方适当降低发送数据的速度。
TCP是全双工通信的,全双工通信的双方既是发送方,也是接收方
停止等待协议
无差错的情况
A发送分组M1,发完就暂停发送,等待B的确认
B收到M1就向A发送确认
A在收到了对M1的确认后,就再发送下一个分组M2
同样,在收到B对M2的确认后,再发送M3
超时重传
可靠性传输协议是这样设计的,A只要超过了一段事件仍然没有收到确认,就认为刚才发过去的分组丢失了,因而重传前面发过的分组。
要实现超时重传,就要在每次发送完一个分组设置一个超时计时器。但A只要在超时计时器到期之前收到了相应确认,就撤销超时计时器。
超时计时器
A在为每一个已发送的分组都设置了一个超时计时器,但A只要在超时计时器到期之前收到了相应确认,就撤销该超时计时器。
传输流程
A在发送完一个分组后,必须暂时保留已发送的分组副本(为发生超时重传时使用)
分组和确认分组都必须进行编号,这样才能明确是哪一个发送出去的分组收到了确认,而哪一个分组还没有收到确认。
超时计时器设置重传的事件应当比数据在分组传输的平均往返时间更长一点。
确认丢失和确认迟到
A在设定的超时重传时间内没收到确认,但并无法知道是自己发送的分组出错丢失,或者是B发送的确认丢失了。
丢弃这个重复的分组M1,不向上层交付
向A发送确认,不能认为已经发送过确认就不再发送,因为A之所以重传M1就表示A没有收到对M1的确认。
一种可能出现的情况。传输过程中没有出现差错,但是B对分组M1的确认迟到了,A会收到重复的确认。对重复确认的处理很简单,收下后就丢弃,B仍然会收到重复的M1,并且同样要丢弃重复的M1,并重传确认分组。
自动重传请求ARQ
停止等待协议
停止等待协议的信道利用率太低
缺点是信道利用率太低,A发送数据包后要等B确认回来才能继续发送下一个数据包,通道利用率太低了。
流水线传输协议
流水线传输可提高信道利用率
为了提高传输效率,发送方可以不使用低效率的停止等待协议,而是采用流水线传输
连续ARQ协议
连续ARQ协议的工作原理
连续ARQ协议规定,发送方每收到一个确定,就把发送窗口向前滑动一个分组的位置。
累积确认
优点
容易实现,即使确认丢失也不必重传
缺点
不能向发送方反应出接收方已经正确收到所有分组的信息
接收方一般都是采用累计确认的方式,这就是说,接收方不必对收到的分组逐个发送确认,而是在收到几个分组后,对按顺序到达的最后一个分组发送确认,这就表示到这个分组为止的所有分组都已经正确收到了。
报文格式
图 TCP报文段的首部格式
确认ack
TCP规定,在连接建立后所有传送的报文段都必须把ack置1
推送PSH(push)
接收方TCP收到psh=1的报文段,就尽快地(即推送向前)交付接收应用进程,而不再等到整个缓存都填满了后再向上交付。
复位RST(reset)
表明TCP连接中出现严重差错,如由于主机崩溃或者其他原因,必须释放连接,然后再重新建立运输连接。
RST也可称为重建位或者重置位。
同步SYN(SYNchronization)
在连接建立时用来同步序号。
当SYN = 1而ACK = 0时,表明这是一个连接请求报文段。
则应在响应报文段中使SYN = 1和ack = 1
终止FIN
当FIN = 1时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接。
窗口 占2字节
窗口字段明确指出了现在允许对方发送的数据量。窗口值是经常在动态变化着。
假设确认号是701,窗口字段是1000,这就表明从701号算起,发送此报文段的乙方还有接收1000个字节数据(字节序号是701~1700)的接收缓存空间
MSS
MSS是每一个TCP报文段中的数据字段的最大长度
TCP报文段长度减去TCP首部长度
主机未填写这一项,则MSS的默认值是536字节长
滑动窗口
A发送了11个字节的数据
P3 - P1 = A 的发送窗口(又称为通知窗口)
P2 - P1 = 已发送但尚未收到的字节数。
P3 - P2 = 允许发送但尚未发送的字节数(又称为可用窗口)
发送窗口
根据B给出的窗口值,A构造出自己的发送窗口
窗口是20(字节),而确认号是31(这个表明B期望收到的下一个序号是31,而序号30为止的数据已经收到了)
发送窗口里面的序号表示允许发送的序号。
发送缓存
1.发送应用程序传送给发送方TCP准备发送的数据。
2.TCP已经发送出但尚未收到确认的数据。
接收窗口
A收到新的信号确认号,发送窗口向前移动。
假定B收到了序号位31的数据,并把序号为31 ~ 33 的数据交付主机(ack),然后B删除这些数据。接着把接收窗口向前移动3个序号,同时给A发送确认,其中窗口值仍为20,但确认号是34
接收缓存
1.按序到达的,但尚未被接收应用程序读取的数据。
2.未按序到达的数据。
特殊情况
双方窗口大小不一
虽然A的发送窗口是根据B的接收窗口设置的,但在同一时刻,A的发送窗口并不总是和B的接收窗口一样大。这是因为通过网络传送窗口值需要。
发送方A还可能根据网络当时的阻塞情况适当减少自己的发送窗口数值。
不按序到达的数据
TCP通常对不按序到达的数据先临时存放在接收窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程。
接收方必须有累计确认的功能
接收方可以在合适的时候发送确认,也可以在自己有数据要发送时把确认信息顺便捎带上。
超时重传的时间
自适应算法
它记录一个报文段发出的时间,以及收到相应的确认的时间,这两个时间之差就是报文段的往返时间RTT。
收到的确认是对哪一个报文段的确认
Karn算法
在计算加权平均RTTS时,只要报文段重传了,就不采用其往返时间样本。这样得出的加权平均RTTS和RTO就较准确。
修正:报文段每重新传一次,就把超时重传时间RTO增大一些。
典型的做法是取新的重传时间为2倍的旧的重传时间。
选择确认SACK
流量控制
利用可变窗口进行流量控制举例
三次流量控制,第一次把窗口减小到rwnd = 300, 第二次又减到rwnd = 100, 最后减到rwnd =0, 即不允许发送方再发送数据了。
所谓流量控制(flow control)就是让发送方的发送速率不要太快,要让接收方来得及接收。
持续计时器
TCP为每一个连接设有一个持续计时器(persistence timer)
只要TCP连接乙方收到对方的零窗口通知,就启动持续计时器。
B发送rwnd=0之后释放了200空间,发送rwnd=200的时候包丢了,A还一直为B的rwnd=0不断发送数据,需要有一个机制来打破僵局。
若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。
如果窗口仍然是零,那么收到这个报文段的一方就重新设置持续计时器。如果窗口不是零,那么死锁的僵局就可以打破了。
作用
流量控制往往指点对点通信量的控制,是个端对端的问题(接收端控制发送端)。流量控制所要做的就是抑制发送端发送数据的速率,以便使接收端来的及接收
传输效率
发送时机
第一种机制是TCP维持一个变量,它等于最大报文段长度MSS。只要缓存中存放的数据达到MSS字节时,就组装成一个TCP报文段发送出去。
第二种机制是由发送方的应用进程指明要求发送报文段,即TCP支持的推送(push)操作。
第三种机制是发送方的一个计时器期限到了,这时就把当前已有的缓存数据装入报文段(但长度不能超过MSS)发送出去。
Nagle算法
算法如下:若发送应用进程把要发送的数据逐个字节地送到TCP的发送缓存,则发送方就要把第一个数据字节先发送出去,把后面到达的数据字节都缓存起来。当发送方收到对第一个数据字符的确认后,再把发送缓存中的所有数据组装成一个报文段发送出去,同时继续对随后到达的数据进行缓存,只有在收到对前一个报文段的确认后才继续发送下一个报文段。当数据到达较快而网络速率较慢时,用这样的方法可明显地减少所用地网络宽带。
Nagle算法还规定,当到达的数据已达到发送窗口大小的一半或已达到报文段的最大长度时,就立即发送一个报文段。
这样做就可以有效地提高网络吞吐量。
糊涂窗口综合症
TCP接收方地缓存已满,而交互式地应用进程一次只从接收到缓存中读取1个字节(这样就使接收缓存空间仅腾出一个字节),然后向发送方确认,并把窗口设置为1个字节(但发送数据报时40字节长)。接着,发送方又发来1个字节地数据(请注意,发送方发送的IP数据报师41字节长)。接收方发回确认,仍然将窗口设置为1个字节。这样进行下去,使网路的效率更低。
解决问题
可以让接收方等待一段时间,使得或者接收缓存已有足够空间容纳一个最长的报文段,或者等到接收缓存已有一般空闲的空间。
只要出现这两种情况之一,接收方就发出确认报文,并向发送方通知当前窗口大小。
此外,发送方也不要发送太小的报文段,而是把数据累积成足够大的报文段,或者达到接收方缓存空间的一半大小。
总结
窗口一直处于1字节的状态,频繁收发数据导致网络效率很低,可以通过2种方式来解决,一让接收方等待一段时间使接收缓存能够有足够空间容纳一个最长的报文段。二发送方不要发送太小的报文段,把数据累积成足够大的报文段再发送。
拥塞控制
网络资源
计算机网络中的链路容量,即带宽,交换结点中的缓存和处理机等,都是网络资源。
在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络性能就要变坏,这种情况就叫拥塞。
如果一个路由器没有足够的缓存空间,它就会丢弃一些新到的分组。
路由器丢弃分组会导致发送方进行数据包重传,如此反复进行重传多次,可见拥塞引起的重传并不会缓解网络的拥塞,反而会加剧网络的拥塞。
作用
拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不至过载。
拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。
拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。
拥塞控制方法
满开始(slow-start)
发送方每收到一个确认就把窗口cwnd加1
由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。
每经过一个传输轮次(transmission round), 拥塞窗口cwnd就加倍。
1 2 4 8 16 加倍到 65535
拥塞避免(congestionavoidance)
慢开始和拥塞避免算法的实现举例
思路是让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方地拥塞窗口cwnd加1,而不是加倍。这样,拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢很多。
假定拥塞窗口的数值增长到24时,网络出现超时(这很可能就是网络发生拥塞了)。更新后的sstresh值变为12(即变为出现超时时的拥塞窗口数值24的一半),拥塞窗口再重新设置为1,并执行慢开始算法。当cwnd = ssthresh = 12时改为执行拥塞避免算法。拥塞窗口按线性规律增长,每经过一个往返时间增加一个MSS的大小。
快重传(fast retransmit)
快重传的示意图
当发送方连续收到三个重复确认时,就执行了“乘法减小”算法,把慢开始门限ssthresh减半,这是为了预防网络发生拥塞。请注意,接下去不执行满开始算法。
从连续收到三个重复的确认转入拥塞避免
标明了"TCP Reno版本",这是目前使用的很广泛的版本。图中还画出了已经废弃不用的虚线部分(TCP Tahoe版本)。请注意他们的区别时:新的TCP Reno版本在快重传后采用快速恢复算法而不是采用慢开始算法。
快恢复(fast recovery)
采用快恢复算法时,慢开始算法只是在TCP连接建立时和网络出现超时时才使用。
随机早期检测RED
路由器FIFO有界队列
由于队列长度总数是有限的,因此当队列已满时,以后再到达的所有分组将都会被丢弃。这就叫做尾部丢弃策略(tail-drop policy)
RED把路由器的到达队列分成三个区域
RED把路由器的分组到达队列分为三个区域,即正常排队,以概率P丢弃和必须丢弃的区域。
TCP建立连接
用三次握手建立TCP连接
为什么要三次握手
如果只有2次握手,假如A第一次的syn报文只是由于网络超时导致延迟到达,但是由于超时重传机制A再次发送了一次syn报文,B正常收到了建立连接,待通信结束后,第一次的报文才到达,这样就会再次建立连接。
TCP的连接释放
TCP连接释放的过程
请注意,在进入TIME_WAIT现在TCP连接还没有释放掉,必须经过时间等待计时器(Time-wait timer)设置的时间2MSL后,A才进入CLOSED状态,时间MSL叫做最长报文寿命(Maximum Segment Lifetime), RFC792建议设为2分钟。
保活计时器(keepalivetimer)
服务器每收到一次客户的数据,就重新设置保活计时器,时间的设置通常是两小时,若两小时没有收到客户的数据,服务器就发送一个探测报文段,以后则每隔75分钟发送一次,若一连发送10个探测报文后仍无客户的相应,服务器就认为客户端除了故障,接着关闭这个连接。
有限状态机
TCP有限状态机
粗实线箭头表示对客户进程的正常变迁。
粗虚线箭头表示对服务器进程的正常变迁。
另一种细线箭头表示异常变迁。
0 条评论
下一页