UNIX网络编程:共享内存区

IPC形式除了管道、FIFO、信号量以外,还有共享内存区和消息队列。这里主要堆共享内存进行介绍。

共享内存区是可用IPC形式中最快的。一旦这样的内存区映射到共享它的进程地址空间,这些进程间数据的传递就不再涉及内核。共享内存与其他进程通信方式相比较,不需要复制数据,直接读写内存,是一种效率非常高的进程通信方案。但它本身不提供同步访问机制,需要我们自己控制。在LINUX中,只要把共享内存段连接到进程的地址空间中,这个进程就可以访问共享内存中的地址了。为了实现往该共享内存区存放和取出数据的进程间的同步性,防止自己写入的数据被自己读出。这就到了我们之前提到的信号量大显身手的时候了。

共享内存是系统创建的特殊地址空间,允许不相关的多个进程使用这个内存空间,即多个进程能够使用同一块内存中的数据。

LINUX系统提供的共享内存操作函数与信号量、消息队列等类似,主要有以下几个:

(1)int shmget(key_t key,int shmsz,int shmflg);

Shmget()函数分配一块新的共享内存。key是指ftok所得到的键值。Shmsz指明共享内存的大小,以字节为单位,shmflg的设置是标志信息

如果shmget()函数调用成功则返回共享内存的ID;否则返回-1.

(2)void *shmat(int shmid,const void *shmaddr, int shmflg);

Shmat()函数的作用是连接共享内存与某个进程的地址空间。

Shmid是shmget()函数返回的共享内存ID。Shmaddr是共享内存连接到进程中的存放地址,一般设置为空指针,表示交由系统完成这个工作。

如果shmaddr为0 则此段连接到由内核选择的第一个可用地址上,这是推荐的使用方式

如果shmaddr非零,并且没有指定SHM_RND,则此段链接到addr所指的地址上

如果shmaddr非零且指定SHM_RND,则此段链接到shmaddr - (addr mod ulus SHMLBA)所表示的地址上。SHM_RND的意思是低边界地址倍数,它总是2的乘方。该算式是将地址向下取最近的一个SHMLBA的倍数

Shmflg设置共享内存的控制选项,有两个可能取值:SHM_RND(与shmaddr参数相关)与SHM_RDONLY(只允许读)。如果shmat()函数调用成功则返回指向共享内存的指针;否则返回-1.

(3)int shmdt (const void *shmaddr);

Shmdt()函数用来解除进程与共享内存区域的关联,使当前进程不能继续访问共享内存。参数shmaddr是shmat()函数返回的指针。如果操作成功则返回0;失败则返回-1.

(4)int shmctl(int shmid, int cmd,struct shmid_ds *buf);

Shmctl()函数实现对共享内存区域的控制操作。

cmd:指明所要执行的操作,常用的有以下命令:

IPC_STAT:调用shmctl函数,执IPC_STAT命令,获取一个已存在内存区的大小

IPC_SET :调用shmctl函数,执行IPC_SET命令,设置一个已存在内存区中的值

IPC_RMID:调用shmctl函数,执行IPC_RMID命令,删出共享内存区对象。

下面是一个用信号量和共享内存联合一起实现的一个服务器与客户端之间通信的例子,思路是这样的服务器和客户端根据两个信号量进行沟通,信号量1负责服务器的写和客户端的读,而信号量2负责服务器的读和客户端的写(因为半双工管道的原因,不能同时控制读写)。而读写的信息存放在共享内存中,服务器写进数据到共享内存,并发出消息给客户端,客户端在内存中取出数据读。而信号量的作用就在于同步控制,根据0阻塞进程的特性,防止服务器自己写的数据被自己读的错误发生。

服务器端代码:

ser.cpp

#include "utili.h"

