1.声明
当前内容主要用于本人学习和使用Netty,并记录其中的实际控制流程,和一些技巧
当前demo为:
- 客户端向服务器发送消息
- 服务器响应消息
- 优化其中的解析
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形成流程控制图
所以
- 当前的从
客户端写出的消息
,直接经过StringEncoder变成ByteBuf,最后直接传递到了服务器端的NettyServerHandler
进行处理(所以服务器接收的不是String而是ByteBuf
) - 从
服务器端写出的消息
也是直接通过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的时候小心当前的出站和入站规则处理类的放置顺序,使用不当可能导致不同的效果