java在windows上获取网卡的mac地址

1. 写在前面

网上的解决方法大致就是2种,第1种是通过命令行的“ipconfig all”,然后通过Runtime.getRuntime().exec(command)执行该命令,再去输入流中读取执行命令打印输出的内容,通过找到“物理地址”或该英文字样去匹配,然后拿到mac地址。这种方法的缺点是,字样可能是中文也可能是英文,而且不同的系统输出的内容可能或不一样,用字符串匹配去找,不是特别通用,但仍能解决问题。第2种方式是Java的NetworkInterface、InetAddress等接口,网上有很多用这个的方法,但都千篇一律,其实真正能用的代码,我几乎找不到。因为实际上一台机器上可能有多张网卡,每张网卡又可能有多个网络接口,每个网络接口又可能有多个IP地址(IP接口),(我的理解是这样的,有不对的地方,欢迎指出)网上大多数的写法如下:

InetAddress ia = InetAddress.getLocalHost();//获取本地IP对象  
//获得网络接口对象(即网卡),并得到mac地址,mac地址存在于一个byte数组中。  
byte[] mac = NetworkInterface.getByInetAddress(ia).getHardwareAddress();  
//下面代码是把mac地址拼装成String  
StringBuffer sb = new StringBuffer();  
for(int i=0;i<mac.length;i++){ 
	if(i!=0)
		sb.append("-");  

    //mac[i] & 0xFF 是为了把byte转化为正整数  
    String s = Integer.toHexString(mac[i] & 0xFF);  
    sb.append(s.length()==1?0+s:s);  
}  
//把字符串所有小写字母改为大写成为正规的mac地址并返回  
return sb.toString().toUpperCase();  

这会有什么问题呢?

首先InetAddress.getLocalHost()获得不一定就是正确的IP(连接上网的那个IP),前面已经说明了,机器上物理的、虚拟的网络接口很多,IP可能就更多了。由于我笔记本上装了VirtualBox,经我测试,我用这个方法拿到的IP的是我虚拟机的IP:192.168.56.1,而这正是下面虚拟网卡的ip地址,如下图:


所以断然使用这种方法拿到的IP根本就不一定满足你的需求,后面的结果当然也就是错的。

而就我机器上的网路接口有多少呢?我测试了一下,竟有65个之多,当然并不是每一个都可用。如下图:

  

测试代码如下:

public static void test() throws UnknownHostException, SocketException{
	//InetAddress ia = InetAddress.getLocalHost();
	//System.out.println(ia.getHostAddress());
	
	Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
	int cnt = 0;
	while(interfaces.hasMoreElements()){
		cnt += 1;
		NetworkInterface ni = interfaces.nextElement();
		System.out.println("网络接口名称:"+ni.getName());
		System.out.println("网络接口的显示名称:"+ni.getDisplayName());
	}
	System.out.println(cnt);
}
所以再用这些API的时候,你需要先理解NetworkInterface、InetAddress和InterfaceAddress等这些概念,以及他们的相关方法都返回什么,不要盲目的乱写。其实从他们的一些方法就可以知道,比如NetworkInterface.getNetworkInterfaces()返回的不是一个网络接口,而是所有的网络接口,而一个网路接口有方法interface.getInetAddresses():Enumeration<InetAddress>和interface.getInterfaceAddresses:List<InterfaceAddress>就可以知道,每个网络接口上同样不只一个ip接口地址。针对这些问题,我将提出后面的几种解决方法供大家参考,但每个人的需求不同,例如我要得到的是连接上网的那个网络接口的mac地址,大家应针对自己的需求进行调整,主要还是要深入理解这些接口。网上的代码很多,但还是自己亲自测试一下。

这里可以进一步了解这些接口方法:http://www.cnblogs.com/guangshan/p/4712550.htmlJava API研究:获取本地环境所有网卡及每个网卡的所有网络配置

2.代码实现获取mac地址

法1:

如果能提供网卡名称,那么就最方便了。我的连接上网的网卡(准确叫网络接口)名称是eth7,所以我可以用下面的方法,直接得到该网络接口的mac地址:

