[英]Proper way to read (write) through a SocketChannel
我的問題比以下場景更通用,盡管這涵蓋了所需的一切。 適用於 Java 和套接字編程的正確做法。
設想:
我為非阻塞 I/O go 找到的所有示例都是這樣的:
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)
並且我對 go 很好(至少從程序的角度來看)?
此處的文檔未指定所有傳入數據都適合緩沖區的情況。 當我有阻塞 I/O 時,它也會讓人有點困惑:
但是,可以保證,如果通道處於阻塞模式並且緩沖區中至少有一個字節剩余,則此方法將阻塞,直到讀取至少一個字節。
這是否意味着在這里(阻塞 I/O)我需要以任何一種方式read
一個while
循環(我發現的大多數示例都是這樣做的)? write
操作呢?
所以,總而言之,我的問題是,從中間服務器(客戶端到第二台服務器)的角度來看,在我的場景中讀取和寫入數據的正確方法是什么?
如果您沒有調用configureBlocking(false)
,那么是的,您將使用循環來填充緩沖區。
但是......非阻塞套接字的要點是不要掛斷等待任何一個套接字,因為這會延遲所有剩余 sockets 的讀取,其選定的鍵尚未被您的迭代器處理。 實際上,如果十個客戶端連接,其中一個碰巧連接速度很慢,那么其他一些或所有客戶端可能會遇到同樣的速度。
(未指定所選密鑰集的確切順序。查看 Selector 實現 class 的來源是不明智的,因為缺乏任何順序保證意味着 Java SE 的未來版本允許更改順序。)
為避免等待任何一個套接字,請不要嘗試將緩沖區全部填滿 go; 相反,您可以通過每次select()
調用僅讀取一次來讀取套接字可以為您提供的任何內容而不會阻塞。
由於每個 ByteBuffer 可能保存部分數據序列,因此您需要記住每個 Socket 的每個 ByteBuffer 的進度。 幸運的是,SelectionKey 有一個方便的方法來做到這一點: 附件。
您還想記住從每個套接字讀取了多少字節。 因此,現在您需要為每個套接字記住兩件事:字節數和 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)
調用,但正如您所見,使用阻塞通道可能會限制主服務器使用非阻塞通道的優勢。 將與其他服務器的連接也設為非阻塞通道可能是值得的。 這會使事情變得更復雜,所以我不會在這里解決它,除非你想讓我這樣做。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.