I.概要:
1.名詞の説明
ハーフパケット:TCPプロトコル、バッファがあります。送信されたパケットがバッファのサイズよりも大きい場合、ハーフパケット現象が発生します。つまり、パケットは2つの送信に分割されます。
スティッキーパケット:これは、バッファ領域が存在するためでもあります。多数の小さなサイズのパケットが短時間で送信される場合、TCPプロトコル実装の最下層はこれらの小さなパケットを一緒に送信します。
2.概要
まず、セミパケットのスティッキーパケットの出現は、ネットワークフレームワークによるものではなく、TCPプロトコルの実装に起因する問題です。ハーフパケットとスティッキーパケットの解決方法には、主に次の方法があります。私たちが使用するnettyフレームワークで。
1)パケットの終了区切り文字を設定します
2)固定長パケットが送信され、固定長パケットがデコードされて、使用時にスペースが削除されます。
3)ヘッドにマークビットを追加し、使用する場合はマークビットを取り外してください。
3.一般的な組み込みデコーダー
//(1)FixedLengthFrameDecoderを使用して、固定長メッセージのスティッキーパケットの問題を解決します;
//
//(2)LineBasedFrameDecoderとStringDecoderを使用して、キャリッジリターンとラインフィードをメッセージの最後に持つTCPスティッキーパケットの問題を解決します;
//
//(3)DelimiterBasedFrameDecoder特殊区切り文字デコーダーは、メッセージの終わりとして特殊記号を使用してTCPスティッキーパケットの問題を解決するために使用されます;(これは2のカスタムメソッドです)
//
//(4)最後1つはこの記事でもあります。LengthFieldBasedFrameDecoderカスタム長デコーダーを使用して、TCPスティッキーパケットの問題を解決します。
2、エンコードとデコード
1.エンコードおよびデコードプロセス
クライアントのカスタムハンドラーは、LengthProtocoltEncode(位置は最後から2番目)の処理後に情報を送信し、プロトコル名と長さフィールドを追加してサーバーに送信します。サーバーは、LengthFieldBasedFrameDecoderから継承されたデコーダーを通過します(位置は最初です)自動処理、長さフィールドデコーダーLenghtProtocoltDecode処理(位置は2番目)に渡され、最後にサーバー側の最後のプロセッサーに渡されてクライアント入力を出力し、Echoはデータを返します。
注:エンコーダー位置を入力すると、入力は前から後ろに処理されます。したがって、長さをデコードした後、長さを削除し、出力は反対方向になります。したがって、最後から2番目の位置を処理する必要があります。実際の出力コンテンツ。(さらに、文字列エンコーダーを追加する場合、カスタムプロセッサーはデータを書き込み、文字列を書き込むため、カスタムプロセッサーの背後に配置する必要があります。これは、文字列エンコーダーで処理する必要があります)
2.合意ルール
[2長プロトコルバージョン、2長フィールド、実際のパケットコンテンツ]
3.3つの主要なエンコーディングおよびデコーディングプロセッサ
public class AllHandlers {
/**
*
* 继承LengthFieldBasedFrameDecoder的解码器,主要是传入自定义的参数。
*/
public static class LengthPackageSpliter extends LengthFieldBasedFrameDecoder {
/**
* 参数[1] : 长度字段偏移,参数[2] : 长度字段占用尺寸
*/
public LengthPackageSpliter() {
super(Integer.MAX_VALUE, Constants.LENGTH_OFFSET, Constants.LENGTH_BYTES_COUNT);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
return super.decode(ctx, in);
}
}
/**
* 消息编码器,用于发送消息后,对消息内容自动增加协议字段、长度字段。
*/
public static class LengthProtocoltEncode extends MessageToByteEncoder<ByteBuf> {
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
int length = msg.readableBytes();
ByteBuf buff = Unpooled.buffer(Constants.PROTOCOL_HEADLENGTH + length);
buff.writeShort(Constants.PROTOCOL_VERSION);
buff.writeShort(length);
buff.writeBytes(msg);
out.writeBytes(buff);
}
}
// 消息解码器:方式1
// public static class LenghtProtocoltDecode extends ChannelInboundHandlerAdapter {
//
// @Override
// public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// // 去除长度字符串
// ByteBuf buff = (ByteBuf) msg;
// short vs = buff.readShort();
// short length = buff.readShort();
// ByteBuf buf = buff.readBytes(length);
// super.channelRead(ctx, buf);
// }
// }
// 消息解码器:方式2
public static class LenghtProtocoltDecode extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
short vs = in.readShort();
short length = in.readShort();
ByteBuf buf = in.readBytes(length);
out.add(buf);
}
}
3、コードの実装
1.サーバー
public class PkgServer {
public static void main(String[] args) {
new PkgServer().bind(7000);
}
public void bind(int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LengthPackageSpliter());
ch.pipeline().addLast(new LenghtProtocoltDecode());
ch.pipeline().addLast(new LengthProtocoltEncode());
ch.pipeline().addLast(new ServerHandler());
}
});
ChannelFuture future = b.bind(port).sync();
System.out.println("server is start!");
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
SocketChannel channel = (SocketChannel) ctx.channel();
System.out.println("链接报告信息:有一客户端链接到本服务端");
System.out.println("链接报告IP:" + channel.localAddress().getHostString());
System.out.println("链接报告Port:" + channel.localAddress().getPort());
System.out.println("链接报告完毕");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buff = (ByteBuf) msg;
byte[] readdata = new byte[buff.readableBytes()];
buff.readBytes(readdata);
String result = new String(readdata);
System.out.println("server receive msg:" +result);
String response = "echo" + result;
buff = Unpooled.buffer(response.getBytes().length);
buff.writeBytes(response.getBytes());
ctx.writeAndFlush(buff);
}
}
}
2.クライアント
public class PkgClient {
public static void main(String[] args) throws Exception {
PkgClient client = new PkgClient();
client.bind("127.0.0.1", 7000);
}
public void bind(String address, int port) throws Exception {
EventLoopGroup loopGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(loopGroup).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new LengthPackageSpliter());
channel.pipeline().addLast(new LenghtProtocoltDecode());
channel.pipeline().addLast(new LengthProtocoltEncode());
channel.pipeline().addLast(new MyClientHandler());
}
});
// 启用客户端连接
ChannelFuture future = b.connect(address, port).sync();
future.channel().closeFuture().sync();
} finally {
loopGroup.shutdownGracefully();
}
}
public static class MyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
byte[] data = ("this is one line message.".getBytes());
ByteBuf buff = Unpooled.buffer(data.length);
buff.writeBytes(data);
ctx.writeAndFlush(buff);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
byte[] readdata = new byte[buf.readableBytes()];
buf.readBytes(readdata);
System.out.println("client receive:" + new String(readdata));
}
}
}
3.文字列型のエンコードとデコードを追加します
上記の場合、送信と受信の両方にByteBufを使用し、送信に文字列型、受信に文字列型を使用するように変換します。これには、StringDecoderとStringEncoderの2つのエンコーダー(これら2つのエンコード)を使用する必要があります。
プロセッサーはプロセッサーチェーン(パイプライン)のどこに配置されていますか?
まず、StringDecoderはメッセージ入力であり、ByteBufをStringに変換します。これは、最後のin型プロセッサとカスタムプロセッサの間に配置する必要があります。つまり、3番目の位置です。
StringEncoderは、カスタムプロセッサの文字列入力を受け取り、それをByteBufタイプに変換します。次に、カスタムプロセッサの前の位置に配置する必要があります。プロセッサチェーン全体は、次の順序である必要があります。
ch.pipeline().addLast(new LengthPackageSpliter());
ch.pipeline().addLast(new LenghtProtocoltDecode());
ch.pipeline().addLast(new StringDecoder()); // 增加String解码
ch.pipeline().addLast(new LengthProtocoltEncode());
ch.pipeline().addLast(new StringEncoder()); // 增加String编码
ch.pipeline().addLast(new MyClientHandler());
次に、それに応じて、データが入力および読み取られる場所を次のように変換します。
public static class MyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// byte[] data = ("this is one line message.".getBytes());
// ByteBuf buff = Unpooled.buffer(data.length);
// buff.writeBytes(data);
// ctx.writeAndFlush(buff);
ctx.writeAndFlush("this is one line message");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ByteBuf buf = (ByteBuf)msg;
// byte[] readdata = new byte[buf.readableBytes()];
// buf.readBytes(readdata);
// System.out.println("client receive:" + new String(readdata));
System.out.println("client receive:" +msg );
}
}
サーバーの変更は類似しており、省略されています
終わり!!!