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

进程间通讯的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 下一个连接请求,这样效率十分低,所以可以采用多线程的方式来解决,每个进程或线程处理一个连接请求。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

华为云安全防护,九河云综合分解优劣势分析

随着全球化的发展&#xff0c;越来越多的企业开始寻求在国际市场上扩展业务&#xff0c;这一趋势被称为企业出海。然而&#xff0c;企业在海外扩张面临诸多隐患与安全挑战&#xff0c;其中因为地域的不同&#xff0c;在安全性方面与国内相比会变得薄弱&#xff0c;从而导致被黑…

展开说说:Android列表之RecyclerView

RecyclerView 它是从Android5.0出现的全新列表组件&#xff0c;更加强大和灵活。用于显示列表形式 (list) 或者网格形式 (grid) 的数据&#xff0c;替代ListView和GridView成为Android主流的列表组件。可以说Android客户端只要有表格的地方就有RecyclerView。 RecyclerView 内…

无痛接入FastText算法进行文本分类(附代码)

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享&#xff0c;包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…

未来已来,如何打造智慧养殖场?

近年来&#xff0c;国家出台了一系列扶持政策&#xff0c;以促进养殖行业高质量发展&#xff0c;推动行业转型升级。在国家政策和市场需求的双重驱动下&#xff0c;养殖行业正迎来前所未有的发展机遇。智慧养殖以其高效、智能和可持续的特点&#xff0c;正逐步取代传统养殖方式…

【消息队列】RabbitMQ集群原理与搭建

目录 前言1、集群搭建1.1、安装RabbitMQ1.1.1、前置要求1.1.2、安装Erlang环境①创建yum库配置文件②加入配置内容③更新yum库④正式安装Erlang 1.1.3、安装RabbitMQ1.1.4、RabbitMQ基础配置1.1.5、收尾工作 1.2、克隆VMWare虚拟机1.2.1、目标1.2.2、克隆虚拟机1.2.3、给新机设…

涉案财物管理系统|DW-S405系统实现涉案财物科学化管理

随着社会的不断发展&#xff0c;犯罪形式日益复杂&#xff0c;涉案财物的种类和数量也不断增加。传统的涉案财物管理方式已经无法满足现代执法办案的需求。因此&#xff0c;建立一套科学、高效、规范的警用涉案财物管理系统成为公安机关亟待解决的问题。 涉案财物管理系统DW-S…

哈希表 | 哈希查找 | 哈希函数 | 数据结构 | 大话数据结构 | Java

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;毛毛张今天分享的内容&#x1f586;是数据结构中的哈希表&#xff0c;毛毛张主要是依据《大话数据结构&#x1f4d6;》的内容来进行整理&#xff0c;不…

vue的学习--day2

如有错误&#xff0c;烦请指正~ 目录 一、什么是单页面应用程序 二、使用工具&#xff1a;node.js 三、工具链 易错点 一、什么是单页面应用程序 多个组件&#xff08;例如登录、注册等以vue结尾的都叫做组件&#xff09;在一个页面显示&#xff0c;叫单页面应用…

Linux C 程序 【02】创建线程

1.开发背景 上一个篇章&#xff0c;基于 RK3568 平台的基础上&#xff0c;运行了最简单的程序&#xff0c;然而我们使用了 Linux 系统&#xff0c;系统自带的多线程特性还是比较重要的&#xff0c;这个篇章主要描述线程的创建。 2.开发需求 设计实验&#xff1a; 创建一个线程…

极验行为式验证码适配HarmonyOS 鸿蒙SDK下载

现阶段&#xff0c;越来越多的开发者正在积极加入鸿蒙生态系统。随着更多开发者的参与&#xff0c;早在去年9月&#xff0c;极验就成为首批拥有鸿蒙NEXT内测版本和手机系统测试机会的验证码供应商。 为了提高各开发者及企业客户集成鸿蒙版本行为验4.0的效率&#xff0c;方便大家…

Android 通知组

一. 通知组简介 从 Android 7.0&#xff08;API 级别 24&#xff09;开始&#xff0c;您可以在一个组中显示相关通知。如下所示: 图 1. 收起&#xff08;顶部&#xff09;和展开&#xff08;底部&#xff09;的通知组。 注意 &#xff1a;如果应用发出 4 条或更多条通知且未…

大数据平台需要存算分离吗?某保险集团:以 ZBS 优化资源利用率,缩短业务用时超一半

金融机构普遍采用“存算一体”架构支撑基于 Hadoop 框架的大数据平台。而随着金融业务的多元化发展&#xff0c;不同业务对计算和存储的需求差异较大&#xff0c;由于“存算一体”架构共享存储与计算资源&#xff0c;经常会出现资源需求不均衡、资源利用率低下、难以灵活调度等…

工具篇:鸿蒙DevEco Studio5.0版本下载及安装

1、下载中心地址 下载中心 | 华为开发者联盟-HarmonyOS开发者官网&#xff0c;共建鸿蒙生态 2、安装 DevEco Studio支持Windows和macOS系统&#xff0c;下面将针对两种操作系统的软件安装方式分别进行介绍。 Windows环境 运行环境要求 为保证DevEco Studio正常运行&#…

Mysql需要知道的点

目录 一、数据库的三范式是什么 二、Mysql数据库引擎有哪些 三、说说Innodb与MYISAM的区别 四、数据库的事务 五、索引是什么 六、优化手段有哪些 七、简单说一说 drop&#xff0c;delete与truncate的区别 八、什么是视图 九、什么是内连接、左外连接、右外连接&#x…

Ubuntu20.04使用Samba

目录 一、Samba介绍 Samba 的主要功能 二、启动samba 三、主机操作 四、Ubuntu与windows系统中文件互联 五、修改samba路径 一、Samba介绍 Samba 是一个开源软件套件&#xff0c;用于在 Linux 和 Unix 系统上实现 SMB&#xff08;Server Message Block&#xff09;协议…

[行业原型] Web端原型案例:康欣医疗后台管理系统

​医疗管理系统是一个业务复杂&#xff0c;功能庞大的系统&#xff0c;以下为HIS医院管理系统的常见模块&#xff0c;供大家参考。 本周为大家带来Web端原型案例&#xff1a;康欣医疗后台管理系统&#xff0c;先上原型&#xff1a; 完整文档加班主任微信号 添加班主任回复 “1…

ansible常用模块详解

一、Ansible 1.1 简介 Ansible是自动化运维工具&#xff0c;能实现跨主机对应用编排管理部署。 Ansible能批量配置、部署、管理上千台主机&#xff0c;是应用级别的跨主机编排工具。 比如以前需要切换到每个主机上执行的一或多个操作&#xff0c;使用Ansible只需在固定的一…

练习实践:ubuntu18.04安装、配置Nginx+PHP环境,两种配置方式,多站点

参考来源&#xff1a; https://help.aliyun.com/document_detail/464753.html https://www.cnblogs.com/laosan007/p/12803287.html https://blog.csdn.net/qq_55364077/article/details/132207083 【安装同版本7.2的php】 需要知道对应php和nginx的安装版本 需要安装php-fpm…

stl之string

构造函数 void test1() {string s1;//不传参cout << s1 << endl;string s2("123456");cout << s2 << endl;string s3(s2);cout << s3 << endl;string s4(s2, 1, 5);cout << s4 << endl;string s5("123456&quo…

PHP 网络通信底层原理分析

大家好&#xff0c;我是码农先森。 引言 我们日常的程序开发大多数都是以业务为主&#xff0c;很少会接触到底层逻辑。对于我们程序员来说&#xff0c;了解程序的底层运行逻辑&#xff0c;更有助于提升我们对程序的理解。我相信大多数的人&#xff0c;每天基本上都是完成业务…