4.5 进程通信之信号通信
4.5.1 信号机制
-
一般地,中断主要是指硬件中断,比如GPIO中断、定时器中断,这些硬件中断时外设模块工作时,发送给CPU的,也是一种异步方式。
-
信号是软件层次上对中断机制的一种模拟,是一种异步通信方式。
-
Linux内核通过信号通知用户进程,不同的信号类型代表不同的事件。
-
Linux对早期的unix信号机制进行了扩展。
4.5.2 常见信号类型
4.5.3 信号发送函数
kill函数
头文件 #include <unistd.h>
#include <signal.h>
函数原型int kill(pid_t pid, int sig);
参数 pid : 指定接收进程的进程号
0代表同组进程;-1代表所有除了INIT进程和当前进程之外的进程
sig : 信号类型
返回值 成功返回0,失败返回EOF
raise函数
头文件 #include <unistd.h>
#include <signal.h>
函数原型int raise(int sig);
参数 sig : 信号类型
返回值 成功返回0,失败返回EOF
alarm函数
头文件 #include <unistd.h>
#include <signal.h>
函数原型 int alarm(unsigned int seconds);
参数 seconds 定时器的时间
返回值 成功返回上个定时器的剩余时间,失败返回EOF
pause函数
进程调用这个函数后会一直阻塞,直到而被信号中断,功能和sleep类似。
头文件 #include <unistd.h>
#include <signal.h>
函数原型 int pause(void);
返回值 成功返回0,失败返回EOF
signal函数
设置信号响应方式,请注意这个函数和kill、killall的区别,我们中文使用者会理解为发信号,实际上它并不是发信号。
头文件 #include <unistd.h>
#include <signal.h>
函数原型 void (*signal(int signo, void(*handler)(int)))(int)
参数 signo 要设置的信号类型
handler 指定的信号处理函数;
返回值 成功返回0,失败返回EOF
4.5.4 进程捕捉信号
程序源码(参考jz2440\process_single\send_single.c)
01 /**********************************************************************
02 * 功能描述: 1.捕捉终端发送过来的信号
03 * 输入参数: 无
04 * 输出参数: 无
05 * 返 回 值: 无
06 * 修改日期 版本号 修改人 修改内容
07 * -----------------------------------------------
08 * 2020/05/16 V1.0 zh(ryan) 创建
09 ***********************************************************************/
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <sys/types.h>
15 #include <signal.h>
16
17 void handler(int signo)
18 {
19 switch (signo) {
20 case SIGINT:
21 printf("I have got SIGINT\n");
22 break;
23
24 case SIGQUIT:
25 printf("I have got SIGQUIT\n");
26 break;
27
28 default:
29 printf("don't respond to this signal[%d]\n", signo);
30 exit(0);
31 }
32 }
33
34 int main(int argc, char *argv[])
35 {
36 signal(SIGINT, handler);
37 signal(SIGQUIT, handler);
38 while (1)
39 pause();
40 return 0;
41 }
JZ2440实验
- 编译
arm-linux-gcc send_single.c -o send_single
- 拷贝到NFS文件系统
cp send_single /work/nfs_root/first_fs
- 运行
./send_single
实际上在利用共享内存实现进程间通信时,我们已经使用到了信号通信,父进程写完共享内存后发送信号通知子进程,子进程收到信号后开始读共享内存,这里就不在给出两个进程之间使用信号通信的例子了,请读者参考共享内存中实现两个进程通信的代码。
4.6 进程通信之socket通信
4.6.1 什么是socket
先思考一个问题:网络环境中的进程如何实现通信?比如当我们使用QQ和好友聊天的时候,QQ进程是如何与服务器以及你好友所在的QQ进程之间通信的?这些靠的就是socket来实现的。
Socket起源于UNIX,Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。在《有名管道》那一节中,我们知道socket也是一种文件类型,只不过socket是一种伪文件,存在于内核缓冲区中,大小不变,一直是0。
socket文件一定是成对出现的,server端有一个套接字文件,client端也有一个套接字文件,每个进程需要和对应的套接字文件绑定,每个进程通过读写它的套接字文件,交由内核实现,如下所示。
一般地,socket用来实现网络环境中,不同主机上的进程通信,但是也可以用来在同一个主机上的不同进程之间通信,本小节主要探讨socket用在同一个主机上的不同进程间通信。
4.6.2 相关函数
socket函数
创建socket文件描述符函数
头文件#include <sys/types.h>
#include <sys/socket.h>
原型: int socket(int domain, int type, int protocol)
返回值: 成功,消息队列ID,出错 -1
bind函数
将socket文件描述符和某个socket文件绑定
头文件#include <sys/types.h>
#include <sys/socket.h>
原型: int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数: sockfd:利用系统调用socket()建立的套接字描述符
addr:代表需要绑定的本地地址信息
addrlen: 本地地址信息长度
返回值: 成功,消息队列ID,出错 -1
listen函数
设置监听某个socket文件描述符,设置能够同时和服务端连接的客户端数量,一般只有server会调用
头文件#include <sys/types.h>
#include <sys/socket.h>
原型: int listen(int sockfd, int backlog);
参数: sockfd:利用系统调用socket()建立的套接字描述符
backlog:server可以接受连接的最大client数量
返回值: 成功,消息队列ID,出错 -1
accept函数
等待client建立连接的函数,一般只有server会调用
头文件#include <sys/types.h>
#include <sys/socket.h>
原型: int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
参数: sockfd:利用系统调用socket()建立的套接字描述符
addr:指向已经建立连接的对端client地址信息的指针
addrlen: 对端client地址信息长度
返回值: 成功,消息队列ID,出错 -1
connet函数
client主动连接server函数,一般只有client才会调用
头文件#include <sys/types.h>
#include <sys/socket.h>
原型: int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数: sockfd:利用系统调用socket()建立的套接字描述符
addr:指向已经建立连接的对端server地址信息的指针
addrlen: 对端server地址信息长度
返回值: 成功,消息队列ID,出错 -1
send函数
发送数据函数
头文件#include <sys/types.h>
#include <sys/socket.h>
原型: ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数: sockfd:指向要发送数据的socket文件描述符,已经建立连接的文件描述符
buf: 存放要发送数据的缓冲区
len: 实际要发送数据的字节数
flags: 一般为0或者如下的宏
MSG_DONTROUTE 绕过路由表查找
MSG_DONTWAIT 仅本操作非阻塞
MSG_OOB 发送或接收带外数据
MSG_PEEK 窥看外来消息
MSG_WAITALL 等待所有数据
返回值: 成功,消息队列ID,出错 -1
recv函数
接收数据函数
头文件#include <sys/types.h>
#include <sys/socket.h>
原型: ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数: sockfd:已经建立连接的文件描述符
buf: 存放要接收数据的缓冲区
len: 实际要接收数据的字节数
flags:一般为0或者如下的宏
MSG_DONTROUTE 绕过路由表查找
MSG_DONTWAIT 仅本操作非阻塞
MSG_OOB 发送或接收带外数据
MSG_PEEK 窥看外来消息
MSG_WAITALL 等待所有数据
返回值: 成功,消息队列ID,出错 -1
4.6.3 socket实现进程间通信
程序实现一般步骤
Server端
1.创建socket
2.绑定socket
3.设置监听
4.等待客户端连接
5.发送/接收数据
Client端
1.创建socket
2.绑定socket
3.连接
4.发送/接收数据
server源码(参考jz2440\process_socket\server.c)
01 /**********************************************************************
02 * 功能描述: 1.server打印client发送过来的字符串,并将该字符串回发给client
03 * 输入参数: 无
04 * 输出参数: 无
05 * 返 回 值: 无
06 * 修改日期 版本号 修改人 修改内容
07 * -----------------------------------------------
08 * 2020/05/16 V1.0 zh(ryan) 创建
09 ***********************************************************************/
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <string.h>
17 #include <arpa/inet.h>
18 #include <sys/un.h>
19
20 int main(int argc, char *argv[])
21 {
22 int lfd ,ret, cfd;
23 struct sockaddr_un serv, client;
24 socklen_t len = sizeof(client);
25 char buf[1024] = {0};
26 int recvlen;
27
28 //创建socket
29 lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
30 if (lfd == -1) {
31 perror("socket error");
32 return -1;
33 }
34
35 //如果套接字文件存在,删除套接字文件
36 unlink("server.sock");
37
38 //初始化server信息
39 serv.sun_family = AF_LOCAL;
40 strcpy(serv.sun_path, "server.sock");
41
42 //绑定
43 ret = bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
44 if (ret == -1) {
45 perror("bind error");
46 return -1;
47 }
48
49 //设置监听,设置能够同时和服务端连接的客户端数量
50 ret = listen(lfd, 36);
51 if (ret == -1) {
52 perror("listen error");
53 return -1;
54 }
55
56 //等待客户端连接
57 cfd = accept(lfd, (struct sockaddr *)&client, &len);
58 if (cfd == -1) {
59 perror("accept error");
60 return -1;
61 }
62 printf("=====client bind file:%s\n", client.sun_path);
63
64 while (1) {
65 recvlen = recv(cfd, buf, sizeof(buf), 0);
66 if (recvlen == -1) {
67 perror("recv error");
68 return -1;
69 } else if (recvlen == 0) {
70 printf("client disconnet...\n");
71 close(cfd);
72 break;
73 } else {
74 printf("server recv buf: %s\n", buf);
75 send(cfd, buf, recvlen, 0);
76 }
77 }
78
79 close(cfd);
80 close(lfd);
81 return 0;
82 }
client源码(参考jz2440\process_socket\client.c)
01 /**********************************************************************
02 * 功能描述: 1.client从标准输入获取到一个字符串,然后将这个字符串发送给server
03 * 输入参数: 无
04 * 输出参数: 无
05 * 返 回 值: 无
06 * 修改日期 版本号 修改人 修改内容
07 * -----------------------------------------------
08 * 2020/05/16 V1.0 zh(ryan) 创建
09 ***********************************************************************/
10
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <string.h>
17 #include <arpa/inet.h>
18 #include <sys/un.h>
19
20 int main(int argc, char *argv[])
21 {
22 int lfd ,ret;
23 struct sockaddr_un serv, client;
24 socklen_t len = sizeof(client);
25 char buf[1024] = {0};
26 int recvlen;
27
28 //创建socket
29 lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
30 if (lfd == -1) {
31 perror("socket error");
32 return -1;
33 }
34
35 //如果套接字文件存在,删除套接字文件
36 unlink("client.sock");
37
38 //给客户端绑定一个套接字文件
39 client.sun_family = AF_LOCAL;
40 strcpy(client.sun_path, "client.sock");
41 ret = bind(lfd, (struct sockaddr *)&client, sizeof(client));
42 if (ret == -1) {
43 perror("bind error");
44 return -1;
45 }
46
47 //初始化server信息
48 serv.sun_family = AF_LOCAL;
49 strcpy(serv.sun_path, "server.sock");
50 //连接
51 connect(lfd, (struct sockaddr *)&serv, sizeof(serv));
52
53 while (1) {
54 fgets(buf, sizeof(buf), stdin);
55 send(lfd, buf, strlen(buf)+1, 0);
56
57 recv(lfd, buf, sizeof(buf), 0);
58 printf("client recv buf: %s\n", buf);
59 }
60
61 close(lfd);
62 return 0;
63 }
JZ2440实验
- 编译
arm-linux-gcc server.c -o server
arm-linux-gcc client.c -o client
- 拷贝到NFS文件系统
cp server /work/nfs_root/first_fs
cp client /work/nfs_root/first_fs
- 运行
为方便看程序运行结果,server在后台执行;client在前台运行,client能够接收来自终端的输入。
./server &
./client
4.6.4 一个server和多个client之间的通信
在实际项目开发中, 更常见的一种场景是:一个server要和多个client之间通信,这部分实现方式交给读者自行实现,实现的方式有很多,比如如下两种方式,当然还要其他方法。
-
多进程实现,一个主进程用来实现检测client的连接,每检测一次client连接,则为这个client创建一个专门的进程,用于实现两者间通信。
-
也可以使用多线程实现,一个主线程用来检测client的连接,每检测一次client连接,则为这个client创建一个专门的线程,用于实现两者间通信。