Netty相比于Socket的优缺点
- Socket的缺点
- Socket的Api繁多,学习成本高
- 要想编写出高质量的NIO程序需要熟悉多线程编程
- Netty的优点
- Api简单,学习成本低
- 功能强大,内置多种协议
- 性能高
- 社区活跃,会及时的更新处理漏洞
客户端代码
package com.example.netty_demo.client;
import com.example.netty_demo.handler.MyClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class MyClient {
public static void main(String[] args) throws Exception {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
//创建bootstrap对象,配置参数
Bootstrap bootstrap = new Bootstrap();
//设置线程组
bootstrap.group(eventExecutors)
//设置客户端的通道实现类型
.channel(NioSocketChannel.class)
//使用匿名内部类初始化通道
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//添加客户端通道的处理器
ch.pipeline().addLast(new MyClientHandler());
}
});
System.out.println("客户端准备就绪,随时可以起飞~");
//连接服务端
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6666).sync();
//对通道关闭进行监听
channelFuture.channel().closeFuture().sync();
} finally {
//关闭线程组
eventExecutors.shutdownGracefully();
}
}
}
服务端代码
package com.example.netty_demo.server;
import com.example.netty_demo.handler.MyServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class MyServer {
public static void main(String[] args) throws Exception {
//创建两个线程组 boosGroup、workerGroup
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务端的启动对象,设置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//设置两个线程组boosGroup和workerGroup
bootstrap.group(bossGroup, workerGroup)
//设置服务端通道实现类型
.channel(NioServerSocketChannel.class)
//设置线程队列得到连接个数
.option(ChannelOption.SO_BACKLOG, 128)
//设置保持活动连接状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
//使用匿名内部类的形式初始化通道对象
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//给pipeline管道设置处理器
socketChannel.pipeline().addLast(new MyServerHandler());
}
});//给workerGroup的EventLoop对应的管道设置处理器
System.out.println("java技术爱好者的服务端已经准备就绪...");
//绑定端口号,启动服务端
ChannelFuture channelFuture = bootstrap.bind(6666).sync();
//对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
bootStrap和ServerBootStrap
分别是为客户端和服务端启动类提供一个工厂类
使用BootStrap和ServerBootStrap创建启动器的步骤
- 设置EventLoopGroup线程组
- 设置channel通道类型
- 设置option参数
- 设置handler流水线
- 进行端口绑定
- 启动
- 等待通道关闭
- 优雅关闭
group()
服务端必须要两个线程组,使用时可以设置线程,默认线程数是Cpu核数的两倍。
- bossGroup:负责监听客户端的连接,专门负责与客户端进行连接,并把连接注册到WorkerGroup的Selector里。
- WorkerGroup:workerGroup负责处理每个连接发生的读写事件。
channel
用于设置通道类型
- NioSocketChannel:异步非阻塞客户端TCP Socket连接
- NioServerSocketChannel:异步非阻塞服务端TCP Socket连接
- OioSocketChannel:同步阻塞
- OioServerSocketChannel:...
- NioSctpChannel:异步客户端sctp连接
- NioSctpServerChannel: ...
option和childOption
- option设置的是服务端接收到的连接,也就是bossGroup进程
- ServerSocketChannel参数,option的参数
- SO_BACKLOG Socket参数:服务端默认接收连接的队列的长度,如果连接已满,则拒绝客户端的连接。默认值:windows200 其他128。
- ServerSocketChannel参数,option的参数
- childOption设置的是客户端的连接,workerGroup进程
- SocketChannel参数,childOption的参数
- SO_RCVBUF Socket参数:TCP数据接收缓冲区大小。
- TCP_NODELAY TCP参数:立即发送数据,默认为true
- SO_KEEPALIVE Socket参数:连接保持活性,默认false,当开启时,TCP会主动探测空闲连接的有效性。
- SocketChannel参数,childOption的参数
设置流水线
- channelHandler是具体处理请求的处理器,实际上每一个channel都有一个处理器的流水线。
- bootStrap中childHandler方法需要初始化管道实例化channelInitializer这时候需要重写initChannel()初始化管道的方法
- 处理器Handler有两种方法
- channelInboundHandlerAdapter(入站处理器)
- ChannelActive 连接建立事件
- ChannelRead 读取事件
- ChannelReadComplete 读完成事件
- channelOutboundHandler(出站处理器)
- bind 绑定端口
- connect 连接服务端
- read 读事件
- write 写事件
- flush 刷新事件
- close 关闭channel
- channelInboundHandlerAdapter(入站处理器)
优雅的关闭EventLoopGroup
- 调用.shutdowmGracefully()方法
channel
- 为用户提供
- 当前的状态
- 获取状态的方法
- isOpen: 通道是否打开
- isRegister:通道是否注册到EventLoop
- isActive:是否通道处于活动并已连接
- isWritable:当I/O线程立即执行请求的写入操作时
- 获取状态的方法
- channel配置参数(例如接收缓冲区的大小)
- 获取配置参数的方法
-
ChannelConfig config = channel.config();//获取配置参数 //获取ChannelOption.SO_BACKLOG参数, Integer soBackLogConfig = config.getOption(ChannelOption.SO_BACKLOG); //因为我启动器配置的是128,所以我这里获取的soBackLogConfig=128
-
- 获取配置参数的方法
- channel支持的IO操作(读,写,连接,绑定),以及处理与channel相关联的所有IO事件和请求的channelpipeline
- 读,写等操作
- write,read,flush
-
//获取ChannelPipeline对象 ChannelPipeline pipeline = ctx.channel().pipeline(); //往pipeline中添加ChannelHandler处理器,装配流水线 pipeline.addLast(new MyServerHandler());
- 读,写等操作
- 当前的状态
Selector
用于监听事件,管理注册到Selector中的channel,实现多路复用器。
pipeline和channelPipeline
一个channel中只能有一个channelPipeLine,这个channelPipeLine在channel创建时被创建,channelPipeline包含了一个channelHandler的列表,且所有channelHandler都会注册到channelPipeLine中。
ChannelHandlerContext
eventLoopGroup
每个Group包含一个或多个EventLoop,而每个EventLoop中维护一个Selector实例。
轮询机制的实现原理
- 变量 ++;
- 返回相应变量作为下标的结果。
Netty怎么实现一个端口支持多种协议
- 协议探测(使用ByteToMessageDecoder):根据数据的前几个字节(至少4个字节(HTTP和WebSocket的前几个字节相同))判断出消息所属的协议类型。
- 修改pipeline:移除当前的处理器链,添加相应协议的处理器链到pipeline中
- 消息传递:将已读取的数据传递给新的处理器