On Android, what should a complete UDP communication module look like?

On Android, what should a complete UDP communication module look like?

Comparative Analysis of the Differences between TCP and UDP

In this article, the differences between the two are analyzed in terms of reliability, data transmission, applicable scenarios, etc. The purpose of this article is to introduce how to connect one phone to another phone via a hotspot on an Android device. In this scenario, what aspects should be considered in the complete UDP communication module, how should it be optimized, and how to avoid some pitfalls?

[[206556]]

Using UDP in Java

We all know that most Android applications are developed using Java. How do you use the UDP protocol in Java?

We didn't talk about Socket in the previous article. In fact, Socket can be understood as the encapsulation of TCP and UDP protocols at the program usage level, providing some APIs for programmers to call and develop. This is the most superficial meaning of Socket.

In Java, the classes related to UDP include DatagramSocket, DatagramPacket, etc. Their usage will not be introduced in detail here.

OK, assuming that everyone has a general understanding of their use, we can officially start the content of this article.

Initialize a UDPSocket

First create a class called UDPSocket.

  1. public UDPSocket(Context context) {
  2. this.mContext = context;
  3. int cpuNumbers = Runtime.getRuntime().availableProcessors();
  4. // Initialize the thread pool according to the number of CPUs
  5. mThreadPool = Executors.newFixedThreadPool(cpuNumbers * POOL_SIZE);
  6. //Record the time when the object was created
  7. lastReceiveTime = System.currentTimeMillis();
  8. }

In the construction method, we perform some initialization operations. Simply put, we create a thread pool and record the current time in milliseconds. As for what they are used for, see below:

  1. public void startUDPSocket() {
  2. if (client != null ) return ;
  3. try {
  4. // Indicates that this Socket listens for data on the set port.
  5. client = new DatagramSocket(CLIENT_PORT);
  6. if (receivePacket == null ) {
  7. // Create a packet to receive data
  8. receivePacket = new DatagramPacket(receiveByte, BUFFER_LENGTH);
  9. }
  10. startSocketThread();
  11. } catch (SocketException e) {
  12. e.printStackTrace();
  13. }
  14. }

Here we first create a DatagramSocket as a "client". In fact, UDP itself does not have the concept of client and server, but only the concept of sender and receiver. Let's temporarily regard the sender as a client.

When creating a DatagramSocket object, a port number is passed in. This port number can be defined within a range, indicating that this DatagramSocket listens for data on this port.

Then a DatagramPacket object is created as the receiving packet of the data.

***Call startSocketThread to start the thread that sends and receives data.

  1. /**
  2. * Start the thread to send data
  3. */
  4. private void startSocketThread() {
  5. clientThread = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. Log.d(TAG, "clientThread is running..." );
  9. receiveMessage();
  10. }
  11. });
  12. isThreadRunning = true ;
  13. clientThread.start();
  14. startHeartbeatTimer();
  15. }

First, the purpose of the clientThread thread is to call the DatagramSocket receive method. Since the receive method is blocked, it cannot be placed in the main thread, so a child thread is naturally started. receiveMessage is used to process the received UDP datagram. We will not look at this method for receiving data first, after all, no one has sent a message yet, so there is naturally no question of receiving it.

Heartbeat packets maintain a "long connection"

Coming to the first point of this article, we all know that UDP itself has no concept of connection. The scenario of applying UDP and TCP on the Android side is that one mobile phone connects to the hotspot of another mobile phone, and the two are in the same local area network. When the two do not know the existence of each other, how can they find each other?

Through the heartbeat packet method, both parties send a UDP packet at regular intervals. If the other party receives it, they can know the other party's IP and establish communication.

  1. private static final long TIME_OUT = 120 * 1000;
  2. private static final long HEARTBEAT_MESSAGE_DURATION = 10 * 1000;
  3. /**
  4. * Start heartbeat, timer interval is ten seconds
  5. */
  6. private void startHeartbeatTimer() {
  7. timer = new HeartbeatTimer();
  8. timer.setOnScheduleListener(new HeartbeatTimer.OnScheduleListener() {
  9. @Override
  10. public void onSchedule() {
  11. Log.d(TAG, "timer is onSchedule..." );
  12. long duration = System.currentTimeMillis() - lastReceiveTime;
  13. Log.d(TAG, "duration:" + duration);
  14. if (duration > TIME_OUT) {//If I don't receive my heartbeat packet for more than two minutes, I will assume that the other party is not online.
  15. Log.d(TAG, "Timeout, the other party has gone offline" );
  16. // Refresh time and re-enter the next heartbeat cycle
  17. lastReceiveTime = System.currentTimeMillis();
  18. } else if (duration > HEARTBEAT_MESSAGE_DURATION) {//If he doesn't receive my heartbeat packet within ten seconds, send another one.
  19. String string = "hello, this is a heartbeat message" ;
  20. sendMessage(string);
  21. }
  22. }
  23. });
  24. timer.startTimer(0, 1000 * 10);
  25. }

