close
Android Socket範例
Socket介紹
有爬過一些文,了解當前的網路傳輸除了上一章的HTTP還有Socket的方式,而Socket可以分兩種
一種是連接的TCP應用服務,另一種是無連接的UDP應用服務
比較容易理解的說法是:TCP為打電話的方式(連接性)、UDP為發簡訊的方式(無連接性)
上一章的HTTP使用的是請求回應方式,表示當APP發出請求時建立連結通道,當客戶端向伺服器發送完請求,伺服器端才能向客戶端返回資料
而Socket的TCP/IP連線方式是雙方建立連結後可以直接進行資料的雙向傳輸,就不需要每次由客戶端向伺服器發送請求
而UDP連線方式提供無連接的資料傳輸,UDP在發送資料前不需建立連結,不對資料傳輸檢查,即可發送數據包
TCP協議的Socket連接:
伺服器端:
1.實作一個ServerSocket物件並在Server端開個端口
2.調用ServerSocket的accept()連接客戶端(在沒有客戶端連進來時,該方法會讓執行緒阻塞)
3.當客戶端連進來時可以透過InputStream、outputStream做輸出入
客戶端:
1.實作一個Socket物件並指定Server端IP與端口
2.當連上伺服器後可以透過InputStream、outputStream做輸出入
UDP協議的資料傳輸:
伺服器端:
1.實作一個DatagramSocket物件並在Server端開個端口
2.創建一個空的DatagramPacket數據包物件並指定大小,用來接收數據
3.使用DatagramSocket的receive()接收客戶端發送的數據(與TCP的調用ServerSocket的accept()類似,在沒有客戶端連進來時,該方法會讓執行緒阻塞)
客戶端:
1.客戶端也創建一個DatagramSocket物件並且開個端口
2.創建一個InetAddress物件設定要傳送到的Server端的IP地址
3.定義好要發送的資料,創建一個DatagramPacke數據包,並設定該數據包發送到哪個網路地址以及端口
4.最後使用DatagramSocket的send()發送數據包
Socket範例:
以下範例為app作為Client端,而主機用java作為Server端,傳輸的資料就此省略,依使用者需求再自行修改
使用的是TCP協議的Socket連接,本人只有在小作品上用過TCP,而UDP協議的資料傳輸等以後有使用再回來補上
上一章有提到過Android 4.0 之後,有明文規定所有的網路行為都不能在主執行緒(Main Thread)執行,主執行緒又稱UI執行緒(UI Thread),如果要在主執行緒做網路的事,就需要使用Thread-執行緒或是AsyncTask-異步任務
任何有關UI的東西都在主執行緒中執行,若是你的程式佔據主執行緒很久,使用者體驗會非常的差
Log.d("text","thread id="+String.valueOf(Thread.currentThread().getId()).toString); //使用該LOG可以知道當下是使用哪個執行緒,1為主執行緒
app-Client端:
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.Socket; import org.json.JSONObject; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; public class Client_Socket extends Activity { private Thread thread; //執行緒 private Socket clientSocket; //客戶端的socket private BufferedWriter bw; //取得網路輸出串流 private BufferedReader br; //取得網路輸入串流 private String tmp; //做為接收時的緩存 private JSONObject json_write,json_read; //從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{ // IP為Server端 InetAddress serverIp = InetAddress.getByName("192.168.0.1"); 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()) { // 取得網路訊息 tmp = br.readLine(); //宣告一個緩衝,從br串流讀取值 // 如果不是空訊息 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(); //當斷線時自動關閉房間 } } }; @Override protected void onDestroy() { //當銷毀該app時 super.onDestroy(); try { json_write=new JSONObject(); json_write.put("action","離線"); //傳送離線動作給伺服器 Log.i("text","onDestroy()="+json_write+"\n"); //寫入後送出 bw.write(json_write+"\n"); bw.flush(); //關閉輸出入串流後,關閉Socket //最近在小作品有發現close()這3個時,導致while (clientSocket.isConnected())這個迴圈內的區域錯誤 //會跳出java.net.SocketException:Socket is closed錯誤,讓catch內的處理再重複執行,如有同樣問題的可以將下面這3行註解掉 bw.close(); br.close(); clientSocket.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); Log.e("text","onDestroy()="+e.toString()); } } }
以上範例只在onDestroy()做輸出,可自行在控制項做監聽器,輸出資料至java伺服器一樣使用JSON格式
而上面的IP只是範例,到時候更改為Server端的IP
java-Server端:
package Server_Socket; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import org.json.JSONObject; public class Server_Socket { private static Thread th_close; //執行緒 private static int serverport = 5050; private static ServerSocket serverSocket; //伺服端的Socket private static ArrayList<Socket> socketlist=new ArrayList<Socket>(); // 程式進入點 public static void main(String[] args){ try { serverSocket = new ServerSocket(serverport); //啟動Server開啟Port接口 System.out.println("Server開始執行"); th_close=new Thread(Judge_Close); //賦予執行緒工作(判斷socketlist內有沒有客戶端網路斷線) th_close.start(); //讓執行緒開始執行 // 當Server運作中時 while (!serverSocket.isClosed()) { // 呼叫等待接受客戶端連接 waitNewPlayer(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } private static Runnable Judge_Close=new Runnable(){ //讓執行緒每兩秒判斷一次SocketList內是否有客戶端強制斷線 @Override public void run() { //在此抓取的是關閉wifi等斷線動作 // TODO Auto-generated method stub try { while(true){ Thread.sleep(2000); //每兩秒執行一輪 for(Socket close:socketlist){ if(isServerClose(close)) //當該客戶端網路斷線時,從SocketList剔除 socketlist.remove(close); } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; private static Boolean isServerClose(Socket socket){ //判斷連線是否中斷 try{ socket.sendUrgentData(0); //發送一個字節的緊急數據,默認情況下是沒有開啟緊急數據處理,不影響正常連線 return false; //如正常則回傳false }catch(Exception e){ return true; //如連線中斷則回傳true } } // 等待接受客戶端連接 public static void waitNewSocket() { try { Socket socket = serverSocket.accept(); // 呼叫創造新的使用者 createNewThread(socket); } catch (Exception e) { e.printStackTrace(); } } // 創造新的使用者 public static void createNewThread(final Socket socket) { // 以新的執行緒來執行 Thread t = new Thread(new Runnable() { @Override public void run() { try { // 增加新的使用者 socketlist.add(socket); //取得網路輸出串流 BufferedWriter bw = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())); // 取得網路輸入串流 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String tmp; JSONObject json_read,json_write; // 當Socket已連接時連續執行 while (socket.isConnected()) { tmp = br.readLine(); //宣告一個緩衝,從br串流讀取值 // 如果不是空訊息 if(tmp!=null){ //將取到的String抓取{}範圍資料 tmp=tmp.substring(tmp.indexOf("{"), tmp.lastIndexOf("}") + 1); json_read=new JSONObject(tmp); //從客戶端取得值後做拆解,可使用switch做不同動作的處理與回應 }else{ //在此抓取的是使用使用強制關閉app的客戶端(會不斷傳null給server) //當socket強制關閉app時移除客戶端 socketlist.remove(socket); break; //跳出迴圈結束該執行緒 } } } catch (Exception e) { e.printStackTrace(); } } }); // 啟動執行緒 t.start(); } }
在serverSocket連線後,用serverSocket.isClosed()判斷ServerSocket是否關閉,沒關閉則等待客戶端連進來
連線進來的每個Socket都存進ArrayList,也可寫個User物件存進Socket而ArrayList存進User
在Judge_Close是判斷客戶端有無使用直接關閉網路或強制斷線的手段,抓取到有客戶端斷線時從列表移除
waitNewSocket()是在接收到客戶端時呼叫createNewThread()創建一個新的執行緒各別處理Socket
在 while (socket.isConnected()) 內可以使用switch將讀取到的資料依動作各別讀取與回傳資料
在Server端開port可能會被防火牆擋下,如發現無法連上可能需要關閉防火牆或防毒
最後記住在AndroidManifest.xml文件添加網路權限
<uses-permission android:name="android.permission.INTERNET" />
=============================================================================================
補充範例中的isConnected()、.isClosed()這兩種方法其實都不太準確
isConnected()只是判斷第一次連接時是否有連上,連上了以後就永遠是true值,就算socket斷開他還是true
isClosed()是判斷使用者是否有對socket呼叫close()方法而已,並不是真的判斷socket是否斷開
這兩個方法取得的值都只是設定好的狀態,其實都不能真正的判斷socket是否斷線,在server端可以向範例這樣設置sendUrgentData(0)來判斷client的網路是否有關閉,下面還有判斷client是否有傳null值代表該client的app被強制關閉,也可以手動寫個按下上一頁關閉client app時傳送指定的動作通知server端可以關閉對client的連結,而client端可以將while (clientSocket.isConnected())寫在try-catch區域內,當處理socket輸出入發生錯誤斷開時就能跳出while執行catch處理
文章標籤
全站熱搜
留言列表