int main(int argc, char *argv[])
{
    /*创建一个唯一的键值并判断是否创建成功*/
    key_t key_id;
    key_id = ftok(argv[1], ID);
    if(key_id == -1){
        printf("ftok error.\n");
        exit(1);
    }
    //用上面创建的键值创建一块大小为1024的共享内存。要么创建要么返回EEXIST错误,权限为所数用户、所数组、其他用户可读可写。并判断它是否创建成功
    key_t shm_id = shmget(key_id, 1024, IPC_CREAT|IPC_EXCL|0666);
    if(shm_id == -1){
        printf("shmget error.\n");
        exit(1);
    }
    //在创建的共享内存中建立连接,地址由系统分配,并检测连接是否成功
    char* addr = (char *)shmat(shm_id, NULL, 0);
    if(addr == (char *)-1){
        printf("shmat error.\n");
        shmctl(shm_id, IPC_RMID, NULL);
        exit(1);
    }
    //重新创建一个新的键值并判断是否创建成功
    key_t sem_key, sem_id;
    sem_key = ftok(argv[1], ID1);
    if(sem_key == -1){
        printf("sem ftok error.\n");
        exit(1);
    }
    //用上面创建的键值创建两个信号量集合,并判断信号量集合是否创建成功
    sem_id = semget(sem_key, 2, IPC_CREAT|IPC_EXCL|0666);
    if(sem_id == -1){
        printf("semget error.\n");
        exit(1);
    }
    //给两个信号量都赋初值0
    union semun init;
    init.val = 0;
    semctl(sem_id, 0, SETVAL, init);
    semctl(sem_id, 1, SETVAL, init);

    //定义两个sembuf的结构体,用来控制信号量资源数
    struct sembuf p = {0, -1, 0};
    struct sembuf v = {1, 1, 0};

    while(1){
        //服务器写数据【此时0号信号量的值为0,处于阻塞状态,所以它不会将自己写入的数据读出】
        printf("Ser:>");
        gets(addr);
        //判断它写入的数据是否为quit,
        if(strncmp(addr, "quit", 4) == 0){
            semop(sem_id, &v, 1);   //用结果体v对相应的信号量进行设置。保证结束信息可以令客户端读到,防止客户端一直在等待服务器端的数据
            shmdt(addr);            //断开共享内存连接
            shmctl(shm_id, IPC_RMID, NULL);   //删除共享内存区
            //删除之前创建的两个信号量集
            semctl(sem_id, 0, IPC_RMID);
            semctl(sem_id, 1, IPC_RMID);
            break;
        }
        //如果输入数据部为quit,用结果体v对相应的信号量进行设置。保证信息可以令客户端读到
        semop(sem_id, &v, 1);

        //服务器读数据【此时1号信号量的值为0,客户端读操作处于阻塞状态】
        semop(sem_id, &p, 1);//改变0号信号量的值,让服务器的读操作处于运行状态
        printf("Cli:>%s\n", addr);
        //比较客户端传来的数据是不是quit,如果是quit则断开连接,并将值前创建的共享内存区和信号量删除掉然后退出。
        if(strncmp(addr, "quit", 4) == 0){
            shmdt(addr);
            semctl(shm_id, IPC_RMID, NULL);
            semctl(sem_id, 0, IPC_RMID);
            semctl(sem_id, 1, IPC_RMID);
            break;
        }
    }

        return 0;
}

客户端程序:

cli.cpp:

#include "utili.h"

