Android TCP通訊的簡單一實例以及常見問題[逾時/主線程阻塞]

來源:互聯網
上載者:User

個人更喜歡著眼於執行個體,從最簡單的開始,一步步進行測試。

理論什麼的先放一邊,把程式跑起來再說。只有跑起來了,才會有動力去繼續往下學,參透整個代碼的運行機制。


本次的執行個體目標是——

類比一個PC伺服器與android端的通訊,目標是盡量的做到精簡,使代碼僅留下所需核心部分,降低筆記代碼的閱讀難度。

--------------------------

>【執行個體】

PC上的伺服器的代碼:

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.ServerSocket;import java.net.Socket;public class SocketServer {    //監聽連接埠12345    private static final int PORT = 12345;        public static void main(String[] args) {          try {              System.out.println("等待用戶端");              ServerSocket serverSocket = new ServerSocket(PORT);              Socket clientSocket = serverSocket.accept();              System.out.println("用戶端上線");            while (true) {                  //迴圈監聽用戶端請求                  try {                      //擷取輸入資料流                      BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));                      //擷取從用戶端發來的資訊                      String msg = in.readLine();                     System.out.println("用戶端訊息:"+msg);                  } catch (IOException e) {                      System.out.println("讀寫錯誤");                      e.printStackTrace();                  } finally {                    serverSocket.close();                    clientSocket.close();                    System.out.println("伺服器關閉");                      break;                }            }            } catch (Exception e) {              System.out.println("連接埠被佔用");              e.printStackTrace();          }      }  }

從中可以看出伺服器的搭建主要有以下步驟:

1.建立伺服器的Socket,並設定一個監聽的連接埠PORT

ServerSocket serverSocket = new ServerSocket(PORT);

2.將伺服器的ServerSocket套接到用戶端的Socket上:(未套上時,會一直阻塞。諸位可以試試)

Socket clientSocket = serverSocket.accept();

由於需要進行迴圈監聽,因此擷取訊息的操作應放在一個while大迴圈中:

3.從用戶端發來的clientSocket上擷取輸入資料流的抽象類別,然後執行個體化,並進行讀寫。

 BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));   String msg = in.readLine(); 

安卓上的用戶端代碼:

@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Button b=(Button)findViewById(R.id.button);             b.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                new Thread(net).start();//新的子線程            }        });    }    Runnable net=new Runnable() {        @Override        public void run() {            try {                Socket socket;                //socket=new Socket("192.168.1.102", 12345);//注意這裡                socket = new Socket();                SocketAddress socAddress = new InetSocketAddress("192.168.1.102", 12345);                socket.connect(socAddress, 3000);//逾時3秒 //發送給服務端的訊息                String msg = "Good Night";                try {                    //擷取輸出資料流並執行個體化                    BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));                    out.write(msg+"\n");//防止粘包                    out.flush();//不加這個flush會怎樣。                } catch (Exception e) {                    e.printStackTrace();                } finally {                    //關閉Socket                    socket.close();                    System.out.println("用戶端關閉");                }            }    catch (Exception e) {                System.out.println("連結錯誤");                e.printStackTrace();            }        }    };
從中,可以看到用戶端的搭建有以下步驟:

1.建立用戶端本身的通訊端Socket:

socket = new Socket();

2.建立一個串連(我知道這裡和代碼不一樣,你可能會存疑,但請往下看)

socket = new Socket(ip,port);

3.發送訊息:

BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));out.write(msg);out.flush();//不加這個flush會怎樣。
4.關閉用戶端Socket:
socket.close();


現在,對於Android的TCP通訊功能還遠沒有完成,不過快了,先別急,繼續往下看:

觀察以上android用戶端代碼。再想想,如何在Activity中使用相關的用戶端代碼呢。為什麼我專門寫了一個線程來進行網路操作呢。

