不登高山,不知天之高也; linux
不临深溪,不知地之厚也。编程
荀子《劝学》数组
linux应用层主要是一个个独立任务的进程在运行,可是不少时候,在工做中咱们可能不多去从新写一个进程,缓存
大部分的工做都是分配到了一个进程内的模块或者提供进程内特定功能的接口开发,这篇文章是想简单说明下,微信
做为一个进程,在实际开发过程当中,可能用到的一些编程方法好比:main参数解析,信号注册、回调函数、数据结构
线程建立、文件操做(FIFE *fp)、进程间通讯(socket);每一种我都会附一个简单的实例。多线程
一、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接口来完成工做。socket
函数的定义tcp
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
二、信号注册,做为一个进程,颇有必要注册必定的信号,防止进程异常退出时,本身蒙圈。
函数定义:
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...
三、回调函数
其实在信号注册中咱们就使用了回调函数,可是此回调函数不是咱们本身定义的类型,本身用来调用执行,
咱们这里作一个回调函数的调用示例,差异就在于回调函数的定义与调用时机,根据本身的实际须要定义就能够。
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
四、线程建立
线程主要是用来阻塞接受异步消息,或者完成耗时与周期性的任务,重点须要关注的是线程结束时线程资源的回收问题,
不少人会忽略这部分,会用到 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...
五、文件操做,文件操做很广泛,如记录数据,读入数据等。
文件操做通常要注意,明确操做的fp在文件中的位置,fclose前刷新缓存,对写入或者读出的返回作判断,异常或者结束
操做时关闭fp,一样还有open read write接口。二者区别:
一、缓冲文件系统与非缓冲系统的区别
缓冲文件系统(fopen):在内存为每一个文件开辟一个缓存区,当执行读操做,从磁盘文件将数据读入内存缓冲区,装满后从
内存缓冲区依次读取数据。写操做同理。
内存缓冲区的大小影响着实际操做外存的次数,缓冲区越大,操做外存的次数越少,执行速度快,效率高。缓冲区大小由机
器而定。借助文件结构体指针对文件管理,可读写字符串、格式化数据、二进制数据。
非缓冲文件系统(open):依赖操做系统功能对文件读写,不设文件结构体指针,只能读写二进制文件。
二、open属于低级IO,fopen属于高级IO
三、open返回文件描述符,属于用户态,读写需进行用户态与内核态切换。 fopen返回文件指针
四、open是系统函数,不可移植 fopen是标准C函数,可移植
五、通常用fopen打开普通文件,open打开设备文件
六、若是顺序访问文件,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
六、进程间通讯:包括管道(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部落】,获取更多精华文章,海量编程资料,让咱们一块儿进步,一块儿成长。