一.什么是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";
}