Android 局域網內功能模塊開發,教你怎麼快速獲取局域網內所有IP並且進行通信------ MulticastSocket

在當今的互聯網時代,很多互聯網公司、方案公司、智能設備公司或多或少都會接觸一些局域網內的相關開發,比如某公司研發了一個app,該app需求是在局域網和網域網都可以獲取自己好友的消息或信息,網域網下技術人員可以通過服務器轉接信息和發送,實現交互,但是在非聯網的局域網下使用部分非使用網絡的功能,這就需要研究一些比較不常用的類,在通常情況下,可能大部分人首先想到的肯定是0-255的逐個去ping,這樣效率超級低!而且粗暴的方式還可能導致oom,之前說到的 ping ,就是比如局域網下發射信號的主機即服務器,這裏我就形象的說是路由器吧,比如路由器的ip是192.168.0.1,那連接它的其他設備的ip被分配的ip也是192.168.0.xxx,這裏的xxx是一個取值範圍0-255,很多時候大家爲了方便就採用循環來對0-255這樣的一個一個的去ping,也就是像192.168.0.2、192.168.0.3......這樣一直到255,效率非常慢。。。

所以今天就給大家分享一個好東西,java.net.MulticastSocket

MulticastSocket 繼承自 DatagramSocket

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package java.net;

import java.io.IOException;
import java.util.Enumeration;
import libcore.io.IoUtils;

/**
 * This class implements a multicast socket for sending and receiving IP
 * multicast datagram packets.
 *
 * @see DatagramSocket
 */
public class MulticastSocket extends DatagramSocket {
    /**
     * Stores the address supplied to setInterface so we can return it from getInterface. The
     * translation to an interface index is lossy because an interface can have multiple addresses.
     */
    private InetAddress setAddress;

    /**
     * Constructs a multicast socket, bound to any available port on the
     * local host.
     *
     * @throws IOException if an error occurs.
     */
    public MulticastSocket() throws IOException {
        setReuseAddress(true);
    }

    /**
     * Constructs a multicast socket, bound to the specified {@code port} on the
     * local host.
     *
     * @throws IOException if an error occurs.
     */
    public MulticastSocket(int port) throws IOException {
        super(port);
        setReuseAddress(true);
    }

    /**
     * Constructs a {@code MulticastSocket} bound to the address and port specified by
     * {@code localAddress}, or an unbound {@code MulticastSocket} if {@code localAddress == null}.
     *
     * @throws IllegalArgumentException if {@code localAddress} is not supported (because it's not
     * an {@code InetSocketAddress}, say).
     * @throws IOException if an error occurs.
     */
    public MulticastSocket(SocketAddress localAddress) throws IOException {
        super(localAddress);
        setReuseAddress(true);
    }

    /**
     * Returns an address of the outgoing network interface used by this socket. To avoid
     * inherent unpredictability, new code should use {@link #getNetworkInterface} instead.
     *
     * @throws SocketException if an error occurs.
     */
    public InetAddress getInterface() throws SocketException {
        checkOpen();
        if (setAddress != null) {
            return setAddress;
        }
        InetAddress ipvXaddress = (InetAddress) impl.getOption(SocketOptions.IP_MULTICAST_IF);
        if (ipvXaddress.isAnyLocalAddress()) {
            // the address was not set at the IPv4 level so check the IPv6
            // level
            NetworkInterface theInterface = getNetworkInterface();
            if (theInterface != null) {
                Enumeration<InetAddress> addresses = theInterface.getInetAddresses();
                if (addresses != null) {
                    while (addresses.hasMoreElements()) {
                        InetAddress nextAddress = addresses.nextElement();
                        if (nextAddress instanceof Inet6Address) {
                            return nextAddress;
                        }
                    }
                }
            }
        }
        return ipvXaddress;
    }

    /**
     * Returns the outgoing network interface used by this socket.
     *
     * @throws SocketException if an error occurs.
     */
    public NetworkInterface getNetworkInterface() throws SocketException {
        checkOpen();
        int index = (Integer) impl.getOption(SocketOptions.IP_MULTICAST_IF2);
        if (index != 0) {
            return NetworkInterface.getByIndex(index);
        }
        return NetworkInterface.forUnboundMulticastSocket();
    }

    /**
     * Returns the time-to-live (TTL) for multicast packets sent on this socket.
     *
     * @throws IOException if an error occurs.
     */
    public int getTimeToLive() throws IOException {
        checkOpen();
        return impl.getTimeToLive();
    }

    /**
     * Returns the time-to-live (TTL) for multicast packets sent on this socket.
     *
     * @throws IOException if an error occurs.
     * @deprecated Use {@link #getTimeToLive} instead.
     */
    @Deprecated
    public byte getTTL() throws IOException {
        checkOpen();
        return impl.getTTL();
    }

    /**
     * Adds this socket to the specified multicast group. A socket must join a
     * group before data may be received. A socket may be a member of multiple
     * groups but may join any group only once.
     *
     * @param groupAddr
     *            the multicast group to be joined.
     * @throws IOException if an error occurs.
     */
    public void joinGroup(InetAddress groupAddr) throws IOException {
        checkJoinOrLeave(groupAddr);
        impl.join(groupAddr);
    }

    /**
     * Adds this socket to the specified multicast group. A socket must join a
     * group before data may be received. A socket may be a member of multiple
     * groups but may join any group only once.
     *
     * @param groupAddress
     *            the multicast group to be joined.
     * @param netInterface
     *            the network interface on which the datagram packets will be
     *            received.
     * @throws IOException
     *                if the specified address is not a multicast address.
     * @throws IllegalArgumentException
     *                if no multicast group is specified.
     */
    public void joinGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException {
        checkJoinOrLeave(groupAddress, netInterface);
        impl.joinGroup(groupAddress, netInterface);
    }

    /**
     * Removes this socket from the specified multicast group.
     *
     * @param groupAddr
     *            the multicast group to be left.
     * @throws NullPointerException
     *                if {@code groupAddr} is {@code null}.
     * @throws IOException
     *                if the specified group address is not a multicast address.
     */
    public void leaveGroup(InetAddress groupAddr) throws IOException {
        checkJoinOrLeave(groupAddr);
        impl.leave(groupAddr);
    }

    /**
     * Removes this socket from the specified multicast group.
     *
     * @param groupAddress
     *            the multicast group to be left.
     * @param netInterface
     *            the network interface on which the addresses should be
     *            dropped.
     * @throws IOException
     *                if the specified group address is not a multicast address.
     * @throws IllegalArgumentException
     *                if {@code groupAddress} is {@code null}.
     */
    public void leaveGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException {
        checkJoinOrLeave(groupAddress, netInterface);
        impl.leaveGroup(groupAddress, netInterface);
    }

    private void checkJoinOrLeave(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException {
        checkOpen();
        if (groupAddress == null) {
            throw new IllegalArgumentException("groupAddress == null");
        }

        if (netInterface != null && !netInterface.getInetAddresses().hasMoreElements()) {
            throw new SocketException("No address associated with interface: " + netInterface);
        }

        if (!(groupAddress instanceof InetSocketAddress)) {
            throw new IllegalArgumentException("Group address not an InetSocketAddress: " +
                    groupAddress.getClass());
        }

        InetAddress groupAddr = ((InetSocketAddress) groupAddress).getAddress();
        if (groupAddr == null) {
            throw new SocketException("Group address has no address: " + groupAddress);
        }

        if (!groupAddr.isMulticastAddress()) {
            throw new IOException("Not a multicast group: " + groupAddr);
        }
    }

    private void checkJoinOrLeave(InetAddress groupAddr) throws IOException {
        checkOpen();
        if (groupAddr == null) {
            throw new IllegalArgumentException("groupAddress == null");
        }
        if (!groupAddr.isMulticastAddress()) {
            throw new IOException("Not a multicast group: " + groupAddr);
        }
    }

    /**
     * Sends the given {@code packet} on this socket, using the given {@code ttl}. This method is
     * deprecated because it modifies the TTL socket option for this socket twice on each call.
     *
     * @throws IOException if an error occurs.
     * @deprecated Use {@link #setTimeToLive} instead.
     */
    @Deprecated
    public void send(DatagramPacket packet, byte ttl) throws IOException {
        checkOpen();
        InetAddress packAddr = packet.getAddress();
        int currTTL = getTimeToLive();
        if (packAddr.isMulticastAddress() && (byte) currTTL != ttl) {
            try {
                setTimeToLive(ttl & 0xff);
                impl.send(packet);
            } finally {
                setTimeToLive(currTTL);
            }
        } else {
            impl.send(packet);
        }
    }

    /**
     * Sets the outgoing network interface used by this socket. The interface used is the first
     * interface found to have the given {@code address}. To avoid inherent unpredictability,
     * new code should use {@link #getNetworkInterface} instead.
     *
     * @throws SocketException if an error occurs.
     */
    public void setInterface(InetAddress address) throws SocketException {
        checkOpen();
        if (address == null) {
            throw new NullPointerException("address == null");
        }

        NetworkInterface networkInterface = NetworkInterface.getByInetAddress(address);
        if (networkInterface == null) {
            throw new SocketException("Address not associated with an interface: " + address);
        }
        impl.setOption(SocketOptions.IP_MULTICAST_IF2, networkInterface.getIndex());
        this.setAddress = address;
    }

    /**
     * Sets the outgoing network interface used by this socket to the given
     * {@code networkInterface}.
     *
     * @throws SocketException if an error occurs.
     */
    public void setNetworkInterface(NetworkInterface networkInterface) throws SocketException {
        checkOpen();
        if (networkInterface == null) {
            throw new SocketException("networkInterface == null");
        }

        impl.setOption(SocketOptions.IP_MULTICAST_IF2, networkInterface.getIndex());
        this.setAddress = null;
    }

    /**
     * Sets the time-to-live (TTL) for multicast packets sent on this socket.
     * Valid TTL values are between 0 and 255 inclusive.
     *
     * @throws IOException if an error occurs.
     */
    public void setTimeToLive(int ttl) throws IOException {
        checkOpen();
        if (ttl < 0 || ttl > 255) {
            throw new IllegalArgumentException("TimeToLive out of bounds: " + ttl);
        }
        impl.setTimeToLive(ttl);
    }

    /**
     * Sets the time-to-live (TTL) for multicast packets sent on this socket.
     * Valid TTL values are between 0 and 255 inclusive.
     *
     * @throws IOException if an error occurs.
     * @deprecated Use {@link #setTimeToLive} instead.
     */
    @Deprecated
    public void setTTL(byte ttl) throws IOException {
        checkOpen();
        impl.setTTL(ttl);
    }

    @Override
    synchronized void createSocket(int aPort, InetAddress addr) throws SocketException {
        impl = factory != null ? factory.createDatagramSocketImpl() : new PlainDatagramSocketImpl();
        impl.create();
        try {
            impl.setOption(SocketOptions.SO_REUSEADDR, Boolean.TRUE);
            impl.bind(aPort, addr);
            isBound = true;
        } catch (SocketException e) {
            close();
            throw e;
        }
    }

    /**
     * Returns true if multicast loopback is <i>disabled</i>.
     * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the
     * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}.
     *
     * @throws SocketException if an error occurs.
     */
    public boolean getLoopbackMode() throws SocketException {
        checkOpen();
        return !((Boolean) impl.getOption(SocketOptions.IP_MULTICAST_LOOP)).booleanValue();
    }

    /**
     * Disables multicast loopback if {@code disable == true}.
     * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the
     * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}: true means disabled, false
     * means enabled.
     *
     * @throws SocketException if an error occurs.
     */
    public void setLoopbackMode(boolean disable) throws SocketException {
        checkOpen();
        impl.setOption(SocketOptions.IP_MULTICAST_LOOP, Boolean.valueOf(!disable));
    }
}

瞭解一下該類後可以開始下面的編程,首先說一下客戶端,再說服務端,最後說明使用方法

①客戶端代碼核心:

	@Override
	protected void onResume() {
		super.onResume();
		/**
		 * @author Engineer-Jsp
		 * 筆者在該 Activity 的 onResume()函數初始化接收的偵聽
		 */
		onBrodacastReceiver();
	}
②onBrodacastReceiver()函數:

	MulticastSocket multicastSocket;
	
