Android Socket 發送與接收數據問題: 發送后的數據接收到總是粘包


先說明一下粘包的概念: 發送時是兩個單獨的包、兩次發送,但接收時兩個包連在一起被一次接收到。
在以前 WinCE 下 Socket 編程,確實也要處理粘包的問題,沒想到在 Android 下也遇到了。
首先想從發送端能否避免這樣的問題,例如: (1) 調用強制刷數據完成發送的函數;(2) 設置發送超時。
1 先試了調用 flush() 函數,但運行后現象依舊
2 設置發送超時是 Windows 平台的做法,但在 Android 平台下是否有類似的設置呢?
查看 Socket 類的實現代碼:java.net.socket socket.class 文件后發現,還是有函數可以完成這樣的設置的。請看如下函數和變量的說明:

1 /** 
2  * Sets this socket's {@link SocketOptions#TCP_NODELAY} option. 
3  */  
4 public void setTcpNoDelay(boolean on) throws SocketException {  
5     checkOpenAndCreate(true);  
6     impl.setOption(SocketOptions.TCP_NODELAY, Boolean.valueOf(on));  
7 }  

1 /** 
2  * This boolean option specifies whether data is sent immediately on this socket. 
3  * As a side-effect this could lead to low packet efficiency. The 
4  * socket implementation uses the Nagle's algorithm to try to reach a higher 
5  * packet efficiency if this option is disabled. 
6  */  
7 public static final int TCP_NODELAY = 1;  

一般情況下,只需要調用如下的代碼即可:

1 public Socket clientSocket = null;  
2 // 實例化對象並連接到服務器  
3 clientSocket = new Socket("172.25.103.1",12589);  

不用做任何設置就可以完成與服務器/客戶端的通訊,剛開始我也是這樣做的。所以,遇到了上面的問題。

1 // 發送數據包,默認為 false,即客戶端發送數據采用 Nagle 算法;  
2 // 但是對於實時交互性高的程序,建議其改為 true,即關閉 Nagle 算法,客戶端每發送一次數據,無論數據包大小都會將這些數據發送出去  
3 clientSocket.setTcpNoDelay(true);  

看以下較完整的 Socket 初始化與設置過程:

 1 /* * * * * * * * * * 客戶端 Socket 通過構造方法連接服務器 * * * * * * * * * */  
 2 try {  
 3   // 客戶端 Socket 可以通過指定 IP 地址或域名兩種方式來連接服務器端,實際最終都是通過 IP 地址來連接服務器  
 4   // 新建一個Socket,指定其IP地址及端口號  
 5   Socket clientSocket = new Socket("172.25.103.1",12589);  
 6   // 客戶端socket在接收數據時,有兩種超時:1. 連接服務器超時,即連接超時;2. 連接服務器成功后,接收服務器數據超時,即接收超時  
 7   // 設置 socket 讀取數據流的超時時間  
 8   clientSocket.setSoTimeout(5000);  
 9   // 發送數據包,默認為 false,即客戶端發送數據采用 Nagle 算法;  
10   // 但是對於實時交互性高的程序,建議其改為 true,即關閉 Nagle 算法,客戶端每發送一次數據,無論數據包大小都會將這些數據發送出去  
11   clientSocket.setTcpNoDelay(true);  
12   // 設置客戶端 socket 關閉時,close() 方法起作用時延遲 30 秒關閉,如果 30 秒內盡量將未發送的數據包發送出去  
13   clientSocket.setSoLinger(true, 30);  
14   // 設置輸出流的發送緩沖區大小,默認是4KB,即4096字節  
15   clientSocket.setSendBufferSize(4096);  
16   // 設置輸入流的接收緩沖區大小,默認是4KB,即4096字節  
17   clientSocket.setReceiveBufferSize(4096);  
18   // 作用:每隔一段時間檢查服務器是否處於活動狀態,如果服務器端長時間沒響應,自動關閉客戶端socket  
19   // 防止服務器端無效時,客戶端長時間處於連接狀態  
20   clientSocket.setKeepAlive(true);  
21   // 客戶端向服務器端發送數據,獲取客戶端向服務器端輸出流  
22   OutputStream osSend = clientSocket.getOutputStream();  
23   OutputStreamWriter osWrite = new OutputStreamWriter(osSend);  
24   BufferedWriter bufWrite = new BufferedWriter(osWrite);  
25   // 代表可以立即向服務器端發送單字節數據  
26   clientSocket.setOOBInline(true);  
27   // 數據不經過輸出緩沖區,立即發送  
28   clientSocket.sendUrgentData(0x44);//"D"  
29   // 向服務器端寫數據,寫入一個緩沖區  
30   // 注:此處字符串最后必須包含“\r\n\r\n”,告訴服務器HTTP頭已經結束,可以處理數據,否則會造成下面的讀取數據出現阻塞  
31   // 在write() 方法中可以定義規則,與后台匹配來識別相應的功能,例如登錄Login() 方法,可以寫為write("Login|LeoZheng,0603 \r\n\r\n"),供后台識別;  
32   bufWrite.write("Login|LeoZheng,0603 \r\n\r\n");  
33   // 發送緩沖區中數據 - 前面說調用 flush() 無效,可能是調用的方法不對吧!  
34   bufWrite.flush();  
35 }  
36 catch (UnknownHostException e) {  
37     e.printStackTrace();  
38 } catch (IOException e) {  
39     e.printStackTrace();  
40 }  
 1 /* * * * * * * * * * Socket 客戶端讀取服務器端響應數據 * * * * * * * * * */  
 2 try {  
 3   // serverSocket.isConnected 代表是否連接成功過  
 4   // 判斷 Socket 是否處於連接狀態  
 5   if(true == serverSocket.isConnected() && false == serverSocket.isClosed()) {  
 6     // 客戶端接收服務器端的響應,讀取服務器端向客戶端的輸入流  
 7     InputStream isRead = serverSocket.getInputStream();  
 8     // 緩沖區  
 9     byte[] buffer = new byte[isRead.available()];  
10     // 讀取緩沖區  
11     isRead.read(buffer);  
12     // 轉換為字符串  
13     String responseInfo = new String(buffer);  
14     // 日志中輸出  
15     Log.i("Socket Server", responseInfo);  
16   }  
17   // 關閉網絡  
18   serverSocket.close();  
19 }  
20 catch (UnknownHostException e) {  
21     e.printStackTrace();  
22 } catch (IOException e) {  
23     e.printStackTrace();  
24 }  
 1 /* * * * * * * * * * Socket 客戶端通過 connect 方法連接服務器 * * * * * * * * * */  
 2 try {  
 3   Socket serverSocket = new Socket();  
 4   // 使用默認的連接超時  
 5   serverSocket.connect(new InetSocketAddress("172.25.103.1",12589));        // 連接超時 3 秒: serverSocket.connect(new InetSocketAddress("172.25.103.1",12589),3000);  
 6     
 7   // 關閉 socket  
 8   serverSocket.close();  
 9 }  
10 catch (UnknownHostException e) {  
11     e.printStackTrace();  
12 } catch (IOException e) {  
13     e.printStackTrace();  
14 }  

最后說明一點: 無論 Socket 如何設置,接收方是一定要處理粘包的問題的。即在接收時,對接收到的數據進行分析,看是否存在數據不全或粘包的現象。


免責聲明!

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



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