Netty4:一个简单的消息传递的demo(分析和解析)

1.声明

当前内容主要用于本人学习和使用Netty,并记录其中的实际控制流程,和一些技巧

当前demo为:

  1. 客户端向服务器发送消息
  2. 服务器响应消息
  3. 优化其中的解析

2.基本demo

1.服务器端启动类


import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.hy.netty.server.handler.NettyServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
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 io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * 
 * @author hy
 * @createTime 2021-04-17 07:38:13
 * @description 通过官方的文件创建一个netty服务器
 *
 */
public class NettyServer {
	private final int port;
	private final EventLoopGroup parentGroup;
	private final EventLoopGroup childGroup;
	private final ServerBootstrap bootstrap;

	public NettyServer(int port) {
		this.port = port;
		this.parentGroup = new NioEventLoopGroup();
		this.childGroup = new NioEventLoopGroup();
		bootstrap = new ServerBootstrap();
	}

	// 启动当前netty服务器的方法
	public void start() throws Exception {

		// 创建nettyServer的处理器
		final NettyServerHandler nettyServerHandler = new NettyServerHandler(this);
		bootstrap.group(parentGroup, childGroup) // 向当前的服务启动中添加组
				.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
					@Override
					public void initChannel(SocketChannel ch) throws Exception {
						ch.pipeline()
						.addLast(nettyServerHandler)
						.addLast(new StringEncoder())
						.addLast(new StringDecoder());
					}
				}).childOption(ChannelOption.SO_KEEPALIVE, true);

		ChannelFuture future = bootstrap.bind(port).sync();

		if (future.isSuccess()) {
			System.out.println("服务端启动成功");
		} else {
			System.out.println("服务端启动失败");
			future.cause().printStackTrace();
		}

		future.channel().closeFuture().sync();// 这里必须要使用,否则又可能报错
	}

	// 关闭当前的Netty服务器
	public void close() {
		parentGroup.shutdownGracefully();
		childGroup.shutdownGracefully();
	}


	public static void main(String[] args) throws Exception {
		int port = 8080;
		new NettyServer(port).start();// 创建一个Netty服务并使用8080端口,然后启动
	}
}

服务器的消息Handler


import com.hy.netty.server.NettyServer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;

/**
 * @description 创建自己的Netty服务处理器,用于处理当前NettyServer中接受的请求的数据
 * @author hy
 * @date 2019-10-08
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter { // (1)

	private final NettyServer nettyServer;

	public NettyServerHandler(NettyServer nettyServer) {
		this.nettyServer = nettyServer;
	}

	// 这里是管道容器中的数据的读取和处理
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
		System.out.println("调用NettyServerHandler当前的channelRead方法!");
		// 通过bytebuf来进行读取数据
		String message = null;
		if (msg instanceof String) {
			message = (String) msg;
		} else if (msg instanceof ByteBuf) {
			ByteBuf in = (ByteBuf) msg;
			message = in.toString(io.netty.util.CharsetUtil.UTF_8);// 这里用于接受消息,使用utf-8编码
		}
		try {

			System.out.println("NettyServer:接收到消息【 " + message + " 】");
			if ("CLIENT CLOSED".equalsIgnoreCase(message)) {
				System.out.println("客户连接已关闭!");
				nettyServer.close();// 关闭当前的服务器
			} else {
				// 当前服务器的操作
				ctx.channel().writeAndFlush("服务器已接收消息!");
			}

		} finally {
			ReferenceCountUtil.release(msg); // 必须要释放
		}
	}

	// 出现异常的时候调用的方法
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		System.out.println("调用NettyServerHandler当前的exceptionCaught方法!");
		cause.printStackTrace();
		ctx.close();
	}
}

2.客户端启动类


import java.util.Scanner;
import com.hy.netty.client.handler.NettyClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
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.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * @description 通过官方文档创建netty的客户端
 * @author hy
 * @date 2019-10-08
 */
public class NettyClient {
	private final String host;// 当前netty客户端连接的主机地址
	private final int port;// 当前netty客户端连接的端口
	private final EventLoopGroup mainGroup;
	private final Bootstrap bootstrap;
	private Channel channel;

	public NettyClient(String host, int port) {
		this.host = host;
		this.port = port;
		this.mainGroup = new NioEventLoopGroup();
		this.bootstrap = new Bootstrap();
	}

	public Channel getChannel() {
		return this.channel;
	}

	// 启动当前netty服务器的操作
	public void start() throws Exception {
		bootstrap
		.group(mainGroup)
		.channel(NioSocketChannel.class)
		.option(ChannelOption.SO_KEEPALIVE, true)
		.handler(new ChannelInitializer<SocketChannel>() {
			@Override
			public void initChannel(SocketChannel ch) throws Exception {
				ch.pipeline()
				.addLast(new NettyClientHandler())
				.addLast(new StringEncoder())
				.addLast(new StringDecoder());
			}
		});

		// 开始连接当前的服务器地址
		ChannelFuture future = bootstrap.connect(host, port).sync(); 
		// 添加监听器,用于判断是否连接成功或者连接失败
		future.addListener(new ChannelFutureListener() {

			public void operationComplete(ChannelFuture future) throws Exception {
				if (future.isSuccess()) {
					System.out.println("连接服务器成功");

				} else {
					System.out.println("连接服务器失败");
					future.cause().printStackTrace();
					mainGroup.shutdownGracefully(); // 关闭线程组
				}

			}
		});
		this.channel = future.channel();
		
	}

