Demo使用的Netty版本:
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.74.Final</version>
</dependency>
丢弃协议(Discard Protocol)
什么是丢弃协议?
丢弃协议是很简单的协议,它是一种丢弃任何收到的数据并且不会有任何响应的协议。下面通过Netty来实现。
编写处理收到数据时的处理类
package vip.huhailong.handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
public class DiscardServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
try{
while (buf.isReadable()){
System.out.print((char)buf.readByte());
System.out.flush();
}
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
代码解析
DiscardServerHandler
类继承了ChannelInboundHandlerAdapter
,它是ChannelInboundHandler
接口的一个实现。ChannelInboundHandler
提供了可以覆盖各种事件处理的方法。channelRead()
方法是重写的一个方法,从客户端接收到新的数据时就会调用该方法,上面代码中在接收消息后会打印该消息。- 要实现丢弃协议需要丢弃收到的数据,
ByteBuf
是一个引用计数对象,必须通过release()
方法显示的释放,通常的做法是使用try/finally
块种在try
中处理完消息后在finally
中通过ReferenceCountUtil.release(Object msg)
方法来释放。 exceptionCaught()
在Netty由于I/O或处理时间时抛出异常时被调用,上面代码中在发生异常时会打印堆栈信息以及关闭关联的Channel。
编写启动服务
package vip.huhailong.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
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 vip.huhailong.handler.DiscardServerHandler;
public class DiscardServer {
private int port;
public DiscardServer(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)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true);
ChannelFuture future = b.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new DiscardServer(port).run();
}
}
代码解析
NioEventLoopGroup
是一个处理I/O操作的多线程事件循环,Netty为不同类型的传输提供了各种EventLoopGroup
实现,在这个例子中为了实现一个服务端的应用程序,因此需要使用俩个NioEventLoopGroup
,第一个,bossGroup
用来接受传入的连接。第二个,workerGroup
是当bossGroup
一旦接受连接并将接受的连接注册到worker中就会处理接受连接的流量。使用多少线程以及它们如何映射到创建的Channel取决于EventLoopGroup
的实现。ServerBootstrap
是一个设置服务器的辅助类,它可以简化通过Channel这种繁琐的方式创建服务器的过程。- 上面代码中
channel(NioServerSocketChannel.class)
是通过指NioServerSocketChannel
类来实例化一个新的Channel来接受传入的连接。 ChannelInitializer
是一个特殊的处理程序,目的是用来帮助用户配置新的Channel。像上述代码中我们添加了自己的DiscardServerHanlder
类来实现丢弃协议的处理。option()
方法和childOption()
方法的区别,option()
方法用于接受传入NioServerSocketChannel
。childOption()
用于父ServerChannel
接受的Channels
,在上面的程序中为NioSocketChannel
。
运行结果
Echo协议
什么是Echo协议?
echo协议和上面的丢弃协议唯一的区别就是当服务器收到消息后会将消息返回客户端,它的实现也和丢弃协议一样很简单,只需要在上面的丢弃协议代码中简单的修改就可以实现。
修改处理类
将丢弃协议服务器中的DiscardServerHandler
中的代码修改为如下:
package vip.huhailong.handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
public class DiscardServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.write(msg);
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
主要是修改了方法channelRread
,删除之前方法中的代码,添加ChannelHandlerContext
中的write和flush来实现echo协议。
代码解析
ChannelHandlerContext
对象提供了各种操作来帮助我们触法各种I/O事件和操作。这里我们调用write(Object)
方法是将接收到的消息逐字写入。请注意,这里没有像丢弃协议那样显示的进行释放,这是因为当消息被写入网络时,Netty会自动为我们释放它。ctx.write(Object)
不会将消息写入通道中,它是被缓冲到内部,然后由ctx.flush()
方法刷新到通道中,或者我们也可以直接调用方法ctx.writeAndFlush(msg)
,效果和上面是一样的。
当我们再次通过telnet测试时会发现发送的内容会回显到telnet客户端中。表明echo协议成功。