2014年12月29日

話務量 電話流量單位 erlang

erlang 是話務量的單位,簡單地說,是表示每小時連續的總通話長度數量,我們可根據此話務量的數值,作為設計電話通訊網路流量的規模參考數值。

由來

在 1946 年,CCITT(International Telegraph and Telephone Consultative Committee) 也就是現在的 ITU-T (International Telecommunication Union-Telecommunication Standardization Sector) 將電話話務流量命名為 erlang,命名來自丹麥數學家及統計學家 Agner Krarup Erlang

A.K. Erlang 是在 1878 年出生於丹麥,是電信流量理論的專家,在 1909年發表了第一個著作: The Theory of Probabilities and Telephone Conversations。1929年過世後,到了1940年代,erlang 這個電信流量計算公式被大家廣為接受。

erlang 計算範例

如果一條電話線被佔用 1 小時,話務量就是 1 erlang。
如果一條電話線被佔用 0.5 小時,話務量是 0.5 erlang。

假設有一組使用者,在一個小時內產生了30通電話,平均每一通電話持續了 5 分鐘,那麼 erlang 值可以按照以下步驟計算:

  1. 一小時內的電話流量總時間 = 通話數量 (BHCA) X 平均每一通電話的通話時間 (Holding Time, hrs)
  2. 通話數量為 30
  3. 平均每一通電話的通話時間為 5/60 (hrs)
  4. 30 * 5 / 60 = 2.5

因此在這個範例中,erlang 值就是 2.5。

Traffic Models

當取得 erlang 數值後,就可以套用到話務模型中,用以計算電話系統必須要提供幾條外撥線路給這一群使用者使用。

目前有三種話務模型,其中以 Erlang B 最為常被使用

  1. Erlang B
  2. Extended Erlang B
  3. Erlang C

Erlang B 有三個參數

  1. BHT: Busy Hour Traffic (in Erlangs)
    這就是剛剛計算出來的 erlang 數值,要以此電話系統最忙碌的那一個小時內的資料,計算出此 erlang 數值。
  2. Blocking
    這是因為外撥線路不足,發生無法外撥電話的錯誤通話數,如果是0.01,就表示每 100 通電話,會發生 1 次無法外撥電話的錯誤。
  3. Lines
    就是我們需要知道的計算結果,在這一組電話系統中,要提供幾條外撥線路的數量。

Erlang B 線上計算頁面 可計算出結果,如果是以 BHT = 2.5,Blocking = 0.01 去計算,可得到 Lines 結果為 7 。

Trunking 中繼

Trunking 中繼是兩點間的一條傳輸通道,通常兩點都是電話交換中心。Trunking 在以前是利用實體線路將兩點連接起來,隨著科技發展,中繼的概念不僅應用於無線通信中,基於網際網路的電信交換機的trunk也可以稱為一種中繼。

Erlang Traffic Model 中計算得到的結果 Lines,就是用在連接兩個電話交換中心之間的 trunking 能夠支援的同時通話線路數量。

參考資料

  1. wiki: Erlang (unit)
  2. What is erlang
  3. 話務量
  4. 什麼是Erlang B公式,什麼是Erlang C公式?

2014年12月22日

如何使用 snmp4j 處理 trap

以下以 snmp4j 套件提供處理 trap 的範例。

multithread trap receiver

trap receiver 必須在本機建立一個 UDP Port 162 的 server,一直等待 client 端發送 UDP trap,在處理這樣的 server時,multithread 是必要的。

程式範例

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.CommandResponder;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.MessageDispatcherImpl;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.mp.MPv3;
import org.snmp4j.security.AuthMD5;
import org.snmp4j.security.PrivDES;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.security.UsmUser;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TcpAddress;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.MultiThreadedMessageDispatcher;
import org.snmp4j.util.ThreadPool;

public class SNMPTrapReceiver implements CommandResponder {
    public static Logger logger = LoggerFactory
            .getLogger(SNMPTrapReceiver.class.getName());

    private MultiThreadedMessageDispatcher dispatcher;
    private Snmp snmp = null;
    private Address listenAddress;
    private ThreadPool threadPool;
    private int n = 0;
    private long start = -1;

    public SNMPTrapReceiver() {
    }

    public static void main(String[] args) {
        new SNMPTrapReceiver().run();
    }

    private void run() {
        try {
            init();
            snmp.addCommandResponder(this);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void init() throws UnknownHostException, IOException {
        threadPool = ThreadPool.create("Trap", 10);
        dispatcher = new MultiThreadedMessageDispatcher(threadPool,
                new MessageDispatcherImpl());
        listenAddress = GenericAddress.parse(System.getProperty(
                "snmp4j.listenAddress", "udp:0.0.0.0/162"));
        TransportMapping<?> transport;
        if (listenAddress instanceof UdpAddress) {
            transport = new DefaultUdpTransportMapping(
                    (UdpAddress) listenAddress);
        } else {
            transport = new DefaultTcpTransportMapping(
                    (TcpAddress) listenAddress);
        }
        USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(
                MPv3.createLocalEngineID()), 0);
        usm.setEngineDiscoveryEnabled(true);

        snmp = new Snmp(dispatcher, transport);
        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1());
        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());
        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv3(usm));
        SecurityModels.getInstance().addSecurityModel(usm);
        snmp.getUSM().addUser(
                new OctetString("MD5DES"),
                new UsmUser(new OctetString("MD5DES"), AuthMD5.ID,
                        new OctetString("UserName"), PrivDES.ID,
                        new OctetString("PasswordUser")));
        snmp.getUSM().addUser(new OctetString("MD5DES"),
                new UsmUser(new OctetString("MD5DES"), null, null, null, null));

        snmp.listen();
    }

    public void processPdu(CommandResponderEvent event) {
        if (start < 0) {
            start = System.currentTimeMillis() - 1;
        }
        n++;
        if ((n % 100 == 1)) {
            logger.info("Processed "
                    + (n / (double) (System.currentTimeMillis() - start))
                    * 1000 + "/s, total=" + n);
        }

        StringBuffer msg = new StringBuffer();
        msg.append(event.toString());
        Vector<? extends VariableBinding> varBinds = event.getPDU()
                .getVariableBindings();
        if (varBinds != null && !varBinds.isEmpty()) {
            Iterator<? extends VariableBinding> varIter = varBinds.iterator();
            while (varIter.hasNext()) {
                VariableBinding var = varIter.next();
                msg.append(var.toString()).append(";");
            }
        }
        logger.info("Message Received: " + msg.toString());
    }
}

這是發送 trap 的範例,有區分 snmp v1, v2c, v3 三種協定。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.CommandResponder;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.ScopedPDU;
import org.snmp4j.Snmp;
import org.snmp4j.TransportMapping;
import org.snmp4j.UserTarget;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.SecurityLevel;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.security.UsmUser;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.IpAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.DefaultPDUFactory;

