Netty 客户端与服务端收发消息demo
客户端与服务端收发消息,要实现的具体功能是:在控制台输入一条消息之后按回车键,校验完客户端的登录状态之后,把消息发送到服务端;服务端收到消息之后打印,并向客户端发送一条消息,客户端收到消息之后打印。
客户端NettyClient
import com.xc.xcspringboot.x_netty.client.handler.*; import com.xc.xcspringboot.x_netty.protocol.PacketCodec; import com.xc.xcspringboot.x_netty.protocol.request.MessageRequestPacket; import com.xc.xcspringboot.x_netty.util.LoginUtil; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import java.util.Date; import java.util.Scanner; import java.util.concurrent.TimeUnit; public class NettyClient { private static final int MAX_RETRY = 5; private static final String HOST = "127.0.0.1"; private static final int PORT = 8000; public static void main(String[] args) { NioEventLoopGroup workerGroup = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); // 引导器引导启动 bootstrap .group(workerGroup) // 指定线程模型,驱动连接的数据读写 .channel(NioSocketChannel.class) // 指定IO模型为NioSocketChannel,表示IO模型为NIO .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 表示连接的超时时间,超过这个时间,如果仍未连接到服务端,则表示连接失败。 .option(ChannelOption.SO_KEEPALIVE, true) // 表示是否开启TCP底层心跳机制,true表示开启。 .option(ChannelOption.TCP_NODELAY, true) // 表示是否开始Nagle算法,true表示关闭,false表示开启。 .handler(new ChannelInitializer<Channel>() { // 给引导类指定一个Handler,主要定义连接的业务处理逻辑 @Override protected void initChannel(Channel channel) { // channel.pipeline().addLast(new StringEncoder()); // channel.pipeline().addLast(new FirstClientHandler()); channel.pipeline().addLast(new ClientHandler()); } }); // 建立通道 /* * 配置完线程模型、IO模型、业务处理逻辑之后,调用connect方法进行连接, * 可以看到connect方法有两个参数,第一个参数可以填写IP或者域名,第二个参数填写端口号。 * 由于connect方法返回的是一个Future,也就是说这个方法是异步的,通过addListener方法可以监听连接是否成功,进而打印连接信息。 */ // Channel channel = bootstrap.connect("127.0.0.1", 8000).channel(); connect(bootstrap, HOST, PORT, MAX_RETRY); } private static void connect(Bootstrap bootstrap, String host, int port, int retry) { bootstrap.connect(host, port).addListener(future -> { if (future.isSuccess()) { System.out.println(new Date() + ": 连接成功,启动控制台线程……"); Channel channel = ((ChannelFuture) future).channel(); startConsoleThread(channel); } else if (retry == 0) { System.err.println("重试次数已用完,放弃连接!"); } else { // 第几次重连 int order = (MAX_RETRY - retry) + 1; // 本次重连的间隔 int delay = 1 << order; System.err.println(new Date() + ": 连接失败,第" + order + "次重连…… delay:" + delay); /* * 定时任务调用的是bootstrap.config().group().schedule(), * 其中bootstrap.config()这个方法返回的是BootstrapConfig,它是对Bootstrap配置参数的抽象, * 然后bootstrap.config().group()返回的就是我们在一开始配置的线程模型workerGroup, * 调用workerGroup的schedule方法即可实现定时任务逻辑。 */ bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit.SECONDS); } }); } /*private static void startConsoleThread(Channel channel) throws InterruptedException { while (true) { channel.writeAndFlush(new Date() + " Hello world"); Thread.sleep(2000); } }*/ private static void startConsoleThread(Channel channel) { new Thread(() -> { while (!Thread.interrupted()) { if (LoginUtil.hasLogin(channel)) { // 在判断是否登录成功的时候取出这个标志位 System.out.println("输入消息发送至服务端: "); Scanner SC = new Scanner(System.in); String line = SC.nextLine(); MessageRequestPacket packet = new MessageRequestPacket(); packet.setMessage(line); ByteBuf buffer = channel.alloc().buffer(); PacketCodec.INSTANCE.encode(buffer, packet); channel.writeAndFlush(buffer); } } }).start(); } }
ClientHandler
import com.xc.xcspringboot.x_netty.protocol.Packet; import com.xc.xcspringboot.x_netty.protocol.PacketCodec; import com.xc.xcspringboot.x_netty.protocol.request.LoginRequestPacket; import com.xc.xcspringboot.x_netty.protocol.response.LoginResponsePacket; import com.xc.xcspringboot.x_netty.protocol.response.MessageResponsePacket; import com.xc.xcspringboot.x_netty.util.LoginUtil; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.util.Date; public class ClientHandler extends ChannelInboundHandlerAdapter { // 当 Channel 已经连接/绑定并且已经就绪时调用 public void channelActive(ChannelHandlerContext ctx) { System.out.println(new Date() + ":客户端开始登录"); //创建登录对象 LoginRequestPacket loginRequestPacket = new LoginRequestPacket(); // loginRequestPacket.setUserId(UUID.randomUUID().toString()); loginRequestPacket.setUserName("flash"); loginRequestPacket.setPassword("pwd"); //编码 ByteBuf buffer = ctx.alloc().buffer(); PacketCodec.INSTANCE.encode(buffer, loginRequestPacket); // 写数据 ctx.channel().writeAndFlush(buffer); } // 当从 Channel 读取数据时被调用 public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf byteBuf = (ByteBuf) msg; Packet packet = PacketCodec.INSTANCE.decode(byteBuf); if (packet instanceof LoginResponsePacket) { // 登录逻辑 LoginResponsePacket loginResponsePacket = (LoginResponsePacket) packet; if (loginResponsePacket.isSuccess()) { LoginUtil.markAsLogin(ctx.channel()); // 在登录成功之后,给Channel绑定一个登录成功的标志位 System.out.println(new Date() + ":客户端登录成功"); } else { System.out.println(new Date() + ":客户端登录失败,原因:" + loginResponsePacket.getReason()); } } else if (packet instanceof MessageResponsePacket) { // 消息逻辑 MessageResponsePacket messageResponsePacket = (MessageResponsePacket) packet; System.out.println(new Date() + ":收到服务端的消息:" + messageResponsePacket.getMessage()); } } }
服务端
import com.xc.xcspringboot.x_netty.server.handler.*; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import java.util.Date; public class NettyServer { private static final int PORT = 8000; public static void main(String[] args) { NioEventLoopGroup boosGroup = new NioEventLoopGroup(); // bossGroup表示监听端口,接收新连接的线程组 NioEventLoopGroup workerGroup = new NioEventLoopGroup(); // workerGroup表示处理每一个连接的数据读写的线程组 ServerBootstrap serverBootstrap = new ServerBootstrap(); // 引导类ServerBootstrap,这个类将引导服务端的启动工作 serverBootstrap .group(boosGroup, workerGroup) // .group(bossGroup,workerGroup)给引导类配置两大线程组 .channel(NioServerSocketChannel.class) // 指定服务端的IO模型为NIO NioServerSocketChannel是对NIO类型连接的抽象 .option(ChannelOption.SO_BACKLOG, 1024) // 表示系统用于临时存放已完成三次握手的请求的队列的最大长度 .childOption(ChannelOption.SO_KEEPALIVE, true) // 表示是否开启TCP底层心跳机制,true表示开启。 .childOption(ChannelOption.TCP_NODELAY, true) // 表示是否开启Nagle算法,true表示关闭,false表示开启 .handler(new ChannelInitializer<NioServerSocketChannel>() { // handler()方法用于指定在服务端启动过程中的一些逻辑 @Override protected void initChannel(NioServerSocketChannel ch) { System.out.println("服务端启动过程中..."); } }) .childHandler(new ChannelInitializer<NioSocketChannel>() { // childHandler()方法,给这个引导类创建一个ChannelInitializer,主要是定义后续每个连接的数据读写 protected void initChannel(NioSocketChannel ch) { // 泛型参数NioSocketChannel,这个类就是Netty对NIO类型连接的抽象 /*ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { System.out.println(msg); } });*/ // ch.pipeline().addLast(new FirstServerHandler()); ch.pipeline().addLast(new ServerHandler()); } }); /* * 要启动一个Netty服务端,必须要指定三类属性,分别是线程模型、IO模型、连接读写处理逻辑。 * 有了这三者,之后再调用bind(8000),就可以在本地绑定一个8000端口启动服务端。 */ // .bind(8000); // 给这个ChannelFuture添加一个监听器GenericFutureListener serverBootstrap.bind(PORT).addListener(future -> { if (future.isSuccess()) { System.out.println(new Date() + ": 端口[" + PORT + "]绑定成功!"); } else { System.err.println("端口[" + PORT + "]绑定失败!"); } }); } }
ServerHandler
import com.xc.xcspringboot.x_netty.protocol.Packet; import com.xc.xcspringboot.x_netty.protocol.PacketCodec; import com.xc.xcspringboot.x_netty.protocol.request.LoginRequestPacket; import com.xc.xcspringboot.x_netty.protocol.request.MessageRequestPacket; import com.xc.xcspringboot.x_netty.protocol.response.LoginResponsePacket; import com.xc.xcspringboot.x_netty.protocol.response.MessageResponsePacket; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.util.Date; public class ServerHandler extends ChannelInboundHandlerAdapter { // 当从 Channel 读取数据时被调用 public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf requestByteBuf = (ByteBuf) msg; Packet packet = PacketCodec.INSTANCE.decode(requestByteBuf); // 解码 ByteBuf buffer = ctx.alloc().buffer(); if (packet instanceof LoginRequestPacket) { // 处理登录 LoginRequestPacket loginRequestPacket = (LoginRequestPacket) packet; LoginResponsePacket loginResponsePacket = new LoginResponsePacket(); loginResponsePacket.setVersion(packet.getVersion()); //登录校验 if (valid(loginRequestPacket)) { //校验成功 loginResponsePacket.setSuccess(true); } else { //校验失败 loginResponsePacket.setReason("账号密码校验失败"); loginResponsePacket.setSuccess(false); } //编码 PacketCodec.INSTANCE.encode(buffer, loginResponsePacket); ctx.channel().writeAndFlush(buffer); } else if (packet instanceof MessageRequestPacket) { //处理消息 MessageRequestPacket messageRequestPacket = ((MessageRequestPacket) packet); System.out.println(new Date() + ":收到客户端消息: " + messageRequestPacket.getMessage()); MessageResponsePacket messageResponsePacket = new MessageResponsePacket(); messageResponsePacket.setMessage("服务端回复[" + messageRequestPacket.getMessage() + "] "); PacketCodec.INSTANCE.encode(buffer, messageResponsePacket); ctx.channel().writeAndFlush(buffer); } } private boolean valid(LoginRequestPacket loginRequestPacket) { return true; } }