Android客戶端通過TCP接收伺服器端傳送的資料
引言
因為我確實不懂TCP通訊這一塊兒,最近專案中要實現客戶端接收伺服器端傳送過來的資料(這個資料是int型的,範圍是0~360,而且伺服器端用C語言寫的,每一秒傳送一次,客戶端只需要不斷接收就好了),很開心的用BufferedReader讀取資料,結果發現一直讀取不到資料,這下就慌了,搞了整整半天才用DataInputStream通過byte讀取到資料。
一、BufferedReader
BufferedReader可以從字元輸入流中讀取文字,通過快取來達到高效的讀取字元、陣列等資料。
說白了用它是因為它比較高效,它裡邊預設緩衝區的大小是8k。說下它的兩個主要的函式:
1.Read() 讀取並返回一個單一的字元,做為int型別返回,這個型別的範圍是0~65535,因為一個char的範圍是-128~128,實際值是-128~127,當讀取的值大於等於128時,返回的均是65535,具體程式碼如下:
public class TcpConnection extends Thread { private String ipAddress; private int ipPort; TcpResult tcpResult; public TcpResult getTcpResult() { return tcpResult; } public void setTcpResult(TcpResult tcpResult) { this.tcpResult = tcpResult; } public TcpConnection(String address, int port) { this.ipAddress = address; this.ipPort = port; } Socket socket; InputStream is; BufferedReader br; @Override public void run() { super.run(); try { socket = new Socket(ipAddress, ipPort); socket.setSoTimeout(20000); if(socket.isConnected() && !socket.isClosed()) { is = socket.getInputStream(); Log.e("test", "connect success");br = new BufferedReader(new InputStreamReader((is)); new Thread(new Runnable() { @Override public void run() { int data = 0; try { while ((data=br.read())!=-1) {Log.e("test", "data="+data); tcpResult.onSuccess(data); } } catch (IOException e) { e.printStackTrace(); } } }).start(); } } catch (IOException e) { e.printStackTrace(); Log.e("test", e.toString()); // tcpResult.onFailed(e.toString()); close(); } } public void close() { Log.e("test", "連線斷開"); CloseUtil.closeQuiety(br); CloseUtil.closeQuiety(is); CloseUtil.closeQuiety(socket); } public interface TcpResult { void onSuccess(int result); void onFailed(String error); } }
為了不讓C端的工程師覺得我太菜,我自己用安卓寫了伺服器端先自測,伺服器端的程式碼為:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ServerThread st = new ServerThread(); new Thread(st).start(); } class ServerThread implements Runnable { @Override public void run() { try { ServerSocket ss = new ServerSocket(2000); while (true) { Socket socket = ss.accept(); if (socket.isConnected()) { OutputStream os = socket.getOutputStream(); sendData(os); Log.e("test", "傳送成功"); os.close(); } socket.close(); } } catch (IOException e) { e.printStackTrace(); Log.e("test", "error" + e.toString()); } } } private void sendData(OutputStream outputStream) { for (int i = 1; i < 360; i++) { try { Thread.sleep(1000); outputStream.write(i); Log.e("test", "傳送了" + i); } catch (InterruptedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } }
結果是,客戶端接收到的資料1~127均沒有問題,128開始就是65535,我一下子就懵了,各種百度,就是因為char只能表示一個字元,而一個整數有4個字元,相當於沒讀完資料,所以會有問題。
在BufferedReader中有一個readLine()方法,它相當於讀取每句話,或者到\n(換行)或者\r(回車)才會截止,實際程式碼為把上邊客戶端的接收資料的執行緒改為如下程式碼:
final BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(is)); new Thread(new Runnable() { @Override public void run() { try { String data; while ((data=bufferedReader.readLine())!=null) { Log.e("test", data + " **"); } } catch (IOException e) { e.printStackTrace(); } } }).start();
這樣寫明顯不行,伺服器端是不斷髮送int型資料,我這邊用String接收,伺服器端不可能每次發完一個數據給我加個“\n”或者"\r"結束,到這裡好煩躁,一直各種百度,網上都是這種方法,還有一個read(char[] cbuf,int off,int len),這個方法返回的是一個字元陣列,也不行。
二.DataInputStream
後來寫c的老大實在等不了我了,找我的小夥伴給我幫忙,小夥伴問我要不試試用byte讀取,因為流都是以byte的形式傳輸的,所以這樣讀肯定沒有問題,於是我試著用DataInputStream來讀取資料,程式碼如下:
public class TcpConnection extends Thread { private String ipAddress; private int ipPort; TcpResult tcpResult; public TcpResult getTcpResult() { return tcpResult; } public void setTcpResult(TcpResult tcpResult) { this.tcpResult = tcpResult; } public TcpConnection(String address, int port) { this.ipAddress = address; this.ipPort = port; } Socket socket; InputStream is; DataInputStream dis; @Override public void run() { super.run(); try { socket = new Socket(ipAddress, ipPort); socket.setSoTimeout(20000); if (socket.isConnected() && !socket.isClosed()) { is = socket.getInputStream(); Log.e("test", "connect success"); final byte[] bytes = new byte[2]; dis = new DataInputStream(is); new Thread(new Runnable() { @Override public void run() { int len = 0; try { while ((len = dis.read(bytes)) != -1) { int value1 = bytes[0] & 0xff; int value2 = bytes[1] & 0xff; int iii = (int) ((value2 & 0xff) << 8) | ((value1 & 0xff) << 0); Log.e("test", "len=" + len + " " + value1 + " ** " + value2 + " " + " " + iii); tcpResult.onSuccess(iii); } } catch (IOException e) { e.printStackTrace(); } } }).start(); } } catch (IOException e) { e.printStackTrace(); Log.e("test", e.toString()); // tcpResult.onFailed(e.toString()); close(); } } public void close() { Log.e("test", "連線斷開"); CloseUtil.closeQuiety(dis); CloseUtil.closeQuiety(is); CloseUtil.closeQuiety(socket); } public interface TcpResult { void onSuccess(int result); void onFailed(String error); } }
這樣寫最終沒有問題,從伺服器端接收到的資料最終完美的轉了過來,至此,謝謝我的小夥伴的提醒,哈哈~~
2018年4月4日補加:上邊定義的byte陣列的大小是2,問題剛好出在這兒,因為一個int型的整數包含4個byte,就會出現收到的第一組資料為正常資料,第二組資料為兩個0,即收到一個int型的資料會接收兩次,第一次為兩個byte,第二次也為兩個byte,一個數據也接收了兩次,所以要改變陣列的大小為4。
解釋下下邊這塊兒程式碼:
while ((len = dis.read(bytes)) != -1) { int value1 = bytes[0] & 0xff; int value2 = bytes[1] & 0xff; int iii = (int) ((value2 & 0xff) << 8) | ((value1 & 0xff) << 0); Log.e("test", "len=" + len + " " + value1 + " ** " + value2 + " " + " " + iii); tcpResult.onSuccess(iii); }因為如果伺服器端傳過來的資料是360,360轉成二進位制就是101101000,是9位,所以傳過來的就是兩個byte,所以byte[0]轉成整型就是104(二進位制為1101000),byte[1]轉成int就是1(二進位制是00000001),所以要想得到360,就需要把byte[1]左移8位再加上byte[0],然後把它們轉成int才可以,
int iii = (int) ((value2 & 0xff) << 8) | ((value1 & 0xff)
這句就是實現了上邊的要求,最終可以打印出360,在Java中"|"這個符號是“位運算子”或(二進位制,相應的二進位制位上只要有一個為1,結果就為1,兩個都為0的話結果為0)。研究出來對我這種基礎不好的人來說真的是耗費了太多的時間。
其實我自己寫的伺服器端(上邊的程式碼),如果直接傳送整型資料的話,這邊的收到的會有問題,但是這邊接收C傳送的資料沒有問題,如果Java寫的伺服器端想要把資料發過來併成功接收的話,需要把整型資料寫成字串,通過
"data".getBytes();
來發送,如果是int型的話,傳送"360".getBytes(),才可以。
至此,終於完成了接收資料這項任務,說到底還是要好好努力啊!