不登高山,不知天之高也;
不临深溪,不知地之厚也。
荀子《劝学》
linux应用层主要是一个个独立任务的进程在运行,但是很多时候,在工作中我们可能很少去重新写一个进程,
大部分的工作都是分配到了一个进程内的模块或者提供进程内特定功能的接口开发,这篇文章是想简单说明下,
作为一个进程,在实际开发过程中,可能用到的一些编程方法比如:main参数解析,信号注册、回调函数、
线程创建、文件操作(FIFE *fp)、进程间通信(socket);每一种我都会附一个简单的实例。
1、main参数解析,参数较少,使用简单判断argc并取出对应的argv[i]值就就可以处理,代码如下:
1 #include2 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 #include2 #include 3 #include 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 #include2 typedef void (*sighandler_t)(int); 3 sighandler_t signal(int signum, sighandler_t handler);
参数说明:
signum:要捕捉的信号(查看信号:kill -l,9号SIGKILL信号不能被捕捉);
handler:我们要对信号进行的处理方式。
示例代码:
1 #include2 #include 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 #include2 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 #include2 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 #include2 #include 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 #include2 #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 #include2 #include 3 #include 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 #include2 #include 3 #include 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部落】,获取更多精华文章,海量编程资料,让我们一起进步,一起成长。