The purpose of this heartbeat is to send a message via sendMessage every ten seconds to see if the other party can receive it. If the other party receives the message, the lastReceiveTime time is refreshed.

Here I send a string to the other party every ten seconds.

  1. private static final String BROADCAST_IP = "192.168.43.255" ;
  2. /**
  3. * Send heartbeat packet
  4. *
  5. * @param message
  6. */
  7. public void sendMessage(final String message) {
  8. mThreadPool. execute (new Runnable() {
  9. @Override
  10. public void run() {
  11. try {
  12. InetAddress targetAddress = InetAddress.getByName(BROADCAST_IP);
  13. DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), targetAddress, CLIENT_PORT);
  14. client.send(packet);
  15. Log.d(TAG, "Data sent successfully" );
  16. } catch (UnknownHostException e) {
  17. e.printStackTrace();
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. });
  23. }

Here is the code to send a message. When I first filled in the parameters of DatagramPacket, I had a question. The targetAddress is actually my own IP address. The question is, if I fill in my own IP address and the other party's port, how can I find the other party? You may wonder where the "192.168.43.255" IP address comes from and why it is defined like this?

First, turn on the hotspot on your Android phone, which can be understood as a gateway with a default IP address: "192.168.43.1"

This IP address is not made up by me. It is defined in the Android source code as follows:

WifiStateMachine

  1. ifcg = mNwService.getInterfaceConfig(intf);
  2. if (ifcg != null ) {
  3. /* IP/netmask: 192.168.43.1/255.255.255.0 */
  4. ifcg.setLinkAddress(new LinkAddress(
  5. NetworkUtils.numericToInetAddress( "192.168.43.1" ), 24));
  6. ifcg.setInterfaceUp();
  7. mNwService.setInterfaceConfig(intf, ifcg);
  8. }

So I know the IP address of the party that opens the hotspot, and UDP has another feature when sending messages, that is, the messages sent out can be received by all devices in the entire gateway, so my own IP address is set to "192.168.43.255", so this IP address and "192.168.43.1" are in the same gateway, and the messages you send can be received by it.

As for how to determine whether two IP addresses are in the same network segment:

Determine the size of two IPs and whether they are in the same network segment

Let’s make a stage summary:

First, we created a sending DatagramSocket and started a heartbeat program to send a heartbeat packet at a certain interval.

Because I know that the IP address of the hotspot is the default "192.168.43.1", and the characteristic of UDP is that all devices in the same network segment can receive the sent message. So the sender's IP address is set to "192.168.43.255" in the same network segment as the hotspot.

Events and data

The two modules, event and data, are closely related to the business.

Let's talk about data first. The data format sent by both parties can be defined at will. Of course, I think it is better to define it in the conventional Json format. It can include some key event fields: such as broadcast heartbeat packets, response packets to the other party when receiving heartbeat packets, timeout offline packets, and various business-related data, etc.

Of course, the data is converted into a binary array when it is sent. There is no problem sending Chinese characters, pictures, etc., but there may be some details that need attention. Just google it at any time.

Let's talk about the incident:

What are the non-business related events?

for example:

  • The DatagramSocket.send method is followed by an event where data is sent successfully;
  • The DatagramSocket.receive method is followed by an event indicating successful data reception;
  • If there is no reply after sending the heartbeat packet for a period of time, it is a connection timeout event;
  • Business-related events are related to the data types mentioned above, such as device online, heartbeat packet response, etc.

How are events sent out and notified to each page? You can use Listener or other third-party libraries of event bus, it’s up to you to choose.

Processing received messages

  1. /**
  2. * Process the received message
  3. */
  4. private void receiveMessage() {
  5. while (isThreadRunning) {
  6. try {
  7. if (client != null ) {
  8. client.receive(receivePacket);
  9. }
  10. lastReceiveTime = System.currentTimeMillis();
  11. Log.d(TAG, "receive packet success..." );
  12. } catch (IOException e) {
  13. Log.e(TAG, "UDP data packet reception failed! Thread stopped" );
  14. stopUDPSocket();
  15. e.printStackTrace();
  16. return ;
  17. }
  18. if (receivePacket == null || receivePacket.getLength() == 0) {
  19. Log.e(TAG, "Unable to receive UDP data or the received UDP data is empty" );
  20. continue ;
  21. }
  22. String strReceive = new String(receivePacket.getData(), 0, receivePacket.getLength());
  23. Log.d(TAG, strReceive + " from " + receivePacket.getAddress().getHostAddress() + ":" + receivePacket.getPort());
  24. //Parse the received json information
  25. // After receiving UDP data each time, reset the length. Otherwise, the next received data packet may be truncated.
  26. if (receivePacket != null ) {
  27. receivePacket.setLength(BUFFER_LENGTH);
  28. }
  29. }
  30. }

