前言
当前需求基于Netty基础进行开发,需要补充Netty基础可以参考资料 Netty基本介绍 和 线程模型。
一、需求
-
编写一个 Netty 群聊系统,实现服务器端和客户端之间的数据简单通讯
-
实现多人群聊
-
服务器端:可以监测用户上线,离线,并实现消息转发功能
-
客户端:可以发送消息给其它所有用户,同时可以接受其它用户发送的消息
二、 服务器端编写
-
编写步骤:
-
服务端
-
创建bossGroup线程组: 处理网络事件–连接事件
-
创建workerGroup线程组: 处理网络事件–读写事件
-
创建服务端启动助手
-
设置bossGroup线程组和workerGroup线程组
-
设置服务端通道实现为NIO
-
参数设置
-
创建一个通道初始化对象
-
向pipeline中添加自定义业务处理handler
-
启动服务端并绑定端口,同时将异步改为同步
-
关闭通道和关闭连接池
-
-
自定义handler
- 创建自定义handler,继承SimpleChannelInboundHandler
- 创建Channel的list集合 作为 成员变量,管理所有客户端连接
- 重写 通道就绪事件channelActive(ChannelHandlerContext ctx) ,当有客户端连接时,将通道放入集合
- 重写 通道读取事件channelRead0(ChannelHandlerContext ctx, String msg) ,当服务端读取到某个客户端发送的数据时,向其他客户端进行转发
- 重写 异常处理事件exceptionCaught(ChannelHandlerContext ctx, Throwable cause) ,当发生异常时,从集合中移除channel
- 重写 通道未就绪事件channelInactive(ChannelHandlerContext ctx),当channel下线时会触发此事件,需要从集合中移除channel
-
-
相关代码:
-
ChatServer
public class ChatServer { //端口号 private int port; public ChatServer(int port){ this.port = port; } public void run() throws InterruptedException{ EventLoopGroup bossGroup = null; EventLoopGroup workerGroup = null; try{ //1. 创建bossGroup线程组: 处理网络事件--连接事件 bossGroup = new NioEventLoopGroup(1); //2. 创建workerGroup线程组: 处理网络事件--读写事件 workerGroup = new NioEventLoopGroup(); //3. 创建服务端启动助手 ServerBootstrap serverBootstrap = new ServerBootstrap(); //4. 设置bossGroup线程组和workerGroup线程组 serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class)//5. 设置服务端通道实现为NIO .option(ChannelOption.SO_BACKLOG, 128)//6. 参数设置,设置当前等待队列为128 .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)//6. 参数设置,设置检测socket连接是否活跃 .childHandler(new ChannelInitializer<>() {//7. 创建一个通道初始化对象 @Override protected void initChannel(Channel channel) throws Exception { //8. 向pipeline中添加自定义业务处理handler //添加编解码器 channel.pipeline().addLast(new StringDecoder()); channel.pipeline().addLast(new StringEncoder()); //添加自定义处理handler channel.pipeline().addLast(new ChatServerHandler()); } }); //9. 启动服务端并绑定端口 ChannelFuture channelFuture = serverBootstrap.bind(port); channelFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { if (channelFuture.isSuccess()){ System.out.println("============>>>端口绑定成功"); }else { System.out.println("============>>>端口绑定失败"); } } }); System.out.println("服务器启动成功......"); //10. 关闭通道和关闭连接池 channelFuture.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws InterruptedException { new ChatServer(9988).run(); } }
-
ChatServerHandler
public class ChatServerHandler extends SimpleChannelInboundHandler<String> { public static List<Channel> channelList = new ArrayList<>(); //通道就绪事件 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); //当有新的客户端连接,将通道放入集合 channelList.add(channel); System.out.println("[Server]:" + channel.remoteAddress().toString().substring(1) + "在线。"); } //通道读取事件 @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception { //排除当前发送消息的通道,依次发送给其他客户端 Channel channel = channelHandlerContext.channel(); for (Channel channel1 : channelList) { if (channel1 != channel){ channel1.writeAndFlush("[" + channel.remoteAddress().toString().substring(1) + "]说:" + s); } } } //通道未就绪事件 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); //当有客户端断开连接的时候,移除响应的通道 channelList.remove(channel); System.out.println("[Server]:" + channel.remoteAddress().toString().substring(1) + "下线."); } //异常处理事件 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); Channel channel = ctx.channel(); //移除集合 channelList.remove(channel); System.out.println("[Server]:" + channel.remoteAddress().toString().substring(1) + "异常."); } }
-
二、 客户端编写
-
编写步骤:
-
客户端
-
创建线程组
-
创建客户端启动助手
-
设置线程组
-
设置客户端通道实现为NIO
-
创建一个通道初始化对象
-
向pipeline中添加自定义业务处理handler
-
启动客户端,等待连接服务端,同时将异步改为同步
-
获取控制台输入信息,向服务端发送消息
-
关闭通道和关闭连接池
-
-
自定义handler
- 重写 通道读就绪事件channelRead0(ChannelHandlerContext channelHandlerContext, String s),打印接收到的消息
-
-
相关代码:
-
ChatClient
public class ChatClient { private String ip;//服务端IP private int port;//服务端端口号 public ChatClient(String ip, int port){ this.ip = ip; this.port = port; } public void run() throws InterruptedException{ EventLoopGroup group = null; try { //1. 创建线程组 group = new NioEventLoopGroup(); //2. 创建客户端启动助手 Bootstrap bootstrap = new Bootstrap(); //3. 设置线程组 bootstrap.group(group) .channel(NioSocketChannel.class)//4. 设置客户端通道实现为NIO .handler(new ChannelInitializer<>() {//5. 创建一个通道初始化对象 @Override protected void initChannel(Channel channel) throws Exception { //6. 向pipeline中添加自定义业务处理handler //添加编解码器 channel.pipeline().addLast(new StringDecoder()); channel.pipeline().addLast(new StringEncoder()); //添加自定义客户端处理类 channel.pipeline().addLast(new ChatClientHandler()); } }); //7. 启动客户端,等待连接服务端,同时将异步改为同步 ChannelFuture channelFuture = bootstrap.connect(ip, port).sync(); //8.获取控制台输入信息,向服务端发送消息 Channel channel = channelFuture.channel(); System.out.println("-------" + channel.localAddress().toString().substring(1) + "--------"); Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()){ String msg = scanner.nextLine(); //发送消息 channel.writeAndFlush(msg); } // 9.关闭通道和关闭连接池 channel.closeFuture().sync(); }finally { group.shutdownGracefully(); } } public static void main(String[] args) throws InterruptedException { new ChatClient("127.0.0.1", 9988).run(); } }
-
ChatClientHandler
public class ChatClientHandler extends SimpleChannelInboundHandler<String> { //通道读就绪事件 @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception { System.out.println(s); } }
-
三、测试
-
客户端上线
-
客户端发送消息
-
某一客户端离线