簡體   English   中英

Java NIO 問題/對 isReadable 工作原理的誤解

[英]Java NIO Issue/Misunderstanding of how isReadable works

我發現除了簡單的情況外,NIO 的文檔記錄最多。 即便如此,我已經閱讀了教程和幾次重構並最終退回到最簡單的情況,我仍然偶爾會通過讀取 0 字節的 SocketChannel 來觸發 isReadable。 並非每次執行都會發生。

我曾經在一個單獨的線程中調用附加的 object 的讀取,並認為這可能是競爭條件,但我已經開始在選擇器的線程中進行讀取,但問題仍然存在。 我想它可能是我的測試客戶端,但我不確定是什么會不一致地觸發它,因為客戶端套接字在收到服務器的響應之前不應該關閉。

因此,在包含的代碼中,此代碼段發送的“hello”消息每次都能像我預期的那樣正常

 out.write("hello".getBytes()); out.write(EOT); out.flush();

在此之后,我偶爾會得到一個 0 長度的套接字通道。 有時會從這個片段中得到正確的響應:

 out.write(dataServerCredentials.getBytes()); out.write(EOT); out.flush();

對此的任何見解將不勝感激,它正在慢慢殺死我。 我已經嘗試在這里找到答案,但似乎相關的一個問題並沒有真正說明我的問題。

提前致謝!

下面的代碼片段:

選擇方法:

