Netty
2022-02-22 17:41:52 1 举报
AI智能生成
netty详解
作者其他创作
大纲/内容
netty详解
IO模型
BIO
实现
服务端
创建一个socker
ServerSocket serverSocket = new ServerSocket(9000);
等待客户端连接
Socket clientSocket = serverSocket.accept();
阻塞方法
接受客户端数据
byte[] bytes = new byte[1024];
int read = clientSocket.getInputStream().read(bytes);
read是阻塞方法
响应客户端数据
clientSocket.getOutputStream().write("HelloClient".getBytes());
clientSocket.getOutputStream().flush();
客户端
创建客户端socker
Socket socket = new Socket("localhost", 9000);
向服务端发数据
socket.getOutputStream().write("HelloServer".getBytes());
socket.getOutputStream().flush();
接受服务端回传的数据
byte[] bytes = new byte[1024];
socket.getInputStream().read(bytes);
socket.close();
缺点
IO代码里read操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,浪费资源
如果线程很多,会导致服务器线程太多,压力太大,比如C10K问题
应用场景
BIO 方式适用于连接数目比较小且固定的架构, 这种方式对服务器资源要求比较高, 但程序简单易理解。
NIO
实现
服务端(没有引入多路复用)
创建NIO服务端socket
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
设置ServerSocketChannel为非阻塞
serverSocket.configureBlocking(false);
非阻塞模式accept方法不会阻塞,否则会阻塞
NIO的非阻塞是由操作系统内部实现的,底层调用了linux内核的accept函数
SocketChannel socketChannel = serverSocket.accept();
设置非阻塞并保存在list中
socketChannel.configureBlocking(false);
channelList.add(socketChannel);
遍历连接进行数据读取
Iterator<SocketChannel> iterator = channelList.iterator();
while (iterator.hasNext()) {
SocketChannel sc = iterator.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = sc.read(byteBuffer); // 非阻塞模式read方法不会阻塞,否则会阻塞
if (len > 0) {
System.out.println("接收到消息:" + new String(byteBuffer.array()));}
else if (len == ‐1) { // 如果客户端断开,把socket从集合中去掉
iterator.remove();}}
服务端(引入多路复用)
创建NIO ServerSocketChannel
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
设置ServerSocketChannel为非阻塞
serverSocket.configureBlocking(false);
打开Selector处理Channel,即创建epoll
Selector selector = Selector.open();
把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
阻塞等待需要处理的事件发生
selector.select();
获取selector中注册的全部事件的 SelectionKey 实例
Set<SelectionKey> selectionKeys = selector.selectedKeys();
遍历SelectionKey对事件进行处理
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 如果是OP_ACCEPT事件,则进行连接获取和事件注册
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);}
else if (key.isReadable()) { // 如果是OP_READ事件,则进行读取和打印
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = socketChannel.read(byteBuffer);
客户端
应用场景
NIO方式适用于连接数目多且连接比较短(轻操作) 的架构, 比如聊天服务器, 弹幕系统, 服务器间通讯,编程比较复杂
三大核心组件
Channel(通道)
Buffer(缓冲区)
Selector(多路复用器)
epoll
Selector.open() //创建多路复用器
底层:调用linux内核函数epoll_create创建epoll实例(epfd=epoll_create(256))
socketChannel.register() //将channel注册到多路复用器上
将fd(socketChannel)添加到内部的集合里
selector.select() //阻塞等待需要处理的事件发生
底层:调用linux内核函数epoll_ctl进行事件绑定
底层:调用linux函数epoll_wait等待epoll实例上的事件
涉及操作系统中断请求
总结
实例
redis
nginx
AIO
应用场景
AIO方式适用于连接数目多且连接比较长(重操作)的架构,JDK7 开始支持
netty
为什么Netty使用NIO而不是AIO?
maven依赖
io.netty/netty‐all/4.1.35.Final
目前使用4.x版本,5.x版本已经弃用
实现
服务端
客户端
Netty线程模型
Netty 抽象出两组线程池BossGroup和WorkerGroup,BossGroup专门负责接收客户端的连接, WorkerGroup专
门负责网络的读写
BossGroup和WorkerGroup类型都是NioEventLoopGroup
NioEventLoopGroup 相当于一个事件循环线程组, 这个组中含有多个事件循环线程 , 每一个事件循环线程是NioEventLoop
每个NioEventLoop都有一个selector , 用于监听注册在其上的socketChannel的网络通讯
关键:selector底层对应的就是Nio的epoll模型
每个Boss NioEventLoop线程内部循环执行的步骤有 3 步
处理accept事件 , 与client 建立连接 , 生成 NioSocketChannel
将NioSocketChannel注册到某个worker NIOEventLoop上的selector
处理任务队列的任务 , 即runAllTasks
每个worker NIOEventLoop线程循环执行的步骤
轮询注册到自己selector上的所有NioSocketChannel 的read, write事件
处理 I/O 事件, 即read , write 事件, 在对应NioSocketChannel 处理业务
runAllTasks处理任务队列TaskQueue的任务 ,一些耗时的业务处理一般可以放入TaskQueue中慢慢处
理,这样不影响数据在 pipeline 中的流动处理
每个worker NIOEventLoop处理NioSocketChannel业务时,会使用 pipeline (管道),管道中维护了很多 handler
处理器用来处理 channel 中的数据
ByteBuf详解
组件
Channel
ChannelPipeline
ChannelHandler
编码解码器
netty内部
StringEncoder
StringDecoder
ObjectEncoder
ObjectDecoder
自定义编码解码器
protostuff
粘包拆包
解决方案
消息定长度,传输的数据大小固定长度,例如每段的长度固定为100字节,如果不够空位补空格
在数据包尾部添加特殊分隔符,比如下划线,中划线等,这种方法简单易行,但选择分隔符的时候一定要注意每条数据的内部一定不
能出现分隔符。
发送长度:发送每条数据的时候,将数据的长度一并发送,比如可以选择每条数据的前4位是数据的长度,应用层处理时可以根据长度
来判断每条数据的开始和结束。
netty内部方案
LineBasedFrameDecoder (回车换行分包)
DelimiterBasedFrameDecoder(特殊分隔符分包)
FixedLengthFrameDecoder(固定长度报文来分包)
心跳检测机制
IdleStateHandler
public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds)
readerIdleTimeSeconds: 读超时. 即当在指定的时间间隔内没有从 Channel 读取到数据时, 会触发一个 READER_IDLE 的IdleStateEvent 事件.
writerIdleTimeSeconds: 写超时. 即当在指定的时间间隔内没有数据写入到 Channel 时, 会触发一个 WRITER_IDLE 的IdleStateEvent 事件
allIdleTimeSeconds: 读/写超时. 即当在指定的时间间隔内没有读或写操作时, 会触发一个 ALL_IDLE 的 IdleStateEvent 事件.
注:这三个参数默认的时间单位是秒
IdleStateHandler(boolean observeOutput, long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit)
自动重连
客户端启动时连接失败,需要重连
给ChannelFuture加一个监听器
连接中断开,客户端重连
需要再客户端的channelInactive中加入重连
0 条评论
下一页