2017-04-14 40 views
2

我正在使用this鏈接開發一個簡單的客戶端服務器程序。我的服務器是Mac機,客戶機是Windows機器(Win 10)。爲什麼Java writeBytes只發送第一個TCP數據包中的一個字節並保留在另一個TCP數據包中

客戶端 - 服務器通信正常工作。當我檢查使用Wireshark發送的字節時,在第一個TCP數據包中只發送一個字符。還有另一個TCP數據包發送其餘的數據。

即如果我發送「qwerty」,客戶端發送「q」,服務器響應,然後客戶端發送「werty」。同樣,服務器發送「Q」,客戶端響應,然後服務器發送「WERTY」。

這是我的客戶端服務器代碼。在執行writeBytes()之後,我正在刷新數據。

如何在單個TCP數據包中強制執行數據發送?

客戶端代碼:

import java.io.*; 
import java.net.*; 

class Client 
{ 
    public static void main(String argv[]) throws Exception 
    { 
     String sentence; 
     String modifiedSentence; 
     BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in)); 
     Socket clientSocket = new Socket("192.1.162.65", 6789); 
     DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream()); 
     BufferedReader inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); 
     System.out.println("Enter some text: " + inFromServer); 
     sentence = inFromUser.readLine(); 
     outToServer.writeBytes(sentence + '\n'); 
     outToServer.flush(); 
     modifiedSentence = inFromServer.readLine(); 
     System.out.println("FROM SERVER: " + modifiedSentence); 
     clientSocket.close(); 
    } 
} 

服務器代碼:

import java.io.BufferedReader; 
import java.io.DataOutputStream; 
import java.io.InputStreamReader; 
import java.net.ServerSocket; 
import java.net.Socket; 

public class Server { 

    public static void createListener(ServerSocket serverSocket) throws Exception { 

     String clientSentence; 
     String capitalizedSentence; 
     @SuppressWarnings("resource") 
     ServerSocket welcomeSocket = new ServerSocket(6789); 

     while (true) { 
      @SuppressWarnings("resource") 
      Socket connectionSocket = welcomeSocket.accept(); 
      BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream())); 
      DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream()); 
      clientSentence = inFromClient.readLine(); 
      System.out.println("Received: " + clientSentence); 
      capitalizedSentence = clientSentence.toUpperCase() + '\n'; 
      outToClient.writeBytes(capitalizedSentence); 
      outToClient.flush(); 
     } 
    } 

    public static void main(String[] args) throws Exception { 
     Server.createListener(null); 
    } 
} 

編輯1: 後面這個問題的原因是,我試圖使用同一服務器的代碼將數據發送到另一個客戶端那不在我的控制之下。在這種情況下,上面的代碼只發送第一個TCP數據包。我在Wireshark上看不到第二個TCP數據包。任何關於如何去調試它的建議?

+0

TCP是一個流媒體協議。除了初始連接和連接關閉之外,沒有開始或結束。只有一串字節。 –

+1

有人可以澄清downvote背後的原因,因爲我覺得這是一個合法的問題嗎? – Anit

+0

感謝您的及時接受! – GhostCat

回答

3

據我所知,你不能也更重要的是:你應該不是

問題是:這些庫打算爲您創建一個抽象。 TCP協議實際上是複雜的;最有可能的;你不想處理所有細微的細節。

所以這裏沒有答案:除非你遇到這個實現的真正問題(就像一個不可接受的性能命中) - 你應該看看寫清晰,可讀的代碼;而不是擺弄TCP堆棧實現細節!

例如:你的代碼不處理異常,你只是讓它們通過。你的代碼不關閉流;您的代碼包含禁止警告註釋。很顯然:這是從來沒有記在單元測試。

這樣的事情很重要。如果JVM發送一個或兩個TCP包,則不需要!

+0

謝謝。我正在嘗試使用相同的服務器代碼將數據發送到另一個不在我控制範圍內的客戶端。在這種情況下,上面的代碼只發送第一個TCP數據包。 – Anit

+0

關閉該插座後會發生什麼? – GhostCat

+0

謝謝!這有幫助。 – Anit

1

您無法控制TCP數據包的傳輸方式!別想了。這是較低的操作系統級別的東西的一部分。

直到您的郵件成功發送,您不應該擔心。

0

您可以更改部分行爲以獲得更好的性能。其實這是一種常見的做法。

什麼,你遇到的是由於TCP的面向流的性質(順便說一句,這是怎麼回事寫你的服務器和客戶端時是有用的:How to read all of Inputstream in Server Socket JAVA)。當客戶端寫入套接字時,寫入的數據實際上被寫入發送緩衝區。TCP從發送緩衝區獲取數據,根據不同的變量(讀取Are TCP/IP Sockets Atomic?)獲取數據,並將其發送到TCP段中的另一端。

在你的情況下,TCP獲取第一個字符,很可能是因爲它獲取的數據比寫入發送緩衝區的數據更快,並且發送一個字節的TCP段。當一個ACK從另一端回來時,TCP會發送第二個字節與其餘的字節(實際上,它可以發送,例如,第二條消息中只有兩個字節,然後是其他字節)。

你不能改變第一條消息只發送第一個字符的事實,而實際上它應該是隨機的,有時它可以發送多個字符。但是,您可以在發送剩餘消息之前更改TCP等待ACK的部分。此行爲主要是由於Nagle算法,您可以將其更改爲:

clientSocket.setTcpNoDelay(true); 

之後,您應該看到第二條消息不會等待ACK。在你的測試中,你可能看不出有什麼不同,但如果你在美國東海岸和西海岸的服務器上有一個客戶端,那麼第二條消息可能會延遲20-40毫秒。如果客戶位於中國或印度或南美,延遲甚至可能達到300毫秒。

其他有助於理解的因素是初始擁塞窗口,雖然我認爲它並不影響您的測試。你可以在這裏閱讀更多關於initcwnd:https://www.cdnplanet.com/blog/tune-tcp-initcwnd-for-optimum-performance/

相關問題