JAVA通信系列一:Java Socket技術總結


 本文是學習java Socket整理的資料,供參考。

1       Socket通信原理

1.1     ISO七層模型

1.2     TCP/IP五層模型

 

         應用層相當於OSI中的會話層,表示層,應用層。

         區別參考:http://blog.chinaunix.net/uid-22166872-id-3716751.html

 

1.3     TCP報文

 

(1)序號:Seq序號,占32位,用來標識從TCP源端向目的端發送的字節流,發起方發送數據時對此進行標記。

  (2)確認序號:Ack序號,占32位,只有ACK標志位為1時,確認序號字段才有效,Ack=Seq+1。

  (3)標志位:共6個,即URG、ACK、PSH、RST、SYN、FIN等,具體含義如下:

  (A)URG:緊急指針(urgent pointer)有效。

  (B)ACK:確認序號有效。

  (C)PSH:接收方應該盡快將這個報文交給應用層。

  (D)RST:重置連接。

  (E)SYN:發起一個新連接。

  (F)FIN:釋放一個連接。

 

 需要注意的是:

  (A)不要將確認序號Ack與標志位中的ACK搞混了。

  (B)確認方Ack=發起方Req+1,兩端配對。

1.4     Socket通信

       Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

1.5     三次握手

         Socket連接建立和關閉,詳見:http://www.2cto.com/net/201310/251896.html

2       通信基本概念

2.1     短連接

連接->傳輸數據->關閉連接 

短連接是指SOCKET連接,發送數據,接收數據后,馬上斷開連接。 

比如:

HTTP1.0默認是短連接,無狀態的,瀏覽器和服務器每進行一次HTTP操作,就建立一次連接,但任務結束就中斷連接。 、

Http1.1默認是長連接

無狀態:協議對於事務處理沒有記憶能力;

2.2     長連接

連接->傳輸數據->保持連接 -> 傳輸數據-> 。。。 ->關閉連接。 

建立SOCKET連接后,不管是否使用,一致保持連接。

2.3     半包

接受方沒有接受到一個完整的包,只接受了部分;

原因:TCP為提高傳輸效率,將一個包分配的足夠大,導致接受方並不能一次接受完。

影響:長連接和短連接中都會出現

2.4     粘包

發送方發送的多個包數據到接收方接收時粘成一個包,從接收緩沖區看,后一包數據的頭緊接着前一包數據的尾。

分類:一種是粘在一起的包都是完整的數據包,另一種情況是粘在一起的包有不完整的包

出現粘包現象的原因是多方面的:

1)發送方粘包:由TCP協議本身造成的,TCP為提高傳輸效率,發送方往往要收集到足夠多的數據后才發送一包數據。若連續幾次發送的數據都很少,通常TCP會根據優化算法把這些數據合成一包后一次發送出去,這樣接收方就收到了粘包數據。

2)接收方粘包:接收方用戶進程不及時接收數據,從而導致粘包現象。這是因為接收方先把收到的數據放在系統接收緩沖區,用戶進程從該緩沖區取數據,若下一包數據到達時前一包數據尚未被用戶進程取走,則下一包數據放到系統接收緩沖區時就接到前一包數據之后,而用戶進程根據預先設定的緩沖區大小從系統接收緩沖區取數據,這樣就一次取到了多包數據。

2.5     分包

分包(1):在出現粘包的時候,我們的接收方要進行分包處理;

分包(2):一個數據包被分成了多次接收;

原因:1. IP分片傳輸導致的;2.傳輸過程中丟失部分包導致出現的半包;3.一個包可能被分成了兩次傳輸,在取數據的時候,先取到了一部分(還可能與接收的緩沖區大小有關系)。

影響:粘包和分包在長連接中都會出現

2.6     如何解決半包,粘包問題

出現粘包和半包現象,是因為TCP當中,只有流的概念,沒有包的概念。

UDP不會出現半包,粘包情況,原因是UDP是一個完整的數據包,發送時不進行合並,因此接收的時候就不存在粘包情況。

 

固定長度:每次發送固定長度的數據;

特殊標示:以回車,換行作為特殊標示;獲取到指定的標識時,說明包獲取完整。

字節長度:包頭+包長+包體的協議形式,當服務器端獲取到指定的包長時才說明獲取完整;

 

參考文章(有圖):http://blog.csdn.net/pi9nc/article/details/17165171

2.7     什么時候需要考慮粘包的情況

短連接:不用考慮粘包的情況;

發送數據無結構,如文件傳輸,這樣發送方只管發送,接收方只管接收存儲,也不用考慮粘包;

長連接:需要在連接后一段時間內發送不同結構數據;

 

處理方式:接收方創建一預處理線程,對接收到的數據包進行預處理,將粘連的包分開;

2.8     TCP與UDP的差別

  • 基於連接與無連接;
  • 對系統資源的要求(TCP較多,UDP少);
  • UDP程序結構較簡單;
  • 流模式與數據報模式 ;
  • TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證。
  • 網絡分化:機器本身是好的,但是之間的通信出現問題;
  • 網絡抖動:網絡中的延遲是指信息從發送到接收經過的延遲時間,一般由傳輸延遲及處理延遲組成;而抖動是指最大延遲與最小延遲的時間差,如最大延遲是20毫秒,最小延遲為5毫秒,那么網絡抖動就是15毫秒,它主要標識一個網絡的穩定性。

2.9     其他通信問題

2.10         參考資料

一個包沒有固定長度,以太網限制在46-1500字節,1500就是以太網的MTU,超過這個量,TCP會為IP數據報設置偏移量進行分片傳輸,現在一般可允許應用層設置8k(NTFS系)的緩沖區,8k的數據由底層分片,而應用看來只是一次發送。

 

對於UDP,就不要太大,一般在1024至10K。注意一點,你無論發多大的包,IP層和鏈路層都會把你的包進行分片發送,一般局域網就是1500左右,廣域網就只有幾十字節。分片后的包將經過不同的路由到達接收方,對於UDP而言,要是其中一個分片丟失,那么接收方的IP層將把整個發送包丟棄,這就形成丟包。

 

TCP作為流,發包是不會整包到達的,而是源源不斷的到,那接收方就必須組包。而UDP作為消息或數據報,它一定是整包到達接收方。

 

關於接收,一般的發包都有包邊界,首要的就是你這個包的長度要讓接收方知道,於是就有個包頭信息,對於TCP,接收方先收這個包頭信息,然后再收包數據。一次收齊整個包也可以,可要對結果是否收齊進行驗證。這也就完成了組包過程。UDP,那你只能整包接收了。要是你提供的接收Buffer過小,TCP將返回實際接收的長度,余下的還可以收,而UDP不同的是,余下的數據被丟棄並返回WSAEMSGSIZE錯誤。注意TCP,要是你提供的Buffer佷大,那么可能收到的就是多個發包,你必須分離它們,還有就是當Buffer太小,而一次收不完Socket內部的數據,那么Socket接收事件(OnReceive),可能不會再觸發,使用事件方式進行接收時。

3       Socket實例

3.1     Socket通信模型

 

 

 

3.2     Socket架構完整模型

  • 解決半包,粘包(編解碼)
  • 服務端支持多線程
  • 支持心跳檢測
  • 指定端口實例化一個SeverSocket
  • 調用ServerSocket的accept()方法,以在等待連接期間造成阻塞
  • 獲取位於該底層的Socket的流以進行讀寫操作
  • 將數據封裝成流
  • 對Socket進行讀寫
  • 關閉打開的流

3.3     服務端開發

 

//建立ServerSocket對象,監聽綁定端口

ServerSocket server=new ServerSocket(1000);

 

//建立接收Socket,阻塞響應

Socket client=server.accept();

 

//獲得輸入流,用於接收客戶端信息

InputStream in=server.getInputStream();

//獲得輸出流,用於輸出客戶端信息

 

//對輸入流進行包裝,方便使用

BufferedReader inRead=new BufferedReader(new InputStreamReader(in));

//對輸出流進行包裝,方便使用

PrintWriter outWriter=new PrintWriter(out);

 

while(true)

{

String str=inRead.readLine();//讀入

outWriter.println("輸出到客戶端");

outWriter.flush();//輸出

if(str.equals("end"))

{

break;

}

}

//關閉Socket

client.close();

server.close();

 

3.4     客戶端開發

  • 通過IP地址和端口實例化Socket,請求連接服務器
  • 獲得Socket上的流以進行讀寫
  • 把流封裝進BufferedReader/PrintWriter的實例
  • 對Socket進行讀寫
  • 關閉打開的流

 

//使用Socket,建立與服務端的連接

Socket client=new Socket("127.0.0.1",1000);

InputStream in=client.getInputStream();//獲得輸入流

OutputStream out=client.getOutputStream();//獲得輸出流

//包裝輸入流,輸出流

BufferedReader inRead=new BufferedReader(new InputStreamReader(in));

PrintWriter outWriter=new PrintWriter(out);

//獲得控制台輸入

BufferedReader inConsole=new BufferedReader(new InputStreamReader(in));

while(true)

{

String str=inConsole.readLine();//讀取控制台輸入

outWriter.println(str);//輸出到服務端

outWriter.flush();//刷新緩沖區

if(str.equals("end"))

{

break;

}//退出

System.out.println(inRead.readLine())//讀取服務端輸出

}

client.close();

 

DOS下運行客戶端

java -classpath sockettest-0.0.1-SNAPSHOT.jar cn.com.gome.sockettest.basic.ClientTest

3.5     多線程服務端

 

3.6     心跳檢測

         方法1:socket.sendUrgentData(0);//發送1個字節的緊急數據,默認情況下,服務器端沒有開啟緊急數據處理,不影響正常通信

try{
      socket.sendUrgentData(0xFF);
}catch(Exception ex){
      reconnect();
}

         只要對方Socket的SO_OOBINLINE屬性沒有打開,就會自動舍棄這個字節,而SO_OOBINLINE屬性默認情況下就是關閉的。

 

         方法2:自定義心跳字符串

3.7     各類數據讀寫

         傳輸字節,字符,對象【ObjectInputStream ObjectOutputStream】(實例)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM