我几乎没有调试这个问题两天。在stackoverflow之外/之外进行了大量搜索后,我无法找到答案。
我正在为自定义键/值服务器编写客户端。协议很简单。 如果客户发送
"GET 1 12\r\nkey1\r\nkey2\r\n"
服务器可以重播
"0 1 16\r\nvalue1\r\nvalue2\r\n"
在响应中,第一行表示正文的长度是16个字节,而对于接下来的16个字节,它包含两个键的值。他们被“\ r \ n”分开。
问题在于,在压力测试中,有时我从客户端看到,响应消息看起来格格不入。看起来缓冲区被覆盖了。
e.g。发送
"GET 1 12\r\nkey1\r\nkey2\r\n"
10k次, 在响应缓冲区中我可能会看到
"0 1 16\r\nvalue1\r\nval0 1 16"
看起来这里的value2被下一个响应部分覆盖。
服务器已经在那里工作了很长时间,我认为它运行良好。我也使用tcpdump并证明它是正确的。所以bug应该在客户端。
我按照Netty Telnet示例here进行了小修改。
在DelimiterBasedFrameDecoder之后,处理程序逐个解析这些行并组装响应。
我认为它可能与多线程有关。但即使我将线程数设置为1,问题就是重新编写。
那么我是否以错误的方式使用Netty?
====================
更新 经过更多的调查,我发现它与Netty无关。即使使用简单的Java NIO程序,它也可以重现。它似乎与缓冲区溢出有关。
从tcpdump,我可以看到来自远程服务器的软件包是正确的。
所以我捕获每个ByteBuffer,并在bug发生时将其打印出来。 (我故意将缓冲区大小设置为一个小数字 - 1k。)使用以下代码:
protected void onRead(ByteBuffer buf) throws Exception {
buf.mark();
int l = buf.limit();
int p = buf.position();
byte[] bytes = new byte[l - p];
buf.get(bytes, p, l - p);
String v = new String( bytes, Charset.forName("UTF-8") );
buffers.addFirst(v);
if (buffers.size() > 30) {
buffers.removeLast();
}
buf.reset();
//...
// process one line of buf
};
以下是最后三个缓冲区捕获,因为我逐行处理它。似乎头部“0 0 1040”错误地放置了截断线“20”
*************************************
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
0 0 1040^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
200 0 1040^M
20131101/booking.com.png^M
*************************************
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
0 0 1040^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20
*************************************
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
0 0 1040^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/booking.com.png^M
20131101/boo
*************************************
我还没有确定根本原因。一旦我得到答案,我会回复。
=====================
这是我原来的代码片段, 初始化
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(DECODER);
pipeline.addLast(ENCODER);
pipeline.addLast(new NettyClientHandler());
}
和处理程序:
//@Sharable
public class NettyClientHandler extends SimpleChannelInboundHandler<String> {
boolean head = true;
int len = -1;
ArrayList<String> vals = new ArrayList<>();
@Override
public void channelRead0(ChannelHandlerContext ctx, String request) throws InterruptedException {
if (head) {
vals.clear();
String[] splits = request.split(" ");
len = -1;
try {
len = Integer.parseInt(splits[2]);
} catch (NumberFormatException ex) {
ex.printStackTrace();
}
if (len == -1) {
return;
}
head = false;
} else {
vals.add(request);
len -= (request.length() + 2);
if (len == 0) {
// System.err.print("[");
// for (int i = 0; i < vals.size(); i++) {
// System.err.print(vals.get(i) + ",");
// }
// System.err.println("]");
head = true;
}
}
//System.err.println(request);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
答案 0 :(得分:0)
你假设在TCP中存在消息这样的事情。没有。这是一个字节流。它可以完全根据其奇思妙想为您提供零个,一个或多个字节。如果你期望一个特定的消息长度,你需要循环,直到你得到它,如果它更少,或拆分你已经收到的,如果它更多。
答案 1 :(得分:-1)
经过调查,结果有点令人失望。服务器有一个错误。
回想起来,我在这里学到了一些东西。