一、环境准备
需要提前将需要的jar包导入到项目中:
netty-all-4.1.25.Final.jar
二、简单说明
1. 使用Netty框架来编写服务器代码是为了实现Java的NIO编程
三、服务器端
服务器端分为两部分:调度部分 和 业务逻辑部分
调度部分:
1. 创建调度工具对象:
ServerBootstrap serverBootstrap = new ServerBootstrap();
2. 创建线程池组:
NioEventLoopGroup group = new NioEventLoopGroup();
3. 调度:
serverBootstrap.localAddress(host, port) .group(group) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel channel) throws Exception { channel.pipeline().addLast(new ServerHandler1()); channel.pipeline().addLast(new ServerHandler3()); channel.pipeline().addLast(new ServerHandler4()); channel.pipeline().addLast(new ServerHandler2()); } });
说明:
ServerHandler1,ServerHandler2...是业务逻辑的类,通过channel来调用
这里业务逻辑类必须是继承自ChannelInboundHandler或者ChannelOutboundHandler,或者他们的子类
上述例子中:
ServerHandler1和ServerHandler2都是属于ChannelInboundHandler的子类
ServerHandler3和ServerHandler4都是属于ChannelOutboundHandler的子类
这里有一个规定:最后一个添加的业务逻辑必须是inbound,否则如果最后一个是outbound的话,将不会被执行到。
执行顺序:先执行inbound再执行outbound,inbound按照添加的顺序执行,outbound按照添加的相反顺序执行。
如上述例子中执行顺序为ServerHandler1,ServerHandler2,ServerHandler4,ServerHandler3
3. 绑定服务器:
ChannelFuture channelFuture = serverBootstrap.bind().sync();
4. 关闭channel:
channelFuture.channel().closeFuture().sync();
5. 关闭线程组:
group.shutdownGracefully();
业务逻辑部分:
这里分开来谈
inbound部分:
1. 需要继承自ChannelInboundHandler或者他的子类下
2. 重写方法并在方法中补全相应的业务逻辑:channelRead(ChannelHandlerContext ctx, Object msg)
3. 如果后面还有inbound的流水线,则在后面调用方法:ctx.fireChannelRead(msg);
4. 如果后面就是outbound的流水线了,则在后面调用方法:ctx.write(msg);
outbound部分:
1. 需要继承自ChannelOutboundHandler或者他的子类下
2. 重写方法并在方法中补全相应的业务逻辑:write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
3. 如果后面还有inbound的流水线,则在后面调用方法:ctx.fireChannelRead(msg);
4. 如果后面就是outbound的流水线了,则在后面调用方法:ctx.write(msg);
四、客户端
客户端也分为两部分:调度部分 和 业务逻辑部分
调度部分:
1. 创建调度工具对象:
Bootstrap bootstrap = new Bootstrap();
2. 创建线程池组:
NioEventLoopGroup group = new NioEventLoopGroup();
3. 调度:
bootstrap.remoteAddress(host, port) .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel channel) throws Exception { channel.pipeline().addLast(new ClientHandler2()); channel.pipeline().addLast(new ClientHandler1()); } });
4. 连接服务器:
ChannelFuture channelFuture = bootstrap.connect().sync();
5. 关闭channel:
channelFuture.channel().closeFuture().sync();
6. 关闭线程组:
group.shutdownGracefully();
业务逻辑部分:
同前面服务器端设计相同。
五、发送消息的具体实现
通过ChannelHandlerContext对象发送消息,需要将消息封装成ByteBuf
1. 将发送内容确定好:
String content = "hello server, m client";
2. 将内容转成字节数组:
byte[] bytes = content.getBytes();
3. 创建ByteBuf对象:
ByteBuf byteBuf = Unpooled.buffer(bytes.length);
4. 将字节数组中的内容写入ByteBuf
byteBuf.writeBytes(bytes);
5. 发送消息
ctx.writeAndFlush(byteBuf);
接收消息和发送消息相反,一般会直接拿到一个ByteBuf对象,需要将其反转成字符串
1. 创建字节数组
byte[] bytes = new byte[byteBuf.readableBytes()];
2. 将内容写入字节数组
byteBuf.readBytes(bytes);
3. 转成字符串
new String(bytes,"UTF-8")