linux C程序常用操作
不登高山,不知天之高也;
不臨深溪,不知地之厚也。
荀子《勸學》
linux應用層主要是一個個獨立任務的程序在執行,但是很多時候,在工作中我們可能很少去重新寫一個程序,
大部分的工作都是分配到了一個程序內的模組或者提供程序內特定功能的介面開發,這篇文章是想簡單說明下,
作為一個程序,在實際開發過程中,可能用到的一些程式設計方法比如:main引數解析,訊號註冊、回撥函式、
執行緒建立、檔案操作(FIFE *fp)、程序間通訊(socket);每一種我都會附一個簡單的例項。
1、main引數解析,引數較少,使用簡單判斷argc並取出對應的argv[i]值就就可以處理,程式碼如下:
1 #include <stdio.h> 2 int main(int argc, char *argv[]) 3 { 4 int i = 0; 5 printf("argc is %d\n", argc); 6 for(i = 0; i < argc; i++) 7 printf("argv[%d] is %s\n", i, argv[i]); 8 return 0; 9 }
引數較多時就可以呼叫getopt/getopt_long介面來完成工作。
函式的定義
int getopt(int argc, char * const argv[], const char *optstring);
引數說明:
argc:main()函式傳遞過來的引數的個數
argv:main()函式傳遞過來的引數的字串指標陣列
optstring:選項字串,告知 getopt()可以處理哪個選項以及哪個選項需要引數
程式碼樣列
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<getopt.h> 4 int main(int argc, char *argv[]) 5 { 6 int opt; 7 /*單個字元表示選項沒有引數 輸入格式:-A即可,不加引數 8 *單字元加冒號表示選項有且必須加引數 輸入格式:-B xiaocang或-Bxiaobo(二選一) 9 *單字元加兩個冒號表示選項可以有也可以無 輸入格式:-Cxiaobo(必須挨著) 10 */ 11 char *string = "AB:C::"; 12 while ((opt = getopt(argc, argv, string))!= -1) 13 { 14 /* 下面是常用的兩個獲取選項及其值得變數optarg無需定義,全域性變數 15 * opt '-' 後面的字元,也就是引數字元 16 * optarg 指向當前選項引數(如果有)的指標。 17 */ 18 printf("opt = %c\t\t", opt); 19 printf("optarg = %s\t\t\n", optarg); 20 } 21 return 0; 22 }
樣列輸出:
./argc-opt -A -B xiaocang -Cxiaobo opt = A optarg = (null) opt = B optarg = xiaocang opt = C optarg = xiaobo
2、訊號註冊,作為一個程序,很有必要註冊一定的訊號,防止程序異常退出時,自己蒙圈。
函式定義:
1 #include <signal.h> 2 typedef void (*sighandler_t)(int); 3 sighandler_t signal(int signum, sighandler_t handler);
引數說明:
signum:要捕捉的訊號(檢視訊號:kill -l,9號SIGKILL訊號不能被捕捉);
handler:我們要對訊號進行的處理方式。
示例程式碼:
1 #include <stdio.h> 2 #include <signal.h> 3 void signal_handler(int signal) 4 { 5 printf("Received signal %d\n", signal); 6 printf("do something...\n"); 7 return; 8 } 9 int main(int argc, char *argv[]) 10 { 11 signal(SIGHUP, signal_handler); 12 while(1) 13 sleep(2000); 14 return 0; 15 }
示例測試:
一個視窗後臺掛進程執行 ./a.out &
開另外的視窗傳送訊號 kill -1 pid(a.out程序號)
第一個視窗收到將會收到
Received signal 1
do something...
3、回撥函式
其實在訊號註冊中我們就使用了回撥函式,但是此回撥函式不是我們自己定義的型別,自己用來呼叫執行,
我們這裡做一個回撥函式的呼叫示例,差別就在於回撥函式的定義與呼叫時機,根據自己的實際需要定義就可以。
1 #include <stdio.h> 2 3 /* 定義一個函式指標 確定入參與返回值型別 */ 4 typedef int (* MyCallbak)(int PanJinLian, int XiMengQin); 5 /* 實現一個與上面定義的函式指標入參與返回值型別相同的函式 */ 6 int ThisMyFunc(int PanJinLian, int XiMengQin) 7 { 8 printf("PanJinLian is %d\n", PanJinLian); 9 printf("XiMengQin is %d\n", XiMengQin); 10 printf("do something...\n"); 11 return 0; 12 } 13 int main(int argc, char *argv[]) 14 { 15 int P_adrenaline = 99; 16 int X_adrenaline = 101; 17 MyCallbak CallbakPointer;/* 定義一個函式指標變數 */ 18 CallbakPointer = ThisMyFunc;/* 將函式地址賦予定義的指標 也叫掛鉤子*/ 19 int ret = CallbakPointer(P_adrenaline, X_adrenaline);/* 呼叫函式,執行回撥 */ 20 printf("ret is %d\n", ret); 21 return 0; 22 }
執行返回
1 PanJinLian is 99 2 XiMengQin is 101 3 do something... 4 ret is 0
4、執行緒建立
執行緒主要是用來阻塞接受非同步訊息,或者完成耗時與週期性的任務,重點需要關注的是執行緒結束時執行緒資源的回收問題,
很多人會忽略這部分,會用到 pthread_detach 或者 pthread_join(阻塞等待執行緒結束並回收資源); 多執行緒必將引入同步與
互斥問題,則對於全域性變數,必須要加鎖保護,資料流防止丟失我們會用到佇列。
1 #include <pthread.h> 2 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 3 void *(*start_routine) (void *), void *arg) 4 //Compile and link with -pthread.
引數說明
thread:指向執行緒識別符號的指標。
attr:用來設定執行緒屬性。
start_routine:執行緒執行函式的起始地址。
arg:執行函式的引數。
樣例程式碼:
1 #include<stdio.h> 2 #include <pthread.h> 3 static void mythreadfun( void *arg ) 4 { 5 /*這將該子執行緒的狀態設定為detached,則該執行緒執行結束後會自動釋放所有資源*/ 6 pthread_detach(pthread_self()); 7 printf("arg is %s\n", (char *)arg); 8 int i = 0; 9 while(1) 10 { 11 printf("do something...\n"); 12 if(i++ == 10) 13 break; 14 sleep(2); 15 } 16 return ; 17 } 18 19 int main(int argc, char *argv[]) 20 { 21 pthread_t pthreadid = 0; 22 int ret = 0; 23 char *param = "good"; 24 /* 建立執行緒 */ 25 ret = pthread_create(&pthreadid, NULL, (void *)mythreadfun, (void *)param); 26 if(ret != 0) 27 { 28 printf("create pthread failed."); 29 return; 30 } 31 printf("create pthread success."); 32 while(1) 33 sleep(2000); 34 return 0; 35 }
執行返回
1 ./a.out 2 create pthread success.arg is good 3 do something... 4 do something... 5 do something... 6 do something...
5、檔案操作,檔案操作很普遍,如記錄資料,讀入資料等。
檔案操作一般要注意,明確操作的fp在檔案中的位置,fclose前重新整理快取,對寫入或者讀出的返回做判斷,異常或者結束
操作時關閉fp,同樣還有open read write介面。兩者區別:
1、緩衝檔案系統與非緩衝系統的區別
緩衝檔案系統(fopen):在記憶體為每個檔案開闢一個快取區,當執行讀操作,從磁碟檔案將資料讀入記憶體緩衝區,裝滿後從
記憶體緩衝區依次讀取資料。寫操作同理。
記憶體緩衝區的大小影響著實際操作外存的次數,緩衝區越大,操作外存的次數越少,執行速度快,效率高。緩衝區大小由機
器而定。藉助檔案結構體指標對檔案管理,可讀寫字串、格式化資料、二進位制資料。
非緩衝檔案系統(open):依賴作業系統功能對檔案讀寫,不設檔案結構體指標,只能讀寫二進位制檔案。
2、open屬於低階IO,fopen屬於高階IO
3、open返回檔案描述符,屬於使用者態,讀寫需進行使用者態與核心態切換。 fopen返回檔案指標
4、open是系統函式,不可移植 fopen是標準C函式,可移植
5、一般用fopen開啟普通檔案,open開啟裝置檔案
6、如果順序訪問檔案,fopen比open快、 如果隨機訪問檔案,open比fopen快
1 #include <stdio.h> 2 #include <string.h> 3 int main() 4 { 5 FILE *fp; 6 char *msg = "hello world"; 7 char buffer[20]; 8 fp = fopen("test.txt", "w+");/* 開啟檔案用於讀寫 */ 9 fwrite(msg, strlen(msg) + 1, 1, fp);/* 寫入資料到檔案 */ 10 fseek(fp, 0, SEEK_SET);/* 移動到檔案的開頭 */ 11 fread(buffer, strlen(msg) + 1, 1, fp); /* 讀取並顯示資料 */ 12 printf("%s\n", buffer); 13 fsync(fileno(fp));/* 重新整理快取 */ 14 fclose(fp); 15 return(0); 16 }
執行結果
1 hello world 2 cat test.txt 3 hello world
6、程序間通訊:包括管道(pipe)、有名管道(named pipe)、訊號量(semophore)、訊息佇列(message queue)、
訊號(signal)、共享記憶體(shared memory)、套接字(socket),這裡只說下socket,tcp服務端與客戶端的簡單編
碼流程實現。
server端
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <sys/un.h> 4 #define SERVER_SOCKET_FILE "/tmp/server_socket" 5 int main() 6 { 7 int ret = 0; 8 int listen_fd = -1; 9 int size = 0; 10 int conn_fd = -1; 11 int max_fd = 0; 12 fd_set reads; 13 struct sockaddr_un clt_addr; 14 socklen_t len = sizeof(clt_addr); 15 struct sockaddr_un srv_addr; 16 unsigned char buf[4096]; 17 18 listen_fd = socket(PF_UNIX, SOCK_STREAM, 0); 19 if(listen_fd < 0) 20 { 21 printf("can not create listen socket\n"); 22 return -1; 23 } 24 25 srv_addr.sun_family = AF_UNIX; 26 strncpy(srv_addr.sun_path, SERVER_SOCKET_FILE, sizeof(srv_addr.sun_path)); 27 unlink(SERVER_SOCKET_FILE); 28 ret = bind(listen_fd,(struct sockaddr*)&srv_addr, sizeof(srv_addr)); 29 if(ret < 0) 30 { 31 printf("can not bind server socket"); 32 close(listen_fd); 33 return -1; 34 } 35 36 ret = listen(listen_fd, 5); 37 if(ret < 0) 38 { 39 printf("can not listen the client"); 40 close(listen_fd); 41 return -1; 42 } 43 while(1) 44 { 45 FD_ZERO(&reads); 46 FD_SET(listen_fd, &reads); 47 max_fd = listen_fd; 48 if(conn_fd > 0) 49 { 50 FD_SET(conn_fd, &reads); 51 if(conn_fd > max_fd) 52 { 53 max_fd = conn_fd; 54 } 55 } 56 ret = select(max_fd+1, &reads, 0, 0, NULL); 57 if(ret <= 0) 58 { 59 perror("select fail\n"); 60 return -1; 61 } 62 else 63 { 64 memset(buf, 0, sizeof(buf)); 65 if(FD_ISSET(listen_fd, &reads)) 66 { 67 conn_fd = accept(listen_fd, (struct sockaddr*)&clt_addr, &len); 68 } 69 if(FD_ISSET(conn_fd, &reads)) 70 { 71 printf("recv client msg,conn_fd:%d\n",conn_fd); 72 read(conn_fd, buf, sizeof(buf)); 73 sleep(3); 74 write(conn_fd, "i am server", strlen("i am server")); 75 } 76 } 77 } 78 }
client端
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <sys/un.h> 4 #define SERVER_SOCKET_FILE "/tmp/server_socket" 5 int main() 6 { 7 int ret = 0; 8 int retry = 5; 9 int i = 0; 10 int client_fd = 0; 11 struct sockaddr_un server_addr; 12 char buff[4096] = {0}; 13 int recv_data_len = 0; 14 struct sockaddr_un client_addr; 15 socklen_t sock_len = sizeof(client_addr); 16 if ((client_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) 17 { 18 printf("create sockfd failed\n"); 19 return -1; 20 } 21 printf("update socket create success.\n"); 22 memset(&server_addr,0x00,sizeof(server_addr)); 23 server_addr.sun_family = AF_UNIX; 24 strncpy(server_addr.sun_path, SERVER_SOCKET_FILE, strlen(SERVER_SOCKET_FILE)); 25 for(i = 0; i < retry; i++) 26 { 27 ret = connect(client_fd,(struct sockaddr*)&server_addr,sizeof(server_addr)); 28 if(0 != ret) 29 { 30 printf("cannot connect to the server, retry %d time.\n", i); 31 sleep(1); 32 continue; 33 } 34 else 35 { 36 printf("connect server success.\n"); 37 break ; 38 } 39 } 40 write(client_fd, "hello", strlen("hello")); 41 while(1) 42 { 43 memset(buff, 0, sizeof(buff)); 44 recv_data_len = recvfrom(client_fd, buff, sizeof(buff), 0, (struct sockaddr *)&client_addr, &sock_len); 45 if(recv_data_len <= 0) 46 { 47 sleep(1); //sleep 100ms and receive from socket again 48 printf("recv_data_len is %d.\n", recv_data_len); 49 50 /* 重新連線 省略*/ 51 close(client_fd); 52 continue; 53 } 54 printf("recv data [%s]\n", buff); 55 /* do something */ 56 sleep(1); 57 write(client_fd, "recv data form server", strlen("recv data form server")); 58 } 59 return 0; 60 }
分別編譯命名server client,先執行server 後執行client即可如下所示:
以上就是linux c程序,會涉及到的一些基本的操作,還有很多的比較重要且基本的內容沒有這裡並沒有講到。
接觸了不少專案,總結下來大型專案中涉及多程序,多執行緒,難點在於弄清楚通訊訊息的流向、資料結構的巧妙定義。剩下
的其實就是特定任務的邏輯部分了,如果作者有很好的註釋或者編碼規範,那理解起來也是很快的,新增功能也是很容易的事情。
萬變不離其宗。搞清楚基本的內容、基本原理,我認為在技術成長中很重要。
關注微信公眾號【嵌入式C部落】,獲取更多精華文章,海量程式設計資料,讓我們一起進步,一起成長。&n