public void execute()
{
    initializeServerSocket();

    for (;;)
    {
        try
        {
            System.out.println("Waiting for socket activity");

            selector.select();

            Iterator<SelectionKey> selectedKeys = 
                this.selector.selectedKeys().iterator();
            while(selectedKeys.hasNext())
            {
                SelectionKey key = selectedKeys.next();
                selectedKeys.remove();

                if (!key.isValid()) 
                {
                    continue;
                }

                if (key.isAcceptable())
                {   // New connection
                    // TODO: Create helper method for this that configures user info?
                    System.out.println("Accepting connection");

                    ServerSocketChannel serverSocketChannel =
                        (ServerSocketChannel)key.channel();
                    SocketChannel socketChannel =
                        serverSocketChannel.accept();

                    socketChannel.socket().setSoTimeout(0);
                    socketChannel.configureBlocking(false);
                    SelectionKey newKey = 
                        socketChannel.register(selector, SelectionKey.OP_READ);

                    // Create and attach an AuthProcessor to drive the states of this
                    // new Authentication request
                    newKey.attach(new AuthenticationRequestProcessor(newKey));

                }
                else if (key.isReadable())
                {   // Socket has incoming communication
                    AuthenticationRequestProcessor authProcessor =
                        (AuthenticationRequestProcessor)key.attachment();

                    if (authProcessor == null)
                    {   // Cancel Key
                        key.channel().close();
                        key.cancel();
                        System.err.print("Cancelling Key - No Attachment");
                    }
                    else
                    {   
                        if (authProcessor.getState() ==
                            AuthenticationRequestProcessor.TERMINATE_STATE)
                        {   // Cancel Key
                            key.channel().close();
                            key.cancel();
                        }
                        else
                        {   // Process new socket data
                            authProcessor.process(readStringFromKey(key));
                        }
                    }
                }                    
            }
        }
        catch (IOException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

讀取方法(忽略這里的一些愚蠢,這是從另一個線程中拉出來的)

protected String readStringFromKey(SelectionKey key)
{
    SocketChannel socketChannel = (SocketChannel)key.channel();

    readBuffer.clear();

    String message = null;

    try
    {
        final int bytesRead = socketChannel.read(readBuffer);

        if (-1 == bytesRead)
        {   // Empty/Closed Channel
            System.err.println("Error - No bytes to read on selected channel");
        }
        else
        {   // Convert ByteBuffer into a String
            System.out.println("Bytes Read: " + bytesRead);
            readBuffer.flip();
            message = byteBufferToString(readBuffer, bytesRead);
            readBuffer.clear();
        }
    }
    catch (IOException e)
    {
        // TODO Auto-generated catch block

        e.printStackTrace();
    }

    // Trim EOT off the end of the message
    return message.trim();
}

客戶端片段:

    public void connect()
{
    boolean connectionStatus = false;
    String connectionHost = null;
    int connectionPort = 0;
    String connectionAuthKey = null;

    try
    {   // Login
        authenticationSocket = new Socket(AUTH_HOST, AUTH_PORT);
        out = authenticationSocket.getOutputStream();
        in = new BufferedInputStream(authenticationSocket.getInputStream());

        out.write("hello".getBytes());
        out.write(EOT);
        out.flush();


        StringBuilder helloResponse = new StringBuilder();

        // Read response off socket
        int currentByte = in.read();

        while (currentByte > -1 && currentByte != EOT)
        {
            helloResponse.append((char)currentByte);
            currentByte = in.read();
        }

        outgoingResponses.offer(Plist.fromXml(helloResponse.toString()));
        System.out.println("\n" + helloResponse.toString());

        out.write(credentials.getBytes());
        out.write(EOT);
        out.flush();

        // Read status
        int byteRead;

        StringBuilder command = new StringBuilder();

        do 
        {
            byteRead = in.read();
            if (0 < byteRead) 
            {
                if (EOT == byteRead)
                {
                    Logger.logData(command.toString());

                    Map<String, Object> plist = Plist.fromXml(command.toString());
                    outgoingResponses.offer(plist);

                    // Connection info for Data Port
                    connectionStatus = (Boolean)plist.get(STATUS_KEY);
                    connectionHost = (String)plist.get(SERVER_KEY);
                    connectionPort = (Integer)plist.get(PORT_KEY);
                    connectionAuthKey = (String)plist.get(AUTH_KEY);

                    Logger.logData("Server =>" + plist.get("server"));

                    command = new StringBuilder();

                }
                else
                {
                    command.append((char)byteRead);
                }
            }
        } 
        while (EOT != byteRead);
    }
    catch (UnknownHostException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    catch (IOException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    catch (XmlParseException e)
    {
        Logger.logData("Invalid Plist format");
        e.printStackTrace();
    }
    finally
    {   // Clean up handles
        try
        {
            authenticationSocket.close();
            out.close();
            in.close();
        }
        catch (IOException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    System.out.println("Connection status =>" + connectionStatus);
    System.out.println("Connection host =>" + connectionHost);
    System.out.println("Connection port =>" + connectionPort);

    if (connectionStatus)
    {
        dataServerHost = connectionHost;
        dataServerPort = connectionPort;
        dataServerAuthKey = connectionAuthKey;
        System.out.println("Connecting to data server @: " + dataServerHost + ":" + dataServerPort);
        connectToDataServer();
    }
}

我記得虛假選擇器喚醒是可能的。

有趣的是,當您被告知有要讀的東西時,沒有什么可讀的,但這對於程序來說通常不是問題。 程序在讀取 TCP stream 時通常應該期望任意數量的字節; 而0字節的情況通常不需要特殊處理。

你的程序理論上是錯誤的。 您不能指望您可以一次閱讀整封郵件。 一次讀取可能只返回其中的一部分。 可能只有 1 個字節。 沒有保證。

“正義”的方式是將讀取的所有字節累積在一個緩沖區中。 在緩沖區中尋找 EOT。 如果消息是分段的,則可能需要多次讀取才能到達整個消息。

loop 
  select();
  if readable
     bytes = read()
     buffer.append(bytes)
     while( buffer has EOT at position i)
       msg = buffer[0-i]
       left shift buffer by i

你可以在這個流程中看到,read() 讀取 0 字節並不重要。 這真的與 NIO 無關。 即使在傳統的阻塞TCP IO中,這個策略也必須做到理論上正確。

但是,實際上,如果你確實觀察到整個信息總是一體成型,你就不需要這么復雜了。 您的原始代碼在您的環境中實際上是正確的。

現在您觀察到有時會讀取 0 字節。 那么你之前的實際假設必須被修改。 您可以添加一些特殊的分支來忽略 0 字節塊。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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