文章目录
前言
记录关于Netty相关的知识点,目的是希望考察下自己,查漏补缺,把基本的网络编程知识打牢固。
1. TCP、UDP的区别
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 | 可靠传输,使用流量控制和拥塞控制 |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 | 只能是一对一通信 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
适用场景 | 适用于实时应用(IP电话、视频会议、直播等) | 适用于要求可靠传输的应用,例如文件传输 |
2. TCP协议如何保证可靠传输
对于可靠传输,判断丢包,误码靠的是TCP的段编号以及确认号。TCP为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。
3. TCP的握手、挥手机制
握手机制
TCP连接过程
如下图所示,建立一个TCP连接的过程为三次握手的过程:
第一次握手
客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。
第二次握手
服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。
第三次握手
当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。
为什么 TCP 建立连接需要三次握手,而不是两次?
这是为了防止出现失效的连接请求报文段被服务端接收的情况,从而产生错误。
挥手机制
TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。
第一次挥手
若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。
第二次挥手
B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明 A 到 B 的连接已经释放,不再接收 A 发的数据了。但是因为 TCP 连接是双向的,所以 B 仍旧可以发送数据给 A。
第三次挥手
B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求,然后 B 便进入 LAST-ACK 状态。
第四次挥手
A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。
4. TCP的粘包/拆包原因及其解决方法是什么
粘包原因:
a)应用程序一次写入的数据 < 套接字缓冲区的大小;
b)接收时不及时读取套接字缓冲区的数据;
拆包原因:
a)应用程序一次写入的数据 > 套接字缓冲区的大小;
b)进行MSS最大报文的一个TCP分段时,当TCP报文的长度 - TCP头部的长度 > MSS;
解决方法有三种,具体如下:
(1)发送端给每个数据包添加包首部,首部长度应该至少包含数据包的长度;
(2)固定每次发送的报文长度,不够用0补充;
(3)约定好包的边界,添加首部尾部标识,如特殊字符;
5. Netty的粘包/拆包是怎么处理的,有哪些实现
Netty提供了五种方案解决粘包拆包的问题
行拆包器 LineBasedFrameDecoder,每个应用层数据包,都以换行符作为分隔符,进行分割拆分
分隔符拆包器 DelimiterBasedFrameDecoder,每个应用层数据包,都通过自定义的分隔符,进行分割拆分
基于数据包长度的拆包器 LengthFieldBasedFrameDecoder,将应用层数据包的长度,作为接收端应用层数据包的拆分依据。按照应用层数据包的大小,拆包。这个拆包器,有一个要求,就是应用层协议中包含数据包的长度
(1)FixedLengthFrameDecoder
对于使用固定长度的粘包和拆包场景,可以使用FixedLengthFrameDecoder,该解码一器会每次读取固定长度的消息,如果当前读取到的消息不足指定长度,那么就会等待下一个消息到达后进行补足。
(2) LineBasedFrameDecoder
LineBasedFrameDecoder的作用主要是通过换行符,即\n或者\r\n对数据进行处理。
(3)DelimiterBasedFrameDecoder
DelimiterBasedFrameDecoder的作用则是通过用户指定的分隔符对数据进行粘包和拆包处理。
(4)LengthFieldBasedFrameDecoder
LengthFieldBasedFrameDecoder会按照参数指定的包长度偏移量数据对接收到的数据进行解码,从而得到目标消息体数据。
(5)自定义粘包与拆包器
可以通过实现ByteToMessageDecoder来实现。这里ByteToMessageDecoder的作用是将接收到的ByteBuf数据转换为某个对象数据。通过实现这个抽象类,用户就可以达到实现自定义粘包和拆包处理的目的。
6. 同步与异步、阻塞与非阻塞的区别
个人认为同步和异步是更抽象的概念,是相对的任务而言的。而阻塞和非阻塞就就字面的意思是对当前的进程或线程而言的。
概念之间的区别
同步与异步
同步与异步在不同的场景下有不同的概念,在IO模型中的同步异步,主要区别在当任务A调用任务B的过程中,进程A是否继续进行。
如果A等待B的结果,则为同步;
如果A不等待B的结果,则为异步;
同步状态下任务A的执行时依赖于任务B的,任务A成功是依赖于成功B的。而异步模式下两者是不相关的。
异步的实现方式大概有三种:状态、通知和回调。状态就是任务A去查询任务B的结果如何 ;通知就是等任务B执行完成之后通知任务A来实现;回调就是任务A定义个回调函数,当任务B结束后会自动调用回调函数;
阻塞与非阻塞
阻塞和非阻塞的主要区别在,任务A等待B的结果的过程中,任务A是否会被挂起? 如果A等待的过程中会挂起,则为阻塞; 如果A等待的过程中不会挂起,则为非阻塞。
同步阻塞的情况下,任务A会挂起,同步非阻塞的情况下任务A并不挂起。不挂起的情况下任务A保留有响应信号的能力。
非阻塞的情况下并不会导致线程切换(只是不强制进行线程切换,如果该线程的时间片用完还是会切换的),可能效率更高,cpu利用率也更高,但是cpu可能会无意义空转,这样又会导致性能降低,所以使用何种方式需要看当前的系统情况。
上面两点似乎被分的很清楚,但是实际上这两个概念我认为指的是同一件事情,站的角度不同而已,过分的强调概念是无意义的。同步和异步更多的是两个任务之间数据通信方式,而阻塞非阻塞,则是站在当前线程自身的角度考虑是否可以在保留进程不挂起而继续进行任务来看的。
7. 网络IO模型
网络IO模型有5种,分别是阻塞式I/O、非阻塞式I/O、I/O复用模型、信号驱动I/O 和异步I/O。5种IO模型中前四种全部都是同步IO,只有最后一种是异步IO,这里的同步和异步区别在于同步IO会在数据到达内核区的等待过程中进行各种方式的检查,如果查到了有数据到达了内核区则进行阻塞,因此整个IO的流程是分成2个阶段的。而异步IO的模式是只有数据完成了从内核区到用户区的复制之后才有通知。 而同步IO中根据第一阶段的不同策略又分成很多的不同模型。
8. BIO、NIO、AIO分别是什么
BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持
9. select、poll、epoll的机制及其区别
select,poll,epoll都是IO多路复用(multiplexing)的机制。I/O多路复用的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作
select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
select、poll、epoll的区别:
消息传递方式:
select:内核需要将消息传递到用户空间,需要内核的拷贝动作;
poll:同上;
epoll:通过内核和用户空间共享一块内存来实现,性能较高;
文件句柄剧增后带来的IO效率问题:
select:因为每次调用都会对连接进行线性遍历,所以随着FD剧增后会造成遍历速度的“线性下降”的性能问题;
poll:同上;
epoll:由于epoll是根据每个FD上的callable函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll不会对性能产生线性下降的问题,如果所有socket都很活跃的情况下,可能会有性能问题;
支持一个进程所能打开的最大连接数:
select:单个进程所能打开的最大连接数,是由FD_SETSIZE宏定义的,其大小是32个整数大小(在32位的机器上,大小是3232,64位机器上FD_SETSIZE=3264),我们可以对其进行修改,然后重新编译内核,但是性能无法保证,需要做进一步测试;
poll:本质上与select没什么区别,但是他没有最大连接数限制,他是基于链表来存储的;
epoll:虽然连接数有上线,但是很大,1G内存的机器上可以打开10W左右的连接;
10. 对Netty的了解
Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持。作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也是基于Netty的NIO框架构建。
Netty 利用 Java 高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 构建一个客户端/服务端,其具有高并发、传输快、封装好等特点。
高并发:Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),它的并发性能得到了很大提高 。
传输快:Netty的传输快其实也是依赖了NIO的一个特性——零拷贝。
封装好:Netty封装了NIO操作的很多细节,提供易于使用的API,还有心跳、重连机制、拆包粘包方案等特性,使开发者能能够快速高效的构建一个稳健的高并发应用。
11. Netty跟Java NIO有什么不同,为什么不直接使用JDK NIO类库
- Java NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector,ServerSocketChannel、SocketChannel、ByteBuffer等。
- 需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程涉及到Reactor模式,你必须对多线程和网络编程非常熟悉,才能写出高质量的NIO程序。
- 可靠性能力补齐,工作量和难度非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐的工作量和难度都非常大。
- 基于以上原因,在大多数场景下,不建议直接使用JDK的NIO类库,除非你精通NIO编程或者有特殊的需求。在绝大多数的业务场景中,我们可以使用NIO框架Netty来进行NIO编程,它既可以作为客户端也可以作为服务端,同时支持UDP和异步文件传输,功能非常强大。
12. Netty组件有哪些,分别有什么关联
netty中常用的一些组件
- Bootstrap 和 ServerBootstrap
- Channel 和 ServerChannel
- EventLoop 和 EventLoopGroup
- ChannelPipeline
- ChannelHandler
组件之间的存在关系
- Bootstrap 在 bind() 或 connect() 方法被调用后,会创建一个新的 Channel
- ServerBootstrap bind() 方法调用时,将会创建一个 ServerChannel
,当连接被接受时,ServerChannel 将会创建一个新的子 Channel,
ServerChannel 和子 Channel 之间是一对多的关系 - 一个 EventLoopGroup 包含多个 EventLoop
- 一个Channel在它的生命周期中只会注册在一个EventLoop上
- 一个EvenLoop在运行当中,会被分配给多个Channel
- 一个 channel 对应一个channelPipeline ,一个 channelPipeline 对应多个channelHandler
13. Netty的执行流程
服务端:
(1)创建ServerBootStrap实例
(2)设置并绑定Reactor线程池:EventLoopGroup,EventLoop就是处理所有注册到本线程的Selector上面的Channel
(3)设置并绑定服务端的channel
(4)创建处理网络事件的ChannelPipeline和handler,网络时间以流的形式在其中流转,handler完成多数的功能定制:比如编解码 SSl安全认证
(5)绑定并启动监听端口
(6)当轮训到准备就绪的channel后,由Reactor线程:NioEventLoop执行pipline中的方法,最终调度并执行channelHandler
客户端:
(1)创建BootStrap实例
(2)设置并绑定Reactor线程池:EventLoopGroup,EventLoop就是处理所有注册到本线程的Selector上面的Channel
(3)设置并绑定客户端的channel
(4)创建处理网络事件的ChannelPipeline和handler
(5)发起连接