int main(int argc, char * argv[])
{
    //用与服务器端同样的路径与数值创建一个相同的键值,并判断是否创建成功
    key_t key_id = ftok(argv[1], ID);
    if(key_id == -1){
        printf("ftok error.\n");
        exit(1);
    }
    //利用该键值查找服务器端所创建的共享内存的id号,并判断是否查找成功
    key_t shm_id = shmget(key_id, 0, 0);
    if(shm_id == -1){
        printf("shmget error.\n");
        exit(1);
    }
    //与共享内存建立连接,并保证创建连接成功
    char *addr = (char *)shmat(shm_id,NULL,0);
    if(addr == (void *)-1)
    {
        printf("shmat error.\n");
        exit(1);
    }
     //重新创建一个与服务器中相同的sem_key,用来创建信号量集
     key_t sem_key = ftok(argv[1],ID1);
     if(sem_key == -1)
     {
         printf("sem ftok error.\n");
         exit(1);
     }
     //查找服务器创建的两个信号量集的id号
     key_t sem_id = semget(sem_key, 0, 0);
     if(sem_id == -1)
     {
         printf("semget error.\n");
         exit(1);
     }
     //创建两个sembuf型的结构体,p、v对应操作的信号量下标量semnum(第一个参数)应该与服务器端相反,这样才能实现同步
     struct sembuf p = {1,-1,0};
     struct sembuf v = {0,1,0};

     while(1){
         //客户端读数据,并判断服务器端传来的数据是否为quit,如果为quit直接程序结束
         semop(sem_id, &p, 1);
         printf("Ser:>%s\n", addr);
         if(strncmp(addr, "quit", 4) == 0){
             break;
         }

         //所读到的数据不为quit,客户端进行写数据【此时1号信号量的值为0,客户端的读操作阻塞】
         printf("Cli:>");
         gets(addr);
         //如果客户端传送的数据为quit,则将0号信号量的值加1,然后在退出程序,保证服务器端可以读到数据,从而断开连接,防止服务器端一直等待客户端的数据
         if(strncmp(addr, "quit", 4) == 0){
             semop(sem_id, &v, 1);
             break;
         }
         //如果不为quit,则将0号信号量的值加1,让服务器的读操作处于运行状态
         semop(sem_id, &v, 1);
    }   

         return 0;
}

头文件:

utili.h:

#pragma once

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>

using namespace std;

#define ID  0xFF
#define ID1 0xFE

union semun
{
    int val;            /* value for SETVAL */
    struct semid_ds *buf;   /* buffer for IPC_STAT & IPC_SET */
    unsigned short *array;  /* array for GETALL & SETALL */
    struct seminfo *__buf;  /* buffer for IPC_INFO */
    void *__pad;
}; 

程序运行结果:

这里才真正的体现了信号量的作用,它可以实现同步机制,其实相比于共享内存,IPC形式中的消息队列本身就带有同步形式,后序会对其继续进行整理,欢迎来访~~。

时间: 2024-08-02 22:45:32

UNIX网络编程:共享内存区的相关文章

Unix IPC之共享内存区(1)

1 共享内存区 共享内存区是可用IPC形式中最快的,只有映射和解除映射需要进入内核的系统调用,映射后对共享内存区的访问和修改不再需要系统调用(内核只要负责好页表映射和处理页面故障即可),但通常需要同步手段. 一个客户-服务器间传递文件数据的例子中,FIFO或消息队列等IPC方式通常需要4次内核-进程间的数据复制(但是Posix消息队列可使用内存映射I/O实现,就不一定4次了),每次都需要切换地址空间,开销很大:共享内存区只需要2次跨内核的数据复制. 2  mmap mmap的三个目的: 1.  

Linux环境编程之共享内存区(一):共享内存区简介

Spark生态圈,也就是BDAS(伯克利数据分析栈),是伯克利APMLab实验室精心打造的,力图在算法(Algorithms).机器(Machines).人(People)之间通过大规模集成,来展现大数据应用的一个平台,其核心引擎就是Spark,其计算基础是弹性分布式数据集,也就是RDD.通过Spark生态圈,AMPLab运用大数据.云计算.通信等各种资源,以及各种灵活的技术方案,对海量不透明的数据进行甄别并转化为有用的信息,以供人们更好的理解世界.Spark生态圈已经涉及到机器学习.数据挖掘.

Linux环境编程之共享内存区(二):Posix共享内存区

现在将共享内存区的概念扩展到将无亲缘关系进程间共享的内存区包括在内.Posix提供了两种在无亲缘关系进程间共享内存区的方法: 1.内存映射文件:由open函数打开,由mmap函数把得到的描述符映射到当前进程地址空间中的一个文件.(上一节就是这种技术) 2.共享内存区对象:由shm_open打开一个Posix名字(也许是在文件系统中的一个路径名),所返回的描述符由mmap函数映射到当前进程的地址空间.(本节内容) Posix共享内存区涉及以下两个步骤要求: 1.指定一个名字参数调用shm_open

Linux环境编程之共享内存区(一):共享内存区简单介绍

共享内存区是可用IPC形式中最快的.一旦内存区映射到共享它的进程的地址空间,进程间数据的传递就不再涉及内核.然而往该共享内存区存放信息或从中取走信息的进程间通常须要某种形式的同步.不再涉及内核是指:进程不再通过运行不论什么进入内核的系统调用来彼此传递数据.内核必须建立同意各个进程共享该内存区的内存映射关系.然后一直管理内存区. 默认情况下通过fork派生的子进程并不与其父进程共享内存区. mmap函数把一个文件或一个Posix共享内存区对象映射到调用进程的地址空间.使用该函数的目的有: 1.使用

linux c编程:Posix共享内存区

Posix共享内存区:共享内存是最快的可用IPC形式.它允许多个不相关(无亲缘关系)的进程去访问同一部分逻辑内存.如果需要在两个进程之间传输数据,共享内存将是一种效率极高的解决方案.一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传输就不再涉及内核.这样就可以减少系统调用时间,提高程序效率.共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址空间中.其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去.所有进程都可以访问共享内存中的地址.如果一个进程

UNIX网络编程:卷2-读书笔记

1. Unix进程间的信息共享可以有多种方式.如图: (1)两个进程共享存留于文件系统中的某个文件上的某些信息.为访问这些信息,每个进程都得穿越内核(例如read,write,lseek等).某种形式的同步也是必要的. (2)两个进程共享驻留于内核中的某些信息.管道,System V消息队列和System V信号量都是这种方式.每次操作都设计对内核的一次系统调用. (3)两个进程有一个双方都能访问的共享内存区.可以不需要涉及内核而访问其中的数据.需要某种形式的同步. 2. IPC对象的持续性,如

Unix网络编程-同步

1.互斥锁(量)和条件变量 默认情况下互斥锁和条件变量用于线程间同步,若将它们放在共享内存区,也能用于进程间同步. 1.1 互斥锁 1.概述: 互斥锁(Mutex,也称互斥量),防止多个线程对一个公共资源做读写操作的机制,以保证共享数据的完整性. 用以保护临界区,以保证任何时候只有一个线程(或进程)在访问共享资源(如代码段).保护临界区的代码形式: lock_the_mutex(...); 临界区 unlock_the_mutex(...); 任何时刻只有一个线程能够锁住一个给定的互斥锁. 下面

UNIX网络编程:卷1-读书笔记

1. if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) err_sys("socket error"); 作为一种编码风格,作者总是在这样的两个左括号间加一个空格,提示比较运算的左侧同时也是一个赋值运算. 2. bzero不是ANSI C函数,比memset更好记忆(只有2个参数) 3. htons("主机到网络短整数"),inet_pton("呈现形式到数值") 4. 每当一个套接字

UNIX网络编程入门——TCP客户/服务器程序详解

前言 最近刚开始看APUE和UNP来学习socket套接字编程,因为网络这方面我还没接触过,要等到下学期才上计算机网络这门课,所以我就找了本教材啃了一两天,也算是入了个门. 至于APUE和UNP这两本书,书是好书,网上也说这书是给进入unix网络编程领域初学者的圣经,这个不可置否,但这个初学者,我认为指的是接受过完整计算机本科教育的研究生初学者,需要具有完整计算机系统,体系结构,网络基础知识.基础没打好就上来啃书反而会适得其反,不过对于我来说也没什么关系,因为基础课也都上得差不多了,而且如果书读