局域网通讯方案

       在局域网中,分配给设备的ip是不固定的,所以别的设备是如何获取到本设备的IP跟port的呢?本文章主要是解决不同设备在同一个局域网内的通信问题,B设备通过局域网自动发现A设备的ip跟端口,最后利用socket连接并通讯。

方案一: NSD(Network Service Discovery)服务

      这个服务是搜索附近哪个网络设备可以进行通信,一般指的是局域网内连接同一个wifi的信号源。NSD是属于android设备的服务,如果是ios设备可参考Bonjour服务。故如果不是Android设备类型,请参考方案二!

1、A设备注册nsd服务(可理解A设备为server)

  •  创建NsdServiceInfo对象
//String mServerType = "_http._tcp.";
NsdServiceInfo serviceInfo = new NsdServiceInfo();
serviceInfo.setServiceName(android.os.Build.MANUFACTURER); //设置名字
serviceInfo.setServiceType(mServerType); ////设置连接类型使用基于tcp的http协议进行传输 _http._tcp.
    try {
        String ip = NetWorkUtil.getHostIp(); //自动获取本局域网IP,暴露IP
        serviceInfo.setHost(InetAddress.getByName(ip));
    } catch (UnknownHostException e) {
        e.printStackTrace();
    }
serviceInfo.setPort(9109); //暴露端口
//IP工具类
public class NetWorkUtil {
    //匹配C类地址的IP
    public static final String regexCIp = "^192\\.168\\.(\\d{1}|[1-9]\\d|1\\d{2}|2[0-4]\\d|25\\d)\\.(\\d{1}|[1-9]\\d|1\\d{2}|2[0-4]\\d|25\\d)$";
    //匹配A类地址
    public static final String regexAIp = "^10\\.(\\d{1}|[1-9]\\d|1\\d{2}|2[0-4]\\d|25\\d)\\.(\\d{1}|[1-9]\\d|1\\d{2}|2[0-4]\\d|25\\d)\\.(\\d{1}|[1-9]\\d|1\\d{2}|2[0-4]\\d|25\\d)$";
    //匹配B类地址
    public static final String regexBIp = "^172\\.(1[6-9]|2\\d|3[0-1])\\.(\\d{1}|[1-9]\\d|1\\d{2}|2[0-4]\\d|25\\d)\\.(\\d{1}|[1-9]\\d|1\\d{2}|2[0-4]\\d|25\\d)$";
    public static String getHostIp() {
        String hostIp;
        Pattern ip = Pattern.compile("(" + regexAIp + ")|" + "(" + regexBIp + ")|" + "(" + regexCIp + ")");
        Enumeration<NetworkInterface> networkInterfaces = null;
        try {
            networkInterfaces = NetworkInterface.getNetworkInterfaces();
        } catch (SocketException e) {
            e.printStackTrace();
        }
        InetAddress address;
        while (networkInterfaces.hasMoreElements()) {
            NetworkInterface networkInterface = networkInterfaces.nextElement();
            Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
            while (inetAddresses.hasMoreElements()) {
                address = inetAddresses.nextElement();
                String hostAddress = address.getHostAddress();
                Matcher matcher = ip.matcher(hostAddress);
                if (matcher.matches()) {
                    hostIp = hostAddress;
                    return hostIp;
                }
            }
        }
        return null;
    }
}
  • 创建RegistrationListener(监听注册结果)
listener = new NsdManager.RegistrationListener() {
     @Override
     public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
         Log.e(TAG, "nsdRegist onRegistrationFailed: " + errorCode);
     }
     @Override
     public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode){
         Log.e(TAG, "nsdRegist onUnregistrationFailed: " + errorCode);
     }
     @Override
     public void onServiceRegistered(NsdServiceInfo serviceInfo) {
         Log.d(TAG, "nsdRegist onServiceRegistered="+serviceInfo.toString());
     }
     @Override
     public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
         Log.d(TAG, "nsdRegist onServiceUnregistered="+serviceInfo.toString());
     }
 };
  • 注册注销
//注册
mNsdManager = (NsdManager) getApplication().getSystemService(Context.NSD_SERVICE);
mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, listener);
//注销
mNsdManager.unregisterService(listener);

2、B设备进行查找(B设备可理解为Client)

  • 发现NSD网络onServiceFound
mNsdManager.discoverServices(mServerType, NsdManager.PROTOCOL_DNS_SD, mNSDDiscoveryListener);
mNSDDiscoveryListener = new NsdManager.DiscoveryListener() {
    @Override
    public void onStartDiscoveryFailed(String serviceType, int errorCode) {
        Log.i(TAG, "onStartDiscoveryFailed--> " + serviceType + ":" + errorCode);
    }
    @Override
    public void onStopDiscoveryFailed(String serviceType, int errorCode) {
        Log.i(TAG, "onStopDiscoveryFailed--> " + serviceType + ":" + errorCode);
    }
    @Override
    public void onDiscoveryStarted(String serviceType) {
        Log.i(TAG, "onDiscoveryStarted--> " + serviceType );
    }
    @Override
    public void onDiscoveryStopped(String serviceType) {
        Log.i(TAG, "onDiscoveryStopped--> " + serviceType );
    }
    @Override
    public void onServiceFound(NsdServiceInfo serviceInfo) {//关键的回调方法
        //这里的 serviceInfo里面只有NSD服务器的主机名,要解析后才能得到该主机名的其他数据信息
        Log.i(TAG, "onServiceFound Info: --> " + serviceInfo);
        //开始解析数据
        //第一个参数是扫描得到的对象,第二个参数是解析监听对象
        mNsdManager.resolveService(serviceInfo, mNSDResolveListener);
    }
    @Override
    public void onServiceLost(NsdServiceInfo serviceInfo) {
        Log.i(TAG, "onServiceLost--> " + serviceInfo);
    }
};
  • 解析NSD数据
