Netty应用实例-群聊系统
-
实例要求:
- 编写一个Netty群聊系统,实现服务器和客户端之间的数据简单通讯(非阻塞)
- 实现多人群聊
- 服务端:可以监测用户上线,离线,并实现消息转发功能
- 客户端:通过channel可以无阻塞发送消息给其他所用用户,同时可以接受其他用户发送的消息(由服务器转达得到)
- 目的:进一步理解Netty非阻塞网络编程机制
-
代码
-
服务端GroupChatServer
-
package com.jl.java.web.groupchat; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; /** * @author jiangl * @version 1.0 * @date 2021/5/24 20:56 */ public class GroupChatServer { private int port; public GroupChatServer(int port) { this.port = port; } //编写run方法,处理客户端的请求 public void run(){ NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); NioEventLoopGroup workGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup,workGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG,128) .childOption(ChannelOption.SO_KEEPALIVE,true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); //向pipeline加入一个解码器 pipeline.addLast("decoder",new StringDecoder()); //向pipeline加入一个编码器 pipeline.addLast("encoder",new StringEncoder()); //加入自己的业务处理handler //自己的业务处理handler会被outbound的write方法再次加工处理,然后输出给客户端,outbound的顺序是从后往前的 pipeline.addLast(new GroupChatServerHandler()); } }); System.out.println("Netty服务器启动...."); ChannelFuture channelFuture = b.bind(port).sync(); channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } public static void main(String[] args) { new GroupChatServer(7000).run(); } }
-
-
服务端Handler
-
package com.jl.java.web.groupchat; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.util.CharsetUtil; import io.netty.util.concurrent.GlobalEventExecutor; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * @author jiangl * @version 1.0 * @date 2021/5/24 21:05 */ public class GroupChatServerHandler extends SimpleChannelInboundHandler<String> { //定义一个Channel组,管理所有的channel //GlobalEventExecutor.INSTANCE 是全局的时间执行器,是一个单例 private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static Map<String,Channel> all = new HashMap<String,Channel>(); /** * handlerAdded 表示连接建立,一旦连接,第一个被执行 * 将当前channel加入到channelGroup * @param ctx * @throws Exception */ @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); //该方法会将channelGroup中所有的channel遍历,并发送消息 //不需要自己遍历 channelGroup.writeAndFlush(sdf.format(new Date())+"\n "+" [客户端]"+channel.remoteAddress()+"加入聊天\n"); //将客户加入聊天的信息推送给其他在线的客户端 channelGroup.add(channel); all.put(channel.remoteAddress().toString(),channel); } /** * 表示Channel处于活动状态,提示xx上线 * @param ctx * @throws Exception */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println(sdf.format(new Date())+"\n "+ctx.channel().remoteAddress()+" 上线了~"); } /** * 断开连接,将XX离开信息,推送给当前在线的客户 * @param ctx * @throws Exception */ @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { channelGroup.writeAndFlush(sdf.format(new Date())+"\n "+"[客户端]"+ctx.channel().remoteAddress()+" 离开了~"); System.out.println("当前ChannelGroup的大小:"+channelGroup.size()); all.remove(ctx.channel().remoteAddress().toString()); } /** * 表示Channel处于不活动状态,提示xx离线 * @param ctx * @throws Exception */ @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println(sdf.format(new Date())+"\n "+ctx.channel().remoteAddress()+" 离线了~"); } /** * 读取数据 * @param ctx * @param msg * @throws Exception */ @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { Channel channel = ctx.channel(); /** * 这里简单判断 如果内容里边包含#那么就是私聊 */ if(msg.contains("#")){ String id = msg.split("#")[0]; String body = msg.split("#")[1]; Channel userChannel = all.get(id); String key = channel.remoteAddress().toString().split(":")[1]; userChannel.writeAndFlush(sdf.format(new Date())+"\n 【用户】 "+id+" 私聊你说 : "+body); channel.writeAndFlush(sdf.format(new Date())+"\n [自己]对"+id+"说:"+ body+"\n"); return; } //遍历channelGroup,根据不同的情况,会送不同的消息 channelGroup.forEach(ch->{ if(ch != channel){ ch.writeAndFlush(sdf.format(new Date())+"\n [客户]"+channel.remoteAddress()+"说:"+ msg+"\n"); }else{ ch.writeAndFlush(sdf.format(new Date())+"\n [自己]发送了消息"+channel.remoteAddress()+"说:"+ msg+"\n"); } }); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //关闭通道 ctx.close(); } }
-
-
客户端GroupChatClient
-
package com.jl.java.web.groupchat; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import java.util.Scanner; /** * @author jiangl * @version 1.0 * @date 2021/5/24 21:39 */ public class GroupChatClient { //属性 /** * 主机地址 */ private final String host; /** * 端口号 */ private final int port; public GroupChatClient(String host, int port) { this.host = host; this.port = port; } public void run(){ NioEventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap().group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { //得到pipeline ChannelPipeline pipeline = ch.pipeline(); //加入解码器 pipeline.addLast("decoder", new StringDecoder()); //加入编码器 pipeline.addLast("encoder", new StringEncoder()); //加入自定义的handler pipeline.addLast(new GroupChatClientHandler()); } }); ChannelFuture channelFuture = bootstrap.connect(host, port).sync(); Channel channel = channelFuture.channel(); System.out.println("---------------"+channel.remoteAddress()+"---------------"); System.out.println("#########################################################"); System.out.println("~~~~~~~~~~~~~~~~/ip:端口号#消息内容,这样就能私聊~~~~~~~~~~~~~~~~~~"); System.out.println("#########################################################"); Scanner scanner = new Scanner(System.in); while(scanner.hasNextLine()){ String s = scanner.nextLine(); channel.writeAndFlush(s+"\r\n"); } channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } public static void main(String[] args) { new GroupChatClient("127.0.0.1",7000).run(); } }
-
-
客户端Handler
-
package com.jl.java.web.groupchat; import com.sun.xml.internal.ws.message.stream.OutboundStreamHeader; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; /** * @author jiangl * @version 1.0 * @date 2021/5/24 21:44 */ public class GroupChatClientHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println(msg.trim()); } }
-
-