簡體   English   中英

通過 SocketChannel 讀取(寫入)的正確方法

[英]Proper way to read (write) through a SocketChannel

我的問題比以下場景更通用,盡管這涵蓋了所需的一切。 適用於 Java 和套接字編程的正確做法。

設想:

  • 一台服務器有許多客戶端。 非阻塞 I/O 的使用
  • 服務器是另一台服務器的客戶端。 阻塞 I/O的使用
  • 每種情況有兩種情況:在一種情況下,所有數據都適合分配的字節緩沖區,在第二種情況下,它們不適合(僅適用於一次迭代,不適用於程序的生命周期)。

我為非阻塞 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM