- 网络划分:
OSI体系结构:应用层,表示层,会话层,运输层,网络层,数据链路层,物理层;
TCP/IP体系结构:应用层,运输层,网络层,数据接口层;
五层体系结构:应用层,运输层,网络层,数据链路层,物理层;
各层介绍:
应用层:能够和用户交互,所有能够产生网络流量的程序,他使得应用程序能够直接运行于传输层之上,直接为用户提供服务,包含的主要协议有文件传输协议(FTP)<21>,超文本传输协议(HTTP)<80>,简单邮件传送协议(SMTP),远程登录协议(Telnet),域名服务协议(DNS),网络新闻传送协议(NNTP)。
表示层:信息传递之前对数据进行处理,网络的安全和保密管理,文件的压缩打包,虚拟终端协议(VTP)。
会话层:将会话地址映射为传输地址;选择需要的传输服务质量参数;对会话参数进行协商;识别各个会话连接;传送有限的透明用户数据。
运输层:负责向两个主机进程间的通信提供服务,由于一个主机可同时运行多个进程,因此传输层有复用和分用的功能,复用就是多个应用进程可同时使用运输层的服务,分用则是运输层把收到的信息分别交付给上面应用层相应的进程中。(主要使用UDP/TCP两种协议)
网络层:负责最佳的路径和规划IP地址,路由表,通过IP协议进行地址划分。在不同的网络之间尽力转发数据包,基于数据包的IP地址转发,如果丢失,不负责丢失重传,也不负责顺序,负责将数据传给对应的服务器和计算机,他不管信息应该给那个进程。 (只有TCP协议)传输层会负责重传信息和 信息的顺序问题,也可以保证传递给对应进程。(UDP)只能负责传递给对方进程,不能保证重传和信息的顺序问题。
数据链路层:实现网络上两个相邻节点之间的无差错传输。主要作用包括封装成帧,物理地址寻址,流量控制,差错检测(但是不会纠错,纠错在传输层TCP)。
物理层:物理层并不是物理传输媒体,物理层考虑是怎样才能连接各种计算机的传输媒体上传输数据比特流而不是指具体传输媒体。现有的物理设备种类繁多而通信手段又有许多不同的方式。物理层真正的作用正是要尽可能屏蔽掉这些传输媒体和通信手段的差异,是物理层上方的数据链路层感觉不到这些差异,这样就可以使数据链路层只需要考虑如何完成本层的协议和服务。在物理层这里定义了机械特性、电器特性、功能特性、过程特性从而屏蔽了差异。
(1) 机械特性 指明接口所用的接线器的形状和尺寸、引线数目和排列、固定和锁定装置等等。
(2) 电气特性 指明在接口电缆的各条线上出现的电压的范围。
(3) 功能特性 指明某条线上出现的某一电平的电压表示何意。
(4)规程特性 指明对于不同功能的各种可能事件的出现顺序。
数据传输的过程:
- 发送方应用层产生消息→运输层对消息使用 TCP 或者 UDP 协议进行封装 →网络层使用 IP 协议进行封装→数据链路层对数据进行封装 →物理层将消息转为比特流在物理设备上传输
- 接收方物理层:将比特流转成数据链路层能够解析消息 → 数据链路层:局域网信息传递→网络层:去掉 ip 协议对信息的封装 →运输层:去掉 TCP/UDP 协议的封装→应用层:将信息解析出来。
TCP/IP协议栈:
UDP:https://www.cnblogs.com/128-cdy/p/13190452.html
TCP:https://www.cnblogs.com/128-cdy/p/13179865.html
TCP/IP 体系在网络互连上采用的做法是在网络层(即 IP 层)采用了标准化协议,但相互连接的网络可以是异构的。
IP的常见表示方式:点分十进制。
两级划分 IP 地址的方式:记为:IP 地址 ::= { <网络号>, <主机号>} (32位) ABCD
两级划分存在的问题:
- 利用率低
- 路由表太大降低网络性能 每一个路由器都应当能够从路由表查处应怎样到达其他网络的下一跳路由器。因此,互联网中的网数越多,路由器的路由表项目数越多,这样,即便我们拥有足够多的 IP 地址资源可以给每一个物理网络分配一个网络号,也会导致路由器中路由表中的项目数过多。这样不仅增加了路由器的成本(要求更多存储空间),而且使查找路由表耗时更多,同时使路由器之间定期交换路由信息量急剧增加,使路由器和整个因特网性能下降。
- 两级 IP 地址不够灵活
划分子网
使两级 IP 地址变成为三级 IP 地址,它能够较好的解决上述问题(),并且使用起来很灵活,于是两级的 IP 地址在本单位内部就变成了三级 IP 地址:网络号、子网号、主机号。也可以由以下记法来表示:
IP 地址 = {<网络号>, <子网号>, <主机号>}。
默认的子网掩码:
CIDR 无分类域间路由选择
- CIDR 消除了传统的 A 类、B 类和 C 类地址及划分子网的概念,因此可以更加有效地分配 IPv4 的地址空间,并且在新的 IPv6 使用前容许因特网的规模继续增长。CIDR 把 32 位的 IP 地址划分为两个部分,前面的部分是网络前缀,用来指明网络,后面的部分则用来指明主机,其与分类编址最大的不同,便是网络前缀不局限于 8 的倍数。因此 CIDR 使 IP 地址从三级编址(使用子网掩码)又回到两级地址,但这已经是无分类的两级编址。CIDR 在 IP 地址后面加上斜线“/”,然后写上网络前缀所占的位数。IP 地址 :: = {<网络前缀>,<主机号>}
- CIDR 把网络前缀都相同的连续 IP 地址组成一个“CIDR 地址块”。我们只要知道 CIDR 地址块中的任何一个地址,就可以知道这个地址块的起始地址(最小地址)和终止地址(最大地址),以及地址块中的地址数。
IP数据报:
固定部分
(1) 版本:占 4 位,指 IP 协议的版本。目前广泛使用的 IP 协议版本有两种 IPv4 和 IPv6。
(2) 首部长度:占 4 位,其单位是 4B。所以首部长度必须是 4B 的整数倍。如首部长度字段的 4 个二进制位分别是 1111(对应十进制是 15),则 IP 协议首部的长度是 15 × 4B = 60B(字节)。由于 IP 数据报首部的固定部分长度固定是 20,所以首部字段最小从 0101 开始。
(3) 区分服务:占 8 位,一般情况下不使用该字段。只有使用区分服务时,这个字段才起作用,如要求当前的数据报设置高优先级优先发送。
(4) 总长度:占 16 位,表示首部和数据部分长度之和,单位是字节。
(5) 标识、标志、片偏移是关于 IP 数据报分片的。
(6) 生存时间:占 8 位,表示数据报在网络中的寿命。由发送数据报的源点设置这个字段,其目的是为了防止那些无法交付的数据报无限制的在互联网中兜圈子(例如从路由器 R1 转发到 R2,再转发到 R3,然后又转发到 R1),因而白白浪费网络资源。数据报每经过一个路由器,这个值就会减 1,当减至 0 时,就丢弃该数据报。
(7) 协议:占 8 位,协议字段是指出次数据报所携带的数据是使用的协议。这里记两个协议字段的值:6表示 TCP 协议,17 表示 UDP 协议。
(8) 首部校验和:占 16 位,只校验数据报的首部,不检验数据部分。数据报每经过一个路由器都要重新计算一下首部校验和(一些字段,如生存时间、标志、片偏移可能发生了变化)。
(9) 源地址和目的地址:各占 32 位。
可变部分
(1) 可选字段:长度可变,从 1 字节~40 字节。可变部分是为了增加 IP 数据报的功能,如用来支持排错、测量以及安全等措施。
(2) 填充:IP 数据报的首部长度必须是 4B 的整数倍,所以如果首部长度不满足 4B 整数倍时,就使用填充字段将首部填充到 4B 的整数倍。
五种IO模型
阻塞:当某个事件或者任务在执行过程中,它发出一个请求操作,但是由于该请求操作需要的条件不满足,那么就会一直在那等待,直至条件满足;
非阻塞:当某个事件或者任务在执行过程中,它发出一个请求操作,如果该请求操作需要的条件不满足,会立即返回一个标志信息告知条件不满足,不会一直在那等待。
同步:如果有多个任务或者事件要发生(主要指 IO 事件),这些任务或者事件必须逐个地进行并且必须应用程序参与,一个事件或者任务的执行会导致整个流程的暂时等待,这些事件没有办法并发地执行;
异步:如果有多个任务或者事件发生(主要指 IO 事件),都交给操作系统执行我们就可以去做别的事情并不需要真正的完成 IO 操作,当操作完成之后给我们的应用程序一个通知就可以。
网络种数据传输与内核之间的关系:
消息由发送方产生,从发送方的用户空间传入内核空间借助网络传输介质完成传输,消息会发送到接收方的内核空间,接收方如果要想读取时需要将消息从内核空间拷贝到用户空间。
1.阻塞IO模型
在读写数据过程中会发生阻塞现象,当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。
2.非阻塞IO模型
当用户线程发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。当轮询次数过多会导致CPU占用率非常高
同步阻塞模型(BIO网络模型)
在 Linux 中,对于一次读取 IO 的操作,数据并不会直接拷贝到程序的程序缓冲区。通常包括两个不同阶段:
a. 等待数据准备好,到达内核缓冲区;
b. 从内核向进程复制数据。 对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所有等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用程序缓冲区。
故事描述:小明从家里面先到演唱会现场问售票业务员买票,但是票还没出来,三天以后才出来,小明直接打了个地铺睡在举办商售票大厅,一直等票出来,然后买票。
同步非阻塞模型
与阻塞式 I/O 不同的是,非阻塞的 recvform 系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个 error(EAGAIN 或 EWOULDBLOCK)。进程在返回之后,可以处理其他的业务逻辑,过会儿再发起 recvform 系统调用。采用轮询的方式检查内核数据,直到数据准备好。再拷贝数据到进程,进行数据处理。 在 linux 下,可以通过设置 socket套接字选项使其变为非阻塞。
故事描述:小明从家里面先到演唱会现场问售票业务员买票,但是票还没出来,然后小明走了,办理其他事情去了,然后过了 2 个小时,又去举办商售票大厅买票来了,如果票还没有出来,小明又先去办其他事情了,重复上面的操作,直到有票可以买。
3.多路复用IO模型(NIO)
在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。在Java NIO中,是通过selector.select()去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这种方式会导致用户线程的阻塞。采用多线程+ 阻塞IO 也可达到类似的效果,但是由于在多线程 + 阻塞IO 中,每个socket对应一个线程,这样会造成很大的资源占用,并且尤其是对于长连接来说,线程的资源一直不会释放,如果后面陆续有很多连接的话,就会造成性能上的瓶颈。而多路复用IO模式,通过一个线程就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态时通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。不过要注意的是,多路复用IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。因此对于多路复用IO模型来说,一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。
故事描述:小明想买票看演唱会,都直接给黄牛(selector/epoll)打电话了,说帮我留意买个票,票买了通知我,我自己去取(当我接到黄牛的电话时,我需要花费整个路成的时间去读这个数据,买拿这个票),那么票没出来之前,小明完全可以做自己的事情。
Reactor 模式(反应堆设计模式)
- 单线程Reactor单线程 对于客户端的所有请求使用一个专门的线程去处理,这个线程无限循环地监听是否有客户端的请求抵达,一旦收到客户端的请求,就将其分发给响应处理程序进行处理。特点是只有一个 Reactor 线程,也就是说只有一个 Selector 事件通知器,因此字节的读取 I/O 和后续的业务处理 process()均由 Reactor 线程来做,很显然业务的处理影响后续事件的分发,
- 单Reactor多线程 Reactor对象通过调用select()监控客户端的请求事件,收到事件后,通过dispatch进行分发;如果是建立连接请求,则由Acceptor的accpet来建立连接,然后创建一个Handler对象来处理完成连接的各种事件;但如果不是连接请求,则由Reactor分发调用连接对应的Handler来处理,Handler只负责响应事件,但是不做具体的业务处理,通过read读取数据后交给后面的work线程池的某个线程来处理业务,并且最后将结果返回给Handler。
- 主从Reactor多线程 Reactor(MainReactor)主线程通过调用select()监控客户端的请求事件,收到事件后,通过dispatch进行分发;如果是建立连接请求,则由Acceptor的accpet来处理连接事件,并将其交给Reactor(SubReactor)子线程;(SubReactor)子线程将连接加入到连接队列监听,并创建handler进行事件处理;当有新的事件发生,subReactor就会调用对应的handler来响应事件,handler来read数据并且分发给worker线程池里的线程来处理业务,并且返回结果;handler收到响应结果后,send返回给客户端。MainReactor包含多个SubReactor。
4.信号驱动
5.异步IO模型
常见网络模型
网络编程的基本模型是 C/S 模型,即两个进程间的通信。服务端提供 IP 和监听端口,客户端通过连接操作想服务端监听的地址发起连接请求,通过三次握手连接,如果连接成功建立,双方就可以通过套接字进行通信。传统的同步阻塞模型开发中,ServerSocket 负责绑定 IP 地址,启动监听端口;Socket 负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。
BIO 的服务端通信模型:采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答模型。
缺点:该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈 1:1 的正比关系,Java 中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死掉了。
NIO
NIO与BIO比较
- BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多。BIO基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
- BIO 是阻塞的,NIO 可以设置为非阻塞模式。
- Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),当具体事件发生了之后才会进行相应的处理不会出现阻塞情况。
NIO核心构件
- Selector 选择器(多路复用器)用于检查一个或多个 NIO Channel(通道)的状态是否处于可读、可写。Selector 提供选择已经就绪的任务的能力:Selector 会不断检测注册在其上的 Channel,如果某个 Channel 上面发生读或者写事件,这个Channel 就处于就绪状态,会被 Selector 轮询出来,然后通做出相对应的操作。
- 只需要一个线程去管理多个通道,也就是管理连接和请求;
- 只有在通道真正有读写事件发生时,才会进行读写,就大大的减少了系统的开销,并且不必为每个连接都创建一个线程,不用去维护多个线程;
- 避免了多线程之间的上下文切换导致的开销。
- Buffer(缓冲区)是包在一个对象内的基本数据元素数组。Buffer类相比一个简单数组的优点是它将关于数据的数据内容和信息包含在一个单一的对象中。Buffer类以及它专有的子类定义了一个用于处理数据缓冲区的API。根据数据类型的不同提供了不同的类型的buffer,除了Bollean之外各种类型都有提供:ByteBuffer、IntBuffer 、CharBuffer 、LongBuffer;、FloatBuffer、ShortBuffer、DoubleBuffer。
- Channel(通道)相当于BIO中的Socket,FileChannel(用于文件读写)、DatagramChannel(UDP的数据读写)、ServersocketChannle(类似于ServerSocket)和SocketChannel(类似于Socket)<TCP的>。
Buffer与Channel之间是是双向的传输的 。
三者的关系:
- 每个Channel都对应一个buffer;
- 每个selector对应一个线程,一个selector(也就是一个线程)对应多个channel(连接);
- 切换到哪个channel是由事件决定的;
- selector会更具不同的事件在各个通道上切换;
- buffer是一个内存块,底层是一个数组;读取写入数据都是通过buffer的,是双向的,既可读也可写(flip()切换)。与传统的输入输出流不同。
缓冲区是包在一个对象内的基本数据元素数组。Buffer 类相比一个简单数组的优点 是它将关于数据的数据内容和信息包含在一个单一的对象中。Buffer 类以及它专有的子类定义了 一个用于处理数据缓冲区的 API。根据数据类型的不同提供了不同的类型的 buffer,除了 Bollean 之外各种类型都有提供:ByteBuffer、IntBuffer 、CharBuffer、 LongBuffer、FloatBuffer、ShortBuffer、DoubleBuffer。
Bufer常用属性:
capacity容量:Buffer所能够存放的最大容量
position:Buffer可操作的第一个位置。在往 Buffer 中写数据时会从 Buffer 数组中的 position 位置开始写。从 Buffer 中读数据时会从 Buffer 的 position 开始读。
limit:Buffer最多可操作的数据的位置。在往 Buffer 中写数据时表示最多可写到数据量为 limit。从 Buffer中读数据时需要开启 Buffer 的读模式,读从 position 到 limit 位置的数据(一般是调用 flip 方法使limit=position;poslition=0)。
mark:备忘位置,调用mark()使得 mark=position,调用 reset(),恢复 postion 使position=mark。
BIO与NIO:
- BIO以流的方式处理数据,而NIO以块的方式处理数据,块IO比流IO的效率更高;
- BIO是阻塞的。而NIO是非阻塞的;
- BIO基于字节流和字符流进行操作,而NIO是基于Channel和Buffer进行操作,数据总是从通道读取到缓冲区,或者是从缓冲区写入到通道,Selector用于监听多个通道的事件,因此单线程就可以监听多个客户端的通道。
Netty
工作原理:
- Netty抽象出了两个事件循环组NioEventLoopGroup,也就是两个线程池分别是WorkerGroup和BossGroup,BossGroup专门负责客户端的连接请求,WorkGroup专门负责网络的读写,两个线程池的大小默认是CPU内核数的二倍;
- NioEventLoopGroup含有多个事件循环,每个事件循环都是一个NioEventLoop,表示一个不断执行任务的线程,每个NioEventLoop都有一个Selector,用于监听绑定在其上的Socket网络通信;
- 每个Boss NioEventLoopGroup循环步骤
- 轮训accept事件,处理 NioServerSocketChannel 的状态监测,当有连接到来时,执行 accept(),
- 处理accept事件,也客户端建立连接,并且生成NioSocketChannel(在NIO模型中生产SocketChannel)负责与 Client 端的通信,并且将其注册到某个WorkGroup线程上的Selector上,处理任务队列任务的执行;
- 每个Work NioEventLoopGroup循环主步骤
- 轮询read,write事件
- 处理I/O事件,在对应的NioSocketChannle处理
- 每个work NioEventLoopGroup处理业务时,会使用pipeline通道,pipeline通道中包含了channel,即通过pipeline可以获取到对应的通道,通道中维护了许多的处理器