Android完整Socket解决方案

整体步骤流程

先来说一下整体的步骤思路吧:

发送 UDP 广播,大家都知道 UDP 广播的特性是整个网段的设备都可以收到这个消息。
接收方收到了 UDP 的广播,将自己的 ip 地址,和双方约定的端口号,回复给 UDP 的发送方。
发送方拿到了对方的 ip 地址以及端口号,就可以发起 TCP 请求了,建立 TCP 连接。
保持一个 TCP 心跳,如果发现对方不在了,超时重复 1 步骤,重新建立联系。

整体的步骤就和上述的一样,下面用代码展开:

搭建 UDP 模块

public UDPSocket(Context context) {
  this.mContext = context;
  int cpuNumbers = Runtime.getRuntime().availableProcessors();
  // 根据CPU数目初始化线程池
  mThreadPool = Executors.newFixedThreadPool(cpuNumbers * Config.POOL_SIZE);
  // 记录创建对象时的时间
  lastReceiveTime = System.currentTimeMillis();
  messageReceiveList = new ArrayList<>();
  Log.d(TAG, "创建 UDP 对象");
//  createUser();
 }

首先进行一些初始化操作,准备线程池,记录对象初始的时间等等。

public void startUDPSocket() {
  if (client != null) return;
  try {
   // 表明这个 Socket 在设置的端口上监听数据。
   client = new DatagramSocket(CLIENT_PORT);
   client.setReuseAddress(true);
   if (receivePacket == null) {
    // 创建接受数据的 packet
    receivePacket = new DatagramPacket(receiveByte, BUFFER_LENGTH);
   }
   startSocketThread();
  } catch (SocketException e) {
   e.printStackTrace();
  }
 }

紧接着就创建了真正的一个 UDP Socket 端,DatagramSocket,注意这里传入的端口号 CLIENT_PORT 的意思是这个 DatagramSocket 在此端口号接收消息。

/**
  * 开启发送数据的线程
  */
 private void startSocketThread() {
  clientThread = new Thread(new Runnable() {
   @Override
   public void run() {
    receiveMessage();
   }
  });
  isThreadRunning = true;
  clientThread.start();
  Log.d(TAG, "开启 UDP 数据接收线程");
  startHeartbeatTimer();
 }

我们都知道 Socket 中要处理数据的发送和接收,并且发送和接收都是阻塞的,应该放在子线程中,这里就开启了一个线程,来处理接收到的 UDP 消息(UDP 模块上一篇文章讲得比较详细了,所以这里就不详细展开了)

/**
  * 处理接受到的消息
  */
 private void receiveMessage() {
  while (isThreadRunning) {
   try {
    if (client != null) {
     client.receive(receivePacket);
    }
    lastReceiveTime = System.currentTimeMillis();
    Log.d(TAG, "receive packet success...");
   } catch (IOException e) {
    Log.e(TAG, "UDP数据包接收失败!线程停止");
    stopUDPSocket();
    e.printStackTrace();
    return;
   }
   if (receivePacket == null || receivePacket.getLength() == 0) {
    Log.e(TAG, "无法接收UDP数据或者接收到的UDP数据为空");
    continue;
   }
   String strReceive = new String(receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength());
   Log.d(TAG, strReceive + " from " + receivePacket.getAddress().getHostAddress() + ":" + receivePacket.getPort());
   //解析接收到的 json 信息
   notifyMessageReceive(strReceive);
   // 每次接收完UDP数据后,重置长度。否则可能会导致下次收到数据包被截断。
   if (receivePacket != null) {
    receivePacket.setLength(BUFFER_LENGTH);
   }
  }
 }

在子线程接收 UDP 数据,并且 notifyMessageReceive 方法通过接口来向外通知消息。

/**
  * 发送心跳包
  *
  * @param message
  */
 public void sendMessage(final String message) {
  mThreadPool.execute(new Runnable() {
   @Override
   public void run() {
    try {
     BROADCAST_IP = WifiUtil.getBroadcastAddress();
     Log.d(TAG, "BROADCAST_IP:" + BROADCAST_IP);
     InetAddress targetAddress = InetAddress.getByName(BROADCAST_IP);
     DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), targetAddress, CLIENT_PORT);
     client.send(packet);
     // 数据发送事件
     Log.d(TAG, "数据发送成功");
    } catch (UnknownHostException e) {
     e.printStackTrace();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  });
 }