/*
* 通过网卡名称获取该网卡的mac地址
* @param networkCardName 网卡名,如eth7 
* @return mac地址字符串:a0-xx-xx-xx-xx-cb
*/
public static String getMacAddressByName(String networkCardName) throws SocketException {
	NetworkInterface ni = NetworkInterface.getByName(networkCardName);
	byte[] mac = ni.getHardwareAddress();
	//byte->int->16进制->string
	StringBuffer sb = new StringBuffer();
	for (int i = 0; i < mac.length; i++) {
		if (i != 0)
			sb.append("-");
		String tmp = Integer.toHexString(mac[i] & 0xFF);// 将byte转为正整数。然后转为16进制数
		sb.append(tmp.length() == 1 ? 0 + tmp : tmp);
	}
	System.out.println("测试macAddress:"+sb.toString().toLowerCase());
	return sb.toString().toLowerCase();
}
执行结果:

法2:

当不确定网卡名字时,要获得连接上网的网卡mac地址。通过traceroute的第一跳获得默认网关,然后获取本机的所有网络接口(NetworkInterface)以及每个网络接口的接口地址(InetAddress),通过对比接口地址IP与网关是不是在同一个网段下(根据网络前缀即子网掩码来验证),来判定这个接口地址ip所在的网络接口(NetworkInterface)是连接上网的那个接口,然后他的mac地址就是我们要找的。

	/*
	 * 通过traceroute的第一跳获得默认网关,然后获取本机的所有网络接口(NetworkInterface)以及每个网络接口的接口地址(InetAddress),
	 * 通过对比接口地址IP与网关是不是在同一个网段下(根据网络前缀即子网掩码来验证),来判定这个接口地址ip所在的网络接口(NetworkInterface)是连接上网的那个接口,然后他的mac地址就是我们要找的
	 * @return Set<String>:(a0-xx-xx-xx-xx-cb,0c-xx-xx-xx-xx-xx)
	 */
	public static Set<String> getLocalMacAddress2() throws Exception{
		Set<String> macSet = new HashSet<String>();
		InetAddress ip = null;
		String gateway = getGateway();
		
		Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
		while(interfaces.hasMoreElements()){
			NetworkInterface ni = interfaces.nextElement();
			if(!ni.isVirtual() && !ni.isLoopback() && ni.isUp()){
				List<InterfaceAddress> infs = ni.getInterfaceAddresses();
				for(InterfaceAddress inf : infs){
					ip = inf.getAddress();
					if(ip!=null && ip instanceof Inet4Address && !ip.isLoopbackAddress()){
						if(ip.isSiteLocalAddress()){
							String ipStr = ip.getHostAddress();
							//获得网络前缀
							short maskLen = inf.getNetworkPrefixLength();
							if(getNetworkNumber(ipStr, maskLen).equals(getNetworkNumber(gateway, maskLen))){//对比是否在同一个网段
								//byte->int->16进制->string
								byte[] mac = ni.getHardwareAddress();
								StringBuffer sb = new StringBuffer();
								for(int i=0; i<mac.length; i++){
									if(i!=0)
										sb.append("-");
									String tmp = Integer.toHexString(mac[i]&0xFF);//将byte转为正整数。然后转为16进制数
									sb.append(tmp.length()==1?0+tmp:tmp);
								}
								System.out.println("网络接口名称ni:"+ni.getName()+"---"+"mac地址:"+sb.toString().toLowerCase()+"---"+"ip地址:"+ipStr);
								macSet.add(sb.toString().toLowerCase());
							}
						}	
					}
				}
			}
		}
		return macSet;
	}

        /*
	 * 通过traceroute的第一跳获得网关,缺点:没有连接网络时获取不到
	 * @return String 网关
	 */
	public static String getGateway(){
		String os = System.getProperty("os.name");
		
		if (os != null && os.startsWith("Windows")) {
			try{
				String command = "tracert -d www.baidu.com";
				Process p = Runtime.getRuntime().exec(command);
				BufferedReader br = new BufferedReader(new InputStreamReader(
						p.getInputStream()));
				String line;
				
				String[] tmp = null;
				while ((line = br.readLine()) != null){
					if(line.trim().startsWith("1")){
						tmp = line.trim().split("\\s+");
						if(tmp.length>0 && tmp[0].equals("1")){
							System.out.println("网关:"+tmp[tmp.length-1]);
							return tmp[tmp.length-1];
						}
					}
				}
				br.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return null;
	}
验证:


	/*
	 * 获得某ip地址所在网络的网络号
	 * @param ip 
	 * @param masklen 掩码长度
	 * @return 网络号
	 */
	public static String getNetworkNumber(String ip, short masklen){
		String[] ipStr = ip.split("\\.");
		StringBuffer sb = new StringBuffer();
		sb.append(ipStr[0]);
		for(int i=1; i<ipStr.length; i++){
			if(i<masklen/8)
				sb.append("."+ipStr[i]);
			else
				sb.append(".0");
		}
		return sb.toString();
	}
运行结果:


这里看到了2个mac地址,为什么呢?因为我的笔记本同时通过有线和wifi都连接上了网络


而这个运行结果也恰好证明了我前面说的,机器可以有多个网络接口,他们可以同时连接上网,他们都是有效的,也正处于工作中

另一个要说明的就是这种方法有个缺点,如果没有网络连接时,traceroute命令会获取不到网关,即没有第一跳,如下图:


法3:

通过路由表找到目标网络是"0.0.0.0"的那一条记录(通常是第一条),即为默认网关,然后找到连接默认网关的ip接口,通过这些ip接口(InetAddress),对应上他们所在的网络接口(NetworkInterface),然后获取该网络接口的mac地址即为所求。

可以先看一下路由表:route print


从此时的路由也可以看出,此时有2个ip接口都连接了默认网关,即两个接口都正在连接上网。与法2的结果一致。从这个路由表,我们可以得到网关、连接上网的IP接口,然后通过这些IP接口创建NetworkInterface的实例,继而跟前面几种方法一样,得到相应的mac地址。

	/*
	 * 通过路由表找到连接默认网关的ip接口,通过这些ip接口(InetAddress),对应上他们所在的网络接口(NetworkInterface),然后获取网络接口的mac地址
	 * @return Set<String>:(a0-xx-xx-xx-xx-cb,0c-xx-xx-xx-xx-39)
	 */
	public static Set<String> getLocalMacAddress() throws UnknownHostException, SocketException {
		Set<String> macSet = new HashSet<String>();
		
		for(Object ipAndGatewayObj : getIpAndGateway()){
			//String gateway = ((JSONObject)ipAndGatewayObj).get("gateway").toString();
			String ip = ((JSONObject)ipAndGatewayObj).get("ip").toString();
			InetAddress ia = InetAddress.getByName(ip);
			NetworkInterface ni = NetworkInterface.getByInetAddress(ia);
			byte[] mac = ni.getHardwareAddress();
			//byte->int->16进制->string
			StringBuffer sb = new StringBuffer();
			for (int i = 0; i < mac.length; i++) {
				if (i != 0)
					sb.append("-");
				String tmp = Integer.toHexString(mac[i] & 0xFF);// 将byte转为正整数。然后转为16进制数
				sb.append(tmp.length() == 1 ? 0 + tmp : tmp);
			}
			System.out.println("网络接口名称ni:"+ni.getName()+"---"+"mac地址:"+sb.toString().toLowerCase()+"---"+"ip地址:"+ip);
			macSet.add(sb.toString().toLowerCase());
		}
		return macSet;

	}
	/*
	 * 通过路由表的目的网络是'0.0.0.0'获得网关
	 * @return List<JSONObject> (网关,ip)
	 */
	public static List getIpAndGateway(){
		String os = System.getProperty("os.name");
		List<JSONObject> netInfoList = new ArrayList<JSONObject>();
		if (os != null && os.startsWith("Windows")) {
			try{
				String command = "route print";
				Process p = Runtime.getRuntime().exec(command);
				BufferedReader br = new BufferedReader(new InputStreamReader(
						p.getInputStream()));
				String line;
				
				String[] tmp = null;
				JSONObject netInfo = null;
				while ((line = br.readLine()) != null) {
					tmp = line.trim().split("\\s+");
					if (tmp.length > 0 && tmp[0].equals("0.0.0.0")) {
						netInfo = new JSONObject();
						netInfo.put("gateway", tmp[2]);
						netInfo.put("ip", tmp[3]);
						netInfoList.add(netInfo);
					}
				}
				br.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return netInfoList;
	}
运行结果与法2一致:





评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值