Socket network programming learning notes UDP assisted TCP point-to-point transmission case -- UDP broadcast, search and obtain IP/Port

1. UDP search IP and port

Implementation principle: in a LAN, you don't know the IP address of the server, but only the UDP Port common to the server. In this case, you want to realize the TCP connection. TCP is a point-to-point connection, so you need to know the connection IP address and Port of TCP.

How to get the IP and Port of TCP? It can be realized through UDP search. After the search format is agreed between our server and all our clients, we can initiate broadcasting at the client, and then the server will judge whether these received broadcasts need to be processed after receiving the broadcasting. Then the server will send back these broadcasts to the corresponding Port (address).

In this way, the client can receive UDP packets sent back by the server. The received data packets include port number, IP address, etc.

According to the above process, the IP address of TCP server and TCP port can be obtained through UDP search, and then these information can be used to realize TCP connection.

  • Build basic password message
    • Principle: if you want to realize UDP interaction, you must agree on a set of public data formats, that is, the basic password header. If there is no agreed password message, the message sent by others will be sent back after reaching our server, which will lead to the exposure of our own basic information (such as IP\Port).
  • LAN broadcast password message (specify port)
  • Receive the specified Port loopback message (get the client IP and Port)
    • The client IP here refers to the server side

As shown in the figure above, BroadCast sends a BroadCast. If there is a device (server) interested, it will be sent back to BroadCast. If all three (servers) are interested, they will be sent back to BroadCast.

2. UDP search cancellation implementation

Relevant processes:

  • Asynchronous thread receives echo message
  • Asynchronous thread waiting for completion (timed)
  • Turn off wait - terminate thread wait

3. Code logic

3.1 TCP/UDP basic information fields

TCPConstants.java

/**
 * @ClassName TCPConstants
 * @Description TODO
 * @Author wushaopei
 * @Date 2022/2/27 12:58
 * @Version 1.0
 */
public class TCPConstants {

    // Server solidified UDP receive port
    public static int PORT_SERVER = 30401;
}

UDPConstants.java

/**
 * @ClassName UDPConstants  UDP Basic information
 * @Description TODO
 * @Author wushaopei
 * @Date 2022/2/27 12:58
 * @Version 1.0
 */
public class UDPConstants {

    // Common header (all 8 bytes are 7, which is recoverable)
    public static byte[] HEADER = new byte[]{7,7,7,7,7,7,7,7};
    // Server solidified UDP receive port
    public static int PORT_SERVER = 30201;
    // Client loopback port
    public static int PORT_CLIENT_RESPONSE = 30202;
}

3.2 tools

package clink.net.qiujuer.clink;

public class ByteUtils {
    /**
     * Does this byte array begin with match array content?
     *
     * @param source Byte array to examine
     * @param match  Byte array to locate in <code>source</code>
     * @return true If the starting bytes are equal
     */
    public static boolean startsWith(byte[] source, byte[] match) {
        return startsWith(source, 0, match);
    }

