.Net初入java,netty通讯编程
1.什么是Netty
Netty是一个事件驱动的高性能Java网络库,是一个隐藏了背后复杂性而提供一个易于使用的API的客户端/服务端框架。Netty以其高性能和可扩展性,使开发者专注于真正感兴趣的地方。它的一个主要目标就是促进“关注点分离”:使业务逻辑从网络基础设施应用程序中分离。
不仅仅是Netty框架,其他框架的设计目的也大都是为了使业务程序和底层技术解耦,使程序员更加专注于业务逻辑实现,提高开发质量和效率。Netty为什么性能如此之高,主要是其内部的Reactor模型机制。
2.Netty如何写一个 echo 服务器
加入引用
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>4.1.29.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
<version>4.1.29.Final</version>
<scope>compile</scope>
</dependency>
1.启动代码
这里作为netty的启动服务类方法。
public void run() throws IOException,Exception {
//Group:群组,Loop:循环,Event:事件,这几个东西联在一起,相比大家也大概明白它的用途了。
//Netty内部都是通过线程在处理各种数据,EventLoopGroup就是用来管理调度他们的,注册Channel,管理他们的生命周期。
//NioEventLoopGroup是一个处理I/O操作的多线程事件循环
//bossGroup作为boss,接收传入连接
//因为bossGroup仅接收客户端连接,不做复杂的逻辑处理,为了尽可能减少资源的占用,取值越小越好
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//workerGroup作为worker,处理boss接收的连接的流量和将接收的连接注册进入这个worker
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//ServerBootstrap负责建立服务端
//你可以直接使用Channel去建立服务端,但是大多数情况下你无需做这种乏味的事情
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
//指定使用NioServerSocketChannel产生一个Channel用来接收连接
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_REUSEADDR, true)
//ChannelInitializer用于配置一个新的Channel
//用于向你的Channel当中添加ChannelInboundHandler的实现
.childHandler(new ChannelInitializer<SocketChannel>() {
public void initChannel(SocketChannel ch) throws Exception {
//ChannelPipeline用于存放管理ChannelHandel
//ChannelHandler用于处理请求响应的业务逻辑相关代码
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("framing-dec", new DecoderQc());
pipeline.addLast("msgpack encoder", new EcoderMsgpack());
pipeline.addLast(new QcServerHandler());
};
})
//对Channel进行一些配置
//注意以下是socket的标准参数
//BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
//Option是为了NioServerSocketChannel设置的,用来接收传入连接的
.option(ChannelOption.SO_BACKLOG, 128)
//是否启用心跳保活机制。在双方TCP套接字建立连接后(即都进入ESTABLISHED状态)并且在两个小时左右上层没有任何数据传输的情况下,这套机制才会被激活。
//childOption是用来给父级ServerChannel之下的Channels设置参数的
.childOption(ChannelOption.SO_KEEPALIVE, true);
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync();
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
//sync()会同步等待连接操作结果,用户线程将在此wait(),直到连接操作完成之后,线程被notify(),用户代码继续执行
//closeFuture()当Channel关闭时返回一个ChannelFuture,用于链路检测
f.channel().closeFuture().sync();
} finally {
//资源优雅释放
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
2.抽象解析类的实现
netty里有专门解析的类ByteToMessageDecoder ,只要继承就可以重写decode方法,这里自己加入了一些最大长度和最小长度的限制同时检查包头和包长以及校验码。
public abstract class DecoderBase extends ByteToMessageDecoder {
/**
* @return 数据长度最小值
*/
public abstract int get_minFrameLength();
/**
* @return 数据长度最大值
*/
public int get_maxFrameLength()
{
return 10240;
}
/**
* @param input 当前接收数组
* @return 从下一个位置起查找包头,返回所剩余的字节数(即:rest值)
*/
protected int findDataHead(ByteBuf input)
{
for (int i = 0; i < input.readableBytes() - checkHeadLen(); i++)
{
if (checkHead(input, input.readerIndex() + i))
{
return input.readerIndex() + i;
}
}
return -1;
}
/**
* @param input 当前接收数组
* @param readIndex 检查包头是否正确
* @return
*/
protected abstract boolean checkHead(ByteBuf input, int readIndex);
/**
* @return 获取包头长度
*/
protected abstract int checkHeadLen();
/**
* @param input 当前接收数组
* @return 根据协议内容获取整个包的长度
*/
protected abstract int getBodyLength(ByteBuf input);
/**
* 重写解析方法过滤
* @param context
* @param input
* @param output
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext context, ByteBuf input, List<Object> output) throws Exception {
if (input.readableBytes() <= get_minFrameLength())//还不够 最小帧的 数据
return;
if (!checkHead(input, input.readerIndex()))
{
int headIndex = findDataHead(input);
if (headIndex < 0)
{
//找不到包头
input.readSlice(input.readableBytes() - checkHeadLen() + 1);
return;
}
ByteBuf abandonData = input.readSlice(headIndex - input.readerIndex());
int length = abandonData.readableBytes();
byte[] array = new byte[length];
abandonData.getBytes(abandonData.readerIndex(), array);
//错误包头,遗弃包头之前数据,并打印出来
System.out.println(array);
return;
}
int dataLen = getBodyLength(input);
if (dataLen > get_maxFrameLength())
{
ByteBuf abandonData = input.readSlice(1);
int length = abandonData.readableBytes();
byte[] array = new byte[length];
abandonData.getBytes(abandonData.readerIndex(), array);
//错误包头,遗弃包头之前数据,并打印出来
System.out.println(array);
}
if (input.readableBytes() < dataLen)
{
//包头正确,但实际长度小于标识长度,包不完整等下次解析
return;
}
if (input.readableBytes() >= dataLen)
{
//包头正确,但实际长度大于标识长度,只解析本次处理的字节数,剩下的交给下一次解析处理
ByteBuf frame = input.readSlice(dataLen);
frame.retain();
output.add(frame);
}
}
}
这里继承抽象类,然后自己去实现。
/*实现抽象解析类*/
public class DecoderQc extends DecoderBase {
}
3.编码类实现
netty默认是可以发送实体类的,这里用String传入,但是需要重新编码为ByteBuf 才能发送。
public class EcoderMsgpack extends MessageToByteEncoder<String> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, String encodeCommandBase, ByteBuf byteBuf) throws Exception {
ByteBuf raw = Unpooled.wrappedBuffer(encodeCommandBase.getBytes(CharsetUtil.UTF_8));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println ("服务器发送:" + LocalDateTime.now().format(formatter)+":" +encodeCommandBase.toString ());
byteBuf.writeBytes(raw);
}
}
4.数据的接收
数据的接收需要继承ChannelInboundHandlerAdapter 方法并重写channelRead方法,接收到的msg为Object,具体操作看代码处理。
public class QcServerHandler extends ChannelInboundHandlerAdapter {
/**
* 每个传入消息都会调用
* 处理传入的消息需要复写这个方法
*
* @param ctx
* @param msg
* @throws Exception
* @see ChannelInboundHandlerAdapter#channelRead(ChannelHandlerContext, Object)
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
//Discard the received data silently
//ByteBuf是一个引用计数对象实现ReferenceCounted,他就是在有对象引用的时候计数+1,无的时候计数-1,当为0对象释放内存
try {
byte[] array = convertByteBufToBytes((ByteBuf) msg);
if (array == null) {
return;
}
boolean flag = DecoderQc.checkCrc(array);
if (flag) {
String result = new String(array, "utf8");
//数据处理
//......
}
} finally {
//暂不做处理
}
}
/**
* @param buf 接收字节数组
* @return 分包字符串
*/
public byte[] convertByteBufToBytes(ByteBuf buf) {
int length = buf.readableBytes();
if (length > 0) {
byte[] array = new byte[length];
if (buf.hasArray()) { // 处理堆缓冲区
buf.getBytes(buf.arrayOffset() + buf.readerIndex(), array);
} else { // 处理直接缓冲区以及复合缓冲区
buf.getBytes(buf.readerIndex(), array);
}
return array;
} else {
return null;
}
}
}