public class SNMPTrapGeneratorClient {
    public static Logger logger = LoggerFactory
            .getLogger(SNMPTrapGeneratorClient.class.getName());

    private static final String community = "public";
    private static final String trapOid = ".1.3.6.1.2.1.1.6";
    private static final String ipAddress = "127.0.0.1";
    private static final int port = 162;

    public static void main(String args[]) {
        sendSnmpV1V2Trap(SnmpConstants.version1);
        sendSnmpV1V2Trap(SnmpConstants.version2c);
        sendSnmpV3Trap();
    }

    /**
     * This methods sends the V1/V2 trap
     * 
     * @param version
     */
    private static void sendSnmpV1V2Trap(int version) {
        // send trap
        sendV1orV2Trap(version, community, ipAddress, port);
    }

    private static PDU createPdu(int snmpVersion) {
        PDU pdu = DefaultPDUFactory.createPDU(snmpVersion);
        if (snmpVersion == SnmpConstants.version1) {
            pdu.setType(PDU.V1TRAP);
        } else {
            pdu.setType(PDU.TRAP);
        }
        pdu.add(new VariableBinding(SnmpConstants.sysUpTime));
        pdu.add(new VariableBinding(SnmpConstants.snmpTrapOID, new OID(trapOid)));
        pdu.add(new VariableBinding(SnmpConstants.snmpTrapAddress,
                new IpAddress(ipAddress)));
        pdu.add(new VariableBinding(new OID(trapOid), new OctetString("Major")));
        return pdu;
    }

    private static void sendV1orV2Trap(int snmpVersion, String community,
            String ipAddress, int port) {
        try {
            // create v1/v2 PDU
            PDU snmpPDU = createPdu(snmpVersion);

            // Create Transport Mapping
            TransportMapping<?> transport = new DefaultUdpTransportMapping();
            transport.listen();

            // Create Target
            CommunityTarget comtarget = new CommunityTarget();
            comtarget.setCommunity(new OctetString(community));
            comtarget.setVersion(snmpVersion);
            comtarget.setAddress(new UdpAddress(ipAddress + "/" + port));
            comtarget.setRetries(2);
            comtarget.setTimeout(5000);

            // Send the PDU
            Snmp snmp = new Snmp(transport);
            snmp.send(snmpPDU, comtarget);
            logger.info("Sent Trap to (IP:Port)=> " + ipAddress + ":" + port);
            snmp.close();
        } catch (Exception e) {
            logger.error("Error: ", e);
        }
    }

    /**
     * Sends the v3 trap
     */
    private static void sendSnmpV3Trap() {
        try {
            Address targetAddress = GenericAddress.parse("udp:" + ipAddress
                    + "/" + port);
            TransportMapping<?> transport = new DefaultUdpTransportMapping();
            Snmp snmp = new Snmp(transport);
            USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(
                    MPv3.createLocalEngineID()), 0);
            SecurityModels.getInstance().addSecurityModel(usm);
            transport.listen();

            snmp.getUSM().addUser(
                    new OctetString("MD5DES"),
                    new UsmUser(new OctetString("MD5DES"), null, null, null,
                            null));

            // Create Target
            UserTarget target = new UserTarget();
            target.setAddress(targetAddress);
            target.setRetries(1);
            target.setTimeout(11500);
            target.setVersion(SnmpConstants.version3);
            target.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV);
            target.setSecurityName(new OctetString("MD5DES"));

            // Create PDU for V3
            ScopedPDU pdu = new ScopedPDU();
            pdu.setType(ScopedPDU.NOTIFICATION);
            pdu.add(new VariableBinding(SnmpConstants.sysUpTime));
            pdu.add(new VariableBinding(SnmpConstants.snmpTrapOID,
                    SnmpConstants.linkDown));
            pdu.add(new VariableBinding(new OID(trapOid), new OctetString(
                    "Major")));

            // Send the PDU
            snmp.send(pdu, target);
            logger.info("Sending Trap to (IP:Port)=> " + ipAddress + ":"
                    + port);
            snmp.addCommandResponder(new CommandResponder() {
                public void processPdu(CommandResponderEvent arg0) {
                    logger.info(arg0);
                }
            });
            snmp.close();
        } catch (Exception e) {
            logger.error("Error: ", e);
        }
    }
}

執行結果

trap 發送端,很單純地就直接把 trap 送出去

2014-12-08 10:43:51,195 [main] INFO  SNMPTrapGeneratorClient 91
 Sent Trap to (IP:Port)=> 127.0.0.1:162
2014-12-08 10:43:51,204 [main] INFO  SNMPTrapGeneratorClient 91
 Sent Trap to (IP:Port)=> 127.0.0.1:162
2014-12-08 10:43:51,224 [main] INFO  SNMPTrapGeneratorClient 137
 Sending Trap to (IP:Port)=> 127.0.0.1:162

trap 接收端

2014-12-08 10:43:51,279 [Trap.1] INFO  SNMPTrapReceiver 102
 Processed 1000.0/s, total=2
2014-12-08 10:43:51,279 [Trap.0] INFO  SNMPTrapReceiver 118
 Message Received: CommandResponderEvent[securityModel=1, securityLevel=1, maxSizeResponsePDU=65535, pduHandle=PduHandle[0], stateReference=StateReference[msgID=0,pduHandle=PduHandle[0],securityEngineID=null,securityModel=null,securityName=public,securityLevel=1,contextEngineID=null,contextName=null,retryMsgIDs=null], pdu=V1TRAP[reqestID=0,timestamp=0:00:00.00,enterprise=0.0,genericTrap=0,specificTrap=0, VBS[1.3.6.1.2.1.1.3.0 = Null; 1.3.6.1.6.3.1.1.4.1.0 = 1.3.6.1.2.1.1.6; 1.3.6.1.6.3.18.1.3.0 = 127.0.0.1; 1.3.6.1.2.1.1.6 = Major]], messageProcessingModel=0, securityName=public, processed=false, peerAddress=127.0.0.1/51136, transportMapping=org.snmp4j.transport.DefaultUdpTransportMapping@13fdb89, tmStateReference=null]1.3.6.1.2.1.1.3.0 = Null;1.3.6.1.6.3.1.1.4.1.0 = 1.3.6.1.2.1.1.6;1.3.6.1.6.3.18.1.3.0 = 127.0.0.1;1.3.6.1.2.1.1.6 = Major;
