UDP搜索IP与端口

43 篇文章 3 订阅

一、UDP优势

UDP可以作为广播发送

可以用于搜索

更专注于传输速度

基于报文进行传输

 

二、TCP优势

基于连接的传输

相对于UDP传输更加精确

更能保证数据传输的稳定性与健壮性,如果不发生异常,一定能确定数据完整地送达

 

三、TCP、UDP使用场景

3.1 知道服务器的ip地址和端口可以通过TCP进行连接

3.2 在局域网中不知道服务器的ip地址,仅知道服务器公共的UDP端口,那么如何使用TCP进行连接呢?

TCP是点对点的连接,那么一定要知道服务器的ip地址和端口。

那么如何知道ip地址和端口呢?

可以通过UDP进行搜索,当服务器与所有的客户端约定好搜索的格式后,可以在客户端发起UDP广播。在广播接收者服务器收到广播后,判断该广播是否需要处理。如果需要,那么会回送广播到对应的ip和端口。当回送的时候,客户端就可以收到服务器回送的UDP的包。该UDP包中包含了发送者的ip地址和端口号。所以,可以通过UDP的搜索得到TCP所需要的ip地址和端口,然后再通过TCP进行对应的连接。

 

四、UDP搜索IP与端口

4.1 步骤

构建基础口令消息

局域网广播口令消息(指定端口)

接收指定端口回送消息(得到客户端IP、Port)

 

4.2 步骤详解

首先,向局域网内通过UDP发送广播。

如果有设备对该广播感兴趣,那么会回送该消息到发送广播的服务端指定端口上来。

 

五、UDP搜索取消实现

异步线程接收回送消息

异步线程等待完成(定时)

关闭等待-终止线程等待

六、图示

七、代码

服务端:

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();
        }
    }
}

 客户端:

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;
        }
    }
}

 

 

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值