There are several points worth noting when processing received messages:

  • The receive method is blocking, and will remain blocked until a data packet is received, so it must be placed in a child thread;
  • After each message is received, call receivePacket.setLength again;
  • After receiving the message, refresh the value of lastReceiveTime and suspend sending heartbeat packets;
  • The specific business of processing received data is the issue of sending data we just talked about, which depends on the business.

The concept of "user"

We have discussed the characteristics of UDP above. If a mobile phone has turned on the hotspot and multiple mobile phones are connected to it, it can receive messages sent by multiple mobile phones. If the sender's port is the same as the receiver's port, even the message you sent yourself can be received by yourself. This is very embarrassing, that is, we have to exclude messages sent to ourselves and distinguish messages sent from different mobile phones. At this time, there should be a concept of "user".

Create a User object and check which attributes you can use to view your business. The examples in this article include ip, imei, and softversion.

  1. /**
  2. * Create local user information
  3. */
  4. private void createUser() {
  5. if (localUser == null ) {
  6. localUser = new Users();
  7. }
  8. if (remoteUser == null ) {
  9. remoteUser = new Users();
  10. }
  11. localUser.setImei(DeviceUtil.getDeviceId(mContext));
  12. localUser.setSoftVersion(DeviceUtil.getPackageVersionCode(mContext));
  13. if (WifiUtil.getInstance(mContext).isWifiApEnabled()) {// Determine whether the hotspot is currently enabled
  14. localUser.setIp( "192.168.43.1" );
  15. } else {// Currently, wifi is turned on
  16. localUser.setIp(WifiUtil.getInstance(mContext).getLocalIPAddress());
  17. remoteUser.setIp(WifiUtil.getInstance(mContext).getServerIPAddress());
  18. }
  19. }
  20. /**
  21. * <p><b>IMEI.</b></p> Returns the unique device ID, for example, the IMEI for GSM and the MEID
  22. * or ESN for CDMA phones. Return   null if device ID is   not available.
  23. * <p>
  24. * Requires Permission: READ_PHONE_STATE
  25. *
  26. * @param context
  27. * @return  
  28. */
  29. public synchronized static String getDeviceId(Context context) {
  30. if (context == null ) {
  31. return   "" ;
  32. }
  33. String imei = "" ;
  34. try {
  35. TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
  36. if (tm == null || TextUtils.isEmpty(tm.getDeviceId())) {
  37. // Dual SIM dual standby needs to obtain IMEI through phone1 and phone2, and the IMEI of phone1 is taken by default.
  38. tm = (TelephonyManager) context.getSystemService( "phone1" );
  39. }
  40. if (tm != null ) {
  41. imei = tm.getDeviceId();
  42. }
  43. } catch (SecurityException e) {
  44. e.printStackTrace();
  45. }
  46. return imei;
  47. }</p>

I won't expand all the codes here. If you have the IMEI number of the mobile phone, it is easy to distinguish the identity. You can distinguish different senders and remove the messages sent to yourself. Of course, if more information is needed, you can distinguish it according to your own business and send it as a messge through Socket.

Written at the end:

Up to now, most of the contents of this article have been introduced. Some students may ask, you need to use a heartbeat to maintain a fake "long connection", which is troublesome to use, and you may also suffer from the pain of packet loss caused by UDP, why not choose TCP? Good question, in fact, this version was the first version made at that time, and then the TCP+UDP method was used to complete this module. Let's take a look at the improved version with TCP in the next article.

<<:  Tips for using Erdua, a mobile web debugging tool

>>:  Introduce Gradle dependencies in Android projects like npm

Recommend

Electric car time-sharing rental: a tough piece of cake

Electric car time-sharing rental (also known as &...

Where are the future opportunities in China's social field?

Recently, advertisements for a social APP have be...

Are there bacteria that "eat paper" in nature?

When I saw a netizen asking "Are there bacte...

Analysis of Toutiao’s recommendation mechanism!

Preface If there is any company in China's cu...

3 elements for beginners of APP operation and promotion strategy!

Operation and promotion require the most user res...

Planning and promotion: an advanced guide to planning! (recommended collection)

Most planners have heard of theories such as USP,...

Is it true that the “super collider” is about to be built in Qinhuangdao?

The Large Hadron Collider (LHC) in Geneva is the ...