mNSDResolveListener = new NsdManager.ResolveListener() {
    @Override
    public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
    }

    @Override
    public void onServiceResolved(NsdServiceInfo serviceInfo) {//到这里就是我们要的最终数据信息
        Log.i(TAG, "resolution : " + serviceInfo.getServiceName() + " \n host_from_server: " + serviceInfo.getHost() +
                        "\n port from server: " + serviceInfo.getPort());
        String hostAddress = serviceInfo.getHost().getHostAddress();
        Client client = new Client(MainActivity.this, hostAddress, serviceInfo.getPort());
        client.sendPackage(); //使用netty进行socket进行长连接通信
        Log.i(TAG, "hostAddress ip--> " + hostAddress );
    }
};

好了,最后两个设备之间就可以无痛感的进行通讯了,用户无需知道对方的ip跟端口,也无需做其他手动连接操作。

方案二: 局域网广播

      简单说一下这个方案的实现思想:B设备往局域网广播一条协议信息,局域网中的设备A收到并识别这条协议,给B设备单播回了包含ip、port的信息,最后B设备就可以通过socket跟A通讯了。

1、 A设备监听广播,并筛选协议信息

//开启一个新线程,来接收局域网发来的消息:
public class AcceptUDP extends Thread {
        public DatagramSocket socket;
        @Override
        public void run() {
            try {
                //监听本机的端口       
                socket = new DatagramSocket(8088);
                Log.i("WifiController", "***");
                //创建一个buffer来接受client传过来的数据
                byte[] buffer = new byte[2048];
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                Log.i("WifiController", "***");
                while (true) {
                   //阻塞当前线程来接受数据
                    Log.i("WifiController", "%***%");
                    //接收局域网内的所有广播
                    socket.receive(packet);
                    String re = new String(packet.getData(), 0, packet.getLength());
                    Log.i("WifiController", "&***&" + re);
                   //对广播内容进行筛选
                    if (re.equalsIgnoreCase("IP")) {
                        String back = Build.MODEL + "&" + NetWorkUtil.getHostIp() + "&8099";
                        Log.i("WifiController", back);
                       // 把当前机子的IP通过广播发送到局域网中
                        DatagramPacket datagramPacket = new DatagramPacket(back.getBytes(), back.length(), packet.getSocketAddress());
                        socket.send(datagramPacket);
                        Log.i("WifiController", "" + packet.getSocketAddress());
                    }
                }
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }   

2、B设备发送协议信息到局域网询问主机信息

class SendMessage extends Thread {
    public DatagramSocket socket;
    public int port;
    public SocketReceive socketReceive;
    public Context context;

    public SendMessage(Context context, int port, SocketReceive socketReceive) {
        this.port = port;
        this.socketReceive = socketReceive;
        this.context = context;
    }

    @Override
    public void run() {
        // Send message;
        while (!Thread.currentThread().isInterrupted()) {
            String mes = "IP";
            //把message转成byte数组
            byte[] buffer = mes.getBytes();
            byte[] backbuff = new byte[2048];
            //因为下面的socket。recevice()会阻塞当前线程,无法不断地往局域网发送广播。所server端 未打开APP或者处于关机状态建议把发送数据包的部分代码独立出来,不然server端接收不到广播。
            try {
                Log.e("SEND", "ONE");
                //利用java提供的DatagramSocket 进行操作,因为其可以存放数据包。
                socket = new DatagramSocket();
                //把message加入datagramsocket里。
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length, getBroadcastAddress(context), port);
                socket.setBroadcast(true);
                //创建接受广播的packet
                DatagramPacket datagramPacket = new DatagramPacket(backbuff, backbuff.length);
                //发送广播
                socket.send(packet);
                //在接收广播时,会阻塞当前的线程,一直等到超时,或接收到数据。
                socket.receive(datagramPacket);
                String res = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
                Log.e("SEND", "&&" + res);
                if (res != null) {
                    //把接收到的地址保存起来!
                    Log.d("SEND", "receive="+res);
                    if (socketReceive!=null) {
                        socketReceive.receive(res);
                    }
                    //如接收到结果,则把当前线程给结束。
                    SendMessage.this.interrupt();
                }
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (socket != null) {
                    //最后关闭Datagramsocket
                    socket.close();
                }
            }
        }

    }
    /**
     * 获取局域网广播地址
     **/
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static InetAddress getBroadcastAddress(Context context) throws UnknownHostException {

        WifiManager wifi = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
        DhcpInfo dhcp = wifi.getDhcpInfo();
        if(dhcp== null) {
            return InetAddress.getByName("255.255.255.255");
        }
        WifiInfo wifiinfo = wifi.getConnectionInfo();
        Log.d("textxx","Wifi info----->"+wifiinfo.getIpAddress());
        Log.d("textxx","DHCP info gateway----->"+ Formatter.formatIpAddress(dhcp.gateway));
        Log.d("textxx","DHCP info netmask----->"+Formatter.formatIpAddress(dhcp.netmask));

        int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask;
        byte[] quads = new byte[4];
        for (int k = 0; k < 4; k++)
            quads[k] = (byte) ((broadcast >> k * 8) & 0xFF);
        return InetAddress.getByAddress(quads);
        //return InetAddress.getByName("10.3.3.255");
    }
     /**
      * 获取到Server IP
      **/
    public interface SocketReceive {
        public void receive(String res);
    }

}

最后,receive(String res) 中包含了A的ip、port信息,即可实现socket连接通讯!

发布了18 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/LIANGJIANGLI/article/details/104063098