Android Complete Socket Solution

  • 2021-08-17 01:02:49
  • OfStack

Overall step flow

First, let's talk about the overall steps:

Send UDP broadcast, everyone knows that the characteristic of UDP broadcast is that the devices of the whole network segment can receive this message.
The receiver receives the broadcast of UDP and replies to the sender of UDP with his ip address and the port number agreed by both parties.
When the sender gets the ip address and port number of the other party, he can initiate TCP request and establish TCP connection.
Keep 1 TCP heartbeat. If you find that the other party is gone, repeat 1 step over time and re-establish contact.

The overall steps are just like the above 1, and the following is expanded with code:

Build UDP module


public UDPSocket(Context context) {
  this.mContext = context;
  int cpuNumbers = Runtime.getRuntime().availableProcessors();
  //  According to CPU Number initializes thread pool 
  mThreadPool = Executors.newFixedThreadPool(cpuNumbers * Config.POOL_SIZE);
  //  Record the time when the object was created 
  lastReceiveTime = System.currentTimeMillis();
  messageReceiveList = new ArrayList<>();
  Log.d(TAG, " Create  UDP  Object ");
//  createUser();
 }

First, do some initialization operations, prepare thread pool, record the initial time of objects, and so on.


public void startUDPSocket() {
  if (client != null) return;
  try {
   //  Indicates that this  Socket  Listen for data on the set port. 
   client = new DatagramSocket(CLIENT_PORT);
   client.setReuseAddress(true);
   if (receivePacket == null) {
    //  Object that accepts data  packet
    receivePacket = new DatagramPacket(receiveByte, BUFFER_LENGTH);
   }
   startSocketThread();
  } catch (SocketException e) {
   e.printStackTrace();
  }
 }

The real UDP Socket terminal, DatagramSocket, is then created, noting that the incoming port number CLIENT_PORT means that this DatagramSocket receives messages at this port number.


/**
  *  Open the thread that sends data 
  */
 private void startSocketThread() {
  clientThread = new Thread(new Runnable() {
   @Override
   public void run() {
    receiveMessage();
   }
  });
  isThreadRunning = true;
  clientThread.start();
  Log.d(TAG, " Open  UDP  Data receiving thread ");
  startHeartbeatTimer();
 }

We all know that Socket has to deal with the sending and receiving of data, and both sending and receiving are blocked and should be placed in sub-threads. Here, a thread is opened to deal with the received UDP message (UDP module is described in detail in one article, so it will not be expanded in detail here)


/**
  *  Processing received messages 
  */
 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 Packet reception failed! Thread stop ");
    stopUDPSocket();
    e.printStackTrace();
    return;
   }
   if (receivePacket == null || receivePacket.getLength() == 0) {
    Log.e(TAG, " Unable to receive UDP Data or received UDP Data is empty ");
    continue;
   }
   String strReceive = new String(receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength());
   Log.d(TAG, strReceive + " from " + receivePacket.getAddress().getHostAddress() + ":" + receivePacket.getPort());
   // Parse the received  json  Information 
   notifyMessageReceive(strReceive);
   //  After each reception, UDP Data, reset the length. Otherwise, the next received packet may be truncated. 
   if (receivePacket != null) {
    receivePacket.setLength(BUFFER_LENGTH);
   }
  }
 }

The UDP data is received on the child thread, and the notifyMessageReceive method notifies the message externally through the interface.


/**
  *  Send heartbeat packets 
  *
  * @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);
     //  Data sending event 
     Log.d(TAG, " Data sent successfully ");
    } catch (UnknownHostException e) {
     e.printStackTrace();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  });
 }

Then startHeartbeatTimer starts a heartbeat thread, and broadcasts an UDP message every 5 seconds. Note that getBroadcastAddress is the acquired network segment ip. When sending this UDP message, all devices in the whole network segment can receive it.

At this point, the UDP on the sending side is completed.

Build TCP module

Next, the TCP module should come out. The purpose of sending heartbeat broadcast by UDP is to find the ip address and agreed port of the corresponding device, so in the receiving method of UDP data:


/**
  *  Deal with  udp  Messages received 
  *
  * @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();
  }
 }

The purpose of this method is to get the UDP message sent to me by the other party's UDPServer end, and tell me its ip address and the port number we agreed in advance.

How do I get an ip for one device?


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);
 }

Now that I have got the other party's ip and the agreed port number, I can finally open an TCP client.


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  Successful creation ...");
   return true;
  } catch (Exception e) {
   e.printStackTrace();
  }
  return false;
 }

When the TCP client is successfully established, we can send and receive messages through TCP Socket.

Detail processing

The next step is to deal with some details, such as our UDP heartbeat. When TCP is successfully established, we have to stop UDP heartbeat:


if (startTcpConnection(ip, Integer.valueOf(port))) {//  Try to establish  TCP  Connect 
     if (mListener != null) {
      mListener.onSuccess();
     }
     startReceiveTcpThread();
     startHeartbeatTimer();
    } else {
     if (mListener != null) {
      mListener.onFailed(Config.ErrorCode.CREATE_TCP_ERROR);
     }
    }
   // TCP Connection has been successfully established, stop  UDP  Heartbeat bag. 
   public void stopHeartbeatTimer() {
    if (timer != null) {
     timer.exit();
     timer = null;
    }
 }

Heartbeat protection for TCP connections:


/**
  *  Start the heartbeat 
  */
 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) {// If it exceeds 105 If you don't receive my heartbeat package for seconds, you think the other party is not online. 
     Log.d(TAG, "tcp ping  Timeout, the other party has been offline ");
     stopTcpConnection();
     if (mListener != null) {
      mListener.onFailed(Config.ErrorCode.PING_TCP_TIMEOUT);
     }
    } else if (duration > HEARTBEAT_MESSAGE_DURATION) {// If he doesn't receive my heartbeat packet for more than two seconds, send it again 1 A. 
     JSONObject jsonObject = new JSONObject();
     try {
      jsonObject.put(Config.MSG, Config.PING);
     } catch (JSONException e) {
      e.printStackTrace();
     }
     sendTcpMessage(jsonObject.toString());
    }
   }
  });
  timer.startTimer(0, 1000 * 2);
 }

First of all, I will send an ping packet to the other party every two seconds to see if the opposite party is there. If I haven't been answered for more than 15 seconds, it means that the other party has dropped the line and shut down the TCP terminal on my side. Enter the onFailed method.


public void startUDPSocket() {
  if (client != null) return;
  try {
   //  Indicates that this  Socket  Listen for data on the set port. 
   client = new DatagramSocket(CLIENT_PORT);
   client.setReuseAddress(true);
   if (receivePacket == null) {
    //  Object that accepts data  packet
    receivePacket = new DatagramPacket(receiveByte, BUFFER_LENGTH);
   }
   startSocketThread();
  } catch (SocketException e) {
   e.printStackTrace();
  }
 }
0

When the TCP connection times out, I restart the broadcast heartbeat of UDP, looking for devices waiting to connect. Enter the next step loop.

For details such as data transmission format, this is related to business. Just decide for yourself.

You can also open different thread channels according to your business model, whether it is CPU intensive or IO intensive. This involves the knowledge of threads.

Source code sharing: https://github.com/itsMelo/AndroidSocket


Related articles: