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