目录
1.Netty简单实例
关于入门的觉得的这个博主的文章写的就很好,简洁简单,主要是关于Netty的关键类的讲解:
Netty基本使用(一)_cfy137000的博客-CSDN博客_netty基本使用
(1)Netty服务器
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
public class NettyServer
{
public static void main(String[]args)
{
System.out.println("启动服务器端口8082");
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println("receive client:"+msg);
}
});
}
})
.bind(8082);
}
}
(2)Netty客户端
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
public class NettyClient {
public static void main(String[] args) {
try {
new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {//
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new StringEncoder());
}
})
.connect("127.0.0.1", 8082)
.sync()
.channel()
.writeAndFlush("hello Chenfy!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
启动测试,先启动服务器,然后启动客户端,可以看到控制台log,这个就是最简单的实例
2.添加自定义的消息解码器
由于最最基础的Netty收发消息,每个消息的结束,都需要一个标记符号来定义结束,也就是定义一串字符标记作为每个消息的结尾,但是实际生产活动中,我们不可能直接用 “常量字符串” 作为消息结束符,你没法保证,这个“常量字符串”也有可能出现在正常的消息体内容中的,因此可以定义一个自定义的解码器,用来定义,怎么判定消息结束:
添加自定义的消息解码器,其中 MyCustomMessageDecoder类 就是 具体的解码器实现
package com.ceyee.system3d.netty.core;
import com.ceyee.system3d.netty.net.MyCustomMessageDecoder;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
@DependsOn("chatHandler")
public class WSServerInitialzer extends ChannelInitializer<SocketChannel> {
private static WSServerInitialzer wsServerInitialzer;
@Autowired
private ChatHandler chatHandler;
@PostConstruct
private void init(){
wsServerInitialzer = this;
wsServerInitialzer.chatHandler = this.chatHandler;
}
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// delemiter = Unpooled.copiedBuffer(NettyConst.dataEndingFlag);
//maxLength表示当前解码器可以解码的最大长度。
//failFast表示如果超过了这个最大长度是否马上抛出异常
//stripDelimiter表示是否带着换行符一起解析
//discarding表示当前解析器处于丢弃模式
//discardedBytes表示已经丢弃的字节
//自定义的解码器
pipeline.addLast(
new MyCustomMessageDecoder(),
wsServerInitialzer.chatHandler
);
}
}
具体自定义的消息解码器的类实现:我的消息分割定义是,每个消息头塞一个4字节,用来作为消息标识,但是这个解码器 仍然有一个bug,就是健康的条件必须是客户端的包 每个消息都是作为一个包发送,如果存在分片发送就会出现bug
package com.ceyee.system3d.netty.net;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class MyCustomMessageDecoder extends ByteToMessageDecoder {
// 消息头:发送端写的是一个int,占用4字节。
private final static int HEAD_LENGTH = 4;
protected Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// logger.info("解码器收到 in.readableBytes()="+in.readableBytes()+",capacity="+in.capacity());
if (in.readableBytes() < HEAD_LENGTH) {
return;
}
// 标记一下当前的readIndex的位置
in.markReaderIndex();
// 读取数据长度
int dataLength = in.readInt();
// logger.info("此时 dataLength="+dataLength);//+",capacity="+in.capacity()+"@");
// 我们读到的消息体长度为0,这是不应该出现的情况,这里出现这情况,关闭连接。
if (dataLength < 0) {
ctx.close();
}
//读到的消息体长度如果小于我们传送过来的消息长度,则resetReaderIndex. 这个配合markReaderIndex使用的。把readIndex重置到mark的地方
if (in.readableBytes() < dataLength) {
//yanruTODO 这里 是有bug的,如果 处理正常的前提 是基于收到的客户端的包一定是确定的 包头长度+包体数的格式,万一客户端分片发送,这里就会被丢弃。
//后续看这个文档解决:https://www.cnblogs.com/zjyingchang/p/9970301.html
in.resetReaderIndex();
return;
}
// 将缓冲区的数据读到字节数组
byte[] body = new byte[dataLength];
in.readBytes(body);
ByteBuf resp = Unpooled.copiedBuffer(body);
ReferenceCountUtil.retain(resp);//这行
//2020-5-20 yanrufix 找到了病症,byte数组不能转为字符窜,这个new string会出问题的!
out.add(resp);//body);
}
}