我正在使用 NIO 进行练习,并尝试使用客户端和服务器端制作一个简单的应用程序。这个应用程序应该只是从客户端向服务器发送字节消息,并获取其他消息作为响应。我的代码在下面。但我有很多不同的问题。
有时来自方法 readMessage()
的行 int bytesRead = socketChannel.read(byteBuffer);
或 bytesRead = socketChannel.read(byteBuffer);
> 读取无限的零字节序列并抛出 OOM 错误。
有时来自服务器的响应看起来像 that而不是 {"class":"server.PasswordHashResponse","xoredHash":"RV5GX1JVAwADBEVZWwFGTAhZQ1FGX1tYQ11ZVwA\u003d"}
。
有时响应有奇怪的尾部,如下所示:{"class":"server.PasswordHashResponse","xoredHash":"RV5GX1JVAwADBEVZWwFGTAhZQ1FGX1tYQ11ZVwA\u003d"}YQ11ZVwA\u003d
服务器和客户端都使用相同的方法进行读写。
我从客户端发送 {"class":"server.PasswordHashRequest","login":"admin"}
并期望 {"class":"server.PasswordHashResponse","xoredHash":"RV5GX1JVAwADBEVZWwFGTAhZQ1FGX1tYQ11ZVwA\u003d"}
。
使用相同的代码,我现在可以得到一个问题,几分钟后又可以得到另一个问题。
我已经尝试过我所知道的一切。我是否在 Java 中遇到了段错误?
客户端代码:
@Test
public void main() throws Exception {
System.out.println("Opening socket");
InetSocketAddress socketAddress = new InetSocketAddress("localhost", 9090);
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector, OP_CONNECT);
socketChannel.connect(socketAddress);
PasswordHashRequest request = new PasswordHashRequest("admin");
System.out.println("Socket open");
while (true) {
System.out.println("Client selector awoken");
selector.select();
for (SelectionKey selectionKey : selector.selectedKeys()) {
if (selectionKey.isConnectable()) {
socketChannel.finishConnect();
selectionKey.interestOps(OP_WRITE);
} else if (selectionKey.isReadable()) {
String response = ServerManager.readMessage((SocketChannel) selectionKey.channel());
System.out.println(response);
server.interrupt();
} else if (selectionKey.isWritable()) {
ServerManager.sendMessage(request, (SocketChannel) selectionKey.channel());
System.out.println("Request sent");
selectionKey.interestOps(OP_READ);
}
}
}
}
服务器端代码:
public void run() {
System.out.println("Main thread started");
while (true) {
try {
// Get ready channels
int readyChannels = selector.select();
if (readyChannels == 0) { continue; }
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
// Handle Events
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// New Client
if (key.isAcceptable()) {
System.out.println("New Client Accepted");
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
serverSocketChannel.configureBlocking(false);
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
SelectionKey clientKey = socketChannel.register(selector, SelectionKey.OP_READ);
Random randomInt = new Random(System.currentTimeMillis());
clientKey.attach(randomInt.nextInt(Integer.SIZE - 1));
}
// Client has sent data
else if (key.isReadable()) {
handleInput(key);
}
keyIterator.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
读取方法:
public static String readMessage(SocketChannel socketChannel) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
byteBuffer.clear();
StringBuilder stringBuilder = new StringBuilder();
int bytesRead = socketChannel.read(byteBuffer);
while (bytesRead != -1) {
byteBuffer.flip();
String byteString = new String(byteBuffer.array(), Charset.forName("UTF-8"));
stringBuilder.append(byteString);
byteBuffer.clear();
bytesRead = socketChannel.read(byteBuffer);
}
socketChannel.shutdownInput();
return stringBuilder.toString();
}
写入方法:
public static void writeMessage(String message, SocketChannel channel) throws IOException {
message += "\r\n";
System.out.println(message);
int bufferLength = 16;
byte[] responseBytes = message.getBytes();
int offset = 0;
ByteBuffer buf = ByteBuffer.allocate(bufferLength);
while (responseBytes.length > offset) {
buf.clear();
int div = responseBytes.length - offset;
if (div >= bufferLength) {
buf.put(responseBytes, offset, bufferLength);
} else {
buf.put(responseBytes, offset, div);
}
buf.flip();
channel.write(buf);
offset += bufferLength;
}
channel.shutdownOutput();
}
最佳答案
- 如果
bytesRead <= 0
,您的读取方法应该停止读取 - 构造字符串时应考虑缓冲区限制
- 如果
write()
,您的写入方法应该停止尝试写入返回零,然后(并且只有那时)为 OP_WRITE 注册 channel ,并且仅在触发时继续写入。
在这里看到许多类似的问题。
关于java - NIO读写的很多问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44347420/