效果图:
服务端:
package cn.itcast.netty.chat; 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.concurrent.GlobalEventExecutor; import java.util.ArrayList; import java.util.List; //自定义一个服务器端业务处理类 public class ChatServerHandler extends SimpleChannelInboundHandler<String> { public static List<Channel> channels = new ArrayList<>(); @Override //通道就绪 public void channelActive(ChannelHandlerContext ctx) { Channel inChannel=ctx.channel(); channels.add(inChannel); System.out.println("[Server]:"+inChannel.remoteAddress().toString().substring(1)+"上线"); } @Override //通道未就绪 public void channelInactive(ChannelHandlerContext ctx) { Channel inChannel=ctx.channel(); channels.remove(inChannel); System.out.println("[Server]:"+inChannel.remoteAddress().toString().substring(1)+"离线"); } @Override //读取数据 protected void channelRead0(ChannelHandlerContext ctx, String s) { Channel inChannel=ctx.channel(); for(Channel channel:channels){ if(channel!=inChannel){ channel.writeAndFlush("["+inChannel.remoteAddress().toString().substring(1)+"]"+"说:"+s+" "); } } } }
package cn.itcast.netty.chat; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; //聊天程序服务器端 public class ChatServer { private int port; //服务器端端口号 public ChatServer(int port) { this.port = port; } public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline pipeline=ch.pipeline(); //往pipeline链中添加一个解码器 pipeline.addLast("decoder",new StringDecoder()); //往pipeline链中添加一个编码器 pipeline.addLast("encoder",new StringEncoder()); //往pipeline链中添加自定义的handler(业务处理类) pipeline.addLast(new ChatServerHandler()); } }); System.out.println("Netty Chat Server启动......"); ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); System.out.println("Netty Chat Server关闭......"); } } public static void main(String[] args) throws Exception { new ChatServer(9999).run(); } }
客户端:
package cn.itcast.netty.chat; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; //自定义一个客户端业务处理类 public class ChatClientHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception { System.out.println(s.trim()); } }
package cn.itcast.netty.chat; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Scanner; //聊天程序客户端 public class ChatClient { private final String host; //服务器端IP地址 private final int port; //服务器端端口号 public ChatClient(String host, int port) { this.host = host; this.port = port; } public void run(){ EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch){ ChannelPipeline pipeline=ch.pipeline(); //往pipeline链中添加一个解码器 pipeline.addLast("decoder",new StringDecoder()); //往pipeline链中添加一个编码器 pipeline.addLast("encoder",new StringEncoder()); //往pipeline链中添加自定义的handler(业务处理类) pipeline.addLast(new ChatClientHandler()); } }); ChannelFuture cf=bootstrap.connect(host,port).sync(); Channel channel=cf.channel(); System.out.println("------"+channel.localAddress().toString().substring(1)+"------"); Scanner scanner=new Scanner(System.in); channel.writeAndFlush("我是一个客户端"); while (scanner.hasNextLine()){ String msg=scanner.nextLine(); channel.writeAndFlush(msg+" "); } } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { new ChatClient("127.0.0.1",9999).run(); } }
package cn.itcast.netty.chat; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Scanner; //聊天程序客户端 public class ChatClient { private final String host; //服务器端IP地址 private final int port; //服务器端端口号 public static Channel channel; public ChatClient(String host, int port) { this.host = host; this.port = port; } public void run(){ EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch){ ChannelPipeline pipeline=ch.pipeline(); //往pipeline链中添加一个解码器 pipeline.addLast("decoder",new StringDecoder()); //往pipeline链中添加一个编码器 pipeline.addLast("encoder",new StringEncoder()); //往pipeline链中添加自定义的handler(业务处理类) pipeline.addLast(new ChatClientHandler()); } }); ChannelFuture cf=bootstrap.connect(host,port).sync(); channel=cf.channel(); System.out.println("------"+channel.localAddress().toString().substring(1)+"------"); channel.writeAndFlush("run方法"); // Scanner scanner=new Scanner(System.in); // while (scanner.hasNextLine()){ // String msg=scanner.nextLine(); // channel.writeAndFlush(msg+" "); // } } catch (Exception e) { e.printStackTrace(); } finally { //group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { new ChatClient("127.0.0.1",9999).run(); channel.writeAndFlush("clinet-main方法"); } }
解决沾包
/** * @description: netty消息解码器 * @author: * @create: 2018-11-30 21:58 **/ public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder { /** * @param maxFrameLength 第一个参数代表最大的序列化长度 * @param lengthFieldOffset 代表长度属性的偏移量 简单来说就是message中 总长度的起始位置(Header中的length属性的起始位置) 4 * @param lengthFieldLength 代表长度属性的长度 整个属性占多长(length属性为int,占4个字节) 4 */ public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) { super(maxFrameLength, lengthFieldOffset, lengthFieldLength); } /** * 将bytebuf解码成想要的对象 * @param ctx 上下文环境 * @param in 输入 */ @Override protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { //1 调用父类(LengthFieldBasedFrameDecoder)方法: 返回整包或者空 ByteBuf frame = (ByteBuf)super.decode(ctx, in); if(frame == null){ return null; } NettyMessage message = new NettyMessage(); Header header = new Header(); header.setCrcCode(frame.readInt()); header.setLen(frame.readInt()); // header.setStation(frame.readShort()); // header.setSource(frame.readBytes(10).toString(Charset.forName("UTF-8"))); // header.setDestination(frame.readBytes(10).toString(Charset.forName("UTF-8"))); header.setStation(frame.readBytes(20).toString(Charset.forName("UTF-8"))); header.setSource(frame.readBytes(20).toString(Charset.forName("UTF-8"))); header.setDestination(frame.readBytes(20).toString(Charset.forName("UTF-8"))); header.setComponent(frame.readShort()); header.setType(frame.readByte()); Object body = null; if (frame.readableBytes() > 0){//说明body中有值 if((header.getType() == NettyMessageTypeEnum.DEVICE_REAL_REQ.getCode()) || (header.getType() == NettyMessageTypeEnum.DEVICE_BASE_REQ.getCode()) ){ body = frame.toString(Charset.forName("GBK")); }else{ body = frame.toString(Charset.forName("utf-8")); } message.setBody(body); } message.setHeader(header); message.setBody(body); return message; } }
/** * @description: netty消息编码器 * @author: * @create: 2018-11-30 21:59 **/ public class NettyMessageEncoder extends MessageToByteEncoder<NettyMessage> { /** * 将NettyMessage对象编码成ByteBuffer对象 * @param ctx 连接的上下文环境 * @param msg 需要编码的消息 * @param sendBuffer 编码完成的结果 */ @Override protected void encode(ChannelHandlerContext ctx, NettyMessage msg, ByteBuf sendBuffer) throws Exception { //TODO: 如果在handler中抛出异常 会不会断掉通道? 需要做一个试验 //TODO:将对象作为值传递 并后续继续使用 可读性差 bytebuffer if (msg == null || msg.getHeader() == null){ throw new Exception("encode message is null!!"); } Header header = msg.getHeader(); sendBuffer.writeInt(header.getCrcCode()); sendBuffer.writeInt(0); // sendBuffer.writeShort(header.getStation()); // sendBuffer.writeBytes(header.getSource().getBytes(),0,10); // sendBuffer.writeBytes(header.getDestination().getBytes(),0,10); sendBuffer.writeBytes(header.getStation().getBytes(),0,20); sendBuffer.writeBytes(header.getSource().getBytes(),0,20); sendBuffer.writeBytes(header.getDestination().getBytes(),0,20); sendBuffer.writeShort(header.getComponent()); sendBuffer.writeByte(header.getType()); if (msg.getBody() != null){ sendBuffer.writeBytes(JsonHelper.toJson(msg.getBody()).getBytes()); // sendBuffer.writeBytes(JsonUtil.toJson(msg.getBody(), JsonSerialize.Inclusion.NON_NULL).getBytes(Charset.forName("UTF-8"))); } sendBuffer.setInt(4,sendBuffer.readableBytes()-8); } }