1. 程式人生 > >Android客戶端通過TCP接收伺服器端傳送的資料

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(),才可以。

至此,終於完成了接收資料這項任務,說到底還是要好好努力啊!