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一致:





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