| 項目 | 日期 | |
|---|---|---|
| * |
Environment
| |
| 1 | Ubuntu 上的 PPPoE 設定 |
2020/05/01
|
| 2 | ssh 公鑰認證登入 |
2015/02/16
|
| 3 | 在 linux 中無法切換身份? (su: Permission denied) |
2017/12/22
|
| 4 | RHEL8 無法安裝套件 No match for argument: xxx |
2022/03/27
|
| 5 | install nfs server@Ubuntu |
2023/11/08
|
| * |
Shell Scripting
| |
| 1 | 使用 sort、join 處理 csv 檔案 |
2015/01/16
|
| 2 | EBCDIC <=> ASCII 間的轉換 |
2015/01/17
|
| * |
C Programming
| |
| 1 | Linux Network Programming - 初體驗 |
2015/05/23
|
| 2 | byte ordering functions |
2015/05/26
|
| 3 | fork Function |
2015/06/06
|
Google Code Prettify
2015年10月25日 星期日
Linux / Unix
2015年10月21日 星期三
concurrency programming
| 分類 | 項目 | 日期 | |
|---|---|---|---|
| 1 |
Java
| java.util.concurrent.locks - 臨界區間的讀寫 |
2015/08/13
|
| 2 |
Java
| java.util.concurrent - Future & Callable |
2015/08/15
|
| 3 |
Java
| java.util.concurrent - ExecutorService |
2015/08/16
|
| 4 |
C
| fork Function |
2015/06/06
|
| 5 |
Design Pattern
| Producer-Consumer Pattern |
2015/10/17
|
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]$
根據上述的執行結果,說明如下:
- fork 函數會產生一個子行程,子行程會執行父行程 (parent process) fork 之後的指令。
- fork 函數在父行程會傳回子行程的 pid,在子行程則傳回 0,子行程如果要得到父行程的 pid,可以透過 getppid 函數得到值。
- 上面程式讓父行程睡 2 秒後再印出結果,所以第一個結果是子程式印出的,第二個結果是父行程印出的,通常確實是會得到如上的結果 (除了 pid、getpid 的值會不同之外),但是,實際上有時會是父行程先印出,因為那個行程先執行,由 OS 決定,這裡父行程睡 2 秒只是增加子行程先執行的機率。
- 上述程式的全域變數 (global variable) 和區域變數 (local variable) 於子行程中都被加 1,但是都沒有影響到父行程的值,這表示不管是全域變數或區域變數,父行程、子行程都不共用!
- 子行程的輸出接在父行程 fork 前已輸出的內容之後,父行程後面的輸出又接在子行程的輸出之後,完全不會互相覆蓋,因為父行程和子行程會共享在 fork 之前已開啟的所有檔案描述符 (file descriptions),也就是說兩個行程會共享這些檔案的檔案指標! 標準輸出是在父行程一開始執行時就被開啟的,所以會被兩個行程共享。
除了上述程式所展現的父行程、子行程關係外,另外針對行程的一些基本觀念整理如下:
- UNIX 系統在系統啟動後,會啟動許多 process 來處理一些系統面的事,其中一個 pid 為 1 的 process 稱為 init process,是負責啟動和關閉系統。
- 程式以 fork 啟動一個或數個子行程,當子行程結束後,父行程要負責將其佔用的資源都回收後,才可真正將其結束,在子行程已經執行完,但是父行程尚未將其資源回收前,這個子行程就稱為 zombie (僵屍)。
- 如果父行程比子行程更早結束,子行程並不會被迫結束,而是會交由 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 程式,有如下一段程式碼:
兩個函式是 host to netowrk 的縮寫,l 和 s 就是 long 和 short,htonl 用來處理 32 位元的值,htons 用來處理 16 位元的值。要特別注意,就算是在 64 位元電腦上,htonl 還是只用來處理 32 位元的值!
在一般人的直覺裡,如果一個 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、24行中呼叫的 htonl、htons 就是本篇要介紹的,他們會將如 1513、INADDR_ANY 這樣的常數值,轉成合乎網路傳輸所需要的值 (轉換後如果值改變的話,基本上就是高位元、低位元排列順序變了造成的!)。
23 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
24 servaddr.sin_port = htons(1513);
兩個函式是 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 將許多常用的函式重新定義,對於初學者來說,反而會被混淆,我將這些函式還原為原始函式。
(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 端顯示如下結果:
訂閱:
意見 (Atom)


