IP and UDP port search

A, UDP advantage

UDP can be transmitted as a broadcast

It can be used for search

More focused on transmission speed

Packet-based transmission

 

Two, TCP advantage

Based on the transmission connection

More accurate with respect to UDP transport

Can guarantee the stability and robustness of data transmission, if no abnormality occurs, the data must be able to determine the complete delivery

 

Three, TCP, UDP usage scenarios

3.1 know the server's ip address and port can be connected by TCP

3.2 do not know the ip address of the server in the LAN, the server knows only public UDP port, then how to connect using TCP do?

TCP is a connection point to point, then we must know the ip address and port of the server.

So how do you know the ip address and port it?

Can search by UDP, when the server and all the clients agreed to form a good search, you can initiate a UDP broadcast client. After the broadcast receiver receives a broadcast server determines whether the broadcast needs to process. If necessary, it will send back to the corresponding broadcast ip and port. When loopback, the client can receive server returns a UDP packet. The UDP contains the ip address and port number of the sender of the packet. It is possible to obtain ip address and TCP port UDP required search, and then to the corresponding TCP connection.

 

Four, UDP and IP port search

Step 4.1

Construction of the foundation password messages

LAN broadcast messages password (designated ports)

Receiving specified port loopback message (get client IP, Port)

 

4.2 Detailed steps

First, send a broadcast to the LAN via UDP.

If there is interest in the broadcast equipment, it will send back the message to send broadcast server designated port up.

 

Five, UDP search dematerialization

Asynchronous thread receives echo message

Asynchronous thread waits for completion (timing)

Close waiting - waiting for the thread to terminate

Six icon

Seven, the code

Server:

package server;

import clink.net.qiu.clink.utils.ByteUtils;
import constants.UDPConstants;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.ByteBuffer;
import java.util.UUID;

public class ServerProvider {

    private static Provider PROVIDER_INSTANCE;

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

    public 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
        final byte[] buffer = new byte[128];

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

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

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

            try {
                //监听port端口
                ds = new DatagramSocket(UDPConstants.PORT_SERVER);
                //接收消息的Packet
                DatagramPacket receivePack = new DatagramPacket(buffer, buffer.length);

                while (!done) {
                    ds.receive(receivePack);

                    //打印接收到的信息与发送者的信息
                    //发送者的IP地址
                    String clientIp = receivePack.getAddress().getHostAddress();
                    int clientPort = receivePack.getPort();
                    int clientDataLen = receivePack.getLength();
                    byte[] clientData = receivePack.getData();
                    //头部之后跟着的是指令,指令使用两个字节的short进行存储,指令之后跟着客户端回送端口
                    //客户端回送端口是int值,占四个字节
                    boolean isValid = clientDataLen >= (UDPConstants.HEADER.length + 2 + 4)
                            && ByteUtils.startsWith(clientData, UDPConstants.HEADER);

                    System.out.println("ServerProvider receive from ip:" + clientIp
                            + "\tport:" + clientPort + "\tdataValid:" + isValid);

                    if (!isValid) {
                        //无效继续
                        continue;
                    }

                    //解析命令与回送端口
                    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))));
                    //判断合法性
                    if(cmd==1 && responsePort>0){
                        //构建一份回送数据
                        ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
                        byteBuffer.put(UDPConstants.HEADER);
                        //回送命令定义为2
                        byteBuffer.putShort((short)2);
                        //TCP的port
                        byteBuffer.putInt(port);
                        byteBuffer.put(sn);

                        int len = byteBuffer.position();
                        //直接给发送者发送一份构建信息
                        DatagramPacket responsePacket = new DatagramPacket(buffer,
                                len,
                                receivePack.getAddress(),   //客户端地址
                                responsePort);              //接收者的ip端口
                        ds.send(responsePacket);
                        System.out.println("ServerProvider response to:"+clientIp+"\tport:"+responsePort+"\tdataLen:"+len);

                    }else{
                        System.out.println("ServerPorvider receive cmd nonsupport; cmd:"+cmd + "\tport:"+port);
                    }
                }
            } catch (Exception e) {
            } finally {
                close();
            }
        }

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

        public void exit() {
            done = true;
            close();
        }
    }
}

 Client:

package client;

import client.bean.ServerInfo;
import clink.net.qiu.clink.utils.ByteUtils;
import constants.UDPConstants;

import javax.xml.bind.Unmarshaller;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class ClientSearcher {
    private static final int LISTEN_PORT = UDPConstants.PORT_CLIENT_RESPONSE;

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

        //成功收到回送的栅栏
        CountDownLatch receiveLatch = new CountDownLatch(1);
        Listener listener = null;
        try{
            //因为客户端发送广播后,服务端接收到广播会回复一条信息,所以需要先监听
            listener = listen(receiveLatch);
            sendBroadcast();
            //成功接收一条数据或者超时时返回
            receiveLatch.await(timeout, TimeUnit.MILLISECONDS);
        }catch (Exception e){
            e.printStackTrace();
        }
        //完成
        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;
    }

    private static Listener listen(CountDownLatch receiveLatch) throws InterruptedException{
        System.out.println("UDPSearcher start listen.");
        CountDownLatch startDownLatch = new CountDownLatch(1);
        Listener listener = new Listener(LISTEN_PORT,startDownLatch,receiveLatch);
        listener.start();
        //等待线程启动完成
        startDownLatch.await();
        return listener;
    }

    private static void sendBroadcast() throws IOException {
        System.out.println("UDPSearcher sendBroadcast started.");

        //作为搜索方,让系统自动分配端口
        DatagramSocket ds = new DatagramSocket();

        //构建一份请求数据
        ByteBuffer byteBuffer = ByteBuffer.allocate(128);
        //头部
        byteBuffer.put(UDPConstants.HEADER);
        //CMD命令
        byteBuffer.putShort((short) 1);
        //回送端口信息
        byteBuffer.putInt(LISTEN_PORT);
        //直接构建packet
        DatagramPacket requestPacket = new DatagramPacket(byteBuffer.array(),
                byteBuffer.position()+1);
        //广播地址
        requestPacket.setAddress(InetAddress.getByName("255.255.255.255"));
        //设置服务器端口
        requestPacket.setPort(UDPConstants.PORT_SERVER);

        //发送
        ds.send(requestPacket);
        ds.close();
        System.out.println("UDPSearcher sendBroadcast udpPort:"+LISTEN_PORT);
        //完成
        System.out.println("UDPSearcher sendBroadcast finished.");
    }

    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;
        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();
            //通知已启动(虽然主动调用了thread.start()方法,但是线程何时运行是由系统决定的,所以使用startDownLatch进行同步)
            startDownLatch.countDown();

            try {
                //监听回送端口
                ds = new DatagramSocket(listenPort);
                //构建接收实体
                DatagramPacket receivePacket = new DatagramPacket(buffer,buffer.length);

                while (!done){
                    //接收
                    ds.receive(receivePacket);

                    //打印接收到的信息与发送者的信息
                    //发送者的IP地址
                    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);

                    if(!isValid){
                        continue;
                    }

                    //包裹buffer和data是一样的,它们有同样的hash值
                    ByteBuffer byteBuffer = ByteBuffer.wrap(buffer,UDPConstants.HEADER.length,dataLen);
                    final short cmd = byteBuffer.getShort();
                    final int serverPort = byteBuffer.getInt();
                    if(cmd!=2||serverPort<=0){
                        System.out.println("UDPSearcher receive cmd:"+cmd+"\tserverPort:"+serverPort);
                        continue;
                    }

                    String sn = new String(buffer,minLen,dataLen-minLen);
                    ServerInfo serverInfo = new ServerInfo(serverPort,ip,sn);
                    serverInfoList.add(serverInfo);
                    //成功接收到一份
                    receiveDownLatch.countDown();
                }

            }catch (Exception e){

            }finally {
                close();
            }
            System.out.println("UDPSearcher listener finished.");
        }

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

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

 

 

operation result:

Published 174 original articles · won praise 115 · views 830 000 +

Guess you like

Origin blog.csdn.net/nicolelili1/article/details/103956181