![](/img/trans.png)
[英]Java.io.File.canRead() vs Java.nio.Files.isReadable() vs Java.io.FilePermission
[英]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.