本文是學習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】(實例)
