Socket:
有服务器和客户端之分, 其是对 TCP/IP 的封装, 使用 IP 地址加端口, 确定一个唯一的点. 在 Internet 上的主机一般运行了多个服务软件, 同时提供几种服务. 每种服务都打开一个 Socket, 并绑定到一个端口上, 不同的端口对应于不同的服务. 值得注意的是用户使用的端口最好大于 1024, 因为小于 1024 的大部分端口都是被系统占用的.
安卓的线程基本机制
一个程序就是一个进程, 一个进程里可以有多个线程, 每个进程必须有一个主线程. 对应安卓一个应用程序就是一个进程, 其主线程就是平常所说的安卓主 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 编程, 必须开启网络访问权限
<uses-permission Android:name="android.permission.INTERNET" />
编写 Handler 消息处理类:
handler 消息处理类是 MainActivity 类的内部类, 当消息队列不为空时将自动进入, 获取到消息值并分析其中内容
- private Handler mainhandler=new Handler(){
- @Override
- public void handleMessage(Message msg) {
- // 获取到命令, 进行命令分支
- int handi=msg.arg1;
- switch (handi){
- case 0:
- String ormsg=(String)msg.obj;
- disSocket();// 断开网络
- Toast.makeText(MainActivity.this,"发生错误 =>:"+ormsg,Toast.LENGTH_SHORT).show();
- break;
- case 1:
- Toast.makeText(MainActivity.this,"连接成功",Toast.LENGTH_SHORT).show();
- break;
- case 2:
- // 收到数据
- String str1=(String)msg.obj;
- main_rx.setText(str1);
- break;
- default:break;
- }
- }
- };
连接按钮监听:
当连接按钮按下时, 将会立即获取输入框的内容并进行字符串分隔, 得到 IP 地址和端口号, 开启线程进行网络连接
- // 连接按钮监听
- main_conn.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- String strip=main_ip.getText().toString().trim();
- if(strip.indexOf(":")>=0){
- // 开始启动连接线程
- new Socket_thread(strip).start();
- }
- }
- });
发送数据按钮监听:
当发送数据按钮按下时, 将会立即获取到发送输入框的内容, 分别可以调用字符串发送函数和十六进制发送函数进行数据发送
- // 发送按钮监听
- main_send.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // 得到输入框内容
- final String senddata=main_tx.getText().toString().trim();
- if(!senddata.equals("")){
- // 发送因为使用的是线程, 所以先后顺序不一定
- // 发送字符串数据
- sendStrSocket(senddata);
- // 发送十六进制数据
- sendByteSocket(new byte[]{0x01,0x02,0x03});
- }else Toast.makeText(MainActivity.this,"输入不可为空",Toast.LENGTH_SHORT).show();
- }
- });
开始网络连接线程:
该类为 MainActivity 类的内部类, 实现线程连接 socket 服务器, 并获取输入输出流, 并开启接收线程
- class Socket_thread extends Thread
- {
- private String IP="";//ip 地址
- private int PORT=0;// 端口号
- public Socket_thread(String strip){
- // 构造方法需要传递服务器的 IP 地址和端口号
- // 如: 192.168.43.222:8099
- // 进行字符串分隔, 得到服务器 IP 地址和端口号
- String[] stripx= strip.split(":");
- this.IP=stripx[0];
- this.PORT=Integer.parseInt(stripx[1]);
- }
- @Override
- public void run() {
- try {
- disSocket();// 断开上次连接
- if(sock !=null){
- outx.close();
- inx.close();
- sock.close();// 关闭
- sock=null;
- }
- // 开始连接服务器, 此处会一直处于阻塞, 直到连接成功
- sock=new Socket(this.IP,this.PORT);
- // 阻塞停止, 表示连接成功, 发送连接成功消息
- Message message=new Message();
- message.arg1=1;
- mainhandler.sendMessage(message);
- }catch (Exception e) {
- Message message=new Message();
- message.arg1=0;
- message.obj="连接服务器时异常";
- mainhandler.sendMessage(message);
- System.out.println("建立失败 ////////////////////////////////////////////");
- e.printStackTrace();
- return;
- }
- try {
- // 获取到输入输出流
- outx=sock.getOutputStream();
- inx=sock.getInputStream();
- } catch (Exception e) {
- // 发送连接失败异常
- Message message=new Message();
- message.arg1=0;
- message.obj="获取输入输出流异常";
- mainhandler.sendMessage(message);
- System.out.println("流获取失败 ////////////////////////////////////////////");
- e.printStackTrace();
- return;
- }
- // new Outx().start();
- new Inx().start();
- }
- }
关闭 socket 函数:
关闭 socket 之前将先关闭输入输出流, 这样才能更加安全的关闭 socket
- private void disSocket(){
- // 如果不为空, 则断开 socket
- if(sock !=null){
- try {
- outx.close();
- inx.close();
- sock.close();// 关闭
- sock = null;
- }catch (Exception e){
- // 发送连接失败异常
- Message message=new Message();
- message.arg1=0;
- message.obj="断开连接时发生错误";
- mainhandler.sendMessage(message);
- }
- }
- }
数据接收线程实现:
接收线程将实现数据的接收, 并把接收到的数据通过消息发送给处理类, 特别注意的是 inx.read(bu) 返回如果是 -1 则表示服务器断开了连接或者其它非主动调用关闭 socket 方法断开造成的错误
- // 循环接收数据
- class Inx extends Thread{
- @Override
- public void run() {
- while(true){
- byte[] bu=new byte[1024];
- try {
- // 得到 - 1 表示服务器断开
- int conut=inx.read(bu);// 设备重启, 异常 将会一直停留在这
- if(conut==-1){
- // 发送连接失败异常
- Message message=new Message();
- message.arg1=0;
- message.obj="服务器断开";
- mainhandler.sendMessage(message);
- disSocket();// 断开连接
- System.out.println("********** 服务器异常 *********:"+conut);
- return;
- }
- // 必须去掉前后空字符, 不然有这个会有 1024 个字符每次
- strread=new String(bu,"GBK").trim();
- // 发送出收到的数据
- Message message=new Message();
- message.arg1=2;
- message.obj=strread;
- mainhandler.sendMessage(message);
- } catch (IOException e) {
- System.out.println(e);
- }
- } }}
发送字符串函数:
网络编程的最终发送的内容是字节, 所以发送字符串需要通过 getBytes 进行编码
- // 发送字符串
- private void sendStrSocket(final String senddata){
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- // 可以经过编码发送字符串
- outx.write(senddata.getBytes("gbk"));//"utf-8"
- } catch (Exception e) {
- // 发送连接失败异常
- Message message=new Message();
- message.arg1=0;
- message.obj="数据发送异常";
- mainhandler.sendMessage(message);
- }
- }
- }).start();
- }
发送十六进制函数:
通过字节数组, 可以实现多个十六进制数据的发送
- // 发送十六进制
- private void sendByteSocket(final byte[] senddata){
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- // 发送十六进制
- outx.write(senddata);
- } catch (Exception e) {
- // 发送连接失败异常
- Message message=new Message();
- message.arg1=0;
- message.obj="数据发送异常";
- mainhandler.sendMessage(message);
- }
- }
- }).start();
- }
参考:
- https://blog.csdn.net/rabbit_in_android/article/details/50585156
- https://www.imooc.com/article/25134?block_id=tuijian_wz
来源: https://www.cnblogs.com/dongxiaodong/p/10280104.html