	// 创建向服务器发送消息的方法
	public void send(String message) {
		Channel channel = this.getChannel();
		channel.writeAndFlush(message);
	}

	// 客户端的关闭操作
	public void close() {
		send("CLIENT CLOSED");
		mainGroup.shutdownGracefully();// 关闭当前的线程组
	}

	// 当前客户端的操作
	public void clientOption() {
		@SuppressWarnings("resource")
		Scanner clientInput = new Scanner(System.in);
		while (true) {
			System.out.println("请输入发送的字符(exit或者quit退出!):");
			String nextLine = clientInput.nextLine();
			if ("exit".equalsIgnoreCase(nextLine) || "quit".equalsIgnoreCase(nextLine)) {
				break;
			}
			send(nextLine);
		}
		close();// 关闭当前的客户端
	}

	public static void main(String[] args) throws Exception {
		NettyClient client = new NettyClient("localhost", 8080);
		client.start();
		client.send("NettyClient:你好我是客户端,请求连接!");// 这里可以使用
		client.clientOption();
	}

}

客户端消息Handler类

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
/**
 * 
 * @author hy
 * @createTime 2021-04-17 07:59:25
 * @description 创建自己的服务处理器
 *
 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter { 

	// 每次有数据的时候会被调用
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
    	System.out.println("NettyClientHandler:调用当前的channelRead方法!");
    	 ByteBuf in = (ByteBuf) msg;
        try {
			System.out.println("开始读取服务器发送的数据!");
			String researchMessage = in.toString(io.netty.util.CharsetUtil.UTF_8);
			System.out.println("NettyClient:接收数据【 "+researchMessage+" 】");//这里用于接受消息
		} finally {
			ReferenceCountUtil.release(msg);//(3)这里必须要释放
		}
    }
    
    // 第一次连接上服务器的时候被调用
    @Override
    public void channelActive(final ChannelHandlerContext ctx) { 
    	System.out.println("NettyClientHandler:调用当前的channelActive方法!");
    }
    
    //出现异常的时候,直接关闭这个连接
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
    	System.out.println("NettyClientHandler:调用当前的exceptionCaught方法!");
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

3.启动和测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

测试成功!
发现问题,其中传递的是ByteBuf,但是我使用writeAndFlush是传递的为String类型

4.查看和解析当前的Handler以及解码和加码器

1.查看当前的StringEncoder和StringDeocder·
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
也就是说当前的StringEncoder和StringDecoder就是入站解码器和出站加码器

整理当前的pipline中的handler形成流程控制图

在这里插入图片描述

所以

  1. 当前的从客户端写出的消息,直接经过StringEncoder变成ByteBuf,最后直接传递到了服务器端的NettyServerHandler进行处理(所以服务器接收的不是String而是ByteBuf)
  2. 服务器端写出的消息也是直接通过StringEncoder直接变成ByteBuf传递给客户端NettyClientHandler所以接收的也是ByteBuf

思考,直接将StringDecoder这个入站解析放在NettyHandler的前面不就行了,就可以直接得到String类型的数据,不用解码了!

在这里插入图片描述

5.修改demo

1.修改当前的服务器中的NettyServerHandler
在这里插入图片描述
2.修改当前服务器中的NettyServer中的pipline添加顺序
在这里插入图片描述

bootstrap.group(parentGroup, childGroup) // 向当前的服务启动中添加组
				.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
					@Override
					public void initChannel(SocketChannel ch) throws Exception {
						/*ch.pipeline()
						.addLast(nettyServerHandler)
						.addLast(new StringEncoder())
						.addLast(new StringDecoder());*/
						ch.pipeline()										
						.addLast(new StringDecoder())
						.addLast(nettyServerHandler)
						.addLast(new StringEncoder());
					}
				}).childOption(ChannelOption.SO_KEEPALIVE, true);

3.修改客户端的NettyClientHandler

在这里插入图片描述

4.修改客户端的NettyClient的pipline方式
在这里插入图片描述

.handler(new ChannelInitializer<SocketChannel>() {
			@Override
			public void initChannel(SocketChannel ch) throws Exception {
				/*
				ch.pipeline()
				.addLast(new NettyClientHandler())
				.addLast(new StringEncoder())
				.addLast(new StringDecoder());*/
				ch.pipeline()	
				.addLast(new StringDecoder())
				.addLast(new NettyClientHandler())
				.addLast(new StringEncoder());	
			}
		});

此时修改完毕

6.重新测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
测试成功!

7.总结

1.使用netty的时候小心当前的出站和入站规则处理类的放置顺序,使用不当可能导致不同的效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值