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