Google Code Prettify

2015年7月10日 星期五

Tibco RV - fault tolerance

有很多系統不允許(在某個特定時段)服務中斷,這類的系統不只會有備援,通常還會有 fault tolerance 機制,當系統掛掉時,備援的系統會自動啟動服務。Tibco RV 也有提供這樣的機制。

在了解程式如何編寫之前,我先對 RV 提供的 fault tolerance 作簡單的說明。
  • group name
RV 的 fault tolerance 以 group name 來區分,也就是同一個 group  name 的程式會成為一個群組,相互備援。
  • active & inactive
RV 將 fault tolerance 的系統狀態分成三個階段,如下:
    1. DEACTIVATE: 顧名思義,就是非屬於提供服務的狀態,這時候系統當然就別做什麼事,只要一直傾聽 RV 的訊息,當傾聽到要切換狀態時再進行相關工作。
    2. PREPARE_TO_ACTIVATE: 當 RV 送來這個狀態,表示原本正提供服務的系統可能出了狀況,備援的系統要開始準備啟動,通常收到這個 event 時,會進行一些系統資源初始化的工作,以便當程式真的要啟動提供服務時,可以儘快啟動,以使服務中斷的時間儘可能的縮短。
    3. ACTIVATE: 收到這個 event,就真的要接手服務了!
  • fault tolerance callback function
上述三個狀態的切換,RV 會透過 onFtAction 這個 callback function 通知備援的程式,所以程式要實作 TibrvFtMemberCallback 這個介面。
  • active goal
在我們將測試的例子裡,只會啟動兩個 reciver 程式,一次只會有一個程式提供服務 (接收 generator 傳來的數字並輸出到 console),但是,實務上要有幾支程式處於 ACTIVATE 狀態是可以設定的,這個數字稱為 active goal。
  • weight
每支程式建立 fault tolerance 時,在建構子的參數裡會有個 weight 的參數,這個值由 1 到整數的最大值,數字越大優先權越大,所以,如果有三支程式 A、B、C,其權重 (weight) 分別為 50、30、100,當 RV 要從這三支程式裡挑一支啟動來提供服務時,就會挑權重最大的 C。
  • heart beats
每個處於 ACTIVATE 狀態的程式,會不停的送出 heart beats (心跳) 訊息給其它同群組的程式,以表示它還活著,還在正常提供服務。
這裡舉的例子會有兩支程式,一支命名為 NumberGenerator,很簡單的每秒送出一個累加的數字,另一支命名為 NumberReceiver,會由 RV 接收 NumberGenerator 送出的數字,並輸出到 console。假設接收數字的這個系統是我們負責的系統,是服務不能中斷的,我們以 RV 提供的 fault tolerance 機制,讓 NumberReceiver 萬一掛掉時,可以立即啟動另一個備援的系統,所以,測試時我們會同時啟動兩個 NumberReceiver 先啟動的會成為 active 的服務,後啟動的為備援,先來看一下程式。 
 1 package idv.steven.rv.ft;
 2 
 3 import java.io.UnsupportedEncodingException;
 4 
 5 import com.tibco.tibrv.Tibrv;
 6 import com.tibco.tibrv.TibrvException;
 7 import com.tibco.tibrv.TibrvMsg;
 8 import com.tibco.tibrv.TibrvRvdTransport;
 9 import com.tibco.tibrv.TibrvTransport;
10 
11 public class NumberGenerator {
12     private String service = "7500";
13     private String network = ";225.1.1.1";
14     private String daemon = "tcp:7500";
15     private String subject = "DEMO.FT.NUM";
16     
17     public void run() {
18         try {
19             Tibrv.open(Tibrv.IMPL_NATIVE);
20             TibrvMsg.setStringEncoding("Big5");
21             TibrvTransport transport = new TibrvRvdTransport(service, network, daemon);
22             
23             for(int i=1; i<=1000; i++) {
24                 TibrvMsg msg = new TibrvMsg();
25                 msg.setSendSubject(subject);
26                 msg.update("number", i);
27                 
28                 transport.send(msg);
29                 
30                 try {
31                     Thread.sleep(1000);
32                 } catch (InterruptedException e) {
33                 }
34             }
35             
36             Tibrv.close();
37         } catch (TibrvException | UnsupportedEncodingException e) {
38             e.printStackTrace();
39         }
40     }
41 
42     public static void main(String[] args) {
43         NumberGenerator gen = new NumberGenerator();
44         gen.run();
45         System.out.println("stop");
46     }
47 }
上面的 NumberGenerator.java 很簡單的每秒送出一個累加的數字,由 1 累加到 1000 為止。對於上面程式有不了解的,可以先參考「Tibco RV request/reply 的同步與非同步」,之後再回頭來看 fault tolerance。接下來要進入主題,看一下 fault tolerance 程式。
  1 package idv.steven.rv.ft;
  2 
  3 import com.tibco.tibrv.Tibrv;
  4 import com.tibco.tibrv.TibrvException;
  5 import com.tibco.tibrv.TibrvFtMember;
  6 import com.tibco.tibrv.TibrvFtMemberCallback;
  7 import com.tibco.tibrv.TibrvListener;
  8 import com.tibco.tibrv.TibrvMsg;
  9 import com.tibco.tibrv.TibrvMsgCallback;
 10 import com.tibco.tibrv.TibrvRvdTransport;
 11 
 12 public class NumberReceiver implements TibrvMsgCallback, TibrvFtMemberCallback, Runnable {
 13     private String service = "7500";
 14     private String network = ";225.1.1.1";
 15     private String daemon = "tcp:7500";
 16     private String subject = "DEMO.FT.NUM";
 17     
 18     private String ftservice = "7504";
 19     private String ftnetwork = ";225.1.10.1";
 20     private String ftdaemon = "tcp:7504";
 21     
 22     private String ftgroupName = "DEMO.FT.GROUP";
 23     private int     ftweight = 50;
 24     private int     activeGoalNum = 1;
 25     private double  hbInterval = 1.5;
 26     private double  prepareInterval = 3; 
 27     private double  activateInterval = 4.8;
 28     
 29     private TibrvRvdTransport transport = null;
 30     private TibrvListener listener = null;
 31     
 32     private boolean active = false;
 33     
 34     @Override
 35     public void run() {
 36         try {
 37             Tibrv.open(Tibrv.IMPL_NATIVE);
 38             transport = new TibrvRvdTransport(service, network, daemon);
 39             TibrvRvdTransport fttransport = new TibrvRvdTransport(ftservice, ftnetwork, ftdaemon);
 40             fttransport.setDescription("fault tolerance");
 41             
 42             new TibrvFtMember(Tibrv.defaultQueue(), // TibrvQueue 
 43                       this,                 // TibrvFtMemberCallback
 44                       fttransport,          // TibrvTransport    
 45                       ftgroupName,          // groupName
 46                       ftweight,             // weight
 47                       activeGoalNum,        // activeGoal
 48                       hbInterval,           // heartbeatInterval
 49                       prepareInterval,      // preparationInterval,
 50                                             // Zero is a special value,
 51                                             // indicating that the member does 
 52                                             // not need advance warning to activate 
 53                       activateInterval,     // activationInterval
 54                       null);                // closure
 55             
 56             
 57             while(true) {
 58                 try {
 59                       Tibrv.defaultQueue().dispatch();
 60                 }
 61                 catch (TibrvException e) {
 62                    System.err.println("Exception dispatching default queue:");
 63                    System.exit(0);
 64                 }
 65                 catch(InterruptedException ie) {
 66                    System.exit(0);
 67                 }
 68             }
 69         } catch (TibrvException e) {
 70             e.printStackTrace();
 71         }
 72     }
 73     
 74     void enableListener() {
 75         try {
 76           // Subscribe to subject        
 77             listener = new TibrvListener(Tibrv.defaultQueue(),
 78                                          this, 
 79                                          transport, 
 80                                          subject, 
 81                                          null);
 82             System.out.println("Start Listening on: " + subject);    
 83         }
 84         catch (TibrvException e) {
 85            System.err.println("Failed to create subject listener:");
 86            System.exit(0);
 87         }
 88     }
 89     
 90     void disableListener() {
 91        listener.destroy();
 92        System.out.println("Destroy Listener on Subject: " + subject);    
 93     }
 94 
 95     @Override
 96     public void onFtAction(TibrvFtMember member, String ftgroupName, int action) {
 97         if (action == TibrvFtMember.PREPARE_TO_ACTIVATE) {
 98             System.out.println("TibrvFtMember.PREPARE_TO_ACTIVATE invoked...");
 99             System.out.println("*** PREPARE TO ACTIVATE: " + ftgroupName);
100         }
101         else if (action == TibrvFtMember.ACTIVATE) {
102             System.out.println("TibrvFtMember.ACTIVATE invoked...");
103             System.out.println("*** ACTIVATE: " + ftgroupName);
104             enableListener();
105             active = true;
106         }
107         else if (action == TibrvFtMember.DEACTIVATE) {
108             System.out.println("TibrvFtMember.DEACTIVATE invoked...");
109             System.out.println("*** DEACTIVATE: " + ftgroupName);
110             disableListener();
111             active = false;
112         }
113     }
114 
115     @Override
116     public void onMsg(TibrvListener listener, TibrvMsg msg) {
117         if (subject.equals(listener.getSubject())) {
118             try {
119                 int num = msg.getAsInt("number", 0);
120                 System.out.println("number: " + num);
121             } catch (TibrvException e) {
122                 e.printStackTrace();
123             }
124         }
125     }
126 
127     public static void main(String[] args) throws InterruptedException {
128         NumberReceiver rcv = new NumberReceiver();
129         Thread tRcv = new Thread(rcv);
130         tRcv.start();
131         tRcv.join();
132         System.out.println("stop");
133     }
134 }




