SSDP在Android上的实现

一.什么是SSDP:

SSDP(Simple Service Discovery Protocol),简单服务发现协议,用于发现局域网里面的设备和服务。

 SSDP消息分为设备查询消息、设备通知消息两种,通常情况下,使用更多地是设备查询消息。

设备查询消息-格式例子如下:

M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: 5
ST: ssdp:all

其中第一行是消息头,固定;

HOST对应的是广播地址和端口,其中239.255.255.250:1900是SSDP默认的地址和端口;

MAN后面的ssdp:discover为固定,

MX为最长等待时间,

ST:查询目标,它的值可以是:

upnp:rootdevice 仅搜索网络中的根设备 

uuid:device-UUID 查询UUID标识的设备 

urn:schemas-upnp-org:device:device-Type:version 查询device-Type字段指定的设备类型,设备类型和版本由UPNP组织定义。 

其中,第三种一般可以用来自定义设备,如:ST: urn:schemas-upnp-org:device:Server:1

在设备接收到查询请求并且查询类型(ST字段值)与此设备匹配时,设备必须向多播地址239.255.255.250:1900回应响应消息。一般形如:

HTTP/1.1 200 OK
CACHE-CONTROL: max-age = seconds until advertisement expires
DATE: when reponse was generated
EXT:
LOCATION: URL for UPnP description for root device
SERVER: OS/Version UPNP/1.0 product/version
ST: search target
USN: advertisement UUID

 最常用的设备发现就讲完了,不常用的设备通知和设备发现差别不大,主要是:

http头不同,设备通知的头为

NOTIFY * HTTP/1.1

无MX,增加:

NT        在此消息中,NT头必须为服务的服务类型。

NTS     表示通知消息的子类型,必须为ssdp:alive或者ssdp:byebye

USN     表示不同服务的统一服务名,它提供了一种标识出相同类型服务的能力

典型的设备通知消息格式如下:

NOTIFY * HTTP/1.1

HOST: 239.255.255.250:1900

CACHE-CONTROL: max-age = seconds until advertisement expires

LOCATION: URL for UPnP description for root device

NT: search target

NTS: ssdp:alive

USN: advertisement UUID

二.Android设备上如何实现SSDP:

具体在Android设备使用时候,需要注意以下事项:首先,Android的Wifi,默认情况下是不接受组播的。默认情况下,应用是不接收组播信息的,这样要接收处理的报文太多,很快就会把电池用尽。要知道移动设备电量续航一直是瓶颈。

要想打开组播功能,有以下几个步骤:

  • 在Manifest文件中加入:android.permission.CHANGE_WIFI_MULTICAST_STATE,这个权限
  • 获取到MulticastLock对象,这个对象不能直接实例化,要通过WifiManager间接得到,工厂模式
  • 调用MulticastLock对象的acquire方法,获取到组播锁
  • 相应的,用完组播,为了不浪费电量,要调用MulticastLock的release方法释放锁

下面写了一个Demo:

先在AndroidManifest.xml中添加权限:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

MainActivity.java

public class MainActivity extends Activity implements OnClickListener {

    private WifiManager.MulticastLock multicastLock;
    private List<String> listReceive = new ArrayList<String>();
    private static final String TAG = "@@@";
    private TextView tvReceive;//显示搜寻结果

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvReceive = (TextView) findViewById(R.id.tv_show_receive);
        Button btn = (Button) findViewById(R.id.btnSendSSDPSearch);
        btn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                SendMSearchMessage();
            }
        }).start();
    }

    /**
     * 获取组锁,使用后记得及时释放,否则会增加耗电。为了省电,Android设备默认关闭
     */
    private void acquireMultiLock() {
        WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
        multicastLock = wm.createMulticastLock("multicastLock");
        multicastLock.setReferenceCounted(true);
        multicastLock.acquire();//使用后,需要及时关闭
    }

    /**
     * 释放组锁
     */
    private void releaseMultiLock() {
        if (null != multicastLock) {
            multicastLock.release();
        }
    }


    private void SendMSearchMessage() {
        acquireMultiLock();
        SSDPSearchMsg searchMsg = new SSDPSearchMsg(SSDPConstants.ALL);
        SSDPSocket sock = null;
        try {
            //发送
            sock = new SSDPSocket();
            sock.send(searchMsg.toString());
            Log.i(TAG, "要发送的消息为:" + searchMsg.toString());
            //接收
            listReceive.clear();
            while (true) {
                DatagramPacket dp = sock.receive(); // Here, I only receive the same packets I initially sent above
                String c = new String(dp.getData()).trim();
                String ip = dp.getAddress().toString().trim();
                Log.e(TAG, "接收到的消息为:\n" + c + "\n来源IP地址:" + ip);
                //接收时候一遍后,直接跳出循环
                if (listReceive.contains(c)) break;
                else listReceive.add(c);
            }
            sock.close();
            releaseMultiLock();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //显示接收结果
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < listReceive.size(); i++) {
                    sb.append(i).append("\r\t").append(listReceive.get(i))
                            .append(NEWLINE).append("-----------------------").append(NEWLINE);
                }
                String s = sb.toString();
                tvReceive.setText(s);
                Log.d(TAG, "result = " + s);
            }
        });
    }
}

