標籤:
先說明一下粘包的概念: 發送時是兩個單獨的包、兩次發送,但接收時兩個包連在一起被一次接收到。
在以前 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 如何設定,接收方是一定要處理粘包的問題的。即在接收時,對接收到的資料進行分析,看是否存在資料不全或粘包的現象。
Android Socket 發送與接收資料問題: 發送後的資料接收到總是粘包