2014-12-08 10:43:51,286 [Trap.1] INFO  SNMPTrapReceiver 118
 Message Received: CommandResponderEvent[securityModel=2, securityLevel=1, maxSizeResponsePDU=65535, pduHandle=PduHandle[1338161657], stateReference=StateReference[msgID=0,pduHandle=PduHandle[1338161657],securityEngineID=null,securityModel=null,securityName=public,securityLevel=1,contextEngineID=null,contextName=null,retryMsgIDs=null], pdu=TRAP[requestID=1338161657, errorStatus=Success(0), errorIndex=0, VBS[1.3.6.1.2.1.1.3.0 = Null; 1.3.6.1.6.3.1.1.4.1.0 = 1.3.6.1.2.1.1.6; 1.3.6.1.6.3.18.1.3.0 = 127.0.0.1; 1.3.6.1.2.1.1.6 = Major]], messageProcessingModel=1, securityName=public, processed=false, peerAddress=127.0.0.1/51138, transportMapping=org.snmp4j.transport.DefaultUdpTransportMapping@13fdb89, tmStateReference=null]1.3.6.1.2.1.1.3.0 = Null;1.3.6.1.6.3.1.1.4.1.0 = 1.3.6.1.2.1.1.6;1.3.6.1.6.3.18.1.3.0 = 127.0.0.1;1.3.6.1.2.1.1.6 = Major;
2014-12-08 10:43:51,312 [Trap.2] INFO SNMPTrapReceiver 118
 Message Received: CommandResponderEvent[securityModel=3, securityLevel=1, maxSizeResponsePDU=65428, pduHandle=PduHandle[1828414888], stateReference=null, pdu=TRAP[{contextEngineID=80:00:13:70:01:c0:a8:01:39:e2:ca:22:3e, contextName=}, requestID=1828414888, errorStatus=0, errorIndex=0, VBS[1.3.6.1.2.1.1.3.0 = Null; 1.3.6.1.6.3.1.1.4.1.0 = 1.3.6.1.6.3.1.1.5.3; 1.3.6.1.2.1.1.6 = Major]], messageProcessingModel=3, securityName=MD5DES, processed=false, peerAddress=127.0.0.1/51139, transportMapping=org.snmp4j.transport.DefaultUdpTransportMapping@13fdb89, tmStateReference=null]1.3.6.1.2.1.1.3.0 = Null;1.3.6.1.6.3.1.1.4.1.0 = 1.3.6.1.6.3.1.1.5.3;1.3.6.1.2.1.1.6 = Major;

Reference

Trap Receiver

2014年12月15日

如何使用 snmp4j 進行查詢

SNMP 的原理跟概念 分為 Agent 與 Client 兩端需要處理,如果不是提供機器與服務的,就不需要做 Agent 這個 Server,一般最基本的 SNMP 程式設計是撰寫 SNMP Client,Client 又有兩個部份 (1) SNMP GET, Walk (2) Trap Receiver,以下以 snmp4j 套件提供第一個部份 SNMP GET, Walk 的範例。

SNMP GET

給予 server ip,這邊刻意將程式區分為 connect, snmpget, close 三個部份,因為 UDP 本身是 connection less 的一種網路連線,未來在使用時,只要連結起來,應該就能持續發送 SNMP GET,並取得結果。

snmpGet 有兩種 method,第一種就單純地只接受一個 oid 參數,第二種,是接受一個 oid List,可一次查詢多個 oid 的結果。

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.Null;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;

public class SnmpGet {
    public static Logger logger = LoggerFactory.getLogger(SnmpGet.class
            .getName());

    private static int version = SnmpConstants.version1;
    private static String protocol = "udp";

    private static CommunityTarget target = null;
    private static DefaultUdpTransportMapping udpTransportMapping = null;
    private static Snmp snmp = null;

    public static void main(String[] args) {

        String ip = "192.168.1.8";
        String community = "public";
        int port = 161;

        Variable var = null;
        SnmpGet tester = new SnmpGet();
        // tester.snmpGet(ip, port, community, oidval);
        try {
            tester.connect(ip, port, community);
            // System Uptime(系統運行時間) 的 oid, 最前面的 . 可以忽略不寫
            String oid = ".1.3.6.1.2.1.1.3.0";
            var = tester.snmpGet(oid);
            logger.info(oid + " = " + var);

            Thread.sleep(3 * 1000);
            logger.info("");
            oid = ".1.3.6.1.2.1.1.1.0";
            var = tester.snmpGet(oid);
            logger.info(oid + " = " + var);

            Thread.sleep(3 * 1000);
            logger.info("");
            logger.info("SNMP GetList");
            List<String> oidList = new ArrayList<String>();
            oidList.add(".1.3.6.1.2.1.1.1.0");
            oidList.add(".1.3.6.1.2.1.1.3.0");
            oidList.add(".1.3.6.1.2.1.1.5.0");

            List<VariableBinding> vblist = tester.snmpGet(oidList);
            for (VariableBinding vb : vblist) {
                logger.info("oid:" + vb.getOid() + ", var=" + vb.getVariable());
            }
        } catch (Exception e) {
            logger.error("Error:", e);
        } finally {
            tester.close();
        }
    }

    public void connect(String ip, int port, String community) throws Exception {
        String address = protocol + ":" + ip + "/" + port;
        // CommunityTarget target = SnmpUtil.createCommunityTarget(address,
        // community, version, 2 * 1000L, 3);

        target = new CommunityTarget();
        target.setCommunity(new OctetString(community));
        target.setAddress(GenericAddress.parse(address));
        target.setVersion(version);
        target.setTimeout(2 * 1000L); // milliseconds
        target.setRetries(3); // retry 3次

        try {
            udpTransportMapping = new DefaultUdpTransportMapping();
            // 這裡一定要呼叫 listen, 才能收到結果
            udpTransportMapping.listen();
            snmp = new Snmp(udpTransportMapping);

        } catch (Exception e) {
            logger.error("Error", e);
            throw e;
        }
    }

    public void close() {
        if (snmp != null) {
            try {
                snmp.close();
            } catch (IOException ex1) {
                snmp = null;
            }
        }
        if (udpTransportMapping != null) {
            try {
                udpTransportMapping.close();
            } catch (IOException ex2) {
                udpTransportMapping = null;
            }
        }
    }

    public Variable snmpGet(String oid) throws Exception {
        try {
            PDU pdu = new PDU();
            // pdu.add(new VariableBinding(new OID(new
            // int[]{1,3,6,1,2,1,1,2})));
            pdu.add(new VariableBinding(new OID(oid)));
            pdu.setType(PDU.GET);

            // 以同步的方式發送 snmp get, 會等待target 設定的 timeout 時間結束後
            // 就會以 Request time out 的方式 return 回來
            ResponseEvent response = snmp.send(pdu, target);
            // logger.debug("PeerAddress:" + response.getPeerAddress());
            PDU responsePdu = response.getResponse();

            if (responsePdu == null) {
                logger.debug("Request time out");
            } else {
                Vector<?> vbVect = responsePdu.getVariableBindings();
                logger.debug("vb size:" + vbVect.size());
                if (vbVect.size() == 0) {
                    logger.debug(" pdu vb size is 0 ");
                } else {
                    Object obj = vbVect.firstElement();
                    VariableBinding vb = (VariableBinding) obj;
                    // logger.debug(vb.getOid() + " = " + vb.getVariable());

                    // logger.info("success finish snmp get the oid!");
                    return vb.getVariable();
                }
            }

        } catch (Exception e) {
            logger.error("Error", e);
            throw e;
        }
        return null;
    }

    public List<VariableBinding> snmpGet(List<String> oidList) throws Exception {
        try {
            PDU pdu = new PDU();
            pdu.setType(PDU.GET);
            for (String oid : oidList) {
                pdu.add(new VariableBinding(new OID(oid)));
            }

            // 以同步的方式發送 snmp get, 會等待target 設定的 timeout 時間結束後
            // 就會以 Request time out 的方式 return 回來
            ResponseEvent response = snmp.send(pdu, target);
            // logger.debug("PeerAddress:" + response.getPeerAddress());
            PDU responsePdu = response.getResponse();

            if (responsePdu == null) {
                logger.debug("Request time out");
            } else {
                logger.debug(" response pdu vb size is " + responsePdu.size());
                List<VariableBinding> datalist = new ArrayList<VariableBinding>();
                for (int i = 0; i < responsePdu.size(); i++) {
                    VariableBinding vb = responsePdu.get(i);
                    // logger.debug(vb.getOid() + "=" + vb.getVariable());
                    datalist.add(vb);
                }
                return datalist;
            }

        } catch (Exception e) {
            logger.error("Error", e);
            throw e;
        }
        return null;
    }
}

執行結果

2014-12-04 17:19:11,055 [main] DEBUG SnmpGet 145
 vb size:1
2014-12-04 17:19:11,083 [main] INFO  SnmpGet 50
 .1.3.6.1.2.1.1.3.0 = 1 day, 1:13:23.16
2014-12-04 17:19:14,084 [main] INFO  SnmpGet 53

2014-12-04 17:19:14,085 [main] DEBUG SnmpGet 145
 vb size:1
2014-12-04 17:19:14,086 [main] INFO  SnmpGet 56
 .1.3.6.1.2.1.1.1.0 = Linux server.maxkit.com.tw 2.6.32-431.el6.i686 #1 SMP Fri Nov 22 00:26:36 UTC 2013 i686

SNMP walk

這是使用 GETNext 的方式,當有下一個OID 時,就自動往下抓取,直到沒有資料為止。

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.Null;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;

public class SnmpGet {
    public static Logger logger = LoggerFactory.getLogger(SnmpGet.class
            .getName());

    private static int version = SnmpConstants.version1;
    private static String protocol = "udp";

    private static CommunityTarget target = null;
    private static DefaultUdpTransportMapping udpTransportMapping = null;
    private static Snmp snmp = null;

    public static void main(String[] args) {

        String ip = "192.168.1.8";
        String community = "public";
        int port = 161;

        Variable var = null;
        SnmpGet tester = new SnmpGet();
        // tester.snmpGet(ip, port, community, oidval);
        try {
            tester.connect(ip, port, community);
            logger.info("");
            logger.info("SNMP walk");

            vblist = tester.snmpWalk("1.3.6.1.2.1.1");
            for (VariableBinding vb : vblist) {
                logger.info("oid:" + vb.getOid() + ", var=" + vb.getVariable());
            }
        } catch (Exception e) {
            logger.error("Error:", e);
        } finally {
            tester.close();
        }
    }

    public void connect(String ip, int port, String community) throws Exception {
        String address = protocol + ":" + ip + "/" + port;
        // CommunityTarget target = SnmpUtil.createCommunityTarget(address,
        // community, version, 2 * 1000L, 3);

        target = new CommunityTarget();
        target.setCommunity(new OctetString(community));
        target.setAddress(GenericAddress.parse(address));
        target.setVersion(version);
        target.setTimeout(2 * 1000L); // milliseconds
        target.setRetries(3); // retry 3次

        try {
            udpTransportMapping = new DefaultUdpTransportMapping();
            // 這裡一定要呼叫 listen, 才能收到結果
            udpTransportMapping.listen();
            snmp = new Snmp(udpTransportMapping);

        } catch (Exception e) {
            logger.error("Error", e);
            throw e;
        }
    }

    public void close() {
        if (snmp != null) {
            try {
                snmp.close();
            } catch (IOException ex1) {
                snmp = null;
            }
        }
        if (udpTransportMapping != null) {
            try {
                udpTransportMapping.close();
            } catch (IOException ex2) {
                udpTransportMapping = null;
            }
        }
    }

    /**
     * 1)responsePDU == null<br>
     * 2)responsePDU.getErrorStatus() != 0<br>
     * 3)responsePDU.get(0).getOid() == null<br>
     * 4)responsePDU.get(0).getOid().size() < targetOID.size()<br>
     * 5)targetOID.leftMostCompare(targetOID.size(),responsePDU.get(0).getOid())
     * !=0<br>
     * 6)Null.isExceptionSyntax(responsePDU.get(0).getVariable().getSyntax())<br>
     * 7)responsePDU.get(0).getOid().compareTo(targetOID) <= 0<br>
     */
    public List<VariableBinding> snmpWalk(String targetOid) {
        OID targetOID = new OID(targetOid);

        PDU requestPDU = new PDU();
        requestPDU.setType(PDU.GETNEXT);
        requestPDU.add(new VariableBinding(targetOID));

        try {
            List<VariableBinding> vblist = new ArrayList<VariableBinding>();
            boolean finished = false;
            while (!finished) {
                VariableBinding vb = null;
                ResponseEvent response = snmp.send(requestPDU, target);
                PDU responsePDU = response.getResponse();

                if (null == responsePDU) {
                    logger.debug("responsePDU == null");
                    finished = true;
                    break;
                } else {
                    vb = responsePDU.get(0);
                }
                // check finish
                finished = checkWalkFinished(targetOID, responsePDU, vb);
                if (!finished) {
                    // logger.debug("vb:" + vb.toString());
                    vblist.add(vb);
                    // Set up the variable binding for the next entry.
                    requestPDU.setRequestID(new Integer32(0));
                    requestPDU.set(0, vb);
                }
            }
            // logger.debug("success finish snmp walk!");
            return vblist;
        } catch (Exception e) {
            logger.error("Error: ", e);
        }
        return null;
    }

    /**
     * check snmp walk finish
     * 
     * @param resquestPDU
     * @param targetOID
     * @param responsePDU
     * @param vb
     * @return
     */
    private boolean checkWalkFinished(OID targetOID, PDU responsePDU,
            VariableBinding vb) {
        boolean finished = false;
        if (responsePDU.getErrorStatus() != 0) {
            logger.debug("responsePDU.getErrorStatus() != 0 ");
            logger.debug(responsePDU.getErrorStatusText());
            finished = true;
        } else if (vb.getOid() == null) {
            logger.debug("vb.getOid() == null");
            finished = true;
        } else if (vb.getOid().size() < targetOID.size()) {
            logger.debug("vb.getOid().size() < targetOID.size()");
            finished = true;
        } else if (targetOID.leftMostCompare(targetOID.size(), vb.getOid()) != 0) {
            logger.debug("targetOID.leftMostCompare() != 0");
            finished = true;
        } else if (Null.isExceptionSyntax(vb.getVariable().getSyntax())) {
            logger.debug("Null.isExceptionSyntax(vb.getVariable().getSyntax())");
            finished = true;
        } else if (vb.getOid().compareTo(targetOID) <= 0) {
            logger.debug("Variable received is not "
                    + "lexicographic successor of requested " + "one:");
            logger.debug(vb.toString() + " <= " + targetOID);
            finished = true;
        }
        return finished;

    }
}

測試結果

2014-12-04 17:19:17,087 [main] INFO  SnmpGet 60
 SNMP GetList
2014-12-04 17:19:17,091 [main] DEBUG SnmpGet 182
  response pdu vb size is 3
2014-12-04 17:19:17,091 [main] INFO  SnmpGet 68
 oid:1.3.6.1.2.1.1.1.0, var=Linux server.maxkit.com.tw 2.6.32-431.el6.i686 #1 SMP Fri Nov 22 00:26:36 UTC 2013 i686
2014-12-04 17:19:17,092 [main] INFO  SnmpGet 68
 oid:1.3.6.1.2.1.1.3.0, var=1 day, 1:13:29.25
2014-12-04 17:19:17,092 [main] INFO  SnmpGet 68
 oid:1.3.6.1.2.1.1.5.0, var=server.maxkit.com.tw
2014-12-04 17:19:20,092 [main] INFO  SnmpGet 72

2014-12-04 17:19:20,093 [main] INFO  SnmpGet 73
 SNMP walk
2014-12-04 17:19:20,130 [main] DEBUG SnmpGet 276
 targetOID.leftMostCompare() != 0
2014-12-04 17:19:20,130 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.1.0, var=Linux server.maxkit.com.tw 2.6.32-431.el6.i686 #1 SMP Fri Nov 22 00:26:36 UTC 2013 i686
2014-12-04 17:19:20,130 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.2.0, var=1.3.6.1.4.1.8072.3.2.10
2014-12-04 17:19:20,131 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.3.0, var=1 day, 1:13:32.26
2014-12-04 17:19:20,131 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.4.0, var=Root <root@localhost> (configure /etc/snmp/snmp.local.conf)
2014-12-04 17:19:20,131 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.5.0, var=server.maxkit.com.tw
2014-12-04 17:19:20,132 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.6.0, var=Unknown (edit /etc/snmp/snmpd.conf)
2014-12-04 17:19:20,132 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.8.0, var=0:00:00.01
2014-12-04 17:19:20,132 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.2.1, var=1.3.6.1.6.3.11.2.3.1.1
2014-12-04 17:19:20,132 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.2.2, var=1.3.6.1.6.3.15.2.1.1
2014-12-04 17:19:20,133 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.2.3, var=1.3.6.1.6.3.10.3.1.1
2014-12-04 17:19:20,133 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.2.4, var=1.3.6.1.6.3.1
2014-12-04 17:19:20,133 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.2.5, var=1.3.6.1.2.1.49
2014-12-04 17:19:20,133 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.2.6, var=1.3.6.1.2.1.4
2014-12-04 17:19:20,133 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.2.7, var=1.3.6.1.2.1.50
2014-12-04 17:19:20,133 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.2.8, var=1.3.6.1.6.3.16.2.2.1
2014-12-04 17:19:20,133 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.3.1, var=The MIB for Message Processing and Dispatching.
2014-12-04 17:19:20,134 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.3.2, var=The MIB for Message Processing and Dispatching.
2014-12-04 17:19:20,134 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.3.3, var=The SNMP Management Architecture MIB.
2014-12-04 17:19:20,134 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.3.4, var=The MIB module for SNMPv2 entities
2014-12-04 17:19:20,134 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.3.5, var=The MIB module for managing TCP implementations
2014-12-04 17:19:20,134 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.3.6, var=The MIB module for managing IP and ICMP implementations
2014-12-04 17:19:20,134 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.3.7, var=The MIB module for managing UDP implementations
2014-12-04 17:19:20,134 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.3.8, var=View-based Access Control Model for SNMP.
2014-12-04 17:19:20,135 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.4.1, var=0:00:00.01
2014-12-04 17:19:20,136 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.4.2, var=0:00:00.01
2014-12-04 17:19:20,137 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.4.3, var=0:00:00.01
2014-12-04 17:19:20,138 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.4.4, var=0:00:00.01
2014-12-04 17:19:20,139 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.4.5, var=0:00:00.01
2014-12-04 17:19:20,143 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.4.6, var=0:00:00.01
2014-12-04 17:19:20,143 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.4.7, var=0:00:00.01
2014-12-04 17:19:20,144 [main] INFO  SnmpGet 77
 oid:1.3.6.1.2.1.1.9.1.4.8, var=0:00:00.01

SNMP Asynchronous Get

如果在程式中,無法在發送 snmp request 之後,直接等待結果,例如在 UI 界面上發送 request,但 UI 無法被 blocking 以等待 response 或 timeout 的狀況下,我們就必須要使用非同步 SNMP Get。

程式中主要是利用 ThreadPool,以 multithread 的方式,在背景中發送 request,並以 event listener 作為 call back function。

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.CommunityTarget;
import org.snmp4j.MessageDispatcherImpl;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.event.ResponseListener;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.MultiThreadedMessageDispatcher;
import org.snmp4j.util.ThreadPool;
import org.snmp4j.util.WorkerPool;

public class SnmpClientAsync {
    public static Logger logger = LoggerFactory.getLogger(SnmpClientAsync.class
            .getName());

    private static int version = SnmpConstants.version1;
    private static String protocol = "udp";

    private static CommunityTarget target = null;
    private static DefaultUdpTransportMapping udpTransportMapping = null;
    private static Snmp snmp = null;
    private WorkerPool threadPool = null;

    public static void main(String[] args) {

        String ip = "192.168.1.8";
        String community = "public";
        int port = 161;

        SnmpClientAsync tester = new SnmpClientAsync();
        try {
            tester.connect(ip, port, community);
            logger.info("");
            logger.info("SNMP GetList");
            List<String> oidList = new ArrayList<String>();
            oidList.add(".1.3.6.1.2.1.1.1.0");
            oidList.add(".1.3.6.1.2.1.1.3.0");
            oidList.add(".1.3.6.1.2.1.1.5.0");

            tester.snmpGet(oidList);

            // 非同步,必須等待一段處理時間
            Thread.sleep(3*1000);
        } catch (Exception e) {
            logger.error("Error:", e);
        } finally {
            tester.close();
        }
    }

    public void connect(String ip, int port, String community) throws Exception {
        String address = protocol + ":" + ip + "/" + port;

        target = new CommunityTarget();
        target.setCommunity(new OctetString(community));
        target.setAddress(GenericAddress.parse(address));
        target.setVersion(version);
        target.setTimeout(2 * 1000L); // milliseconds
        target.setRetries(3); // retry 3次

        try {
            threadPool = ThreadPool.create(ip + "SNMPWorkPool", 2);
            MultiThreadedMessageDispatcher dispatcher = new MultiThreadedMessageDispatcher(
                    threadPool, new MessageDispatcherImpl());

            udpTransportMapping = new DefaultUdpTransportMapping();

            snmp = new Snmp(dispatcher, udpTransportMapping);
            snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1());
            snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());
            snmp.getMessageDispatcher().addMessageProcessingModel(new MPv3());
            snmp.listen();
        } catch (Exception e) {
            logger.error("Error", e);
            throw e;
        }
    }

    public void close() {
        if (snmp != null) {
            try {
                snmp.close();
            } catch (IOException ex1) {
                snmp = null;
            }
        }
        if (udpTransportMapping != null) {
            try {
                udpTransportMapping.close();
            } catch (IOException ex2) {
                udpTransportMapping = null;
            }
        }
        if (threadPool != null) {
            threadPool.stop();
            threadPool = null;
        }
    }

    public void snmpGet(List<String> oidList) throws Exception {
        try {
            PDU pdu = new PDU();
            pdu.setType(PDU.GET);
            for (String oid : oidList) {
                pdu.add(new VariableBinding(new OID(oid)));
            }

            snmp.send(pdu, target, null, listener);
        } catch (Exception e) {
            logger.error("Error", e);
            throw e;
        }
    }

    public void printResponse(List<VariableBinding> datalist) {
        for (VariableBinding vb : datalist) {
            logger.info("oid:" + vb.getOid() + ", var=" + vb.getVariable());
        }
    }

    ResponseListener listener = new ResponseListener() {
        public void onResponse(ResponseEvent event) {
            ((Snmp) event.getSource()).cancel(event.getRequest(), this);
            PDU response = event.getResponse();
            PDU request = event.getRequest();
            //logger.debug("[request]:" + request);

            if (response == null) {
                logger.debug("[ERROR]: response is null");
            } else if (response.getErrorStatus() != 0) {
                logger.debug("[ERROR]: response status"
                        + response.getErrorStatus() + " Text:"
                        + response.getErrorStatusText());
            } else {
                logger.debug("Received response Success!!!");
                List<VariableBinding> datalist = new ArrayList<VariableBinding>();
                for (int i = 0; i < response.size(); i++) {
                    VariableBinding vb = response.get(i);
                    logger.debug(vb.toString());
                    datalist.add(vb);
                }
                printResponse(datalist);
            }
        }
    };

}

測試結果

2014-12-08 10:18:07,548 [main] INFO  SnmpClientAsync 49

2014-12-08 10:18:07,581 [main] INFO  SnmpClientAsync 50
 SNMP GetList
2014-12-08 10:18:07,607 [192.168.1.8SNMPWorkPool.0] DEBUG SnmpClientAsync 151
 Received response Success!!!
2014-12-08 10:18:07,607 [192.168.1.8SNMPWorkPool.0] DEBUG SnmpClientAsync 155
 1.3.6.1.2.1.1.1.0 = Linux koko.maxkit.com.tw 2.6.32-431.el6.i686 #1 SMP Fri Nov 22 00:26:36 UTC 2013 i686
2014-12-08 10:18:07,610 [192.168.1.8SNMPWorkPool.0] DEBUG SnmpClientAsync 155
 1.3.6.1.2.1.1.3.0 = 4 days, 18:12:20.49
2014-12-08 10:18:07,611 [192.168.1.8SNMPWorkPool.0] DEBUG SnmpClientAsync 155
 1.3.6.1.2.1.1.5.0 = koko.maxkit.com.tw
2014-12-08 10:18:07,611 [192.168.1.8SNMPWorkPool.0] INFO  SnmpClientAsync 133
 oid:1.3.6.1.2.1.1.1.0, var=Linux koko.maxkit.com.tw 2.6.32-431.el6.i686 #1 SMP Fri Nov 22 00:26:36 UTC 2013 i686
2014-12-08 10:18:07,611 [192.168.1.8SNMPWorkPool.0] INFO  SnmpClientAsync 133
 oid:1.3.6.1.2.1.1.3.0, var=4 days, 18:12:20.49
2014-12-08 10:18:07,612 [192.168.1.8SNMPWorkPool.0] INFO  SnmpClientAsync 133
 oid:1.3.6.1.2.1.1.5.0, var=koko.maxkit.com.tw

Reference

Java實現snmp的get和walk代碼示例

Introduction to snmp4j

2014年12月8日

改變世界的九大演算法 - John MacCormick

本書所介紹的九大演算法是:搜尋引擎的索引(search engine indexing)、網頁排序(page rank)、公鑰加密(public-key cryptography)、錯誤更正碼(error-correcting codes)、模式辨識(pattern recognition,如手寫辨識、聲音辨識、人臉辨識等等)、資料壓縮(data compression)、資料庫(databases)、數位簽章(digital signature)。

寫程式的人很多,做IT工作的也很多,但並不是每一個人都能了解這些常用演算法背後的精神與原理,我也跟一般 IT 工程師一樣,沒有專業到完全了解這些演算法的來歷,沒有辦法把每一個演算法講出來,還能讓 dummy user 聽得懂。

本書的推薦序也講得很明白,要撰寫甚至出版這本書,本身就有很高的風險,科普書的禁忌是:出現的算式越多,賣得越差,所以這本書的目標讀者該是給像我們這樣的 IT 工程師閱讀,但如果是剛畢業沒多久的工程師們,應該都還有印象,在演算法課程時鴨子聽雷的窘樣。

這似乎也多少註定了書本的命運,銷售量肯定不會很高。但我還是強烈建議,應該花時間去把這本書看一看,這可讓我們對關聯式資料庫、Google搜尋等等項目,有更深一層的認識。專業工作者的一項最重要的功能,就是在遇到一些技術上要實作的問題時,能提出一套解決方案,而這本書的知識,就是你備而不用的專業背景知識。

接下來,我只討論「公鑰加密」這個章節,其他的部份,就留待讀者自己去看書了。「公鑰加密」這章的副標題是:用明信片寄送祕密,這個比喻非常貼切,因為網路上的資料,對所有網路節點來說,都是明白且清楚的,把資料放在網路上傳送,也就等同於我們寫了一張明信片投遞出去,所有經手這張明信片的人都能看到,上面寫了什麼。

最直覺的解決方式,就是收送雙方預先協商出一個密碼,但這樣會有另一個問題,當面對另一個不認識的人,我們就沒辦法預先協商密碼了。

作者用顏料混色法,來解釋應該要怎麼處理

  1. A 與 B 各自選擇一個「個人色」
  2. A 與 B 其中一位,公開宣佈一個不同的新顏色為「公共色」
  3. A 與 B 各自將公共色與個人色混合,製造出一個「公共個人混色」
  4. A 取得 B 的「公共個人混色」,再加入自己的「個人色」。同時間 B 取得 A 的「公共個人混色」,再加入自己的「個人色」。結果兩個人製造出完全一樣的三色混色結果。

於是這最終的三色混漆,就成了 A 與 B 的共同祕密混漆。由於 C 不知道 A 與 B 的「個人色」,因此就無法破解「公共個人混色」

電腦不是混色,而是將顏色改為數字,混色改為乘法。最有明的公鑰加密方法是 RSA。了解這個章節搭配上第九章數位簽章的內容,就可以知道,我們怎麼在公開的 Internet 環境中,製作一個在任意的 A 與 B 兩地之間,安全地傳送資料的計算環境。

在我們學習網路程式設計時,通常會連帶學到,區域網路的廣播特性,還有網路監聽側錄 sniffer 程式的使用,因為撰寫網路程式就是在跟封包資料奮戰,我們常常得一個一個 byte 去觀察收送兩端傳送資料的正確性,才知道自己寫的程式到底對或錯。

sniffer 技巧如果用在 hacker 行為上,只要在網路上經過的節點上放置 sniffer 程式,就可以錄製到所有的封包資料,雖然一般使用者沒辦法想像網路的傳輸協定,但都能理解到,有個監聽者在監聽資料的狀況下,沒有資料加密機制,就等於拿一張明信片在上面寫字,告訴大家我在講什麼。

還記得以前的宿舍網路,當時還是用同軸電纜,網路一段一段加上 repeater,當時也是 BBS 流行的時代,所以只要在其中有一台電腦裝上 sniffer,就可以看到大家在 BBS 上跟妹妹 talk 的肉麻對話。

雖然現在的集線器已經都是 switch 而不是 hub,switch 不會將某個 port 的資料轉送到其他所有 port 上,但是 hacker 還是可以透過 ARP 的方式,製造出 man-in-the-middle 的環境,就能取得特定 IP (range) 的進出封包,資料加密機制在現今有缺陷的網路協定上,是不可或缺的技術。

博客來:改變世界的九大演算法:讓今日電腦無所不能的最強概念

2014年12月7日

使用Git的Stash功能

gitstash功能可以將你在當前branch上尚未commit的資料暫存起來,並將這些修改到一半的資料還原。
什麼時候會用到stash呢? 想像以下情境:
你正在某個branch(暫且稱為current_branch)上開發一些新功能;然而做到一半時,你意識到這些新功能應該要先另開一個新的branch(暫且稱為new_branch)才對。
這時候你會怎麼做?
硬著頭皮暫時把東西commit上current_branch?
或是手動將有變更的檔案自己複製下來,然後reset current_branch,建立new_branch,再將有變更的檔案手動複製過去?
當然,你有更好的選擇,那就是- 使用stash指令將尚未commit的資料暫存起來,然後切換到另一個branch,再使用stash pop叫出來。
直接來看範例(假設目前在current_branch):
git stash
git checkout new_branch
git stash pop
此外,Eclipse的egit, 以及Mac的SourceTree軟體中,都可以找到stash的按鍵。
善用stash指令,讓程式設計師更能妥善在應付開發時各種難以預期的狀況。

2014年12月6日

在Docker上部署Java Web Application

Docker很友善,它提供一個Docker Hub平台,在上面你可以找到很多由許多官方提供的Image檔,因此像我這種對於Linux目錄結構以及軟體相依性之類的事情不太熟悉的人,也能夠輕鬆的直接透過Docker Hub抓取Tomcat image,獲得一個官方建制好的Image,直接使用它來將自己開發的Web Application丟上去執行,實在是太方便了!

執行流程

首先先將tomcat官方提供的images抓回來,以下為官方images檔的網址:
https://registry.hub.docker.com/_/tomcat/

進去之後,你可以看到它有很多的Tags,分別代表Tomcat的版本以及它所使用的Java版本,這次我選用tomcat7配上jre8的版本,因此透過以下指令抓取images回來:
docker pull tomcat:7.0.57-jre8
接著我要啟動Container,並且我將它命名為mayer_tomcat,然後將設定其port綁定,將實體機器上18080與Container的8080port做綁定,接著使用-d標籤讓它在背景執行:
docker run --name mayer_tomcat -p 18080:8080 -d tomcat:7.0.57-jre8
這樣一來,Tomcat Container就已經啟動了,由於我是在Mac上測試的關係,因此我必須先下指令查詢boot2docker的ip為何:
bash-3.2$ boot2docker ip

The VM's Host only interface IP address is: 192.168.59.103  
得到ip之後,就可以直接開啓瀏覽器,輸入192.168.59.103:18080,就能看到Tomcat畫面:

接著使用Eclipse開啓一個新的Dynamic Web Project,將之命名為dockerap,這邊我只弄了一個index.html的檔案,內容只印出Hello Docker,然後我將它打包成war檔,並且上傳至網路空間。
接著使用exec指令,執行該Container的bash:
docker exec -it mayer_tomcat /bin/bash
進去之後,預設路徑為/usr/local/tomcat,我先切換至webapps目錄,接著在將該war檔下載回來,最後離開bash:
cd webapps
wget https://dl.dropboxusercontent.com/u/xxxxxxxx/dockerap.war
exit
最後重新啟動Tomcat Container:
docker restart mayer_tomcat
開啓瀏覽器,輸入192.168.59.103:18080/dockerap/,就能看到Web Application已經成功部署在Container上了,大功告成!

2014年12月1日

Google 模式 - Eric Schmidt & Jonathan Rosenberg

這本書是 Eric Schmidt 與 Jonathan Rosenberg 所撰寫,內容是說明 Google 的企業文化,Larry Page 在推薦序中指出,Google 人就該有自主思考的力量,每個人都該大膽思考,探尋可能,解決問題,尋找答案。

Google 的文化也不是在公司開門的第一天就建立起來了,我們該有開放的態度,慢慢地去調整與建立一個屬於我們自己的企業文化。接下來,我只把一些我想再討論的議題 highlight 出來,並嘗試進行一些討論,其中會夾雜著我的個人意見。

持續賺錢的方法

大家都知道 Google 最賺錢的業務就是 Adwords,也因為這個能夠持續獲利的金雞母,讓 Google 可以貫徹自己的企業理念,可以不斷賠錢去實驗一大堆高風險的新專案。

但在這樣思考的同時,我們也該想到,如果 Google 天生沒有擁抱創新的原生泥漿,也可能不會誕生一個 Adwords。有點雞生蛋或是蛋生雞的問題。

