完整代码请查看:https://gitee.com/firewolf/java-io/tree/master/java-io/netty-01-helloworld
在 Java IO(BIO)、伪异步IO、NIO、NIO2(AIO)、Netty这篇文章里面,我们简单的讲解了Netty的一些优点,下面我们使用Netty完成一个简单的群聊功能(多个客户端之间可以进行聊天),同时对Netty里面的一些核心API进行介绍。
一、使用Netty开发群聊
(一)引入netty的jar包
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.29.Final</version>
</dependency>
注意:Netty最新版本为5.0.0.Alpha2,但是已经被废弃了,所以这里依然使用的是Netty4。
(二)Netty群聊服务端
package com.firewolf.java.io.netty.chat;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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;
import java.util.HashMap;
import java.util.Map;
/**
* 作者:刘兴 时间:2019/5/15
**/
public class NettyChatServer {
public static Map<String, Channel> channels = new HashMap<>();
public NettyChatServer(int port) {
//服务端NIO线程组,用于网络事件处理,实际上就是Reactor线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用于接受客户端的连接
EventLoopGroup workGroup = new NioEventLoopGroup(); // 用户SocketChannel进行网络读写请求
try {
ServerBootstrap bootstrap = new ServerBootstrap(); //用于启动NIO的辅助启动类
bootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class) //这个类似于NIO中的NioServerSocketChannel,IO中的ServerSocket,用于绑定端口
.option(ChannelOption.SO_BACKLOG, 1024) //这是TCP参数,这里是设置了backlog的大小
//设置网络IO事件处理器信息,如处理器类、编码、解码等等。
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new MessageServerHandler());
}
});
//绑定端口,同时进行阻塞直到服务关闭
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("启动服务端监听端口:" + port);
//关闭通道
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅退出,释放线程资源
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
/**
* 事件消息处理器,需要继承ChannelInboundHandlerAdapter
*/
class MessageServerHandler extends ChannelInboundHandlerAdapter {
//当有客户端连接的时候,会触发
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//保存所有的连接Channel
channels.put(ctx.channel().id().asLongText(), ctx.channel());
}
//读取到数据之后会触发这个方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String message = new String(bytes); //把读取到的字节信息转换成字符串
System.out.println("有客户端消息:" + message);
buf.release(); //获取到消息后释放ByteBuf
//转发消息
dispathMessage(message, ctx.channel());
}
}
private void dispathMessage(String message, Channel channel) {
String otherResponse = "有人说:" + message;
String meResponse = "我说:" + message;
channels.entrySet().forEach(entry -> {
String response = entry.getKey().equals(channel.id().asLongText()) ? meResponse : otherResponse;
ByteBuf writeBuf = Unpooled.buffer(response.getBytes().length);
writeBuf.writeBytes(response.getBytes());
entry.getValue().writeAndFlush(writeBuf);
});
}
public static void main(String[] args) {
new NettyChatServer(9999);
}
}
(三)Netty群聊客户端
package com.firewolf.java.io.netty.chat;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
import java.util.Scanner;
/**
* 作者:刘兴 时间:2019/5/15
**/
public class NettyChatClient {
public NettyChatClient(String host, int port) {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new MessageClientHandler());
}
});
ChannelFuture f = bootstrap.connect(new InetSocketAddress(host, port)).sync();
System.out.println("连接服务器成功-----");
f.channel().closeFuture().sync();//关闭通道
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
class MessageClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//通道准备就绪,可以写入数据
//启动线程监听用户输入
new Thread(new Runnable() {
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
while (true) {
String message = scanner.nextLine();
ByteBuf buf = Unpooled.buffer(message.getBytes().length);
buf.writeBytes(message.getBytes());
ctx.writeAndFlush(buf);
}
}
}).start();
}
//读取数据
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String message = new String(bytes);
System.out.println(message);
}
}
public static void main(String[] args) {
new NettyChatClient("127.0.0.1", 9999);
}
}
接下来,我们启动Server,然后启动多个Client,就可以进行聊天了。
我们可以看到,Netty相对NIO来说,开发更加简洁。
二、Netty核心API
- ServerBootStrap:服务端启动辅助类,用于设置服务器启动的一些参数
- EventLoopGroup:Netty的Reactor线程池,实际上是一个EventLoop数组。EventLoop的责是处理所有注册到本线程多路复用器Selector上的Channel,
- NioServerSocketChannel:相当于NIO中的ServerSocketChannel
- ChannelPipeline:负责处理网络事件的职责连,管理和执行ChannelHandler,典型网络事件如下:
- 链路注册;
- 链路激活;
- 链路断开;
- 接受到请求消息;
- 请求消息接受并处理完毕;
- 发送应答消息;
- 链路发生异常;
- 发生用户自定义事件;
- ChannelHandler:Netty提供给用户的用于定制和扩展的关键接口。可以用于定制:消息编解码、心跳、安全认证、TSL/SSL认证、流量监控、流量整形等,Netty提供了很多定义好的ChannelHandler,比较实用的有:
- MessageToByteEncoder:系统编码框架,用来把POJO转换成ByteBuf
- ByteToMessageDecoder:系统解码框架,用来把ByteBuf转换成POJO
- ByteToMessageCodec:系统编解码框架
- MessageToMessageDecoder:二次解码器,把一种类型的POJO转换成另外一种类型的POJO
- LengthFieldBasedFrameDecoder:基于长度的半包解码器
- DelimiterBasedFrameDecoder:自定义分隔符解码器
- LineBasedFrameDecoder:回车换行符分割包解码器
- FixedLengthFrameDecoder:固定长度包解码器
- LengthFieldPrepender:会在包的前面添加固定长度
- StringDecoder:把包转换成字符串
- LoggingHandler:打印码流日志
- SslHandler:SSL安全认证
- IdleStateHandler:链路空闲检测
- Base64Decoder:Base64解码器
- Base64Encoder:Base64编码器
- ChannelInboundHandlerAdapter:提供链接事件回调接口,我们通常主要处理这一部分内容
- HttpObjectAggregator:把HttpMessage和HttpContent整合成FullHttpRequest,POST请求的时候,需要使用这个处理器,否则的话,接收不到参数(因为POST请求的参数是携带在请求体里面的)
- HttpRequestDecoder:Http请求编码器
- HttpResponseEncoder:Http响应编码器