    /**
     * Does this byte array begin with match array content?
     *
     * @param source Byte array to examine
     * @param offset An offset into the <code>source</code> array
     * @param match  Byte array to locate in <code>source</code>
     * @return true If the starting bytes are equal
     */
    public static boolean startsWith(byte[] source, int offset, byte[] match) {

        if (match.length > (source.length - offset)) {
            return false;
        }

        for (int i = 0; i < match.length; i++) {
            if (source[offset + i] != match[i]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Does the source array equal the match array?
     *
     * @param source Byte array to examine
     * @param match  Byte array to locate in <code>source</code>
     * @return true If the two arrays are equal
     */
    public static boolean equals(byte[] source, byte[] match) {

        if (match.length != source.length) {
            return false;
        }
        return startsWith(source, 0, match);
    }

    /**
     * Copies bytes from the source byte array to the destination array
     *
     * @param source      The source array
     * @param srcBegin    Index of the first source byte to copy
     * @param srcEnd      Index after the last source byte to copy
     * @param destination The destination array
     * @param dstBegin    The starting offset in the destination array
     */
    public static void getBytes(byte[] source, int srcBegin, int srcEnd, byte[] destination,
                                int dstBegin) {
        System.arraycopy(source, srcBegin, destination, dstBegin, srcEnd - srcBegin);
    }

    /**
     * Return a new byte array containing a sub-portion of the source array
     *
     * @param srcBegin The beginning index (inclusive)
     * @param srcEnd   The ending index (exclusive)
     * @return The new, populated byte array
     */
    public static byte[] subbytes(byte[] source, int srcBegin, int srcEnd) {
        byte destination[];

        destination = new byte[srcEnd - srcBegin];
        getBytes(source, srcBegin, srcEnd, destination, 0);

        return destination;
    }

    /**
     * Return a new byte array containing a sub-portion of the source array
     *
     * @param srcBegin The beginning index (inclusive)
     * @return The new, populated byte array
     */
    public static byte[] subbytes(byte[] source, int srcBegin) {
        return subbytes(source, srcBegin, source.length);
    }
}

Verify that the password is correct. That is, verify the HEADER.

3.3 the code that the server receives the agreed data packet, parses it successfully and sends back the packet

/**
 * @ClassName ServerProvider
 * @Description TODO
 * @Author wushaopei
 * @Date 2022/2/27 13:01
 * @Version 1.0
 */
public class ServerProvider {

    private static Provider PROVIDER_INSTANCE;

    static void start(int port){
        stop();
        String sn = UUID.randomUUID().toString();
        Provider provider = new Provider(sn, port);
        provider.start();
        PROVIDER_INSTANCE = provider;
    }

    static void stop(){
        if(PROVIDER_INSTANCE != null){
            PROVIDER_INSTANCE.exit();
            PROVIDER_INSTANCE = null;
        }
    }


    private static class Provider extends Thread{
        private final byte[] sn;
        private final int port;
        private boolean done = false;
        private DatagramSocket ds = null;
        // Buffer for storing messages
        final byte[] buffer = new byte[128];

        public Provider(String sn, int port){
            super();
            this.sn = sn.getBytes();
            this.port = port;
        }

        @Override
        public void run() {
            super.run();

            System.out.println("UDDProvider Started.");

            try {
                // Listen to 20000 ports
                ds = new DatagramSocket(UDPConstants.PORT_SERVER);
                // Packet to receive the message
                DatagramPacket receivePacket = new DatagramPacket(buffer,buffer.length);
                while(!done){
                    // receive
                    ds.receive(receivePacket);

                    // Print the received information and the sender's information
                    // Sender's IP address
                    String clientIp = receivePacket.getAddress().getHostAddress();
                    int clientPort = receivePacket.getPort();
                    int clientDataLen = receivePacket.getLength();
                    byte[] clientData = receivePacket.getData();
                    boolean isValid = clientDataLen >= (UDPConstants.HEADER.length + 2 + 4) && ByteUtils.startsWith(clientData,UDPConstants.HEADER);
                    System.out.println("ServerProvider receive from ip:" + clientIp + "\tport:" + clientIp +"\tport:"+clientPort+"\tdataValid:"+isValid);

                    if(!isValid){
                        //Invalid continue
                        continue;
                    }

                    // Parse command and loopback port
                    int index = UDPConstants.HEADER.length;
                    short cmd = (short) ((clientData[index++] << 8) | (clientData[index++] & 0xff));
                    int responsePort = (((clientData[index++]) << 24) |
                            ((clientData[index++] & 0xff) << 16) |
                            ((clientData[index++] & 0xff) << 8) |
                            ((clientData[index++] & 0xff)));

                    // Judge legitimacy
                    if( cmd == 1 && responsePort > 0){
                        // Build a loopback data
                        ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
                        byteBuffer.put(UDPConstants.HEADER);
                        byteBuffer.putShort((short)2);
                        byteBuffer.putInt(port);
                        byteBuffer.put(sn);
                        int len = byteBuffer.position();
                        // Build a feedback message directly from the sender
                        DatagramPacket responsePacket = new DatagramPacket(buffer,len,receivePacket.getAddress(),responsePort);
                        ds.send(responsePacket);
                        System.out.println("ServerProvider response to:" + clientIp + "\tport:"+responsePort + "\tdataLen: " + len);
                    }else {
                        System.out.println("ServerProvider receive cmd nonsupport; cmd:" + cmd + "\tport:" + port);
                    }
                }
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void close() {
            if( ds != null ){
                ds.close();
                ds = null;
            }
        }

        /**
         * End of provision
         */
        void exit(){
            done = true;
            close();
        }
    }
}

main startup method:

/**
 * @ClassName Server
 * @Description TODO
 * @Author wushaopei
 * @Date 2022/2/27 13:00
 * @Version 1.0
 */
public class Server {

    public static void main(String[] args) {
        ServerProvider.start(TCPConstants.PORT_SERVER);
        try{
            System.in.read();
        } catch (IOException e){
            e.printStackTrace();
        }
        ServerProvider.stop();
    }
}

3.4 client broadcast sending message package code

Server side message entity:

package client.bean;

/**
 * @ClassName ServerInfo
 * @Description TODO
 * @Author wushaopei
 * @Date 2022/2/27 13:59
 * @Version 1.0
 */
public class ServerInfo {

    private String sn;
    private int port;
    private String address;

    public ServerInfo(int port, String address, String sn) {
        this.sn = sn;
        this.port = port;
        this.address = address;
    }
    
    ellipsis set/get Method
    
}

The client starts the main method:

/**
 * @ClassName Client
 * @Description TODO
 * @Author wushaopei
 * @Date 2022/2/27 13:59
 * @Version 1.0
 */
public class Client {

    public static void main(String[] args) {
        // Define the search time of 10 seconds. If the search is not found after 10 seconds, it is considered that the server is not powered on
        ServerInfo info = ClientSearcher.searchServer(10000);
        System.out.println("Server:" + info);
    }
}

The client receives the specific logic of server-side loopback and broadcast transmission:

/**
 * @ClassName ClientSearcher
 * @Description TODO
 * @Author wushaopei
 * @Date 2022/2/27 13:59
 * @Version 1.0
 */
public class ClientSearcher {

    private static final int LISTENT_PORT = UDPConstants.PORT_CLIENT_RESPONSE;

    public static ServerInfo searchServer(int timeout){
        System.out.println("UDPSearcher Started.");

        //  Successfully received the returned fence
        CountDownLatch receiveLatch = new CountDownLatch(1);
        Listener listener = null;
        try{
            // monitor
            listener = listen(receiveLatch);
            // Send broadcast
            sendBroadCast();
            // Wait for the server to return, blocking for up to 10 seconds
            receiveLatch.await(timeout, TimeUnit.MILLISECONDS);
        }catch (Exception e){
            e.printStackTrace();
        }
        // complete
        System.out.println("UDPSearcher Finished.");
        if(listener == null){
            return null;
        }
        List<ServerInfo> devices = listener.getServerAndClose();
        if(devices.size() > 0){
            return devices.get(0);
        }
        return null;
    }

    /**
     * Listen for messages sent back from the server
     * @param receiveLatch
     * @return
     * @throws InterruptedException
     */
    private static Listener listen(CountDownLatch receiveLatch) throws InterruptedException {
        System.out.println("UDPSearcher start listen.");
        CountDownLatch startDownLatch = new CountDownLatch(1);
        Listener listener = new Listener(LISTENT_PORT, startDownLatch,receiveLatch);
        listener.start();   // Asynchronous operation, enable port listening
        startDownLatch.await();
        return listener;
    }

    /**
     * Send broadcast logic
     * @throws IOException
     */
    private static void sendBroadCast() throws IOException {
        System.out.println("UDPSearcher sendBroadcast started.");

        // As the searcher, let the system automatically assign ports
        DatagramSocket ds = new DatagramSocket();

        // Build a request data
        ByteBuffer byteBuffer = ByteBuffer.allocate(128);
        // head
        byteBuffer.put(UDPConstants.HEADER);
        // CMD naming
        byteBuffer.putShort((short)1);
        // Loopback port information
        byteBuffer.putInt(LISTENT_PORT);
        // Directly build Packet
        DatagramPacket requestPacket = new DatagramPacket(byteBuffer.array(), byteBuffer.position() + 1);
        // Broadcast address
        requestPacket.setAddress(InetAddress.getByName("255,255.255.255"));
        // Set server port
        requestPacket.setPort(UDPConstants.PORT_SERVER);

        // send out
        ds.send(requestPacket);
        ds.close();

        // complete
        System.out.println("UDPSearcher sendBroadcast finished.");

    }

    /**
     * Receiving logic of broadcast message
     */
    private static class Listener extends Thread {
        private final int listenPort;
        private final CountDownLatch startDownLatch;
        private final CountDownLatch receiveDownLatch;
        private final List<ServerInfo> serverInfoList = new ArrayList<>();
        private final byte[] buffer = new byte[128];
        private final int minLen = UDPConstants.HEADER.length + 2 + 4; // 2: CMD command length 4:TCP port number length
        private boolean done = false;
        private DatagramSocket ds = null;

        private Listener(int listenPort,CountDownLatch startDownLatch,CountDownLatch receiveDownLatch){
            super();
            this.listenPort = listenPort;
            this.startDownLatch = startDownLatch;
            this.receiveDownLatch = receiveDownLatch;
        }

       @Override
       public void run(){
            super.run();

            // Notification started
            startDownLatch.countDown();
            try{
                // Listening loopback port
                ds = new DatagramSocket(listenPort);
                // Build receiving entity
                DatagramPacket receivePacket = new DatagramPacket(buffer,buffer.length);
                while( !done){
                    // receive
                    ds.receive(receivePacket);
                    // Print the received information and the sender's information
                    // Sender's IP address
                    String ip = receivePacket.getAddress().getHostAddress();
                    int port = receivePacket.getPort();
                    int dataLen = receivePacket.getLength();
                    byte[] data = receivePacket.getData();
                    boolean isValid = dataLen >= minLen
                            && ByteUtils.startsWith(data, UDPConstants.HEADER);

                    System.out.println("UDPSearch receive form ip:" + ip + "\tport:" + port + "\tdataValid:" + isValid);

                    if( !isValid ) {
                        // Invalid continue
                        continue;
                    }
                    // Skip the password byte and start with the specific data
                    ByteBuffer byteBuffer = ByteBuffer.wrap(buffer,UDPConstants.HEADER.length, dataLen);
                    final short cmd = byteBuffer.getShort(); // Occupy 2 bytes
                    final int serverPort = byteBuffer.getInt(); // Occupy 4 bytes
                    if(cmd != 2 || serverPort <= 0){
                        System.out.println("UDPSearcher receive cmd:" + cmd + "\tserverPort:" + serverPort);
                        continue;
                    }

                    String sn = new String(buffer,minLen,dataLen - minLen);
                    ServerInfo info = new ServerInfo(serverPort,ip,sn);
                    serverInfoList.add(info);
                    // Successfully received a copy
                    receiveDownLatch.countDown();
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                close();
            }
           System.out.println("UDPSearcher listener finished.");
       }

       private void close(){
           if(ds != null){
               ds.close();
               ds = null;
           }
       }

        List<ServerInfo> getServerAndClose() {
            done = true;
            close();
            return serverInfoList;
        }
    }
}

UDP search results code execution

The server starts receiving the result:

UDDProvider Started.
ServerProvider receive from ip:169.254.178.74	port:169.254.178.74	port:61968	dataValid:true
ServerProvider response to:169.254.178.74	port:30202	dataLen: 50

The execution result of the client listening and initiating the broadcast:

UDPSearcher Started.
UDPSearcher start listen.
UDPSearcher sendBroadcast started.
UDPSearcher sendBroadcast finished.
UDPSearch receive form ip:169.254.178.74	port:30201	dataValid:true
UDPSearcher Finished.
Server:ServerInfo{sn='ed4ab162-5d5c-49eb-b80e-6ddeb8b223e0', port=30401, address='169.254.178.74'}
UDPSearcher listener finished.

Process finished with exit code 0

From the above results, it can be seen that after starting the server, the client sends a data packet to the server after starting listen listening, and obtains the return from the server. After analysis, the ip/port can be obtained from the returned data packet, which can be used for TCP connection. In the process of UDP packet parsing, the password ensures that the client and server are effective in sending, receiving and returning messages, so as to avoid unnecessary responses.

Keywords: network socket udp TCP/IP

Added by zbert on Sun, 27 Feb 2022 09:23:37 +0200