我正在使用AsynchronousServerSocketChannel
实现一个简单的服务器。出于测试目的,我创建了一个小型客户端原型,它发送两条消息"hi"
和"stackoverflow"
,然后断开连接。在服务器端,我读取到达的消息并将它们打印到标准输出。当客户端执行时,我期待收到:
message [hi], bytecount 2
message [stackoverflow], bytecount 13
问题是,当服务器调用读回调时,有时两条消息都已到达,所以我得到了
message [histackoverflow], bytecount 15
代替。
问题是,如果可以在服务器端确保消息单独到达,如果是,那该怎么办?
这是处理客户端连接的CompletionHandler
原型:
class CommunicationHandler implements CompletionHandler<AsynchronousSocketChannel, Void> {
private final AsynchronousServerSocketChannel server;
public CommunicationHandler(final AsynchronousServerSocketChannel server) {
this.server = server;
}
@Override
public void failed(Throwable ex, Void attachment) {}
@Override
public void completed(final AsynchronousSocketChannel client, Void attachment) {
// handle client messages
final ByteBuffer buffer = ByteBuffer.allocateDirect(Server.BUFFER_SIZE);
final Session session = new Session();
try {
client.read(buffer, session, new CompletionHandler<Integer, Session>() {
@Override
public void completed(Integer byteCount, final Session currSession) {
if (byteCount == -1) {
return;
}
buffer.flip();
// TODO forward buffer to message handler (probably protocol?)
System.out.println("message [" + convertToString(buffer) + "], byteCount " + byteCount);
buffer.clear();
// read next message
client.read(buffer, currSession, this);
}
@Override
public void failed(Throwable ex, final Session currSession) {}
});
}
// accept the next connection
server.accept(null, this);
}
ByteBuffer
转换为String
:
public static String convertToString(ByteBuffer bb) {
final byte[] bytes = new byte[bb.remaining()];
bb.duplicate().get(bytes);
return new String(bytes);
}
这是一个测试客户端原型:
public class Client {
public final void start() {
try (AsynchronousSocketChannel client = AsynchronousSocketChannel.open();) {
Future<Void> connCall = client.connect(InetAddress.getByName("127.0.0.1"), 8060));
connCall.get();
// client is now connected
// send greeting message
Future<Integer> writeCall = client.write(Charset.forName("utf-8").encode(CharBuffer.wrap("hi")));
writeCall.get();
// Thread.sleep(5000L);
writeCall = client.write(Charset.forName("utf-8").encode(CharBuffer.wrap("stackoverflow")));
writeCall.get();
client.close();
} catch (IOException e) {
} catch (InterruptedException ex) {
} catch (ExecutionException ex) {
}
}
答案 0 :(得分:2)
除了在一次读取中获得两次(或甚至更多次)写入的可能性之外,对于较大的消息(通常大约3k或更多),您可以通过多次读取获得一次写入分割。 TCP是一种流协议,并不保留记录边界,除非是偶然的:What is a message boundary?有两种解决方案通常可以工作,虽然使用异步通道我认为你需要进行自己的缓冲管理,这可能会令人困惑并且难以测试:
在每条记录
在没有使用字节的情况下,在每条记录之后添加分隔符,或者可以使用转义来区分数据和分隔符
和其他一些已经尝试过的人:
正如您的评论所示,请等待足够长的时间,以便在发送第二个请求之前始终读取第一个请求。在开发人员使用的本地网络和测试系统上,这通常是几毫秒甚至更短;在真正的互联网上,它通常是几秒钟,有时是几分钟,理论上可能是几小时甚至几天。
如果记录永远不会超过几个片段(可能是10k左右),请使用UDP(在Java中以DatagramSocket
提供,但不能作为NIO通道AFAICS)并实现自己的协议来处理消息丢失,重复和重新排序(这很难做到,并且经常在30年前在TCP中发现并避免或修复的一些模糊案例中失败)
使用SCTP(在所有AFAICS中都不提供Java,也没有太多其他系统)
除此之外:您的测试客户端以UTF-8发送数据,但new String (byte[])
使用默认编码,该编码取决于平台,而不一定是UTF-8。我不确定它是否有保证,但实际上所有可用的编码都包括ASCII作为子集,您的示例数据是ASCII。但是如果你想为它支持实际的UTF-8数据代码。