1. 程式人生 > >【Java TCP/IP Socket程式設計】----深入剖析----TCP資料傳輸底層實現

【Java TCP/IP Socket程式設計】----深入剖析----TCP資料傳輸底層實現

目錄

 

套接字底層資料結構

TCP資料傳輸底層實現

案例


--------筆記來自於書籍《Java TCP/IP Socket程式設計》

套接字底層資料結構

    要熟悉掌握網路程式設計,就需要理解套接字的具體實現所關聯的資料結構和底層協議的工作細節,TCP套接字更是如此(Socket例項),需要理解套接字(socket)和Java中類Socket的概念,套接字(socket)指的是底層抽象,這種抽象由作業系統提供或者JVM自己實現(如嵌入式系統中),而Java中類Socket上的操作則轉換成了這種底層抽象上的操作。需要注意的是,執行在統一主機上其他程式可能也會通過底層套接字抽象來使用網路,因此會與Java Socket例項競爭系統資源,如埠等。

    “套接字結構”指底層實現(包含了JVM和TCP/IP,通常是後者)的資料結構集,這些資料結構包含了特定Socket例項鎖關聯的資訊。套接字結構除其他資訊外還包含了:

    1.該套接字所關聯的本地和遠端網際網路地址和埠號。

    2.一個FIFO(先進先出,First In First Out)佇列用於存放接收到的等待分配的資料,以及一個用於存放等待傳輸的資料的佇列。

    3.對於TCP套接字,還包含了與開啟和關閉TCP握手相關的額外協議狀態資訊。

    TCP提供了一種可信賴的位元組流服務,任何寫入Socket的OutputStream的資料副本都必須保留,直到其連線的另一端成功接收。向輸出流寫資料並不意味著資料實際上已經被髮送,它們只是被複制到本地緩衝區。就算在Socket的OUtputStream上呼叫flush()操作,也無法保證資料傳送到通道中,此外位元組流服務的自身屬性決定了其無法保留輸入流中訊息的邊界訊息。

    對於DatagramSocket來說,資料包並沒有為重傳進行快取,任何時候呼叫send()方法返回後,資料就已經發送給了執行傳輸任務的網路子系統。如果網路子系統由於某種原因無法處理這些訊息,該資料包將毫無提示地被丟棄(很少發生)。

TCP資料傳輸底層實現

    使用TCP套接字需要注意的是:不能假設在連線的一端將資料寫入輸出流和另有單從輸入流讀取資料之間有任何一致性。

尤其是在傳送端由單個輸出流的write()方法傳輸的資料,可能會通過另一端的多個輸入流read()方法來獲取;而一個read()方法可能會返回多個write()方法傳輸的資料。我們可以認為TCP連線上傳送的所以位元組序列在某一瞬間被分成3個FIFO佇列:

    1.SendQ:在傳送端底層實現中快取的位元組,這些位元組已經寫入輸出流,但還沒有在接收端主機上成功接收。

    2.RecvQ:在接收端底層實現中快取的位元組,等待分配到接收程式,即從輸入流中讀取。

    3.Delivered:接收者從輸入流已經讀取到的位元組。

傳送端:呼叫out.write()方法將向SendQ追加位元組,TCP協議負責將位元組按順序從SendQ移動到RecvQ。這個轉移過程無法由使用者程式控制或者直接觀察到,並且在塊中發生,這些塊的大小在一定程式上獨立與傳遞給write()方法的緩衝區大小。

接收端:從Socket的InputStream讀取資料時,位元組就從RecvQ移動到Delivered中,而轉移的塊的大小依賴於RecvQ中的資料量和傳遞給read()方法緩衝區大小。

需要說明一點的是:在UNIX(Linux)和Windows平臺上,可以用netstat命令檢視:SendQ和RecvQ中的位元組數,本地和遠端IP地址和埠,以及連線狀態等

案例

byte[] buffer0 = new byte[1000];
byte[] buffer1 = new byte[2000];
byte[] buffer2 = new byte[5000];
...
Socket socket = new Socket(destAddr,destPort);
OutputStream out = socket.getOutputStream();
...//設定緩衝區的程式碼
out.write(buffer0);
...//設定緩衝區的程式碼
out.write(buffer1);
...//設定緩衝區的程式碼
out.write(buffer2);
..
socket.close();

說明:其中圓點代表了設定緩衝區資料的程式碼。以下討論中in代表接收端Socket的InputStream,out代表了傳送端socket的OutputStream.

1.上面的程式中3次呼叫out.write()方法後,另一端呼叫in.read()方法前,SendQ,RecvQ,Delivered3個佇列的可能狀態。不同的陰影效果分別代表了上文中3次呼叫write()方法傳輸的不同資料。即第一次寫入1000位元組+第二次寫入中前500位元組在RecvQ佇列中,而第二次寫入中剩餘1500位元組+第三次寫入5000位元組在SendQ佇列中。

2.現在假設接收端呼叫read()方法時使用的緩衝區陣列大小為2000位元組,read()方法呼叫則將把等待分配佇列(RecvQ)中的1500位元組全部移動到陣列中,返回值為1500。注意,這些資料包含了第一次和第二次呼叫write()方法時傳輸的位元組。再過一段時間,當TCP連線傳完更多資料後,這三部分的狀態如下:

3.如果接收端現在呼叫read()方法時使用4000自己的緩衝區陣列,將很多位元組從等待分配的佇列(RecvQ)轉移到已分配佇列(Delivered)中。這包括第二次呼叫write()方法時剩下的1500位元組加上第三次呼叫write()的前2500位元組。此時佇列狀態如下:

下次呼叫read()方法返回的位元組數,取決於緩衝區陣列的大小,以及傳送方套接字/TCP實現通過網路向接收方實現傳輸資料的時機。資料從SendQ到RecvQ緩衝區的移動過程對應用程式協議設計有重要的指導性。