1. 程式人生 > >SNMP4J 處理中文資訊時的問題

SNMP4J 處理中文資訊時的問題

  • 首先說,多數時候你並不會遇到SNMP中文問題
多數網路裝置和網管軟體都是西文的.這個"多數"的意思是95%以上.
目前有可能遇到的中文SNMP問題的場景:
1. 一些國產網路裝置(別提華為,華為路由器的多數網管資訊也是西文的)
2. Windows平臺的SNMP服務

3. 一些國產網管軟體

  • SNMP4J處理中文時的問題:當遇到中文或者編碼值大於 0x80 的字元時,就會直接以十六機制數輸出
現象:
   Vector<VariableBinding> responseVB = respEvent.getResponse().getVariableBindings();
                     
   if(1==responseVB.size()){
     tempV = responseVB.elementAt(0);                                             
     System.out.println(tempV.getOid().toString()+":"+tempV.getVariable());
     System.out.println(tempV.getVariable().getSyntaxString());
                            }
執行程式碼:
1.3.6.1.2.1.2.2.1.2.1:4d:53:20:54:43:50:20:4c:6f:6f:70:62:61:63:6b:20:69:6e:74:65:72:66:61:63:65:00
OCTET STRING
1.3.6.1.2.1.2.2.1.2.2:56:4d:77:61:72:65:20:56:69:72:74:75:61:6c:20:45:74:68:65:72:6e:65:74:20:41:64:61:70:74:65:72:20:66:6f:72:20:56:4d:6e:65:74:38:00
OCTET STRING
1.3.6.1.2.1.2.2.1.2.3:56:4d:77:61:72:65:20:56:69:72:74:75:61:6c:20:45:74:68:65:72:6e:65:74:20:41:64:61:70:74:65:72:20:66:6f:72:20:56:4d:6e:65:74:31:00
OCTET STRING
1.3.6.1.2.1.2.2.1.2.4:52:65:61:6c:74:65:6b:20:52:54:4c:38:31:33:39:20:46:61:6d:69:6c:79:20:50:43:49:20:46:61:73:74:20:45:74:68:65:72:6e:65:74:20:4e:49:43:20:2d:20:ca:fd:be:dd:b0:fc:bc:c6:bb:ae:b3:cc:d0:f2:ce:a2:d0:cd:b6:cb:bf:da:00
OCTET STRING
1.3.6.1.2.1.2.2.1.2.65542:41:53:55:53:20:38:30:32:2e:31:31:62:2f:67:20:57:69:72:65:6c:65:73:73:20:4c:41:4e:20:43:61:72:64:20:2d:20:ca:fd:be:dd:b0:fc:bc:c6:bb:ae:b3:cc:d0:f2:ce:a2:d0:cd:b6:cb:bf:da:00
OCTET STRING   
  • 原因:問題出在SNMP4J的基礎字串類OctetString上。
(SNMP4J體系下的字串是以OctetString的形式而不是String形式存在和處理的。各種和字串有關的資源如community,version,user,password等都是基於OctetString)
問題出在OctetString類的toString方法:
public class OctetString extends AbstractVariable
    implements AssignableFromByteArray, AssignableFromString {
  
    public String toString() {
        if (isPrintable()
) {
          return new String(value);
        }
        return toHexString();
        // 沒通過isPrintable()判斷的,如ASCII控制字元,漢字等,都以16進位制顯示
      }
  public boolean isPrintable() {
    for (int i=0; i<value.length; i++) {
      char c = (char)value[i];
      if ((Character.isISOControl(c) ||
          ((value[i] & 0xFF) >= 0x80)) && (!Character.isWhitespace(c))) {
          // 判斷其是否大於0x80 (即通常所說的大於128的ASCII碼,漢字編碼都在這個區間內
        return false;
      }
    }
    return true;
  }


  • 解決法1:網上有人給的建議是直接把OctetString類裡toString方法裡的if (isPrintable())部分取消掉,就是用基本的String轉換
  public String toString() {
      return new String(value);

  }
測試:
1.3.6.1.2.1.2.2.1.2.1:MS TCP Loopback interface
OCTET STRING
1.3.6.1.2.1.2.2.1.2.2:VMware Virtual Ethernet Adapter for VMnet8
OCTET STRING
1.3.6.1.2.1.2.2.1.2.3:VMware Virtual Ethernet Adapter for VMnet1
OCTET STRING
1.3.6.1.2.1.2.2.1.2.4:Realtek RTL8139 Family PCI Fast Ethernet NIC - 資料包計劃程式微型埠
OCTET STRING
1.3.6.1.2.1.2.2.1.2.65542:ASUS 802.11b/g Wireless LAN Card - 資料包計劃程式微型埠
OCTET STRING

這種方法更不可取,因為isPrintable()方法是用於判斷可顯示字元的,其中不止判斷大於0x80,還有包括控制字元的判斷(0-31,127-159)
(codePoint >= 0x0000 && codePoint <= 0x001F) || (codePoint >= 0x007F && codePoint <= 0x009F);


  • 解決法2:克服法1的缺陷,toString()不變,只修改isPrintable()
public String toString() {
        if (isPrintable()) {
          return new String(value);
        }
        return toHexString();
      }
  public boolean isPrintable() {
    for (int i=0; i<value.length; i++) {
      char c = (char)value[i];
      int codePoint =  (int)value[i];
      if (((codePoint > 0x0000 && codePoint <= 0x001F) ||
           (codePoint >= 0x007F && codePoint <= 0x009F)) && (!Character.isWhitespace(c))) {
        return false;
      }
    }
    return true;
  }

注意:上面codePoint變數的操作,基本就是Character.isISOControl(c)的操作
 但windows 平臺的snmp資訊會以‘\0'結尾,這樣windows平臺任何snmp資訊都通不過Character.isISOControl(c)的判斷
 所以必須把Character.isISOControl(c)中的codePoint >= 0x0000改成codePoint > 0x0000


  • 解決法3:在國外的論壇上,有的比較死板的人要求按正規的“繼承+覆寫方法”的方式做修改,這樣能保證原始碼的不被改變
其實仔細想想,完全不修改原始碼是很難的,所謂“牽一髮而動全身”。

既然是開源的程式碼,該修改就修改.

解決法修改後,獲取mac地址有問題,出現亂碼。最好在出現中文的地方呼叫此方法:

 public static String getChinese(String octetString) {    //snmp4j遇到中文直接轉成16進位制字串
        try {
            String[] temps = octetString.split(":");
            byte[] bs = new byte[temps.length];
            for (int i = 0; i < temps.length; i++)
                bs[i] = (byte) Integer.parseInt(temps[i], 16);


            return new String(bs, "GB2312");
        } catch (Exception e) {
            return null;
        }
    }