一、什么是Netty
Netty是一个NIO服务框架,简化了网咯应用框架的开发难度。Netty是一个事件驱动模型,(将很多阶段抽象成一个个的事件,让后将事件映射到多个回调方法上)。主要的特点有(https://netty.io/index.html):
- 为BIO/NIO提供统一的API
- 是一个灵活的、可扩展的事件驱动模型
- 提供高度可定制化的线程模型
- 支持真正的无连接数据报传输
- 提供高吞吐、低延时,并且占用更少的资源
- 提供最小化的不必要内存拷贝
- 提供对proto Buffer的支持
二、Netty 的初级使用
public class TestServer{
public static void main(String[] args){
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, wokerGroup)
.channel(NioServerSockerChannel.class)
.childHandler(new TestServerInitializer());
ChannelFuture future = bootstrap.bind(8899).sync();
future.channel().closeFuture().sync();
}
}
// 自定义启动初始化器
public class TestServerInitializer extens ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel ch) throws Exception{
ChannelPipeline pipeline = ch.pipiline();
// HttpServerCodec是HttpRequestDecoder和HttpResponseDecoder的组合
pipeline.addLast("httpServerCodec", new HttpServerCodec());
pipeline.addLast("serverHandler", new TestServerHandler());
}
}
// 实现自己的回调方法
public class TestServerHandler extends SimpleChannelInboundHandler<HttpObject>{
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg){
if(msg instanceof HttpObject){
ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
// response.headers().set()
ctx.writeAndFlush(response);
}
}
}
public class MyClient{
public static void main(String[] args){
EventLoopGroup loop = new NioEventLoopGroup();
try{
Bootstrap strap = new Bootstrap();
strap.group(loop).channel(NioSocketChannel.class)
.handler(new MyClientHandler());
ChannelFuture fut = strap.connect("localhost", 8899).sync();
fut.channel.closeFuture().sync();
}finally{
loop.shutdownGracefull();
}
}
}
public class MyClientInitializer extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel ch) throws Exception{
ChannelPipeline pipeline = ch.pipiline();
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast("my client handler", new MyClientHandler());
}
}
public class MyClientHandler extends SimpleChannelInboundHandler<String>{
@Overrie
protected void channelRead0(ChannelHandlerContext ctx, String msg){
// 服务端向客户端传递数据时,客户端的处理逻辑
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
cause.printStackTrace();
ctx.close();
}
@Overrie
protected void channelActive(ChannelHandlerContext ctx){
// 当与服务端建立好连接后,操作
ctx.writeAndFlush("test");
}
}
2.1 SimpleChannelInboundHandler
处理流入channel的事件处理,常见的触发有
- channelActive() channel是活跃状态
- channelRegisterd() 注册channel
- channelAdded(); 添加channel
- channelInactive() 非活跃
- channelUnregistered() 卸载channel
执行顺序是:channelAdded() ->registered()->active()->inactive()->unregistered
2.2 ChannelInitializer
// 自定义启动初始化器
public class TestServerInitializer extens ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel ch) throws Exception{
ChannelPipeline pipeline = ch.pipiline();
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast("my handler", new MyHandler());
}
}
// 这里的泛型主要依据是请求的是什么样的格式数据
public class MyHandler extends SimpleChannelInboundHandler<String>{
// 收到消息后的处理逻辑
@Overrie
protected void channelRead0(ChannelHandlerContext ctx, String msg){
}
}
三、聊天室实例
需求:
第一个客户端A与服务端建立连接后,服务端打印A上线;
后续的客户端与服务端建立好连接后,服务端打印X上线,并且广播X上线给已经建立好的连接。
客户端X发送一条消息后,广播到其余客户端
如果客户端下线后,服务端向所有的客户端广播X下线。
// 核心思想,利用channel的事件机制,将对应建立好的channel加入到一个组里维护
public class MyChatServerHandler extends SimpleChannelInboundHandler<String>{
private static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public void channelRead0(ChannelHandlerContext ctx, String msg){
Channel channel = ctx.channel();
group.forEach(t -> {
if(channel != t){
t.writeAndFlush("非自己发出的消息");
}else{
t.writeAndFlush("自己发出的消息");
}
});
}
@Override
public void handlerAdded(ChannelHandlerContext ctx){
Channel ch = ctx.channel();
group.writeAndFlush(ch.remoteAddress() +"online");
group.add(ch);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx){
Chanel channel = ctx.channel();
group.writeAndFlush(channel.remoteAddress() + "offline");
// group.remove(channel); 这个netty会自动调用,所以可以省略
}
@Override
public void channelActive(ChannelHandlerContext ctx){
Channel channel = ctx.channel();
System.out.println(channel.remoteAddress() + "上线");
}
@Override
public void channelInactive(ChannelHandlerContext ctx){
Channel channel = ctx.channel();
System.out.println(channel.remoteAddress() + "下线");
}
}