Linux系统编程(七)进程间通信IPC

news2024/12/30 4:11:26

进程间通讯的7种方式_进程间通信的几种方法-CSDN博客

  1. 管道 pipe(命名管道和匿名管道);
  2. 信号 signal;
  3. 共享内存;
  4. 消息队列;
  5. 信号量 semaphore;
  6. 套接字 socket;

1. 管道

内核提供,单工,自同步机制。 

1.1 匿名管道

磁盘上无法看到,只能有亲缘关系的进程才能用匿名管道。一般用于父子进程间通信。

pipe(2) 系统调用可以创建一个匿名管道 pipefd,文件描述符 pipefd[0] 为读管道,pipefd[1] 为写管道。  

#include <unistd.h>

int pipe(int pipefd[2]);

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <fcntl.h>              /* Obtain O_* constant definitions */
#include <unistd.h>

int pipe2(int pipefd[2], int flags);

例子,父进程通过管道发送 hello 给子进程:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
  pid_t pid;
  int pipefd[2];
  if (pipe(pipefd) < 0) {
    perror("pipe");
    exit(1);
  }

  pid = fork();

  if (pid > 0) {
    // parent
    close(pipefd[0]);
    write(pipefd[1], "hello", 5);
    close(pipefd[1]);
    wait(NULL);
    exit(0);
  }
  else if (pid == 0) {
    // child
    close(pipefd[1]);
    char buf[50];
    int len = read(pipefd[0], buf, 50);
    printf("%d\n", len);
    write(1, buf, len);
    close(pipefd[0]);
    exit(0);
  }
  else {
    perror("fork");
    exit(1);
  }
  exit(0);
}

1.2 命名管道

磁盘上能看到,文件类型为 p 的文件。

mkfifo(3) 函数可以创建一个 fifo 特殊文件(命名管道)。

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

2 XSI -> SysV

2.1 消息队列,message queue 

主动端,先发包的一方;被动端,先收包的一方;消息队列可以用于没有亲缘关系的进程间通信

例子,proto.h,包含了传递的内容:

#ifndef PROTO_H__
#define PROTO_H__

#define KEYPATH "/etc/services"
#define KEYPROJ 'g'

#define NAMESIZE 32

struct msg_st
{
  long mtype;
  char name[NAMESIZE];
  int math;
  int chinese;
};

#endif

receiver.c,接收者:

#include <stdio.h>
#include <stdlib.h>
#include "proto.h"
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int main()
{
  key_t key;
  int msgid;
  struct msg_st rbuf;

  key = ftok(KEYPATH, KEYPROJ);
  if (key < 0) {
    perror("ftok");
    exit(1);
  }

  msgid = msgget(key, IPC_CREAT | 0600);
  if (msgid < 0) {
    perror("msgget");
    exit(1);
  }

  while (1)
  {
    if (msgrcv(msgid, &rbuf, sizeof(rbuf) - sizeof(long), 0, 0) < 0)
    {
      perror("msgrcv");
      exit(1);
    }
    printf("NAME = %s\n", rbuf.name);
    printf("MATH = %d\n", rbuf.math);
    printf("CHINESE = %d\n", rbuf.chinese);
  }

  msgctl(msgid, IPC_RMID, NULL);

  exit(0);
}

运行接收者可以看到创建的 msg queue: 

sender.c,发送者: 

#include <stdio.h>
#include <stdlib.h>
#include "proto.h"
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

int main()
{
  key_t key;
  struct msg_st sbuf;
  int msgid;

  key = ftok(KEYPATH, KEYPROJ);
  if (key < 0) {
    perror("ftock");
    exit(1);
  }

  msgid = msgget(key, 0);
  if (msgid < 0)
  {
    perror("msgget");
    exit(1);
  }

  sbuf.mtype = 1;
  strcpy(sbuf.name, "lzp");
  sbuf.math = 99;
  sbuf.chinese = 100;
  if (msgsnd(msgid, &sbuf, sizeof(sbuf) - sizeof(long), 0) < 0) {
    perror("msgsend");
    exit(1);
  }

  puts("ok!");

  exit(0);
}

先运行 receiver,然后运行 sender 发送信息: 

2.2 信号量,semaphore arrays

semget、semop、semctl;可以用于没有亲缘关系的进程间通信

2.3 共享内存,shared memory segment

shmget、shmop、shmctl;之前可以通过 mmap 实现共享内存,不过只能在有亲缘关系的进程间通信。匿名 ipc 也只能在有亲缘关系的进程间通信;

3. 网络套接字 socket

3.1 跨主机的传输

  • 字节序大端存储(低地址处放高字节)小端存储(低地址处放低字节,x86);主机字节序(host)和网络字节序(network);主机序转网络序,网络序转主机序;htons,htonl,ntohs,ntohl。
  • 对齐:结构体中 char 类型可能会占 4 个字节;如果不对齐,那么 int 类型的存储地址可能就不是在 4 的倍数的地址上了,可能需要两次访存才能取出一个 int 类型。深入理解字节对齐-CSDN博客
  • 类型长度问题:不同主机间的架构,机器字长可能不一样,结构体类型大小也可能不一样。解决方案(int32_t,int16_t ...);

socket(2) 系统调用如下: 

#include <sys/types.h>    
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
  • domain 指定协议族;
    • AF_INET      IPv4 Internet protocols    ip(7)
    • AF_INET6     IPv6 Internet protocols   ipv6(7)
  • type 指定通信语义;
    • SOCK_STREAM(流式传输):Provides sequenced, reliable, two-way, connection-based byte streams.
    • SOCK_DGRAM(报式传输)  :Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
  • protocol 指定协议族中的协议;

bind(2) 系统调用可以给 socket 绑定地址。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

recvfrom(2) 可以从 socket 上接收一条 message: 

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
  1. recv(2) 是用于流式套接字的,是提前建立好连接的,一对一的,点对点的, 不需要记录对方的 socket 地址;
  2. recvfrom(2) 可以用于报式和流式套接字,每次需要传入需要通信的 socket 地址;

sendto(2) 函数可以发送 msg 到对应的 socket 地址,可以用于流式和报式传输;而 send(2) 函数不需要指定 socket 地址,只能用于事先建立好连接的流式传输。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

3.2 报式套接字(udp)

被动端(先运行)

  1. 取得 socket(socket);
  2. 给 socket 取得地址,相当于绑定本地的地址(bind);
  3. 收/发消息(recvfrom);
  4. 关闭 socket(close);

主动端

  1. 取得 socket;
  2. 给 socket 取得地址(可省略);
  3. 收/发消息;
  4. 关闭 socket;

__attribute__((packed)):packed属性:使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。即不进行对齐。

使用如下指令查看被动段的地址和端口(u代表udp):

netstat -anu

proto.h 约定传输格式和内容: 

#ifndef PROTO_H__
#define PROTO_H__

#define RCVPORT 1989

#define NAMESIZE  11

// communication struct
struct msg_st
{
        char name[NAMESIZE];
        int math;
        int ch;
} __attribute__((packed));




#endif

receiver.c 接收方, 注意多字节需要使用 ntohl() 来转换:

#include <stdio.h>
#include <stdlib.h>
#include "proto.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

#define IPSTRSIZE 40

int main()
{
        int sd; // socket fd
        struct sockaddr_in laddr;
        struct sockaddr_in raddr;
        struct msg_st rbuf;
        socklen_t raddr_len;
        char ipstr[IPSTRSIZE];

        // IPV4 DGRAM UDP
        sd = socket(AF_INET, SOCK_DGRAM, 0/* IPPROTO_UDP */);
        if (sd < 0) {
                perror("socket");
                exit(1);
        }

        laddr.sin_family = AF_INET;
        laddr.sin_port = htons(RCVPORT);
        inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr);
        if (bind(sd, (struct sockaddr *)&laddr, sizeof(laddr)) < 0) {
                perror("bindqqq");
                exit(1);
        }

        /* !!! */
        raddr_len = sizeof(raddr);

        while (1)
        {
                recvfrom(sd, &rbuf, sizeof(rbuf), 0, (struct sockaddr *)&raddr, &raddr_len);
                inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
                printf("message from %s:%d---\n", ipstr, ntohs(raddr.sin_port));
                printf("name = %s\n", rbuf.name);
                printf("name = %d\n", ntohl(rbuf.math));
                printf("name = %d\n", ntohl(rbuf.ch));
        }
        close(sd);

        exit(0);
}

sender.c 发送方,发送报文给对应的地址: 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include "proto.h"
#include <unistd.h>

int main(int argc, char *argv[])
{
        int sd;
        struct msg_st sbuf;
        struct sockaddr_in raddr;

        if (argc < 2)
        {
                fprintf(stderr, "usage..\n");
                exit(1);
        }

        sd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sd < 0)
        {
                perror("socket");
                exit(1);
        }

        //bind();
        strcpy(sbuf.name, "Alan");
        sbuf.math = htonl(99);
        sbuf.ch = htonl(93);

        raddr.sin_family = AF_INET;
        raddr.sin_port = htons(RCVPORT);
        inet_pton(AF_INET, argv[1], &raddr.sin_addr);

        if (sendto(sd, &sbuf, sizeof(sbuf), 0, (struct sockaddr *)&raddr, sizeof(raddr)) < 0)
        {
                perror("sendto");
                exit(1);
        }

        puts("ok\n");

        close(sd);

        exit(0);
}

运行结果: 

报式套接字还能实现多播、广播(全网广播和子网广播)、组播:

可以通过 getsockopt() 和 setsockopt() 来打开广播选项。然后将发送的目标地址改成 255.255.255.255 就可以发送广播了。

多点通信(广播、多播、组播)只能用报式套接字实现,因为流式套接字是一对一的,点对点的。

udp 会出现丢包的问题,TTL生存周期(路由跳转个数) ,丢包是由阻塞造成的,当等待队列快满的时候会发生丢包(网络太拥塞),可以使用流量控制解决(限制发送端的速率)。

3.3 流式套接字(tcp)

协议,约定双方对话的格式。

三次握手容易被ddos攻击,可以去掉半连接池,然后使用cookie(对方的ip+端口加上我放的ip+端口加上一个内核产生的令牌)。

Client 端和 Server 端:

client:

  1. 获取 socket;
  2. 给 socket 取得地址;
  3. 发送连接;
  4. 收/发消息;
  5. 关闭连接;

server:

  1. 获取 socket;
  2. 给 socket 取得地址;
  3. 将 socket 置为监听模式;
  4. 接受连接;
  5. 收/发消息;
  6. 关闭;

服务端(server.c)

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <time.h>
#include "proto.h"

#define IPSTRSIZE 40
#define BUFSIZE 1024

static void server_job(int sd)
{
        char buf[BUFSIZE];
        int len = sprintf(buf, FMT_STAMP, (long long)time(NULL));
        if (send(sd, buf, len, 0) < 0) {
                perror("send()");
                exit(1);
        }
}

int main()
{
        struct sockaddr_in laddr, raddr;
        socklen_t raddr_len;
        char ipstr[IPSTRSIZE];
        int sd = socket(AF_INET, SOCK_STREAM, 0/* default IPPROTO_TCP */);
        if (sd < 0) {
                perror("socket()");
                exit(1);
        }

        laddr.sin_family = AF_INET;
        laddr.sin_port = htons(atoi(SERVERPORT));
        inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr);

        if (bind(sd, (void *)&laddr, sizeof(laddr)) < 0) {
                perror("bind()");
                exit(1);
        }
        // listen for connections on a socket
        if (listen(sd, 200) < 0)
        {
                perror("listen()");
                exit(1);
        }

        raddr_len = sizeof(raddr);
        while (1) {
                // accept a connection on a socket
                int newsd;
                if ((newsd = accept(sd, (void *)&raddr, &raddr_len)) < 0) {
                        perror("accept()");
                        exit(1);
                }

                inet_ntop(AF_INET, &raddr.sin_addr, ipstr, IPSTRSIZE);
                printf("Client: %s : %d\n", ipstr, ntohs(raddr.sin_port));

                server_job(newsd);
                // close newsd
                close(newsd);
        }

        close(sd);

        exit(0);
}

运行后使用 netstat -ant 可以查看:

当连接被释放后,服务端会进入一段时间的 timewait 状态。

客户端(client.c):

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include "proto.h"
int main(int argc, char* argv[])
{
        struct sockaddr_in raddr;
        long long stamp;
        if (argc < 2) {
                fprintf(stderr, "Usage...\n");
                exit(1);
        }

        int sd = socket(AF_INET, SOCK_STREAM, 0);
        if (sd < 0) {
                perror("socket()");
                exit(1);
        }

        // initiate a connection on a socket
        raddr.sin_family = AF_INET;
        raddr.sin_port = htons(atoi(SERVERPORT));
        inet_pton(AF_INET, argv[1], &raddr.sin_addr);
        if (connect(sd, (void *)&raddr, sizeof(raddr)) < 0) {
                perror("connect()");
                exit(1);
        }

        FILE* fp = fdopen(sd, "r+");
        if (fp == NULL) {
                perror("fdopen()");
                exit(1);
        }

        if (fscanf(fp, FMT_STAMP, &stamp) < 1) {
                fprintf(stderr, "bad format\n");
        }
        else {
                fprintf(stdout, "stamp = %lld\n", stamp);
        }

        fclose(fp);
        exit(0);
}

上面这种方法有一个缺点,就是假如 server_job 的任务执行时间太长的话,需要等待 server_job 执行完后才能继续 accept 下一个连接请求,这样效率十分低,所以可以采用多线程的方式来解决,每个进程或线程处理一个连接请求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1871051.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Halcon 如何根据特征过滤区域和XLD

一 如何跟进特征过滤区域和XLD dev_open_window(0,0,512,512,black,WindowHandle)read_image(Image,fabrik)threshold(Image,Region,128,255)connection(Region,ConnectedRegions)*根据面积范围[8000,9000] dev_display(Image)select_shape(ConnectedRegions,SelectedRegions,…

Python和tkinter实现的字母记忆配对游戏

Python和tkinter实现的字母记忆配对游戏 因为这个小游戏用到了tkinter&#xff0c;先简要介绍一下它。tkinter是Python的标准GUI(图形用户界面)库&#xff0c;它提供了一种简单而强大的方式来创建图形界面应用程序。它提供了创建基本图形界面所需的所有工具&#xff0c;同时保…

【Pillow】module ‘PIL.Image‘ has no attribute ‘ANTIALIAS‘问题解决

问题描述 我在使用 SummaryWriter 记录图片数据日志时&#xff0c;遇到了报错&#xff0c;如下图所示&#xff1a; 问题的原因在于&#xff0c;使用的pillow版本已经舍弃了ANTIALIAS&#xff0c;在新版本中已经改为了LANCZOS 问题解决 两种解决方式&#xff1a; 修改源码更…

C++之STL(十一)

1、迭代器适配器 2、插入迭代器 #include <iostream> #include <vector> #include <algorithm> #include <list> using namespace std;void showVec(const vector<int>& v) {for (vector<int>::const_iterator it v.begin(); it ! v.…

如何使用大模型进行文本分类任务?

暑期实习基本结束了&#xff0c;校招即将开启。 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友解惑答疑&…

视频上面怎样编辑文字?4种视频编辑文字方法分享

视频已成为我们日常生活中不可或缺的一部分。无论是社交分享、商业宣传还是个人记录&#xff0c;视频都以其直观、生动的特点吸引着观众的眼球。然而&#xff0c;一个优质的视频&#xff0c;除了画面和音效&#xff0c;文字编辑也是提升观看体验的关键。那么&#xff0c;如何在…

2024百度之星第二场-小度的01串

补题链接&#xff1a; 码蹄集 一道经典线段树板子题。 区间修改01置换&#xff0c;区间查询子串权值。 唯一区别&#xff0c;权值要求的是相邻字符都不同所需修改的最小字符个数。 我们在线段树节点上分别维护当前连续区间&#xff1a; 奇数位是0的个数&#xff08;j0&…

03逻辑门电路

分立门电路&#xff1a; 集成门电路&#xff1a; TTL门电路 MOS门电路&#xff1a;NMOS门电路、PMOS门电路、CMOS门电路 BICMOS门电路&#xff1a;CMOS的高输入阻抗和TTL的高放大倍数的结合 向更低功耗、更高速度发展 MOS管的Rdson在可变电阻区的阻值也一般会小于1000欧姆 …

【自动化测试】Selenium自动化测试框架 | 相关介绍 | Selenium + Java环境搭建 | 常用API的使用

文章目录 自动化测试一、selenium1.相关介绍1.Selenium IDE2.Webdriverwebdriver的工作原理&#xff1a; 3.selenium Grid 2.Selenium Java环境搭建3.常用API的使用1.定位元素2.操作测试对象3.添加等待4.打印信息5.浏览器的操作6.键盘事件7.鼠标事件8.定位一组元素9.多层框架定…

prompt:我是晚餐盲盒,只要你问出“今晚吃什么”我就将为你生成美妙的食物推荐。

使用方法&#xff1a;在ChatGP粘贴下面提示词模型&#xff0c;点击输出。然后再问“晚餐有什么好吃的&#xff1f;”&#xff0c;AI输出丰种食物供你选择。抽到什么吃什么&#xff0c;极大的解决选择困难的问题。 客户需要生成1000条俏皮灵动&#xff0c;趣味盎然&#xff0c;比…

VS2019安装插件image watch

image watch的作用&#xff1a; &#xff08;1&#xff09;放大、缩小图像&#xff1b; &#xff08;2&#xff09;将图像保存到指定的目录&#xff1b; &#xff08;3&#xff09;显示图像大小、通道数&#xff1b; &#xff08;4&#xff09;拖拽图像&#xff1b; &…

动态流体工厂大屏

目录 一 设计原型 二 后台源码 一 设计原型 二 后台源码 namespace 动态流体工厂大屏 {public partial class Form1 : Form{public Form1(){InitializeComponent();}private void Form1_Load(object sender, EventArgs e){Task.Run(() >{while (true){this.Invoke(() >…

基于51单片机电子称—串口显示

基于51单片机电子称设计 &#xff08;仿真&#xff0b;程序&#xff09; 功能介绍 具体功能&#xff1a; 1.矩阵键盘组成按键&#xff0c;输入价格结算、打印&#xff1b; 2.用滑动变阻器和ADC0832模拟称重&#xff1b; 3.LCD1602可以显示重量、单价和总价&#xff1b; 4.…

关于新零售的一些思考

本文作为2024上半年大量输入之后的核心思考之一。工作到一定阶段之后&#xff0c;思考的重要性越来越高&#xff0c;后续会把自己的个人思考记录在这个新系列《施展爱思考》。背景是上半年面临业务转型从电商到新零售&#xff0c;本文是相关大量输入之后的思考&#xff0c;对新…

vue2中的组件自定义事件

1.绑定事件: <组件 :自定义名称"方法" /> 2.调用 this.$emit(方法,参数) 3.关闭 this.$off(方法) 案例: 1.提前准备好组件 Student组件 <template><div class"student"><h1>学校名称:{{ st…

JAVA每日作业day6.27

ok了家人们&#xff0c;今天学习了内部类&#xff0c;话不多说我们一起看看吧。 一&#xff0c;内部类 1.1 内部类概述 将一个类 A 定义在另一个类 B 里面&#xff0c;里面的那个类 A 就称为 内部 类 &#xff0c; B 则称为 外部类 。 内部类分为成员内部类与局部内部类。 1…

修改 app id - 鸿蒙 HarmonyOS Next

修改项目 app id 后通过真机 build run 的时候抛出了如下异常; 项目中更改后的配置与真机的不匹配; {app: {bundleName: "com.xxxxxx.xxx_harmony",vendor: "xxxxxx",versionCode: 1,versionName: "3.5.00",icon: "$media:app_icon",…

9.二维数组的遍历和存储

二维数组的遍历和存储 二维数组的遍历 二维数组a[3][4],可分解为三个一维数组,其数组名分别为: 这三个一维数组都有4个元素,例如:一维数组a[0]的 元素为a[0][0],a[0][1],a[0][2],a[0][3]。所以遍历二维数组无非就是先取出二维数组中得一维数组, 然后再从一维数组中取出每个元…

Open AI限制来袭?用上这个工具轻松破局!

【导语】近日&#xff0c;AI领域掀起了一场不小的波澜。Open AI宣布&#xff0c;从7月9日起&#xff0c;将对部分地区的开发者实施API调用限制。这一消息对于许多依赖Open AI技术的国内初创团队来说&#xff0c;无疑是一个沉重的打击。 对于这些团队而言&#xff0c;Open AI的A…

Spring相关面试题(三)

29 如何在所有的BeanDefinition注册完成后&#xff0c;进行扩展 Bean工厂的后置处理器&#xff0c;在所有的Bean注册完成后&#xff0c;就被执行。 public class A implements BeanFactoryPostProcessor {private String name "a class";private B b; ​public St…