安卓之必須了解的實時通信(Socket)


Socket:

有服務器和客戶端之分,其是對TCP/IP的封裝,使用IP地址加端口,確定一個唯一的點。在Internet上的主機一般運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,並綁定到一個端口上,不同的端口對應於不同的服務。值得注意的是用戶使用的端口最好大於1024,因為小於1024的大部分端口都是被系統占用的。此章將實現安卓socket客戶端編程。

 

安卓的線程基本機制

一個程序就是一個進程,一個進程里可以有多個線程,每個進程必須有一個主線程。對應安卓一個應用程序就是一個進程,其主線程就是平常所說的安卓主UI線程。安卓實現多線程編程,其有一個重要的原則就是更新UI必須在主線程,但耗時操作必須在子線程中,如果耗時操作在主線程編寫(如網絡訪問)當阻塞時間達到一定時,應用就會強制退出,那網絡訪問就面臨着一個不可避免的問題:子線程更新UI操作如何實現。

 

Handler

Handler主要用於異步消息的處理: 有點類似輔助類,封裝了消息投遞、消息處理等接口。當發出一個消息之后,首先進入一個消息隊列,發送消息的函數即刻返回,而另外一個部分在消息隊列中逐一將消息取出,然后對消息進行處理,也就是發送消息和接收消息不是同步的處理。 這種機制通常用來處理相對耗時比較長的操作。

 

Message

Handler接收與處理的消息對象,其中消息類型有

public int arg1和public int arg2:存放簡單的整數類型消息

public Object obj:發送給接收器的任意對象,不管是整數,字符串,某個類對象均可

public int what:用戶自定義的消息代碼,這樣接受者可以了解這個消息的信息,每個handler各自包含自己的消息代碼,所以不用擔心自定義的消息跟其他handlers有沖突。

 

 安卓端實現效果

在同一網絡下的一個設備開啟一個端口的監聽,做為socket服務器,並獲取到服務器設備的IP地址和端口號,將其格式化為 “IP:端口” 進行輸入,如 “193.169.44.198:8081” ,點擊連接即可。安卓作為socket客戶端與服務器交互數據。

 

編程實現

獲取網絡訪問權限:

實現socket編程,必須開啟網絡訪問權限

<uses-permission android:name="android.permission.INTERNET" />

 

編寫Handler消息處理類:

handler消息處理類是MainActivity類的內部類,當消息隊列不為空時將自動進入,獲取到消息值並分析其中內容

 1 private Handler mainhandler=new Handler(){
 2     @Override
 3     public void handleMessage(Message msg) {
 4         //獲取到命令,進行命令分支
 5         int handi=msg.arg1;
 6         switch (handi){
 7             case 0:
 8                 String ormsg=(String)msg.obj;
 9                 disSocket();//斷開網絡
10                 Toast.makeText(MainActivity.this,"發生錯誤=>:"+ormsg,Toast.LENGTH_SHORT).show();
11                 break;
12             case 1:
13                 Toast.makeText(MainActivity.this,"連接成功",Toast.LENGTH_SHORT).show();
14                 break;
15             case 2:
16                 //收到數據
17                 String str1=(String)msg.obj;
18                 main_rx.setText(str1);
19                 break;
20             default:break;
21         }
22     }
23 };

 

連接按鈕監聽:

當連接按鈕按下時,將會立即獲取輸入框的內容並進行字符串分隔,得到IP地址和端口號,開啟線程進行網絡連接 

 1 //連接按鈕監聽
 2 main_conn.setOnClickListener(new View.OnClickListener() {
 3       @Override
 4       public void onClick(View v) {
 5           String strip=main_ip.getText().toString().trim();
 6           if(strip.indexOf(":")>=0){
 7              
 8               //開始啟動連接線程
 9               new Socket_thread(strip).start();
10 
11           }
12 
13       }
14 });

 

發送數據按鈕監聽:

當發送數據按鈕按下時,將會立即獲取到發送輸入框的內容,分別可以調用字符串發送函數和十六進制發送函數進行數據發送

 1 //發送按鈕監聽
 2 main_send.setOnClickListener(new View.OnClickListener() {
 3  @Override
 4  public void onClick(View v) {
 5    //得到輸入框內容
 6    final String senddata=main_tx.getText().toString().trim();
 7 
 8         if(!senddata.equals("")){
 9             //發送因為使用的是線程,所以先后順序不一定
10             //發送字符串數據
11             sendStrSocket(senddata);
12             //發送十六進制數據
13             sendByteSocket(new byte[]{0x01,0x02,0x03});
14 
15            }else Toast.makeText(MainActivity.this,"輸入不可為空",Toast.LENGTH_SHORT).show();
16             }
17 });

 

開始網絡連接線程:

