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.