接着 startHeartbeatTimer 开启一个心跳线程,每间隔五秒,就去广播一个 UDP 消息。注意这里 getBroadcastAddress 是获取的网段 ip,发送这个 UDP 消息的时候,整个网段的所有设备都可以接收到。

到此为止,我们发送端的 UDP 算是搭建完成了。

搭建 TCP 模块

接下来 TCP 模块该出场了,UDP 发送心跳广播的目的就是找到对应设备的 ip 地址和约定好的端口,所以在 UDP 数据的接收方法里:

/**
  * 处理 udp 收到的消息
  *
  * @param message
  */
 private void handleUdpMessage(String message) {
  try {
   JSONObject jsonObject = new JSONObject(message);
   String ip = jsonObject.optString(Config.TCP_IP);
   String port = jsonObject.optString(Config.TCP_PORT);
   if (!TextUtils.isEmpty(ip) && !TextUtils.isEmpty(port)) {
    startTcpConnection(ip, port);
   }
  } catch (JSONException e) {
   e.printStackTrace();
  }
 }

这个方法的目的就是取到对方 UDPServer 端,发给我的 UDP 消息,将它的 ip 地址告诉了我,以及我们提前约定好的端口号。

怎么获得一个设备的 ip 呢?

public String getLocalIPAddress() {
  WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
  return intToIp(wifiInfo.getIpAddress());
 }
 private static String intToIp(int i) {
  return (i & 0xFF) + "." + ((i >> 8) & 0xFF) + "." + ((i >> 16) & 0xFF) + "."
    + ((i >> 24) & 0xFF);
 }

现在拿到了对方的 ip,以及约定好的端口号,终于可以开启一个 TCP 客户端了。

private boolean startTcpConnection(final String ip, final int port) {
  try {
   if (mSocket == null) {
    mSocket = new Socket(ip, port);
    mSocket.setKeepAlive(true);
    mSocket.setTcpNoDelay(true);
    mSocket.setReuseAddress(true);
   }
   InputStream is = mSocket.getInputStream();
   br = new BufferedReader(new InputStreamReader(is));
   OutputStream os = mSocket.getOutputStream();
   pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)), true);
   Log.d(TAG, "tcp 创建成功...");
   return true;
  } catch (Exception e) {
   e.printStackTrace();
  }
  return false;
 }

当 TCP 客户端成功建立的时候,我们就可以通过 TCP Socket 来发送和接收消息了。

细节处理

接下来就是一些细节处理了,比如我们的 UDP 心跳,当 TCP 建立成功之时,我们要停止 UDP 的心跳:

if (startTcpConnection(ip, Integer.valueOf(port))) {// 尝试建立 TCP 连接
     if (mListener != null) {
      mListener.onSuccess();
     }
     startReceiveTcpThread();
     startHeartbeatTimer();
    } else {
     if (mListener != null) {
      mListener.onFailed(Config.ErrorCode.CREATE_TCP_ERROR);
     }
    }
   // TCP已经成功建立连接,停止 UDP 的心跳包。
   public void stopHeartbeatTimer() {
    if (timer != null) {
     timer.exit();
     timer = null;
    }
 }

对 TCP 连接进行心跳保护:

/**
  * 启动心跳
  */
 private void startHeartbeatTimer() {
  if (timer == null) {
   timer = new HeartbeatTimer();
  }
  timer.setOnScheduleListener(new HeartbeatTimer.OnScheduleListener() {
   @Override
   public void onSchedule() {
    Log.d(TAG, "timer is onSchedule...");
    long duration = System.currentTimeMillis() - lastReceiveTime;
    Log.d(TAG, "duration:" + duration);
    if (duration > TIME_OUT) {//若超过十五秒都没收到我的心跳包,则认为对方不在线。
     Log.d(TAG, "tcp ping 超时,对方已经下线");
     stopTcpConnection();
     if (mListener != null) {
      mListener.onFailed(Config.ErrorCode.PING_TCP_TIMEOUT);
     }
    } else if (duration > HEARTBEAT_MESSAGE_DURATION) {//若超过两秒他没收到我的心跳包,则重新发一个。
     JSONObject jsonObject = new JSONObject();
     try {
      jsonObject.put(Config.MSG, Config.PING);
     } catch (JSONException e) {
      e.printStackTrace();
     }
     sendTcpMessage(jsonObject.toString());
    }
   }
  });
  timer.startTimer(0, 1000 * 2);
 }

首先会每隔两秒,就给对方发送一个 ping 包,看看对面在不在,如果超过 15 秒还没有回复我,那就说明对方掉线了,关闭我这边的 TCP 端。进入 onFailed 方法。

@Override
    public void onFailed(int errorCode) {// tcp 异常处理
     switch (errorCode) {
      case Config.ErrorCode.CREATE_TCP_ERROR:
       break;
      case Config.ErrorCode.PING_TCP_TIMEOUT:
       udpSocket.startHeartbeatTimer();
       tcpSocket = null;
       break;
     }
    }

当 TCP 连接超时,我就会重新启动 UDP 的广播心跳,寻找等待连接的设备。进入下一个步骤循环。

对于数据传输的格式啊等等细节,这个和业务相关。自己来定就好。

还可以根据自己业务的模式,是 CPU 密集型啊,还是 IO 密集型啊,来开启不同的线程通道。这个就涉及线程的知识了。

源码分享:https://github.com/itsMelo/AndroidSocket

您可能感兴趣的文章:

  • 详解Android 基于TCP和UDP协议的Socket通信
  • Android使用WebSocket实现多人游戏
  • 详解OkSocket与Android的简单使用
  • Android开发之Socket通信传输简单示例
  • android基于socket的局域网内服务器与客户端加密通信
  • android socket聊天室功能实现
  • SpringBoot webSocket实现发送广播、点对点消息和Android接收
  • Android中Socket大文件断点上传示例
  • android Socket实现简单聊天功能以及文件传输
  • 详解Android使用Socket对大文件进行加密传输
  • 详解Android 通过Socket 和服务器通讯(附demo)
  • Android Socket接口实现即时通讯实例代码
(0)

相关推荐

  • Android使用WebSocket实现多人游戏

    WebSocket 是 HTML5 一种新的协议.它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是: WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样: WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能

  • 详解OkSocket与Android的简单使用

    一个Android轻量级Socket通讯框架,既OkHttp后又一力作. 框架开源地址: https://github.com/xuuhaoo/OkSocket,欢迎star,fork,Issue交流 OkSocket简介 Android OkSocket是一款基于阻塞式传统Socket的一款Socket客户端整体解决方案.您可以使用它进行简单的基于Tcp协议的Socket通讯,当然,也可以进行大数据量复杂的Socket通讯, 支持单工,双工通讯. Maven配置 OkSocket 目前仅支持

  • 详解Android 通过Socket 和服务器通讯(附demo)

    Android 通过Socket 和服务器通讯,是一种比较常用的通讯方式,时间比较紧,说下大致的思路,希望能帮到使用socket 进行通信的人 (1)开启一个线程发送消息    SocketOutputThread 消息是放在队列里的,当有消息后,进入队列,线程唤醒,发送消息,并反馈发送是否成功的回调 (2)开启一个线程接受服务器消息 SocketInputThread 为了防止一直收数据,浪费电池的电,采用NIO的方式读socket的数据,这个是本文的关键 (3)开启一个线程,做心跳,防止so

  • 详解Android 基于TCP和UDP协议的Socket通信

    本来想讲一下基础的网络通信方面的知识点,发现太枯燥乏味了,不过笔试中也经常会问到这方面的问题,所以关于通信方面的知识点,小编会放到面试中去,因为实战中也就面试会用到这方面知识点 Android与服务器的通信方式主要有两种,一是Http通信,一是Socket通信.两者的最大差异在于,http连接使用的是"请求-响应方式",即在请求时建立连接通道,当客户端向服务器发送请求后,服务器端才能向客户端返回数据. 而Socket通信中基于TCP/IP协议的通信则是在双方建立起连接后就可以直接进行数

  • Android中Socket大文件断点上传示例

    什么是Socket?      所谓Socket通常也称作"套接字",用于描述IP地址和端口,是一个通信连的句柄,应用程序通常通过"套接字"向网络发送请求或者应答网络请求,它就是网络通信过程中端点的抽象表示.它主要包括以下两个协议: TCP (Transmission Control Protocol 传输控制协议):传输控制协议,提供的是面向连接.可靠的字节流服务.当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据.TCP提供超时重

  • Android开发之Socket通信传输简单示例

    本文实例讲述了Android Socket通信传输实现方法.分享给大家供大家参考,具体如下: 1.开篇简介 Socket本质上就是Java封装了传输层上的TCP协议(注:UDP用的是DatagramSocket类).要实现Socket的传输,需要构建客户端和服务器端.另外,传输的数据可以是字符串和字节.字符串传输主要用于简单的应用,比较复杂的应用(比如Java和C++进行通信),往往需要构建自己的应用层规则(类似于应用层协议),并用字节来传输. 2.基于字符串传输的Socket案例 1)服务器端

  • android Socket实现简单聊天功能以及文件传输

    干程序是一件枯燥重复的事,每当感到内心浮躁的时候,我就会找小说来看.我从小就喜爱看武侠小说,一直有着武侠梦.从金庸,古龙,梁羽生系列到凤歌(昆仑),孙晓(英雄志)以及萧鼎的(诛仙)让我领略着不一样的江湖. 如果你有好看的武侠系列小说,给我留言哦.题外话就扯这么多了,接着还是上技术. 看看今天实现的功能效果图: 可以这里使用多台手机进行通讯,我采用的服务器发送消息. 是不是只有发送消息,有些显得太单调了.好,在发送消息的基础上增加文件传输.后期会增加视频,音频的传输,增加表情包.那一起来看看图文消

  • Android Socket接口实现即时通讯实例代码

    Android Socket接口实现即时通讯 最近学习Android 通信的知识,做一个小实例,巩固下学习内容,以下内容是网上找的资料,觉得很不错,知识比较全面,大家看下. 首先了解一下即时通信的概念.通过消息通道 传输消息对象,一个账号发往另外一账号,只要账号在线,可以即时获取到消息,这就是最简单的即使通讯.消息通道可由TCP/IP UDP实现.通俗讲就是把一个人要发送给另外一个人的消息对象(文字,音视频,文件)通过消息通道(C/S实时通信)进行传输的服务.即时通讯应该包括四种形式,在线直传.

  • 详解Android使用Socket对大文件进行加密传输

    前言 数据加密,是一门历史悠久的技术,指通过加密算法和加密密钥将明文转变为密文,而解密则是通过解密算法和解密密钥将密文恢复为明文.它的核心是密码学. 数据加密目前仍是计算机系统对信息进行保护的一种最可靠的办法.它利用密码技术对信息进行加密,实现信息隐蔽从而起到保护信息的安全的作用. 项目中使用Socket进行文件传输过程时,需要先进行加密.实现的过程中踏了一些坑,下面对实现过程进行一下总结. DES加密 由于加密过程中使用的是DES加密算法,下面贴一下DES加密代码: //秘钥算法 privat

  • android基于socket的局域网内服务器与客户端加密通信

    实现了基本的socket通信(即两台设备,一台用作服务器,一台用作客户端),服务器进行监听,客户端发送加密数据到服务器,服务器进行解密得到明文. 注意:本项目中使用了ButterKnife及EventBus作为辅助工具,通信建立时默认网络正常(未做局域网网络环境检测),加密方式为AES加密 1.效果图: (1)客户端 (2)服务器端 2.界面布局部分 (1)服务器端布局 function_socket_server.xml <?xml version="1.0" encoding

  • SpringBoot webSocket实现发送广播、点对点消息和Android接收

    1.SpringBoot webSocket SpringBoot 使用的websocket 协议,不是标准的websocket协议,使用的是名称叫做STOMP的协议. 1.1 STOMP协议说明 STOMP,Streaming Text Orientated Message Protocol,是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议. 它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消

  • android socket聊天室功能实现

    前提概要 笔者很久之前其实就已经学习过了socket,当然也是用socket做过了聊天室,但是觉得此知识点比较一般,并无特别难的技术点,于是也并未深究. 然而近期一个项目中对socket的使用却让笔者感觉socket强大无比,可以实现诸多功能. 个人Socket体验 项目主要有关智能家居,需要实现多台手机同时对灯进行操作(开或者关),主要需要实现以下几点: 1.进入界面时获取所有灯的状态. 2.一台手机改变了灯的状态,其他的手机上可以有所显示. 3.硬件上改变了灯的状态(手动开关灯),所有手机上

随机推荐