但很明確的是,Google 在 2002 年以前都還是用最重要的 100個專案的表單來進行資源分配與管理,也就是說,在公司發展的初期,受限於當時的時空背景,Google 還是得用傳統的方式,集中發展的資源在最高決策者認定最有發展潛力的專案上。

一直等到穩定獲利的時空下,才來談 70/20/10 的資源分配法則,這都是為了讓公司規模在快速膨脹下,還能夠保有企業內部創業的原生文化才提出的口號與決定,20% 的資源放在有初步成果的新產品,10% 投入在全新的專案。

10%的資源投資很適當,還有一個道理:創造力熱愛資源受限(creativity loves constraints)。真正有效的想法,反而在受限資源的條件下,讓人可以快速地切入重點。

別聽河馬的意見

河馬也就是公司裡面最高薪資的人的意見,但是決策品質基本上跟薪資高低無關,只要是一個有說服力的理論,就能勝出。

Adword 發展初期遇到了「河馬」布林以及蘇利哈.拉瑪斯兩個點子的對抗,而爭辯的最後,布林的方式被放棄了,原因是布林了解討論中提出的資料,如果換成不了解的河馬,就可能會以職位強壓進行他自己提出的策略。

決策的討論要以資料為基礎進行討論,收集資料成了最重要的前提,如果是大家都沒有做過的事情,沒有解決過的議題,很明顯地就會落入不容易找到佐證資料的狀況。

如果是全新的想法,就可能要做 prototype 來實驗,例如 Google Books 的想法,一開始是用簡單的掃描機制去計算,掃描每一本書所需要耗費的時間,以此推論整個圖書館需要處理多久。

適應改變的能力

人才招募是最重要的事務,找對了人,就能在環境中得到相乘的效果,至於什麼樣的人才,對公司來說是最佳的人才,不同的公司可能有不同的答案,有些公司需要即時的人力,他考慮的只要有基本的程式技能就好了,不需要員工想太多,反正案子進來了,配合客戶把客戶照顧好,就沒有問題了。

在前一次進行面試時,應試者問了我一個問題,你認為我欠缺了什麼樣的能力,應該補足什麼樣的技能?我想了一下,給他的答案是,要擁有「適應改變的能力」。

我給他的理由是,在發展產品的過程中,有很多功能是在公司內部發想,然後進行開發,接下來推送到客戶端,根據回應進行產品功能修正。

什麼樣的功能對產品來說是好的?如果是 Google,可能就是用 20% 的時間先去開發一個 prototype,接下來在內部推動,並取得其他人的回饋意見,最終決策者會判定這樣的修正是不是可以被接受。

20% 的時間事實上是 120%

在沒有看過這本書以前,我們已經聽過 Google 人可以使用 20% 的時間自己進行專案不需要老闆的同意,但事實上這 20% 的 Free Time,還是有一些遊戲規則在背後的。

首先是 20% 的時間不代表每一星期都可以使用星期五作為 Free Time,而是在不影響現有工作的前提下,才能進行自己的專案,止於這 20% 的時間,並不一定就是在公司上班時間的 20%,而是要包含自己的時間。

換句話說,公司鼓勵內部創業,但自己的事情要先做好,而且內部創業,有想法之後,必須自己找到認同你的想法的人加入你的專案,取得別人的認同是首要條件。

至於時間,有些人是利用一個暑假,或是週六週日的時間,沒有人規定可以放掉手邊的工作,直接進行自己的專案。也就是說,20% 的 Free Time,在發展初期可能是 120%,一直要等到有初步成果開花結果下,公司才認定可以分配資源繼續進行下去。

使用 email 的方法

在處理對應客戶的問題的習慣下,我常常會做以下的事情,接收到問題,嘗試去解決並尋找答案,在一天內進行回應,我非常認同,每一個人做事都該盡可能地在很短的時間內,進行回應,就算是告訴客戶,你已經在了解中,但還不知道怎麼解決,這也是一種良性的回應。經常清除 email 收件匣這個建議,就跟快速回應是一樣的,因為要快速回應,我們就不應該常在收件匣裡面看到有還沒有處理過的 email。

讓要求事項很容易後續追蹤這個建議,讓我想到,gmail 的基本功能,其實就是圍繞著這個想法,收到郵件時,首先是過濾器,可輕鬆地設定標籤,決定優先順序,並排除掉 promo 或是垃圾信。接下來是閱讀與處理,如果閱讀後,認定需要追蹤處理,則可以歸類到 todo 的項目中。至於 Draft 也像是一種 todo,還沒有寫完的回信,都是必須要盡快處理的事項。

被挑戰的準備

正如同前面提到的適應改變的能力,我常常在等待我的意見被挑戰與否定,原因是一方面我不認為自己永遠是對的,如果只因為常常主導意見的角色,而壓抑了其他人的聲音,這並不是個健康的互動方式。

我承認有些時候,會因為時程的壓力,我必須進行強勢的時間安排與規格的主導,但在這樣的會議過程中,如果有不一樣的聲音,而且又有絕佳的理由與論點,這是非常有幫助的。如果我可以在還沒有實現的一開始,就馬上調整方法與作法,相對來說就節省了很多成本的浪費。

所以我必須告訴自己說,要有被挑戰的準備,而相對來說,也要做好準備挑戰其他人。

擁抱改變

我認定自己在專案管理上,一直以來常常在進行動態調控,因為我並沒有辦法在產品發展的初期,就知道會遇到什麼困難,因此我必須不斷地根據開發的狀況,遇到的問題與解決的狀況,動態調控人力的分配與資源,甚至有時候還需要更深入去了解問題,並在沒有在該專案上寫過任何一行 code的狀況下,嘗試去想像 programmer 可能會怎麼去寫這一段的程式邏輯,而因為這樣的邏輯缺陷,而造成某些問題。

我認為自己該「擁抱改變」,甚至該擁抱不間斷地改變,這改變並非一夕之間,但確實每天都在發生。企業生命體也該如此,每天持續地做出更適當的決定,在能持續生存的前提下,持續鍛鍊出更健康的公司體質。

如果你的出發點是個人的利益得失,那每天就得帶著鋼盔與龜殼做事,期待不會被子彈掃射,如果出發點是公司的成敗,那麼就該擇善固執,認為是對的事情,就得去做,並嘗試證明你的意見是對的。

2014/11/19 「關於把Google模式用在台灣,我想說的是...」
(轉)從書中Google的實際故事了解google獨樹一幟的管理哲學
《Google模式》:Eric Schmidt 教你 Google 人怎麼使用電子郵件
《Google 模式》:在網路時代找工作如同衝浪,讓技術洞察者為你指引明燈
Google人才招募九大守則,不想解雇員工,一開始就別錄取他
Google模式(讀後心得)
偷學《Google 模式》!學會 70/20/10 法則,建立說 Yes 的企業文化
《Google模式》談人才 -- 招募是最重要的事務!