最近對原來寫的SocketClient代碼進行優化,從整體架構到具體細節,修改的地方比較多。今天有時間把SocketClient的相關知識整理一下。如果有錯誤的地方,還望指正!!!
一、整體流程:
描述如下:
1. 在Android環境下,SocketClient長連接,需要使用service。
2. SocketManagerService是在APK啓動時啓動。
3. SocketManagerService啓動時則SocketClientThread也啓動。
4. View調用SocketManagerService的sendCmd方法發送命令。
5. 如果SocketClient連接斷開,則重新建立連接,並且發送該命令。
6. 狀態返回則通過自定義Listener實現。
二、預備知識:
1. 判斷SocketClient是否與SocketServer連接:
1) . public void sendUrgentData(int value) throws IOException
源碼註釋:
Sends the given single byte data which is represented by the lowest octet of {@code value} as "TCP urgent data".
翻譯:
發送給定的代表最低字節碼值的單字節數據,作爲TCP緊急數據 。
個人理解:
該方法用於判斷SocketClient是否與SocketServer連接。
2.關於SocketClient的超時的理解:
1) . public void connect(SocketAddress remoteAddr, int timeout) throws IOException
源碼註釋:
Connects this socket to the remote host address and port number specified by the
{@code SocketAddress} object with the given timeout. This method will block indefinitely if the timeout is set to zero.
翻譯:
在有超時的情況下,socket連接指定地遠程主機的地址和端口。如果timeout是0,則方法永遠阻塞。
個人理解:
該方法的作用是SocketClient與SocketServer之間建立連接時的超時判斷。
2). public synchronized void setSoTimeout(int timeout) throws SocketException
源碼註釋:
Sets this socket's {@link SocketOptions#SO_TIMEOUT read timeout} in milliseconds.
Use 0 for no timeout. To take effect, this option must be set before the blocking method was called.
翻譯:
設置socketClient讀的超時時間。0表示沒有超時。該方法必須在阻塞方法之間調用。
個人理解:
InputStream的read方法,在timeout的時間內沒有接收到數據,則超時。超時後read方法停止阻塞狀態。
三、問題解答 :
1. 如何實現SocketClient的重新連接?
1). SocketClient設置setSoTime(int timeout) 當超時後,則read停止阻塞,所以線程停止運行。我代碼中timeout設置爲30分鐘。
2). 在SocketManagerService中實現發送命令的方法,代碼如下:
public void sendCameraCmdThread(byte[] cmd) { if (cmd == null) return; Log.i("TEST","====================================sendCameraCmd"); try { socketClientThread.sendUrgentData(); socketClientThread.sendCameraCmdThread(cmd); } catch (Exception e) { //重新連接的代碼 e.printStackTrace(); socketClientThread = new SocketClientThread(); socketClientThread.setByteArrCommand(cmd); socketClientThread.start(); } }
2. SocketClient重新連接後,如何重發命令?
因爲SocketClient重新連接,所以必須在SocketClient重新連接後才能重發命令。在線程中增加 public void setCommandArr(byte[] cmds) 方法。具體代碼如下:
@Override public void run() { super.run(); Timer timer = new Timer(); try { if (clientSocket == null) { clientSocket = new Socket(); clientSocket.connect(new InetSocketAddress(IP,PORT),5000); if (clientSocket!=null) { clientSocket.setReceiveBufferSize(SOCKET_RECV_BUFFER_SIZE); clientSocket.setSoTimeout(30*60*1000); cameraOutputStream = clientSocket.getOutputStream(); cameraInputStream = clientSocket.getInputStream(); if (cameraInputStream!=null) { try { byte[] buffers = new byte[56]; int size = 0; timer.schedule(new TimerTask() { @Override public void run() { if(cmdArr!=null) { for (int i = 0; i < cmdArr.length; i++) { sendCameraCmdThread(cmdArr[i]); } } } }, 100); Log.i("TEST","======================>start time"); while (clientSocket!=null&&(size = cameraInputStream.read(buffers))!= -1) { Log.i("TEST", "===================> receive msg: " +Utils.bytesToHexString(buffers)); } } catch (Exception e) { } } else { Log.e("TEST","=================> cameraInputStream is null"); } } else { } } } catch (Exception e) { } finally { Log.i("TEST","===================>Client Close!"); if(timer!=null) { timer.cancel(); } if (cameraOutputStream!=null) { try { cameraOutputStream.close(); cameraOutputStream=null; } catch (IOException e) { e.printStackTrace(); } } if (cameraInputStream!=null) { try { cameraInputStream.close(); cameraInputStream = null; } catch (IOException e) { e.printStackTrace(); } } if (clientSocket!=null) { try { clientSocket.close(); clientSocket=null; } catch (IOException e) { e.printStackTrace(); } } } }
在SocketClient重新連接後,則使用Timer延時100毫秒後,則發送命令。這樣就能保證發送的命令成功。