答:從4.0開始,安卓就已經不允許在主線程中進行網路相關的操作。這樣設計的原因是,由於網路的延遲、不確定性等因素,加之Socket本身在未套接上時是處於阻塞狀態的,如果在主線程中進行網路相關操作,就會導致整個app被嚴重阻塞。

因此,從4.0起,任何嘗試在主線程中進行網路操作的動作,都會導致拋出“android.os.NetworkOnMainThreadException”的異常。

那我們該如何解決呢。

建立一個子線程,所以,你會在我給出的代碼裡看到以下內容:

    Runnable net=new Runnable() {        @Override        public void run() {           //網路操作        }    }
使用時,直接讓這個Runnable啟動就行了:
new Thread(net).start();


此時你可能會注意到,我在用戶端對Socket的執行個體化,並沒有使用大多數人常寫的:

socket=new Socket("192.168.1.102", 12345);
而是這樣寫:

socket = new Socket();SocketAddress socAddress = new InetSocketAddress("192.168.1.102", 12345);socket.connect(socAddress, 3000);//逾時3秒
為什麼呢。我們知道,socket在未串連上時,會一直處於阻塞狀態,而此時由於socket本身並未執行個體化,導致你無法對socket的逾時時間進行設定。這往往會導致線程卡主很長一段時間,最後拋出error110(TIME_OUT)。

我這麼寫,可以人為的設定逾時時間,也便於進行多次的逾時重播。

另外,android對許可權也卡得很死,有些機型或者系統版本根本不允許你的app使用網路。那麼我們需要在許可權中添加以下內容:

    <!--允許應用程式改變網路狀態-->    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>    <!--允許應用程式改變WIFI串連狀態-->    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>    <!--允許應用程式訪問有關的網路資訊-->    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>    <!--允許應用程式訪問WIFI網卡的網路資訊-->    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>    <!--允許應用程式完全使用網路-->    <uses-permission android:name="android.permission.INTERNET"/>    <uses-permission android:name="android.permission.WAKE_LOCK" />

現在可以測試這個執行個體了。如果你仍然無法成功串連你的PC伺服器,接著往下看。


------------------------

>【排錯】

從排錯的角度來看,我們需要做的,是從伺服器和用戶端兩個方面進行排錯。

首先,我們應當確保伺服器和連接埠是沒問題的。

在eclipse裡開啟伺服器,然後進入cmd,輸入talent localhost 12345。

如果成功,那麼eclipse的控制台會輸出“用戶端已上線”。

然後,我們再測試android用戶端串連時輸入的PC伺服器的ip是沒問題的:(ip和port值請自行確定)

重啟伺服器,進入cmd輸入talent 192.168.1.102 12345。

如果這一步成功,但你的android用戶端依舊無法串連上你的PC伺服器,那麼,我想請你檢查以下你的windows防火牆的設定。

控制台\系統和安全\Windows 防火牆 -> 進階設定 ->入站規則 ,看看以下這幾項是不是被防火牆禁止了:

現在再去測試一遍你的用戶端,看看是否能連上伺服器。大部分的範例代碼的逾時問題(error:110)在此都可以被解決了。


如果還不行,我們接下來檢測安卓用戶端。

首先,確保你的手機給了你的app許可權。

接著,請檢查一遍你設定的連接埠port值,是不是處於1024以下,或者使用了常用軟體及敏感的port的值。如果是,請改成12345再進行測試。

基本上,到了這一步,只要編譯沒有報錯,操作正確,在安卓版本沒有發生大的變化的情況下,已經可以連上伺服器了。


----------------------

>【理論】

執行個體結合基礎,這裡找到了大手子的幾篇理論性的文章,可以參考我以上給出這個小執行個體,再進行深入學習:


Android UDP通訊的實現與歸納:

http://blog.csdn.net/shenpibaipao/article/details/70237697

Java輸入輸出資料流詳解:

http://blog.csdn.net/zsw12013/article/details/6534619

 Socket 通訊原理(Android用戶端和伺服器以TCP&&UDP方式互連):

http://blog.csdn.net/mad1989/article/details/9147661



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.