該類為MainActivity類的內部類,實現線程連接socket服務器,並獲取輸入輸出流,並開啟接收線程

 1 class Socket_thread extends Thread
 2 {
 3     private String IP="";//ip地址
 4     private int PORT=0;//端口號
 5     public Socket_thread(String strip){
 6         //構造方法需要傳遞服務器的IP地址和端口號
 7         //如: 192.168.43.222:8099
 8         //進行字符串分隔,得到服務器IP地址和端口號
 9         String[] stripx= strip.split(":");
10         this.IP=stripx[0];
11         this.PORT=Integer.parseInt(stripx[1]);
12     }
13     @Override
14     public void run() {
15         try {
16 
17             disSocket();//斷開上次連接
18             if(sock !=null){
19                 outx.close();
20                 inx.close();
21                 sock.close();//關閉
22                 sock=null;
23             }
24             //開始連接服務器,此處會一直處於阻塞,直到連接成功
25             sock=new Socket(this.IP,this.PORT);
26 
27             //阻塞停止,表示連接成功,發送連接成功消息
28             Message message=new Message();
29             message.arg1=1;
30             mainhandler.sendMessage(message);
31 
32          }catch (Exception e) {
33             Message message=new Message();
34             message.arg1=0;
35             message.obj="連接服務器時異常";
36             mainhandler.sendMessage(message);
37 
38             System.out.println("建立失敗////////////////////////////////////////////");
39             e.printStackTrace();
40             return;
41         }
42         try {
43             //獲取到輸入輸出流
44             outx=sock.getOutputStream();
45             inx=sock.getInputStream();
46         } catch (Exception e) {
47             //發送連接失敗異常
48             Message message=new Message();
49             message.arg1=0;
50             message.obj="獲取輸入輸出流異常";
51             mainhandler.sendMessage(message);
52 
53             System.out.println("流獲取失敗////////////////////////////////////////////");
54             e.printStackTrace();
55             return;
56         }
57 
58        // new Outx().start();
59         new Inx().start();
60     }
61 }

 

關閉socket函數:

關閉socket之前將先關閉輸入輸出流,這樣才能更加安全的關閉socket 

 1 private void disSocket(){
 2     //如果不為空,則斷開socket
 3     if(sock !=null){
 4         try {
 5             outx.close();
 6             inx.close();
 7             sock.close();//關閉
 8             sock = null;
 9         }catch (Exception e){
10             //發送連接失敗異常
11             Message message=new Message();
12             message.arg1=0;
13             message.obj="斷開連接時發生錯誤";
14             mainhandler.sendMessage(message);
15 
16         }
17     }
18 
19 }

  

數據接收線程實現: 

接收線程將實現數據的接收,並把接收到的數據通過消息發送給處理類,特別注意的是 inx.read(bu) 返回如果是 -1 則表示服務器斷開了連接或者其它非主動調用關閉socket方法斷開造成的錯誤

 1 //循環接收數據
 2 class Inx extends Thread{
 3         @Override
 4         public void run() {
 5         while(true){
 6 
 7         byte[] bu=new byte[1024];
 8         try {
 9            //得到-1表示服務器斷開
10           int conut=inx.read(bu);//設備重啟,異常 將會一直停留在這
11             if(conut==-1){
12                 //發送連接失敗異常
13                 Message message=new Message();
14                 message.arg1=0;
15                 message.obj="服務器斷開";
16                 mainhandler.sendMessage(message);
17                 disSocket();//斷開連接
18                 System.out.println("**********服務器異常*********:"+conut);
19                 return;
20             }
21 
22           //必須去掉前后空字符,不然有這個會有1024個字符每次
23           strread=new String(bu,"GBK").trim();
24           //發送出收到的數據
25           Message message=new Message();
26           message.arg1=2;
27           message.obj=strread;
28           mainhandler.sendMessage(message);
29 
30           } catch (IOException e) {
31               System.out.println(e);
32 
33            }
34  } }}

 

發送字符串函數: 

網絡編程的最終發送的內容是字節,所以發送字符串需要通過getBytes進行編碼

 1 //發送字符串
 2 private void sendStrSocket(final String senddata){
 3     new Thread(new Runnable() {
 4     @Override
 5     public void run() {
 6       try {
 7          //可以經過編碼發送字符串
 8          outx.write(senddata.getBytes("gbk"));//"utf-8"
 9 
10       } catch (Exception e) {
11          //發送連接失敗異常
12          Message message=new Message();
13          message.arg1=0;
14          message.obj="數據發送異常";
15          mainhandler.sendMessage(message);
16         }
17         }
18    }).start();
19 }

 

發送十六進制函數:

通過字節數組,可以實現多個十六進制數據的發送

 1 //發送十六進制
 2  private void sendByteSocket(final byte[] senddata){
 3     new Thread(new Runnable() {
 4       @Override
 5       public void run() {
 6        try {
 7             //發送十六進制
 8             outx.write(senddata);
 9 
10         } catch (Exception e) {
11             //發送連接失敗異常
12              Message message=new Message();
13              message.arg1=0;
14              message.obj="數據發送異常";
15              mainhandler.sendMessage(message);
16            }
17       }
18     }).start();
19 }

  


 參考:

https://blog.csdn.net/rabbit_in_android/article/details/50585156

https://www.imooc.com/article/25134?block_id=tuijian_wz


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM