[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式

目录

前言:

第1章 进程

1.1 进程概述

1.2 线程

1.3 进程的资源类型

1.4 Linux进程的特点

第2章 进程间通信

2.1 概述

2.2 进程通信的应用场景

2.3 进程间通信要共享的对象

2.4 进程间通信必须解决问题

2.5 进程间通信的方式概述

第3章  进程间通信的方式详解

3.1 虚拟文件系统通信

3.2 无名管道:pipe 通信

3.4 信号量:semaphore

3.5 消息队列: message queue

3.6 共享内存:shared memory

3.7 套接字:socket

3.8 信号: Signal


前言:

不同于线程与线程间通信,Linux的进程之间的虚拟内存地址空间是相互隔离的,Linux进程的程序间是相互看不到的,要实现它们之间的通信就必须借助内核和必须定义每个进程都能够识别的标识。本文就是探讨这个问题。

第1章 进程

1.1 进程概述

进程是操作系统内维护程序资源的一个概念。每当我们执行一个可执行程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放。

可以认为进程是一个程序所有资源的统称

1.2 线程

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

1.3 进程的资源类型

(1)线程以及线程id

(2)内存地址空间

  • 逻辑地址空间
  • 各种全局变量:普通变量、结构体变量等
  • 各种句柄(文件句柄、socket句柄等)

(3)CPU

  • CPU寄存器上下文
  • CPU运算时隙

1.4 Linux进程的特点

(1)进程间的隔离性

进程与进程之间的资源是相互隔离的。如下图所示:

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第1张图片

既然进程间的资源是隔离的, 那就不仅仅隔离的是进程内的逻辑地址空间,不仅仅隔离的用户空间的地址,而且包括地址空间内的一切资源:文件句柄、socket句柄、线程id、CPU上下文等等。

(2)与内核的交互性

虽然用户空间和内核空间也是隔离的,但用户空间的程序可以通过内核提供的系统调用,完成内核空间和进程用户空间的交互。

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第2张图片

第2章 进程间通信

2.1 概述

进程用户空间是相互独立的,一般而言是不能相互访问的。

但很多情况下进程间需要互相通信,来完成系统的某项功能。

进程通过与内核及其它进程之间的互相通信来协调它们的行为。

在这里插入图片描述

2.2 进程通信的应用场景

(1)数据传输:

一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到N个字节之间,有可能是少量数据,有可能是大量数据。这个过程需要进行数据的拷贝和传送。

(2)数据共享:

多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到

这个过程不需要进行数据的拷贝,不同进程之间共享相同的一份数据。这种情形,常见于两个进程之间有大额数据传输或实时数据传输。

(3)资源共享:

多个进程之间共享同样的资源(不一定是大量的数据),如系统的外设。

Linux通过把外设规划到内核的虚拟文件系统中,实现外设资源在不同用户进程之间的共享。

为了做到这一点,需要内核提供锁和同步机制。

(4)通知事件:

一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程。

(5)进程控制:

有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

2.3 进程间通信要共享的对象

(1)在用户空间的一段内存(共享内存)

(2)在用户空间的一段内存(消息)

(3)由内核空间管理的磁盘文件

(4)由内核空间管理的设备文件(虚拟文件)

(5)由内核空间管理的信号signal

(6)由内核空间管理的socket

2.4 进程间通信必须解决问题

由于用户进程空间是完全隔离的,要解决他们的共享问题,必须解决如下的几个问题:

(1)共享对象在全局的唯一标识问题。

这个标识必须是独立于用户空间进程的,它不属于用户空间的资源,而应该属于内核空间的资源或整个Linux系统的资源。

像文件句柄id,socket id,线程id,他们都是进程内的资源标识不能作为进程间全局性的资源标识

在Linux中,可以利用的全局标识大致有如下几种:

  • 虚拟文件系统的全路径名称,如/tmp/filename.tmp
  • Signal ID
  • 全局性的Key(32bit或64bit的key id)

(2)把全局标识映射到进程空间标识

程序在进程空间执行时,如果要访问某个对象,必须生产进程空间的对象标识,比如消息队列 id,socket id,文件标识id。

这个映射过程由程序在进程空间中创建某个实际对象实现的,并把全局标识和进程内的标识关联起来。如进程内的程序在创建消息队列时,就必须指定消息队列的全局标识key,然后返回一个进程内的标识:消息队列id。

2.5 进程间通信的方式概述

在Linux中,用户空间的进程之间的所有资源是相互隔离的,因此,他们之间是不能直接通信的。

Linux提供了用户空间与内核空间数据的传递。

因此,用户空间的进程之间的通信,本质上,用户空间的进程借助Linux内核之间进行相互的通信。

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第3张图片

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第4张图片

第3章  进程间通信的方式详解

3.1 虚拟文件系统通信

        这种方式,通过独立于用户进程之外的、由内核管辖的虚拟文件系统中的文件进行通信。

(1)虚拟文件的类型

  • 磁盘文件
  • 内存文件
  • 设备文件

(2)标识

  • 全局性标识:文件路径path

        fd =  creat(const char *filename,mode_t mode)

        fd =  open(const char *pathname, int oflag, ... /* mode_t mode */);

  • 进程内标识:文件描述标识fd

(3)通信的过程

  1. 进程A和B创建和打开相同的虚拟文件,并在各自的内存空间中获得文件句柄id
  2. 进程A通过自己的文件句柄向虚拟文件中写入内容
  3. 进程B通过自己的文件句柄从虚拟文件中读入内容

(4)适用范围

  • 不同进程共享配置文件
  • 不同进程共享硬件外设

(5)优点:

  • 简单
  • 直观
  • 内核文件通信的基本思想被其他通信方式所借鉴。

(6)缺点

  • 数据传输效率低:数据需要经过复杂的虚拟文件系统,数据的传输效率低下。
  • 数据访问效率低:文件中的数据只能顺序访问,不能按照随机的方式访问。
  • 实用范围有限:不适合大规模内存数据的传送。

3.2 无名管道:pipe 通信

(1)管道的类型

        管道分为无名管道,有名管道,有名管道又称为FIFO.

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第5张图片

  • 无名管道:

        是一种半双工的通信方式, 数据只能单向流动, 而且只能在具有亲缘关系的进程间使用. 进程的亲缘关系一般指的是父子关系。当一个进程创建了一个管道, 并调用fork创建自己的一个子进程后,父进程关闭读管道端,子进程关闭写管道端,这样提供了两个进程之间数据流动的一种方式。无名管道的标识可以在父子进程之间传递。

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第6张图片

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第7张图片

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第8张图片

  • 有名管道:

        也是一种半双工的通信方式, 但是它允许无亲缘关系进程间的通信。不同进程之间可以通过一个全局性的key来标识。

(2)说明

  • 管道由pipe函数创建
  • 管道的本质是伪文件(不占用磁盘空间,只占用内存)
  • 管道由两个文件描述符的引用,一个fd[0]读,一个fd[1]写
  • 数据从管道的一端fd[1]写入,从管道的另一端fd[0]读出。fd的方向是固定的。

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第9张图片

  • 管道的原理是内核缓冲区(4k)借助环形队列机制实现,一个读环形队列,一个写环形队列。

(3)无名管道案例

#include 
#include 
#include 
#include 


int main()
{
  int fd[2];                   // 定义存放管道fd的数组。
  pid_t pid;
  char buf[1024];

  int ret=pipe(fd);           // 创建管道,并把管道的标识存放到数组中。

  if(ret==-1)
  {
    perror("pipe error");
    exit(1);
  }

  //创建子进程,实现子进程向父进程发送数据
  pid=fork();
  if(pid==-1)
  {
    perror("fork error");
    exit(1);
  }else if(pid==0){
    //进入子进程
    //1.子进程关闭读端
    close(fd[0]);
    const char *senddata="hello pipe\n";
    int ret=write(fd[1],senddata,strlen(senddata));  //向管道fd[1]写入数据
    if(ret==-1)
    {
      perror("write error");
      exit(1);
    }
  }else if(pid>0){
    //进入父进程
    //2.父进程关闭写端
    close(fd[1]);
    int ret=read(fd[0],buf,sizeof(buf));          //父进程向管道fd[0]读数据
    if(ret==-1)
    {
      perror("read error");
      exit(1);
    }

    int ret2=write(STDOUT_FILENO,buf,ret);       //父进程把从管道读出的数据写到控制台终端

    if(ret2==-1)
    {
      perror("write error");
      exit(1);
    }

  }
  return 0;
}

3.3 有名管道:FIFO 通信

(1)概述

FIFO(First In First Out)一种先入先出(读写数据是只能顺序写入顺序读出)的数据缓存器读写数据时,其内部读写指针自动加1,因此没有外部地址线,使用简单。读写指针是分开的。

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第10张图片

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第11张图片

在上图中, CPU#1代码写进程;CPU#2代表读进程。D-IN和D-OUT代表要写入FIFO的内存数据。

FIFO使用管道文件来标识管道,文件的内容被放进了内存缓冲区,通过read write函数来对管道文件进行读写。

不像pipe仅能用在父子进程这种有亲缘关系的进程之间, FIFO则可以用于任意两个进程之间。

两种管道目前只是实现了半双工的通信模式, 一侧写的时候另外一侧读就会阻塞等待。如果FIFO里无数据, 读进程会阻塞, FIFO里数据满了写进程同样会阻塞。

(2)全局标识: 全路径文件名

创建管道文件 int mkfifo(const char *name,mode_t mode)

(3)有名管道FIFO案例 - 写进程

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define FIFO_NUM1 "/tmp/fifonum1"    //FIFO跨进程标识
#define MAX_BUFFER_SIZE 100

int main(int argc, char * argv[])
{
    int fd;
    char buff[MAX_BUFFER_SIZE];
    int nwrite;
    /* 以只写阻塞方式打开FIFO管道 */

    fd = open(FIFO_NUM1, O_WRONLY);
    if (fd == -1)
    {
        printf("Failed to open fifo\n");
        exit(1);
    }
    while(1)
    {
        bzero(buff,sizeof(buff));
        fgets(buff,sizeof(buff),stdin);
        /* 向管道中写入字符串 */
        if ((nwrite = write(fd, buff, MAX_BUFFER_SIZE)) > 0)
        {
            printf("Write '%s' to FIFO\n", buff);
        }

    }
    close(fd);
    exit(0);
}

(4)有名管道FIFO案例 - 读进程(创建FIFO)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define FIFO_NUM1 "/tmp/fifonum1"    //FIFO跨进程标识
#define MAX_BUFFER_SIZE 100
int main()
{
    char buff[MAX_BUFFER_SIZE];
    int fd;
    int nread;

    /* 判断有名管道是否已存在,若尚未创建,则以相应的权限创建 */
    if (access(FIFO_NUM1, F_OK) == -1)
    {
        if ((mkfifo(FIFO_NUM1, 0666) < 0) && (errno != EEXIST))  //创建FIFO
            {
                 printf("Failed to create fifo file\n");
                exit(1);
            }
    }


    /* 以只读阻塞方式打开有名管道 */
    fd = open(FIFO_NUM1, O_RDONLY);
    if (fd == -1)
    {
        printf("Failed to open fifo\n");
        exit(1);
    }

    while (1)
    {
        bzero(buff, sizeof(buff));
        if ((nread = read(fd, buff, MAX_BUFFER_SIZE)) > 0)
        {
            printf("Read '%s' from fifo\n", buff);
        }
    }
    close(fd);
    exit(0);
}

3.4 信号量:semaphore

(1)概述

信号量是一个计数器,可以用来控制多个线程对共享资源的访问.,它不是用于交换大批数据,而用于多线程之间的同步.它常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源.因此,主要作为进程间以及同一个进程内不同线程之间的同步手段.

共享资源可以是内核管理的外设,也可以是进行之间的共享内存,也可以线程之间的共享变量。

Linux提供了一组精心设计的信号量接口来对信号进行操作,它们不只是针对二进制信号量。

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第12张图片

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第13张图片

信号量:主要用于同步与互斥。为了防止出现因多个进程访问临界资源而引发的一系列问题,信号量可以提供这样一种访问机制,在任一时刻只能有一个执行线程访问代码的临界区域,也就是说信号量是用来协调进程对临界资源的访问。

信号量的操作:信号量是一种特殊的变量,对信号量的访问必须是原子操作,信号量的操作只有两种:P操作(-1,申请资源)和V操作(+1,释放资源)。最简单的信号量只有两种取值0和1,称这样的信号量为二元信号量。可以取值为正整数N的信号量称为多元信号量,它允许多个线程并发的访问资源。

临界资源:能被多个进程共享,但一次只能允许一个进程使用的资源称为临界资源。

临界区:涉及到临界资源的部分代码,称为临界区。

互斥:亦称间接制约关系,在一个进程的访问周期内,另一个进程就不能进行访问,必须进行等待。当占用临界资源的进程退出临界区后,另一个进程才允许去访问此临界资源。

信号量,是一种特殊的变量。只能对信号量执行P操作和V操作

P操作: 如果信号量的值 > 0, 则把该信号量减1, 如果信号量的值 ==0, 则挂起该进程。

V操作: 如果有进程因该信号量而被挂起,则恢复该进程运行, 如果没有进程因该信号量而挂起,则把该信号量加1。

(2)全局标识(跨进程)-- 方法1

int semget((key_t)key , int nsems , int flag)

- key:是一个信号标识,若多个进程使用同一个信号量时,要求在调用semget方法时的key值相同,也就是说,key是跨进程的唯一标识。

- nsems:在创建时使用,用来执行创建的信号量集合中信号量的个数

- flag:指定操作权限,同时可以通过设置IPC_CREAT,来指明本次需要创建的信号量集

- 返回值:失败返回-1,成功返回内核对象的ID值,用于后续在进程空间操作此信号量。

(3)全局标识(跨进程)-- 方法2

sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);

- name是文件的路径名,在linux中sem都是创建在/dev/shm目录下。

3.5 消息队列: message queue

(1)概述

消息队列是消息的链表, 存放在内核中并由消息队列标识符标识. 消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点.消息队列是UNIX下不同进程之间可实现共享资源的一种机制,UNIX允许不同进程将格式化的数据流以消息队列形式发送给任意进程.对消息队列具有操作权限的进程都可以使用msget完成对消息队列的操作控制.通过使用消息类型,进程可以按任何顺序读信息,或为消息安排优先级顺序。

消息队列(Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的资料,包含发生的时间,输入设备的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,直到接收者取回它。

消息队列本身是异步的,它允许接收者在消息发送很长时间后再取回消息,

(2)消息队列框进程全局标识

const key_t g_msg_key = 1234;

msg_handle = msgget(g_msg_key, IPC_PRIVATE | IPC_CREAT);

(3)进程通过消息队列发送

ret = msgsnd(msg_handle, &data, sizeof(msgq_t) - sizeof(long), 0);

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SENDBUF     1024

typedef struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[SENDBUF];    /* message data */
}msgq_t;

const key_t g_msg_key = 1234;

int main(int argc, char* argv[])
{
    int msg_handle, ret, run_flag = 1;
    msgq_t data;
    char sendbuf[SENDBUF];
    long int msgtype = 1;

    msg_handle = msgget(g_msg_key, IPC_PRIVATE | IPC_CREAT);
    if (msg_handle == -1)
    {
        printf("msgget failed with error %d\n", errno);
        exit(EXIT_FAILURE);
    }

    while (run_flag)
    {
        printf("Please input msgtype and msg\n");
        scanf("%ld%s", &msgtype, sendbuf);

        data.mtype = msgtype;
        strcpy(data.mtext, sendbuf);

        ret = msgsnd(msg_handle, &data, sizeof(msgq_t) - sizeof(long), 0);
        if(ret == -1) {
            printf("msgsnd failed with error %d\n", errno);
            exit(EXIT_FAILURE);
        }

        if (strncmp(sendbuf, "end", 3) == 0)
        {
            run_flag = 0;
        }
    }
    
    ret = msgctl(msg_handle, IPC_RMID, 0);
    if(ret == -1) {
        printf("msgctl failed with error %d\n", errno);
        exit(EXIT_FAILURE);    
    }

    return 0;
}

(4)进程通过消息队列接收

ret = msgrcv(msg_handle, &data, BUFSIZ, msgtype, 0);

#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[BUFSIZ];    /* message data */
}msgq_t;

const key_t g_msg_key = 1234;

int main(int argc, char* argv[])
{
    int msg_handle, ret;
    int run_flag = 1;
    long int msgtype = -5;
    msgq_t data;

    msg_handle = msgget(g_msg_key, IPC_PRIVATE | IPC_CREAT);
    if(msg_handle == -1) {
        printf("msgget failed with error %d\n", errno);
        exit(EXIT_FAILURE);
    }

    while (run_flag)
    {
        ret = msgrcv(msg_handle, &data, BUFSIZ, msgtype, 0);
        if(ret == -1) {
            printf("msgrcv failed with error %d\n", errno);
            exit(EXIT_FAILURE);
        } else {
            printf("recv queue type %ld, data: %s\n", data.mtext, data.mtext);
        }
        if(strncmp(data.mtext, "end", 3) == 0) {
            run_flag = 0;
        }
    }
    
    ret = msgctl(msg_handle, IPC_RMID, 0);
    if(ret == -1) {
        printf("msgctl failed with error %d\n", errno);
        exit(EXIT_FAILURE);    
    }

    return 0;
}

3.6 共享内存:shared memory

(1)概述

共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问.共享内存是最快的IPC(进程间通信)方式,它是针对其它进程间通信方式运行效率低而专门设计的.它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步与通信。

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第14张图片

(2)跨进程全局标识: key

int shmget(key_t key, size_t size, int shmflg);

(3)案例-写进程

#include
#include
#include
#include
#include

int main()
{

        int shmid;
        char *shmaddr;

        key_t key;
        key=ftok(".",1);

        shmid= shmget(key,1024*4,IPC_CREAT|0666);   //通过key获得共享内存对象标识

        if(shmid ==-1){
                printf("shmeget error!\n");
                exit(-1);
        }

        shmaddr =shmat(shmid,0,0);  //通过共享内存标识,进行内存映射,获取进程空间地址

        printf("shmat complete\n");
        strcpy(shmaddr,"test data ");  //进程向共享内存空间写数据

        sleep(5);

        shmdt(shmaddr);                //释放共享内存在进程内的地址空间
        shmctl(shmid,IPC_RMID,0);      //释放共享内存对象

        printf("quit!\n");

        return 0;
}

(4)案例-读进程

#include
#include
#include
#include
#include

int main()
{

        int shmid;
        char *shmaddr;

        key_t key;
        key=ftok(".",1);

        shmid= shmget(key,1024*4,0);
        if(shmid ==-1){
                printf("shmeget error!\n");
                exit(-1);
        }

        shmaddr =shmat(shmid,0,0);

        printf("shmat complete\n");
        printf("data:%s\n",shmaddr);

        shmdt(shmaddr);

        printf("quit!\n");

        return 0;
}

3.7 套接字:socket

(1)概述

socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信,也支持同一个机器上不同进程之间的同学。也因为这样,套接字明确地将客户端和服务器区分开来。通过套机值通信的本质是通过Linux TCP/IP协议栈进行进程间的通信,TCP/IP协议栈工作在内核空间,可以为不同的进程共享。

(2)同一个机器不同进程之间通过socket通信的标识:AF_UNIX域套接字

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第15张图片

 每个套接字都有其自己的地址格式,对于AF_UNIX域套接字来说,它的地址由结构sockaddr_un来描述,该结构定义在头文件:

struct sockaddr_un{
    sa_family_t sun_family;   // AF_UNIX,它是一个短整型
    char sum_path[];          // 跨进程的全局路径名
};

(3)不同机器之间通过socket通信的标识:AF_INET域套接字

[架构之路-60]:目标系统 - 平台软件 - 基础中间件 - Linux进程间通信的主要方式_第16张图片

struct sockaddr_in{
    short int sin_family;   //AN_INET
    unsigned short int sin_port;   //端口号
    struct in_addr sin_addr;        //跨机器的全局标识:IP地址
}  

而in_addr被定义为:

struct in_addr{
    unsigned long int s_addr;
}

(4)解读

  • 创建套接字——socket系统调用

该函数来创建一个套接字,并返回一个描述符,该描述符可以用来访问该套接字,其原型如下:

int socket(int domain, int type, int protocol)

函数中的三个参数分别对应前面所说的三个套接字属性。protocol参数设置为0表示使用默认协议。

  • 命名(绑定)套接字——bind系统调用

该函数把通过socket调用创建的套接字命名,从而让它可以被其他进程使用。对于AF_UNIX,调用该函数后套接字就会关联到一个文件系统路径名,对于AF_INF,则会关联到一个IP端口号。函数原型如下:

int bind(int socket, const struct sockaddr *address, size_t address_len)

(5)案例 - 服务器

#include 
#include 
#include 
#include 
#include 
#include 
#include  //define the sockaddr_un structure
int num_reverse(int num);

int main()
{
    /* 断开之前的socket文件 */
    unlink("server_socket");
    
    /* 创建一个socket */
    int server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    
    struct sockaddr_un server_addr;
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, "server_socket");
    
    /* 与本地文件进行绑定 */
    bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    
    /* 监听 */
    if(listen(server_sockfd, 5)<0);
    {
	    perror("Listen failed");
    }
    
    int client_sockfd;
    struct sockaddr_un client_addr;
    socklen_t len = sizeof(client_addr);
    
    while(1)
    {
        printf("server waiting:\n");
        /* 接受一个客户端的链接 */
        client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);
        
        /*数据交换 */
        read(client_sockfd, &num, 4);
        printf("get an integer from client: %d\n", num);
        num=num_reverse(num);
        write(client_sockfd, &num, 4);
        
        /* 关闭socket */
        close(client_sockfd);
    }
    
    return 0;
}

	int num_reverse(int num)
	{
	    int S=0,sum=0;
	    
	    while(num)
	    {
	        S=num%10;
	        sum=10*sum+S;
	        num = num / 10;
	    }
	    
	    return sum;
	}

(6)案例 - 客户端

#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    /* 创建一个socket */
    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    
    struct sockaddr_un address;
    address.sun_family = AF_UNIX;
    strcpy(address.sun_path, "server_socket");
    
    /*从键盘读取需要转置的整数*/
    int num;
    printf("Please enter the num to reverse:\n");

    /* 链接至服务端 */
    int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
    if(result == -1)
    {
        perror("connect failed: ");
        exit(1);
    }
    
    /* 数据处理 */
    write(sockfd, &num, 4);//一个int 4个字节
    read(sockfd, &num, 4);
    printf("get an integer from server: %d\n", num);
    
    /* 关闭socket */
    close(sockfd);
    
    return 0;
}  

3.8 信号: Signal

(1)概述

信号是一种比较复杂的通信方式,用于通知接收进程间的某个异步事件已经发生。

信号是一种软件终端,提供了一种处理异步事件的方法,也是进程间通信的唯一一个异步的通信方式。

Unix中定义了很多信号,有很多条件可以产生信号,对于这些信号有不同的处理方式。

本文会详细讲述信号的机制。如下是跨进程的全局信号标识。

$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

目前Linux 中定义了64中信号,前期定了32种(1-31),后面的33种为实时信号(32-64);

(2)信号的全局标识

信号    属性值    默认处理方式    定义
SIGHUP     1    Term    Hangup detected on controlling terminal or death of controlling process
SIGINT     2    Term    Interrupt from keyboard
SIGQUIT    3    Core    Quit from keyboard
SIGILL     4    Core    Illegal Instruction
SIGTRAP    5    Core    Trace/breakpoint trap
SIGABRT    6    Core    Abort signal from abort(3)
SIGBUS    7    Core    Bus error (bad memory access)
SIGFPE     8    Core    Floating point exception
SIGKILL    9    Term    Kill signal
SIGUSR1    10    Term    User-defined signal 1
SIGSEGV    11    Core    Invalid memory reference
SIGUSR2    12    Term    User-defined signal 2
SIGPIPE    13    Term    Broken pipe: write to pipe with no readers
SIGALRM    14    Term    Timer signal from alarm(2)
SIGTERM    15    Term    Termination signal
SIGSTKFLT    16    Term    Stack fault on coprocessor (unused)
SIGCHLD    17    Ign     Child stopped or terminated
SIGCONT    18    Cont    Continue if stopped
SIGSTOP    19    Stop    Stop process
SIGTSTP    20    Stop    Stop typed at terminal
SIGTTIN    21    Stop    Terminal input for background process
SIGTTOU    22    Stop    Terminal output for background process
SIGURG    23    Ign    Urgent condition on socket (4.2BSD)
SIGXCPU    24    Core    CPU time limit exceeded (4.2BSD)
SIGXFSZ    25    Core    File size limit exceeded (4.2BSD)
SIGVTALRM    26    Term    Virtual alarm clock (4.2BSD)
SIGPROF    27    Term    Profiling timer expired
SIGWINCH    28    Ign    Window resize signal (4.3BSD, Sun)
SIGIO    29    Term    I/O now possible (4.2BSD)
SIGPWR    30    Term    Power failure (System V)
SIGUNUSED    31    Core    Synonymous with SIGSYS
                                                               表1     标准信号

表中处理方式:

  • Term   终止进程
  • Ign      忽略信号
  • Core   终止进程,并且产生core dump 文件
  • Stop   停止进程
  • Cont   如果进程处于stop状态,继续运行进程

注意:

  • SIGKILL 和SIGSTOP 是不能被捕捉、阻塞、忽略;

(3)案例 - 接收信号进程


#include 
#include 
#include 
 
void sig_handle(int sig)
{
	puts("recv SIGINT");
	sleep(5);
	puts("end");
}
 
int main(int argc, char** argv)
{
	struct sigaction act;
	act.sa_handler = sig_handle;  //进程内的信号处理函数
	act.sa_flags = 0;
	
	sigemptyset(&act.sa_mask);

	sigaddset(&act.sa_mask, SIGQUIT); //当进入信号处理函数的时候,屏蔽掉SIGQUIT的递达

	sigaction(SIGINT, &act, NULL);  //注册信号处理函数,信号处理函数在注册进程空间执行。
	
	while(1)
		sleep(1);
	return 0;

}

(4)发送信号的方式:

  • kill
  • killpg
  • raise
  • pthread_kill
  • tgkill
  • abort
  • sigqueue
  • alarm

你可能感兴趣的:(架构之路,linux,架构,中间件,进程间通信)