《Android》『Socket』- 如何透過 Socket 連線連接用戶端與伺服端程式
《Android Developers 參考文獻》
《繼承架構》
extends Object
implements Closeable
java.lang.Object
↳ java.net.Socket
《簡單介紹》
一個網路連線,事實上就是兩個機器的兩個程式之間的連線,我們根據 IP 來區別主機、再根據 Port 來區別程式,而一個 Socket 連線(單向),就是由一個 IP 與一個 Port 來定義的,我們可以把它當作兩個程式與 TCP/IP 連線之間的界面。
《程式範例》
要建立一個 Socket 連線,我們必須寫兩支程式,分別代表 Client 端以及 Server 端,而 Socket 連線又分為兩種協議,分別是 TCP 以及 UDP,這兩種連線的差別在於,TCP 提供的是一個連線導向(Connection Oriented)的可靠傳輸,UDP 則是一個非連線型(Connectionless)的非可靠傳輸協定。這邊先來介紹建立 TCP 協議的 Socket 連線之方式,以下直接透過程式碼片段,說明實作的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
import ...; public class Client extends Activity { private Thread thread; private Socket clientSocket;//客戶端的socket private BufferedWriter bw; //取得網路輸出串流 private BufferedReader br; //取得網路輸入串流 private String tmp; //做為接收時的緩存 private JSONObject jsonWrite, jsonRead; //從java伺服器傳遞與接收資料的json @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); thread=new Thread(Connection); thread.start(); } //連結socket伺服器做傳送與接收 private Runnable Connection=new Runnable(){ @Override public void run() { // TODO Auto-generated method stub try{ //輸入 Server 端的 IP InetAddress serverIp = InetAddress.getByName("10.0.2.2"); //自訂所使用的 Port(1024 ~ 65535) int serverPort = 5050; //建立連線 clientSocket = new Socket(serverIp, serverPort); //取得網路輸出串流 bw = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream())); //取得網路輸入串流 br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); //檢查是否已連線 while (clientSocket.isConnected()) { //宣告一個緩衝,從br串流讀取 Server 端傳來的訊息 tmp = br.readLine(); if(tmp!=null){ //將取到的String抓取{}範圍資料 tmp=tmp.substring(tmp.indexOf("{"), tmp.lastIndexOf("}") + 1); json_read=new JSONObject(tmp); //從java伺服器取得值後做拆解,可使用switch做不同動作的處理 } } }catch(Exception e){ //當斷線時會跳到 catch,可以在這裡處理斷開連線後的邏輯 e.printStackTrace(); Log.e("text","Socket連線="+e.toString()); finish(); //當斷線時自動關閉 Socket } } }; @Override protected void onDestroy() { super.onDestroy(); try { //傳送離線 Action 給 Server 端 jsonWrite = new JSONObject(); jsonWrite.put("action","離線"); //寫入 bw.write(jsonWrite + "\n"); //立即發送 bw.flush(); //關閉輸出入串流後,關閉Socket bw.close(); br.close(); clientSocket.close(); } catch (Exception e) { e.printStackTrace(); } } } |
Client 端
在 Client 端我們宣告了一個繼承 Socket 類別的物件,取名為 clientSocket,在其建構子宣告的當下就會嘗試對指定的 IP 與 Port 建立連線,接著我們用一個 while 迴圈持續檢查 clientSocket.isConnected() 的回傳值,以偵測是否已連線成功並持續的抓取資料,在這迴圈中我們是透過 BufferedReader 物件的 readLine() 方法抓取資料,之後也可以透過 BufferedWriter 物件的 write() 方法傳送資料,換句話說,只要連線是成功的,我們就可以透過 BufferedReader 搭配 InputStreamReader 讀取 Server 端傳來的資料,或者透過 BufferedWriter 搭配 OutputStreamWriter 將資料由 Cleint 端傳給 Server 端。
註:10.0.2.2 是運行 Android Studio 的手機模擬器時,訪問本地端(localhost)的網址,在程式開發時若 Server 端也在同一台電腦上,可以直接使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
import ...; public class Server { private static int serverport = 5050; //自訂的 Port private static ServerSocket serverSocket; //伺服端的Socket private static int count=0; //計算有幾個 Client 端連線 // 用 ArrayList 來儲存每個 Client 端連線 private static ArrayList clients = new ArrayList(); public static void main(String[] args) { try { serverSocket = new ServerSocket(serverport); System.out.println("Server is start."); // 顯示等待客戶端連接 System.out.println("Waiting for client connect"); // 當Server運作中時 while (!serverSocket.isClosed()) { // 呼叫等待接受客戶端連接 waitNewClient(); } } catch (IOException e) { System.out.println("Server Socket ERROR"); } } // 等待接受 Client 端連接 public static void waitNewClient() { try { Socket socket = serverSocket.accept(); ++count; System.out.println("現在使用者個數:"+count); // 呼叫加入新的 Client 端 addNewClient(socket); } catch (IOException e) {} } // 加入新的 Client 端 public static void addNewClient(final Socket socket) throws IOException { // 以新的執行緒來執行 Thread t = new Thread(new Runnable() { @Override public void run() { try { // 增加新的 Client 端 clients.add(socket); // 取得網路串流 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 當Socket已連接時連續執行 while (socket.isConnected()) { // 取得網路串流的訊息 String msg= br.readLine(); if(msg==null){ System.out.println("Client Disconnected!"); break; } //輸出訊息 System.out.println(msg); // 廣播訊息給其它的客戶端 castMsg(msg); } } catch (IOException e) { e.getStackTrace(); } finally{ // 移除客戶端 clients.remove(socket); --count; System.out.println("現在使用者個數:"+count); } } }); // 啟動執行緒 t.start(); } // 廣播訊息給其它的客戶端 public static void castMsg(String Msg){ // 創造socket陣列 Socket[] clientArrays =new Socket[clients.size()]; // 將 clients 轉換成陣列存入 clientArrays clients.toArray(clientArrays); // 走訪 clientArrays 中的每一個元素 for (Socket socket : clientArrays ) { try { // 創造網路輸出串流 BufferedWriter bw; bw = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())); // 寫入訊息到串流 bw.write(Msg+"\n"); // 立即發送 bw.flush(); } catch (IOException e) {} } } } |
Server 端
在 Server 端我們宣告了一個繼承 ServerSocket 類別的物件,取名為 serverSocket,在其建構子宣告的當下就會針對傳入的 Port 啟動 Server,我們接著利用一個 while 迴圈持續檢查 serverSocket.isClosed() 的回傳值是否為 false,當回傳值為 false 時,表示發現了一個 Client 連線的請求,接著調用serverSocket.accept() 來連接 Client 端,此 accept() 方法會回傳一個 Socket 物件,最後我們用一個 while 迴圈持續檢查 socket.isConnected() 的回傳值,以偵測是否已連線成功並持續的抓取由 Client 端傳來的資料。
由於 Server 端可能建立了不只一個的 Socket 連線,因此這邊我們還可以透過自訂的 castMsg() 方法,將資料傳給所有 Client 端。
臉書留言
一般留言