在局域网中,分配给设备的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连接通讯!