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