上面的程式與 fault tolerance 有關的是第 39~54 行及第 96~113 行,現在說明 NumberReceiver.java 程式,如下:
  • Line 12: 所有參與 fault tolerance 的程式都要實作 TibrvFtMemberCallback。
  • Line 39: 建立一個 fault tolerance 的通道。
  • Line 42: 使這支程式成為群組中的一個成員 (member),這裡面有好幾個和時間有關的參數 - hbInterval、prepareInterval、activeInterval,單位是秒,指的是 heart beat 的時間、進入 PREPARE_TO_ACTIVATE 的時間及切換到 ACTIVATE 的時間,在這裡設定為 1.5、3、4.8,表示每三秒會有一次的心跳訊息,當 3 秒鐘都沒有收到訊息即進入 PREPARE_TO_ACTIVATE 狀態,當 4.8 秒沒有收到心跳訊息,就切換到 ACTIVATE 狀態。
  • Line 74~90: 這兩個 method (enableListener、disableListener) 是用來啟動服務及停止服務。
  • Line 96~113: onFtAction 是 fault tolerance 的 callback function,這裡可以接收到狀態改變的 event。
  • Line 116~125: 收到 NumberGenerator 送來的訊息,將它輸出到 console。
現在進行測試 …
  • 首先啟動 NumberGenerator …
  • 開啟兩個「命令提示字元」的視窗,並同時啟動 NumberReceiver,可以看到其中一個開始提供服務,如下圖:
  • 在左邊原本正在提供服務的程式按 Ctrl-C 以中斷服務,可以看到右邊的程式接手了服務。
  • 再次啟動左邊的程式,左邊的程式將處於 DEACTIVATE 的狀態。
  • 在右邊視窗中按 Ctrl-C 中斷右邊的程式,可以看到左邊的程式又接手服務。

【實戰】
上面的例子程式的狀態都是由 RV 偵測後透過 call back method 通知,也就是說 RV 是主動,程式是被動,但是某些應用裡,程式也可能必須改變狀態,例如正處於 ACTIVATE 狀態的程式發現自己其實有一些問題,並沒有正常運作,希望由同群組的別支程式接手服務,是否有辦法自己變更狀態?

TibrvFtMember 的 method 只有一個 setter method - setWeight,用來變更權重,這對於正處於 ACTIVATE 狀態的程式來說,並沒辦法將自己的運作權力交出,解決的辦法就是呼叫 destroy 退出群組,這樣 RV 就會再由群組中找出一個 DEACTIVATE 的 member,通知他提升到 ACTIVATE。

【日劇 - I'm Home】
以木村拓哉演出的日劇來說,這齣戲的收視率當然偏低,但其實這部日劇還不錯看,以家路久(木村拓哉 飾)的失憶後追尋記憶,逐漸帶出許多隱藏的秘密 … 
山口麻友在劇中演出木村拓哉沒有血緣的女兒,她的眼睛相當特別,像月亮一樣彎彎的 ...

2015年6月6日 星期六

fork Function


在「Advanced.Programming.in.the.UNIX.Environment, 3rd.Edition」一書中的8.3節 (p. 230),有個小程式,如下,是用來說明 UNIX 環境中,使用 fork 產生子行程 (child process),要注意的一些事,先看一下程式:

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 
 4 int globvar = 6;
 5 char buf[] = "a write to stdout\n";
 6 
 7 int main(void) {
 8     int var;
 9     pid_t pid;
10 
11     var = 88;
12     if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
13         printf("write error");
14     printf("before fork\n");
15 
16     if ((pid = fork()) < 0) {
17         printf("fork error");
18     }
19     else if (pid == 0) {
20         globvar++;
21         var++;
22     }
23     else {
24         sleep(2);
25     }
26 
27     printf("pid = %ld, getpid = %ld, glob = %d, var = %d\n", (long) pid, (long) getpid(), globvar, var);
28     _exit(0);
29 }

上面的程式很簡單的利用 fork 產生一個子行程,等待 2 秒後,印出一段訊息,顯示 fork 傳回值、行程的 pid (process id)、全域變數及區域變數,執行結果如下: (我的程式命名為 forEx01.c)
[steven@CentOS7 Debug]$ ./forkEx01
a write to stdout
before fork
pid = 0, getpid = 9214, glob = 7, var = 89
pid = 9214, getpid = 9213, glob = 6, var = 88
[steven@CentOS7 Debug]$ 
根據上述的執行結果,說明如下:
  1. fork 函數會產生一個子行程,子行程會執行父行程 (parent process) fork 之後的指令。
  2. fork 函數在父行程會傳回子行程的 pid,在子行程則傳回 0,子行程如果要得到父行程的 pid,可以透過 getppid 函數得到值。
  3. 上面程式讓父行程睡 2 秒後再印出結果,所以第一個結果是子程式印出的,第二個結果是父行程印出的,通常確實是會得到如上的結果 (除了 pid、getpid 的值會不同之外),但是,實際上有時會是父行程先印出,因為那個行程先執行,由 OS 決定,這裡父行程睡 2 秒只是增加子行程先執行的機率。
  4. 上述程式的全域變數 (global variable) 和區域變數 (local variable) 於子行程中都被加 1,但是都沒有影響到父行程的值,這表示不管是全域變數或區域變數,父行程、子行程都不共用!
  5. 子行程的輸出接在父行程 fork 前已輸出的內容之後,父行程後面的輸出又接在子行程的輸出之後,完全不會互相覆蓋,因為父行程和子行程會共享在 fork 之前已開啟的所有檔案描述符 (file descriptions),也就是說兩個行程會共享這些檔案的檔案指標! 標準輸出是在父行程一開始執行時就被開啟的,所以會被兩個行程共享。
除了上述程式所展現的父行程、子行程關係外,另外針對行程的一些基本觀念整理如下:
  1. UNIX 系統在系統啟動後,會啟動許多 process 來處理一些系統面的事,其中一個 pid 為 1 的 process 稱為 init process,是負責啟動和關閉系統。
  2. 程式以 fork 啟動一個或數個子行程,當子行程結束後,父行程要負責將其佔用的資源都回收後,才可真正將其結束,在子行程已經執行完,但是父行程尚未將其資源回收前,這個子行程就稱為 zombie (僵屍)。
  3. 如果父行程比子行程更早結束,子行程並不會被迫結束,而是會交由 init process 管理,也就是說,子行程的 ppid 會被改為 1,之後子行程結束,其資源會由 init process 進行回收。



2015年5月26日 星期二

byte ordering functions

前一篇「Linux Network Programming - 初體驗」目的很單純,就是快速的對 socket 有基本的認識,這一篇要說明 byte ordering 的問題及解決辦法!

在一般人的直覺裡,如果一個 short 有兩個 byte,在記憶體中應該就是由左到右放,就像寫字一樣,由左到右寫,那麼就會是高位元放在左、低位元放在右,這就是 big-endian byte order。可偏偏並非所有的電腦都是這麼放的! 有些電腦是低位元放在左,高位元放在右,這稱為 little-endian byte order。而 socket 的傳輸資料不管原來是什麼型別,傳輸過程一定會是以 byte array 的方式傳輸,接收端接收後,再依原先約定好的格式還原成各種型別。在網路上傳輸的 byte array 順序,是規範成 big-endian。

上圖是「Unix Netowrk Programming」的3.4節中的一個插圖,用來說明 big-endian 和 little-endian 的差別。在開始寫程式前,一定要先了解這些基本的知識,並且知道 C 提供了那些函式來解決這個問題!

回過頭去看前一篇的 server 程式,有如下一段程式碼:
22 servaddr.sin_family = AF_INET;
23 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
24 servaddr.sin_port = htons(1513);
23、24行中呼叫的 htonlhtons 就是本篇要介紹的,他們會將如 1513、INADDR_ANY 這樣的常數值,轉成合乎網路傳輸所需要的值 (轉換後如果值改變的話,基本上就是高位元、低位元排列順序變了造成的!)。
兩個函式是 host to netowrk 的縮寫,l 和 s 就是 long 和 short,htonl 用來處理 32 位元的值,htons 用來處理 16 位元的值。要特別注意,就算是在 64 位元電腦上,htonl 還是只用來處理 32 位元的值!




2015年5月23日 星期六

Linux Network Programming - 初體驗

十幾年沒有寫 C 了,這可以說是重新學習,底下的程式是改寫自 W.Richard Stevens 的名著 - UNIX Network Programming Volume 1,改寫的原因有二:
(1) 在我的開發環境 scientific linux 7.0 上沒辦法正常 compile,可能是因為 Stevens 的程式是在 UNIX 上寫的,與 linux 上略有不同;
(2) Stevens 用 #define 將許多常用的函式重新定義,對於初學者來說,反而會被混淆,我將這些函式還原為原始函式。
這個程式有 client 和 server,server 先啟動,等待 client 連線,待 client 連線後回覆現在的系統時間,client 將收到的值印出。第一個程式是 server,第二個程式是 client。這裡會詳細說明每個函式,因為… 我是初學者...  XD
 1 #include <sys/socket.h>
 2 #include <sys/types.h>
 3 #include <stdio.h>
 4 #include <unistd.h>
 5 #include <netinet/in.h>
 6 #include <string.h>
 7 #include <arpa/inet.h>
 8 #include <time.h>
 9 
10 const int MAXLINE = 100;
11 const int LISTENQ = 1024;
12 
13 int main(int argc, char **argv) {
14     int listenfd, connfd;
15     struct sockaddr_in servaddr;
16     char buff[MAXLINE];
17 
18     time_t ticks;
19     listenfd = socket(AF_INET, SOCK_STREAM, 0);
20 
21     bzero(&servaddr, sizeof(servaddr));
22     servaddr.sin_family = AF_INET;
23     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
24     servaddr.sin_port = htons(1513);
25 
26     bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
27     listen(listenfd, LISTENQ);
28 
29     for( ; ; ) {
30         connfd = accept(listenfd, (struct sockaddr *) NULL, NULL);
31 
32         ticks = time(NULL);
33         snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
34         write(connfd, buff, strlen(buff));
35 
36         close(connfd);
37     }
38 
39 }
  • 第 19 行: 建立一個 socket 連線,不管 client 或 server 程式,一開始都會呼叫這個函式,函式所需要的三個參數說明如下:
    • 第一個參數: AF_INET 表示要建立網際網路的 IPv4 socket 連線,除此之外還可以填入 AF_INET6 (IPv6) 或 AF_UNSPEC (同時可以用 IPv4 及 IPv6),socket 函式及這幾個常數是定義在 socket.h 標頭檔裡,這也是為什麼第一行要引入這個標頭檔的原因,大部份的 socket 相關函式、常數都定義在這個標頭檔。
    • 第二個參數: 可以傳入的值有兩個 - SOCK_STREAM 及 SOCK_DGRAM,要建立 TCP 連線時傳入 SOCK_STREAM,要建立 UDP 連線時傳入 SOCK_DGRAM。
    • 第三個參數: 通常就是傳入 0,socket 將會選擇最適合的通訊協定,也就是第二個參數的說明,至於不傳入 0 時,用在什麼地方? 這個以後再說 (現在我也不知道)。
    • 傳回值: 錯誤時傳回 -1,正確傳回 0,C 語言的習慣就是正確傳回 0,錯誤則傳回錯誤代碼。
  • 第 21 行: bzero 是將指定的位指空間全部清為 0,這不是 ANSI C 的標準函式,ANSI C 的標準函式是 memset,不過 bzero 大多數的平台也都有支援,定義在 string.h 裡。傳入的兩個參數如下:
    • 第一個參數: 要清為 0 的位址。
    • 第二個參數: 位址空間的長度。
  • 第 22~24 行: 設定 server address 的值,這是在第 15 行宣告的變數,struct sockaddr_in 這個結構是為了在 bind 時傳入一些值,這三個值說明如下:
    • sin_family: 如 socket 的第二個參數說明的一樣,這是指要使用 TCP 通訊協定。
    • sin_addr.s_addr: 這裡指定為 INADDR_ANY 是使得任何位址連進來的 socket 連線都可以被接受,在設定前呼叫的 htonl 函式是為了讓值與平台無關,不同的 OS 有些字元排列順序高位元和低位元不一定相同,透過這個函式可以去除這種平台相依性,都轉為網路所規範的順序。
    • sin_port: 指定要 bind 到那個 port,htons 這個函式和 htonl 是一樣的,都是用來去除平台相依性,差別在於 htons 是用在 16 bits 的變數, htonl 是用在 32 bits 的變數。
  • 第 26 行: bind 是將 socket 和 sockaddr 繫結起來的函式,表示第 19 行建立的 socket 連線,將會是允許任何位址連線,而將傾聽的 port 是 1513。
  • 第 27 行: 開始傾聽,最大連線數是 LINTENQ 所定義的值,即 1024 條連線。
  • 第 29 行: 這裡建立一個無窮迴圈,所以這個 server 會一直執行,直到使用者按 ctrl-C 為止。
  • 第 30 行: server  會停在 accept 這裡等待 client 連線,參數說明如下:
    • 第一個參數: 透過傳入傾聽描述子 listenfd,將第 26、27 行傾聽的相關數據傳入。
    • 第二、三個參數: 待我弄懂了再補充  … XD
    • 傳回值: 建立連線後,傳回連線描述子 (connected descriptor),這個描述子會用來與新的 client 連線進行通訊。
  • 第 32 行: 取得目前的系統時間,這個 time 函式定義在 time.h 標頭檔裡。
  • 第 33 行: snprintf 函式定義在 stdio.h 裡,和 sprintf 的最大差別在於 snprintf 的第二個參數指出了第一個參數的空間大小,可防止實際產生出來的字串長度超過第一個參數所宣告的空間。
  • 第 34 行: 將結果寫出給 client,這裡可以看到第一個參數即是 accept 的傳回值,這樣就可以通知系統到底是要將資料傳給那個 client。
  • 第 36 行: 關閉與 client 間的 socket 連線。






 1 #include <stdio.h>
 2 #include <sys/socket.h>
 3 #include <sys/types.h>
 4 #include <netinet/in.h>
 5 #include <strings.h>
 6 #include <arpa/inet.h>
 7 
 8 const int MAXLINE = 100;
 9 
10 void err_sys(const char* x, ...)
11 {
12     perror(x);
13     //exit(1);
14 }
15 
16 void err_quit(const char* x, ...)
17 {
18     perror(x);
19     //exit(1);
20 }
21 
22 int main(int argc, char **argv)
23 {
24     int sockfd, n;
25     char recvline[MAXLINE + 1];
26     struct sockaddr_in servaddr;
27 
28     if (argc != 2) {
29         err_quit("usage: a.out <IPaddress>");
30         return 1;
31     }
32 
33     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
34         err_sys("socket error");
35         return 1;
36     }
37 
38     bzero(&servaddr, sizeof(servaddr));
39     servaddr.sin_family = AF_INET;
40     servaddr.sin_port = htons(1513);
41     if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
42         err_quit("inet_pton error for %s", argv[1]);
43 
44     if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
45         err_sys("connect error");
46 
47     while ((n = read(sockfd, recvline, MAXLINE)) > 0) {
48         recvline[n] = 0;
49         if (fputs(recvline, stdout) == EOF)
50             err_sys("fputs error");
51     }
52 
53     if (n < 0) {
54         err_sys("read error");
55         return 1;
56     }
57 
58     return 0;
59 }
  • 第 41 行: client 程式執行時,要帶入 server 的 IP,inet_pton 函式即是將 IP 的文字格式轉成接下來要呼叫的 connect 所需要的 binary 格式。
  • 第 44 行: 呼叫 connect 連線到 server。
  • 第 47 行: 使用 read 讀取 server 傳回的值,三個參數說明如下:
    • 第一個參數: socket 描述子,第 33 行建立 socket 連線時記錄下來的數值。
    • 第二個參數: 讀取的值要放入的空間。
    • 第三個參數: 放入的空間的最大長度值。
  • 第 49 行: 將結果寫出到標準輸出裝置。
測試時,先執行 server 再執行 client,client 端顯示如下結果:
【日劇: Second Love】
在台灣播出時改名為「愛上女老師」,看這部日劇前,沒特別注意過深田恭子原來那麼偉大,而且都年過三十了,還那麼可愛,算是不簡單,許多人上了年紀還裝可愛,可是會被吐槽的,但是她不會。這部戲為什麼只有七集? 是因為收視率太低嗎? 這就不曉得了,但是除了結局太老套外,倒是還不錯看。


2015年5月10日 星期日

BCD pack & unpack

在程式裡的 I/O 和 CPU 計算比起來,速度差距非常大,網路傳輸又是 I/O  中比較慢的一種,所以,常可見到網路傳輸的資料,會先經過壓縮後再傳輸,接收端接到後再解壓縮。BCD 是一種常用的壓縮技術,當要傳輸的資料中,某一段確定是數字,即可將該段的資料以 BCD 壓縮後傳輸,例如:
123456789665,或許它代表著12點34分56秒789毫秒665奈秒,如果不壓縮直接傳就是 12 個 bytes,BCD 則只會有 6 個 bytes,為什麼? 說明如下:
  1. 一個 byte 有 8 個 bits,用來存數字,實際上只會用到後面 4 個 bits,前面的 4 個 bits 永遠為 0000,例如 5 的 8 個 bits 會是 00000101 (二進位表示法),9 的 8 bits 為 00001001。
  2. BCD 用一個 byte 來存兩個數字,前 4 個 bits (高位元) 存一個數字,後 4 個 bits 再存一個數字,所以,如果有兩個數字 95,用 BCD 儲存就會是 10010101,前 4 個 bits 是 9,後 4 個 bits 是 5。
底下是 BCD 的程式:

public class BCD {
 static public byte[] pack(long number) {
  byte hi, lo;
  String s = String.valueOf(number);
  byte[] data = s.getBytes();
  byte[] bcd = new byte[s.length() / 2];
  for (int i = 0; i < bcd.length; i++) {
   hi = (byte)(data[i * 2] << 4);
   lo = (byte)(data[i * 2 + 1] & 0x0f);
   bcd[i] = (byte)(hi | lo);
  }
  return bcd;
 }
 static public long unpack(byte[] pack, byte[] data) {
  byte hi, lo;
  long number = 0L;
  for (int i = 0; i < pack.length; i++) {
   hi = (byte)((pack[i] >> 4) & 0x0f);
   lo = (byte)(pack[i] & 0x0f);
   data[i * 2] = hi;
   data[i * 2 + 1] = lo;
   number = number * 100 + (hi * 10) + lo;
  }
  return number;
 }
}