布局文件activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnSendSSDPSearch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="搜寻局域网内设备" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tv_show_receive"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:text="搜寻结果" />
    </ScrollView>
</LinearLayout>
public class SSDPSocket {

    private MulticastSocket multicastSocket;
    private InetAddress inetAddress;

    public SSDPSocket() throws IOException {
        //默认地址和端口:port: 1900,  address:239.255.255.250
        multicastSocket = new MulticastSocket(SSDPConstants.PORT); // Bind some random port for receiving datagram
        inetAddress = InetAddress.getByName(SSDPConstants.ADDRESS);
        multicastSocket.joinGroup(inetAddress);
    }

    /* Used to send SSDP packet */
    public void send(String data) throws IOException {
        DatagramPacket dp = new DatagramPacket(data.getBytes(), data.length(), inetAddress, SSDPConstants.PORT);
        multicastSocket.send(dp);
    }

    /* Used to receive SSDP packet */
    public DatagramPacket receive() throws IOException {
        byte[] buf = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buf, buf.length);
        multicastSocket.receive(dp);
        return dp;
    }

    public void close() {
        if (multicastSocket != null) {
            multicastSocket.close();
        }
    }
}
/**
 * Msg的实体类,格式详见toString()
 */
public class SSDPSearchMsg {
    private int mMX = 5; /* seconds to delay response */
    private String mST; /* Search target */

    public SSDPSearchMsg(String ST) {
        mST = ST;
    }

    public int getmMX() {
        return mMX;
    }

    public void setmMX(int mMX) {
        this.mMX = mMX;
    }

    public String getmST() {
        return mST;
    }

    public void setmST(String mST) {
        this.mST = mST;
    }

    /**
     * @ruturn 发送格式:
     * M-SEARCH * HTTP/1.1
     * Host:239.255.255.250:1900
     * Man:"ssdp:discover"
     * MX:5
     * ST:miivii
     */
    @Override
    public String toString() {
        StringBuilder content = new StringBuilder();
        content.append(SL_M_SEARCH).append(NEWLINE);
        content.append(HOST).append(NEWLINE);
        content.append(MAN).append(NEWLINE);
        content.append("MX:" + mMX).append(NEWLINE);
        content.append(mST).append(NEWLINE);
        content.append(NEWLINE);
        return content.toString();
    }
}
public class SSDPConstants {
    /* New line definition */
    public static final String ADDRESS = "239.255.255.250";
    public static final int PORT = 1900;
    public static final String SL_OK = "HTTP/1.1 200 OK";
    public static final String SL_M_SEARCH = "M-SEARCH * HTTP/1.1";
    public static final String HOST = "Host:" + ADDRESS + ":" + PORT;
    public static final String MAN = "Man:\"ssdp:discover\"";
    public static final String NEWLINE = "\r\n";
    public static final String ST_Product = "ST:urn:schemas-upnp-org:device:Server:1";
    public static final String Found = "ST=urn:schemas-upnp-org:device:";
    public static final String Root = "ST: urn:schemas-upnp-org:device:Server:1";
    public static final String ALL = "ST:miivii";
}
搜寻打印结果
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值