9.1 TCPの固定と展開の基本
-
TCPは接続指向でストリーム指向であり、信頼性の高いサービスを提供します。送信側と受信側(クライアントとサーバー)には1組のソケットが必要なので、複数のパケットを受信側に送信するには、送信側で最適化メソッド(Nagleアルゴリズム)を使用します小さな間隔と小さなデータ量のデータを大きなデータブロックに結合し、それをカプセル化します。これにより効率は向上しますが、しかし、受信側が完全なデータパケットを区別することは困難です。、ストリーム指向の通信にはメッセージ保護境界がないため
-
TCPにはメッセージ保護境界がないため、メッセージ境界の問題は受信側で処理する必要があります。これを、スティッキングとアンパックと呼びます。
-
TCPのスティッキングとアンパックの概略図
図の説明:
クライアントが2つのデータパケットD1およびD2をサーバーに送信するとします。サーバーが一度に読み取るバイト数は不明であるため、次の4つの状況が考えられます。
-
サーバーは、2つの独立したデータパケットを2回、つまりD1とD2で読み取ります。
-
サーバーは一度に2つのパケットを受信し、D1とD2はTCPスティッキーパケットと呼ばれる一緒に接着されます。
-
サーバーはデータパケットを2回読み取り、最初はD1およびD2パケットの完全なコンテンツを読み取り、2回目はD2パケットの残りのコンテンツを読み取ります。これはTCPアンパックと呼ばれます。
-
サーバーはデータパケットを2回読み取ります。1回目はD1パッケージのD1_1部分を読み取り、2回目はD1パッケージの残りの部分D1_2と完全なD2パッケージを読み取ります。
9.2 TCPスティッキングとアンパックの例
Nettyプログラムを作成するときに、処理がない場合、貼り付けと展開の問題が発生します。
具体的な例を見てください。
public class MyServerHandler extends SimpleChannelInboundHandler<ByteBuf>{
private int count;//每个Channel有各自的ChannelHandler
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//cause.printStackTrace();
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
byte[] buffer = new byte[msg.readableBytes()];
msg.readBytes(buffer);
//将buffer转成字符串
String message = new String(buffer, Charset.forName("utf-8"));
System.out.println("服务器接收到数据 " + message);
System.out.println("服务器接收到消息量=" + (++this.count));//服务端收到消息的次数
//服务器回送数据给客户端, 回送一个随机id ,
ByteBuf responseByteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString()
+ " ", Charset.forName("utf-8"));
ctx.writeAndFlush(responseByteBuf);
}
}
public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
private int count;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//使用客户端发送10条数据 hello,server 编号
for(int i= 0; i< 10; ++i) {
ByteBuf buffer = Unpooled.copiedBuffer("hello,server " + i, Charset.forName("utf-8"));
ctx.writeAndFlush(buffer);
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
byte[] buffer = new byte[msg.readableBytes()];
msg.readBytes(buffer);
String message = new String(buffer, Charset.forName("utf-8"));
System.out.println("客户端接收到消息=" + message);
System.out.println("客户端接收消息数量=" + (++this.count));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
表示:
サーバー側がメッセージを6回受信サーバー側がメッセージを
7回受信
9.3 TCPスティッキングおよびアンパックソリューション
-
使用する カスタムプロトコル+コーデック 解決する
-
重要なのは、サーバーが毎回読み取るデータの長さの問題を解決することです。この問題が解決されれば、サーバーが多かれ少なかれデータを読み取ることによる問題は発生せず、TCPのスティッキングとアンパックが回避されます。
9.4特定の例を見てください:
-
クライアントは5つのメッセージオブジェクトを送信する必要があり、クライアントは一度に1つのメッセージオブジェクトを送信します
-
サーバーはメッセージを受信するたびに5回デコードし、メッセージを読み取るたびにメッセージオブジェクトをクライアントに返信します。
//Server
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyMessageDecoder());//解码器
pipeline.addLast(new MyMessageEncoder());//编码器
pipeline.addLast(new MyServerHandler());
}
}
public class MyMessageDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyMessageDecoder decode 被调用");
//需要将得到二进制字节码-> MessageProtocol 数据包(对象)
int length = in.readInt();
byte[] content = new byte[length];
in.readBytes(content);
//封装成 MessageProtocol 对象,放入 out, 传递下一个handler业务处理
MessageProtocol messageProtocol = new MessageProtocol();
messageProtocol.setLen(length);
messageProtocol.setContent(content);
out.add(messageProtocol);
}
}
public class MyMessageEncoder extends MessageToByteEncoder<MessageProtocol> {
@Override
protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
System.out.println("MyMessageEncoder encode 方法被调用");
out.writeInt(msg.getLen());
out.writeBytes(msg.getContent());
}
}
//处理业务的handler
public class MyServerHandler extends SimpleChannelInboundHandler<MessageProtocol>{//MessageProtocol接收
private int count;
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//cause.printStackTrace();
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {
//接收到数据,并处理 第一部分
int len = msg.getLen();
byte[] content = msg.getContent();
System.out.println();
System.out.println();
System.out.println();
System.out.println("服务器接收到信息如下");
System.out.println("长度=" + len);
System.out.println("内容=" + new String(content, Charset.forName("utf-8")));
System.out.println("服务器接收到消息包数量=" + (++this.count));
//回复消息 第二部分
String responseContent = UUID.randomUUID().toString();
int responseLen = responseContent.getBytes("utf-8").length;
byte[] responseContent2 = responseContent.getBytes("utf-8");
//构建一个协议包
MessageProtocol messageProtocol = new MessageProtocol();
messageProtocol.setLen(responseLen);
messageProtocol.setContent(responseContent2);
ctx.writeAndFlush(messageProtocol);
}
}
//Client
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyMessageEncoder()); //加入编码器
pipeline.addLast(new MyMessageDecoder()); //加入解码器
pipeline.addLast(new MyClientHandler());
}
}
public class MyMessageEncoder extends MessageToByteEncoder<MessageProtocol> {
@Override
protected void encode(ChannelHandlerContext ctx, MessageProtocol msg, ByteBuf out) throws Exception {
System.out.println("MyMessageEncoder encode 方法被调用");
out.writeInt(msg.getLen());
out.writeBytes(msg.getContent());
}
}
public class MyMessageDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyMessageDecoder decode 被调用");
//需要将得到二进制字节码-> MessageProtocol 数据包(对象)
int length = in.readInt();
byte[] content = new byte[length];
in.readBytes(content);
//封装成 MessageProtocol 对象,放入 out, 传递下一个handler业务处理
MessageProtocol messageProtocol = new MessageProtocol();
messageProtocol.setLen(length);
messageProtocol.setContent(content);
out.add(messageProtocol);
}
}
public class MyClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {
private int count;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//使用客户端发送10条数据 "今天天气冷,吃火锅" 编号
for(int i = 0; i< 5; i++) {
String mes = "今天天气冷,吃火锅";
byte[] content = mes.getBytes(Charset.forName("utf-8"));
int length = mes.getBytes(Charset.forName("utf-8")).length;
//创建协议包对象
MessageProtocol messageProtocol = new MessageProtocol();
messageProtocol.setLen(length);
messageProtocol.setContent(content);
ctx.writeAndFlush(messageProtocol);
}
}
// @Override
protected void channelRead0(ChannelHandlerContext ctx, MessageProtocol msg) throws Exception {
int len = msg.getLen();
byte[] content = msg.getContent();
System.out.println("客户端接收到消息如下");
System.out.println("长度=" + len);
System.out.println("内容=" + new String(content, Charset.forName("utf-8")));
System.out.println("客户端接收消息数量=" + (++this.count));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常消息=" + cause.getMessage());
ctx.close();
}
}