應該很容易看的懂,pack 就是將正常的資料壓縮成 BCD 格式,unpack 是將 BCD 格式的資料還原。測試程式如下:
要被壓縮的資料為 number,值為 123456789665,參考下面的輸出,可以看到 BCD 長度只有 6 bytes,其 hex 值即是我們要傳輸的值; 把 BCD 資料解壓縮,即可回 number 的值。
bcd len: 6
bcd value: [B@439f5b3d
bcd value (hex): 123456789665
data len: 12
data value: [B@685f4c2e
data value (hex) 010203040506070809060605
number: 123456789665
要特別注意的是,上面 unpack 程式中,高位元的資料在向左 shift 4 位到低位元後,一定要再 & 0x0f,將高位元清為 0,因為 shift 時,如果最高的 bit 為 1,往左 shift 4 位,高位的 4 個 bits 會全部變成 1,例如最前面例子舉的 95,二進位是 10010101,5 是存在低位元,只要將高位元清成0 ( & 0x0f),即可得到 5,但是 高位元的 9 向左 shift 4 位後會是 11111001,所以要將高位元再清為 0 (& 0x0f)。

2015年5月5日 星期二

Tibco RV 的 Queue

回顧「Tibco RV request/reply 的同步與非同步」一文裡面的 Server.java,它使用的是 Tibco RV 的 default queue 進行訊息的分派 (line 36、39),這麼做的最大好處是簡單,但是,基於以下理由,一般的應用程式不會使用 default queue,而是會每個應用程式自行建立自己的 queue,理由如下:

  1. 每個 queue 的訊息是以 FIFO 的方式消化掉,如果所有應用程式共用一個,所有的訊息會混雜在一起,會造成塞車。
  2. 不同的 queue 是以 round-robin 的方式消化,default queue 的優先順序是 1,也就是最低的優先順序!
  3. 使用自己建立的 queue,可以設定一些參數,以自己希望的方式消化訊息。

這裡將 Server.java 的訊息分派那一段程式改寫,如下:
             TibrvQueue queue = new TibrvQueue();
             queue.setPriority(10);
             queue.setName("MyQueue");
             queue.setLimitPolicy(TibrvQueue.DISCARD_LAST, 100, 1);
           
             new TibrvListener(queue, this, transport, subject, null);
           
             while (!eventReceived) {
                 eventReceived = queue.timedDispatch(server_timeout);
                 if (eventReceived) {
                     System.out.println("receive a message");
                 }
                 else {
                     System.out.println("timeout");
                 }
             }
從上面的程式,可以看到我們為 queue 設定了比較高的優先順序,也為 queue 取名,以方便 debug,最重要的是,還可以用 setLimitPolicy 設定一些訊息的處理原則,它的三個參數說明如下:
  1. 第一個參數: 當 queue 的訊息滿了,又有新的訊息傳過來,來不及處理時,queue 該如何處理?
  2. 第二個參數: 這個 queue 最多可以放幾個訊息。
  3. 第三個參數: 當要放棄一些訊息時,一次要放棄幾個?




【日劇 - 影子寫手】
同一個故事,每個人看到的面向多不盡相同,這部影集在川原由樹 (水川麻美 飾) 揭露出自己長期當名作家遠野理紗 (中谷美紀 飾) 的槍手後,除了真相被新聞媒體聯手封殺外,她的寫作之路也被各大出版社聯手斬斷了! 最後更在司法上慘敗,面臨鉅額賠償! 這是很正常的,社會的各既得利益者會為保護自己的權益及權力,聯手且不擇手段的打擊任何對手。

台灣這幾年喊的震天響的「轉型正義」,真的要成功,一定會讓目前的許多既得利益階級喪失非常多的特權,這些特權對他們說都是天經地義,原本就屬於他們自己的,就像皇后生下的長子就該立為太子,就該繼承皇位那麼的自然。所以當改革真的開始啟動後,這些既得利益階級(反動勢力?)的反撲是必然的,也絕對是會讓台灣陷入一段時間的動盪、不安。

【每日一字】
class warfare - fighting or disagreement between social classes, usually with the lower classes trying to take power and money away from the upper classes (階級鬥爭)
* Some people think that class warfare is unavoidable and that as the upper classes continue to get richer, the lower classes will star a revolution.
(資料來源: ESL Podcast 573)