安卓之必须了解的实时通信(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