	/**
	 * @author Engineer-Jsp
	 * onBrodacastReceiver()
	 */
	private void onBrodacastReceiver() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					// 接收數據時需要指定監聽的端口號
					multicastSocket = new MulticastSocket(10001);
					// 創建組播ID地址
					InetAddress address = InetAddress.getByName("239.0.0.1");
					// 加入地址
					multicastSocket.joinGroup(address);
					// 包長
					byte[] buf = new byte[1024];
					while (true) {
						// 數據報
						DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
						// 接收數據,同樣會進入阻塞狀態
						multicastSocket.receive(datagramPacket); 
						// 從buffer中截取收到的數據
						byte[] message = new byte[datagramPacket.getLength()];
						// 數組拷貝
						System.arraycopy(buf, 0, message, 0, datagramPacket.getLength());
						// 打印來自組播裏其他服務的or客戶端的ip
						System.out.println(datagramPacket.getAddress());
						// 打印來自組播裏其他服務的or客戶端的消息
						System.out.println(new String(message));
						// 收到消息後可以進行記錄然後二次確認,如果只是想獲取ip,在發送方收到該消息後可關閉套接字,從而釋放資源
						onBrodacastSend(datagramPacket.getAddress());
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

③ onBrodacastSend() 函數

	/**
	 * onBrodacastSend()
	 * @author Engineer-Jsp
	 * @param address ip
	 */
	private void onBrodacastSend(InetAddress address) {
		// 假設 239.0.0.1 已經收到了來自其他組ip段的消息,爲了進行二次確認,發送 "snoop"
		// 進行確認,當發送方收到該消息可以釋放資源
		String out = "snoop";
		// 獲取"snoop"的字節數組
		byte[] buf = out.getBytes();
		// 組報
		DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
		// 設置地址,該地址來自onBrodacastReceiver()函數阻塞數據報,datagramPacket.getAddress()
		datagramPacket.setAddress(address);
		// 發送的端口號
		datagramPacket.setPort(8082);
		try {
			// 開始發送
			multicastSocket.send(datagramPacket);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

這是客戶端的代碼,下面開始上服務端的代碼

①初始化

	@Override
	protected void onResume() {
		super.onResume();
		/**
		 * @author Engineer-Jsp 
		 * 筆者在該 Activity 的 onResume()函數初始化接收和發送
		 * onBrodacastSend() 發送
		 * onBrodacastReceiver() 接收
		 */
		onBrodacastSend();
		onBrodacastReceiver();
	}
② onBrodacastSend() 函數

	InetAddress address;
	MulticastSocket multicastSocket;

	/**
	 * @author Engineer-Jsp 
	 * onBrodacastSend() 發送
	 */
	private void onBrodacastSend() {
		try {
			// 偵聽的端口
			multicastSocket = new MulticastSocket(8082);
			// 使用D類地址,該地址爲發起組播的那個ip段,即偵聽10001的套接字
			address = InetAddress.getByName("239.0.0.1");
			new Thread(new Runnable() {
				@Override
				public void run() {
					while (true) {
						// 獲取當前時間
						String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
						// 當前時間+標識後綴
						time = time + " >>> form server onBrodacastSend()";
						// 獲取當前時間+標識後綴的字節數組
						byte[] buf = time.getBytes();
						// 組報
						DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
						// 向組播ID,即接收group /239.0.0.1  端口 10001
						datagramPacket.setAddress(address); 
						// 發送的端口號
						datagramPacket.setPort(10001);
						try {
							// 開始發送
							multicastSocket.send(datagramPacket);
							// 每執行一次,線程休眠2s,然後繼續下一次任務
							Thread.sleep(2000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
			}).start();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


③onBrodacastReceiver()函數

	/**
	 * @author Engineer-Jsp 
	 * onBrodacastReceiver() 接收
	 */
	private void onBrodacastReceiver() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					// 字節數組的格式,即最大大小
					byte[] buf = new byte[1024];
					while (true) {
						// 組報格式
						DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
						// 接收來自group組播10001端口的二次確認,阻塞
						multicastSocket.receive(datagramPacket);
						// 從buf中截取收到的數據
						byte[] message = new byte[datagramPacket.getLength()];
						// 數組拷貝
						System.arraycopy(buf, 0, message, 0, datagramPacket.getLength());
						// 這裏打印ip字段
						System.out.println(datagramPacket.getAddress());
						// 打印組播端口10001發送過來的消息
						System.out.println(new String(message));
						// 這裏可以根據結接收到的內容進行分發處理,假如收到 10001的 "snoop"字段爲關閉命令,即可在此處關閉套接字從而釋放資源
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

這是服務端的代碼,也分享完了,下面畫一個圖來大概描述一下他們的工作流程圖示意:


使用方法:①首先需要在同一wifi網絡下 ②需要獲取所有ip的手機安裝客戶端,即偵聽10001的那個端口 ③所有需要將信息共享並組播到 239.0.0.1:10001這個group的安裝服務端 ④開啓服務端與客戶端開始進行數據的交互

以上是大致的描述圖,幫助大家理解,謝謝觀博!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章