通过 SocketChannel 读取(写入)的正确方法
Proper way to read (write) through a SocketChannel
我的问题比下面的场景更通用,尽管这涵盖了所有需要的问题。
是为了Java和套接字编程的正确做法。
场景:
- 一个服务器有很多客户端。 非阻塞的用法I/O
- 该服务器是另一台服务器的客户端。 阻塞的用法I/O
- 每种情况有两种情况:在一种情况下,所有数据都适合分配的字节缓冲区,在第二种情况下,它们不适合(仅适用于一次迭代,不适用于程序的生命周期)。
我找到的所有非阻塞示例 I/O 都是这样的:
InetAddress host = InetAddress.getByName("localhost");
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(host, 1234));
serverSocketChannel.register(selector, SelectionKey. OP_ACCEPT);
while (true) {
if (selector.select() <= 0)
continue;
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
key = (SelectionKey) iterator.next();
iterator.remove();
if (key.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
// Do something or do nothing
}
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
socketChannel.read(buffer);
// Something something dark side
if (result.length() <= 0) {
sc.close();
// Something else
}
}
}
如果缓冲区足够大,read
是否会读取来自该特定客户端和该特定请求的所有传入数据,或者我是否需要将其放在 while
循环中?如果缓冲区不够大?
在 write
的情况下,我是否也只做 socketChannel.write(buffer)
就可以了(至少从程序的角度来看)?
文档 here 没有指定所有传入数据都适合缓冲区的情况。当我遇到阻塞 I/O:
时,它也会让人有点困惑
It is guaranteed, however, that if a channel is in blocking mode and there is at least one byte remaining in the buffer then this method will block until at least one byte is read.
这是否意味着在这里(阻塞 I/O)我需要 read
通过 while
循环(我发现的大多数例子都是这样做的)? write
操作怎么样?
所以,总结一下,我的问题是,从中间服务器(客户端到第二个服务器)的角度来看,在我的场景中读取和写入数据的正确方法是什么?
如果您没有调用 configureBlocking(false)
,那么是的,您将使用循环来填充缓冲区。
但是……非阻塞套接字的要点是不要在任何一个套接字上挂起等待,因为这会延迟所有剩余套接字的读取,这些套接字的选定键尚未被您的迭代器处理.实际上,如果有 10 个客户端连接,而其中一个恰好连接速度较慢,则其他部分或所有客户端可能会遇到同样的速度缓慢问题。
(未指定所选键集的确切顺序。查看 Selector 实现的源代码是不明智的 class,因为缺乏任何顺序保证意味着 JavaSE可以更改顺序。)
为了避免等待任何一个套接字,你不要试图一次性填满缓冲区;相反,您可以通过每次 select()
调用只读取一次来读取套接字可以为您提供的任何内容而不会阻塞。
由于每个 ByteBuffer 可能包含部分数据序列,因此您需要记住每个 ByteBuffer 对每个 Socket 的进度。幸运的是,SelectionKey 有一种方便的方法来做到这一点:attachment.
您还想记住从每个套接字读取了多少字节。所以,现在您需要为每个套接字记住两件事:字节数和 ByteBuffer。
class ReadState {
final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
long count;
}
while (true) {
// ...
if (key.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// Attach the read state for this socket
// to its corresponding key.
socketChannel.register(selector, SelectionKey.OP_READ,
new ReadState());
}
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ReadState state = (ReadState) key.attachment();
ByteBuffer buffer = state.buffer;
state.count += socketChannel.read(buffer);
if (state.count >= DATA_LENGTH) {
socketChannel.close();
}
buffer.flip();
// Caution: The speed of this connection will limit your ability
// to process the remaining selected keys!
anotherServerChannel.write(buffer);
}
对于阻塞通道,您可以只使用一个 write(buffer)
调用,但是如您所见,使用阻塞通道可能会限制主服务器使用非阻塞通道的优势。将与其他服务器的连接也设置为非阻塞通道可能是值得的。这会让事情变得更复杂,所以我不会在这里解决这个问题,除非你希望我这样做。
我的问题比下面的场景更通用,尽管这涵盖了所有需要的问题。 是为了Java和套接字编程的正确做法。
场景:
- 一个服务器有很多客户端。 非阻塞的用法I/O
- 该服务器是另一台服务器的客户端。 阻塞的用法I/O
- 每种情况有两种情况:在一种情况下,所有数据都适合分配的字节缓冲区,在第二种情况下,它们不适合(仅适用于一次迭代,不适用于程序的生命周期)。
我找到的所有非阻塞示例 I/O 都是这样的:
InetAddress host = InetAddress.getByName("localhost");
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(host, 1234));
serverSocketChannel.register(selector, SelectionKey. OP_ACCEPT);
while (true) {
if (selector.select() <= 0)
continue;
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
key = (SelectionKey) iterator.next();
iterator.remove();
if (key.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
// Do something or do nothing
}
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
socketChannel.read(buffer);
// Something something dark side
if (result.length() <= 0) {
sc.close();
// Something else
}
}
}
如果缓冲区足够大,read
是否会读取来自该特定客户端和该特定请求的所有传入数据,或者我是否需要将其放在 while
循环中?如果缓冲区不够大?
在 write
的情况下,我是否也只做 socketChannel.write(buffer)
就可以了(至少从程序的角度来看)?
文档 here 没有指定所有传入数据都适合缓冲区的情况。当我遇到阻塞 I/O:
时,它也会让人有点困惑It is guaranteed, however, that if a channel is in blocking mode and there is at least one byte remaining in the buffer then this method will block until at least one byte is read.
这是否意味着在这里(阻塞 I/O)我需要 read
通过 while
循环(我发现的大多数例子都是这样做的)? write
操作怎么样?
所以,总结一下,我的问题是,从中间服务器(客户端到第二个服务器)的角度来看,在我的场景中读取和写入数据的正确方法是什么?
如果您没有调用 configureBlocking(false)
,那么是的,您将使用循环来填充缓冲区。
但是……非阻塞套接字的要点是不要在任何一个套接字上挂起等待,因为这会延迟所有剩余套接字的读取,这些套接字的选定键尚未被您的迭代器处理.实际上,如果有 10 个客户端连接,而其中一个恰好连接速度较慢,则其他部分或所有客户端可能会遇到同样的速度缓慢问题。
(未指定所选键集的确切顺序。查看 Selector 实现的源代码是不明智的 class,因为缺乏任何顺序保证意味着 JavaSE可以更改顺序。)
为了避免等待任何一个套接字,你不要试图一次性填满缓冲区;相反,您可以通过每次 select()
调用只读取一次来读取套接字可以为您提供的任何内容而不会阻塞。
由于每个 ByteBuffer 可能包含部分数据序列,因此您需要记住每个 ByteBuffer 对每个 Socket 的进度。幸运的是,SelectionKey 有一种方便的方法来做到这一点:attachment.
您还想记住从每个套接字读取了多少字节。所以,现在您需要为每个套接字记住两件事:字节数和 ByteBuffer。
class ReadState {
final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
long count;
}
while (true) {
// ...
if (key.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// Attach the read state for this socket
// to its corresponding key.
socketChannel.register(selector, SelectionKey.OP_READ,
new ReadState());
}
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ReadState state = (ReadState) key.attachment();
ByteBuffer buffer = state.buffer;
state.count += socketChannel.read(buffer);
if (state.count >= DATA_LENGTH) {
socketChannel.close();
}
buffer.flip();
// Caution: The speed of this connection will limit your ability
// to process the remaining selected keys!
anotherServerChannel.write(buffer);
}
对于阻塞通道,您可以只使用一个 write(buffer)
调用,但是如您所见,使用阻塞通道可能会限制主服务器使用非阻塞通道的优势。将与其他服务器的连接也设置为非阻塞通道可能是值得的。这会让事情变得更复杂,所以我不会在这里解决这个问题,除非你希望我这样做。