Linux并发程序设计

程序

  • 静态的,存放在磁盘上的指令与数据的有序集合

1. 进程

1.1 进程的含义

  • 执行一个程序所分配的资源的总称
  • 进程是程序的一次执行过程
  • 动态的,包括创建、调度、执行、终止

1.2 进程的组成

  • 正文段
  • 用户数据段
  • 系统数据段
    • 进程控制块(pcb):存放进程属性,如进程标识pid,进程状态和优先级,进程用户,文件描述符表(当前进程打开了哪些文件)
    • cpu寄存器值
    • 堆栈(堆栈的区别,存储啥啥数据类型!!!) 

1.3 进程的状态

1.4 查看进程信息

1.4.1 ps 查看系统进程快照

常用指令:

ps -ef   显示进程最基本的信息

ps -aux  显示进程最基本的信息&进程运行状态 

关于进程状态,man ps 即可查看手册

 1.4.2 top 查看进程动态信息

可以查看每个进程实时占用的资源大小,一般用于系统优化

1.4.3 /proc 进程详细信息

test进程进程号为8100,进入/proc/8100这个文件夹查看详细信息

yike@yike-virtual-machine:/proc/8100$ ls      #进入/proc/8100目录        
arch_status         cwd        mem            patch_state   stat
attr                environ    mountinfo      personality   statm
autogroup           exe        mounts         projid_map    status
auxv                fd         mountstats     root          syscall
cgroup              fdinfo     net            sched         task
clear_refs          gid_map    ns             schedstat     timens_offsets
cmdline             io         numa_maps      sessionid     timers
comm                limits     oom_adj        setgroups     timerslack_ns
coredump_filter     loginuid   oom_score      smaps         uid_map
cpu_resctrl_groups  map_files  oom_score_adj  smaps_rollup  wchan
cpuset              maps       pagemap        stack

# fd储存的是当前进程打开的文件信息,不一一介绍

yike@yike-virtual-machine:/proc/8100$ cat status #查看status该文件
Name:    test        #进程名称
Umask:    0002
State:    S (sleeping)
Tgid:    8100    #进程号
Ngid:    0
Pid:    8100
PPid:    2668
TracerPid:    0
Uid:    1000    1000    1000    1000
Gid:    1000    1000    1000    1000
FDSize:    256
Groups:    4 24 27 30 46 120 133 134 1000 
NStgid:    8100
NSpid:    8100
NSpgid:    8100
NSsid:    2668
VmPeak:        2496 kB
VmSize:        2496 kB
VmLck:           0 kB
VmPin:           0 kB
VmHWM:         576 kB
VmRSS:         576 kB
RssAnon:          64 kB
RssFile:         512 kB
RssShmem:           0 kB
VmData:         176 kB
VmStk:         132 kB
VmExe:           4 kB
VmLib:        1652 kB
VmPTE:          44 kB
VmSwap:           0 kB
HugetlbPages:           0 kB
CoreDumping:    0
THP_enabled:    1
Threads:    1
SigQ:    0/15242
SigPnd:    0000000000000000
ShdPnd:    0000000000000000
SigBlk:    0000000000000000
SigIgn:    0000000000000000
SigCgt:    0000000000000000
CapInh:    0000000000000000
CapPrm:    0000000000000000
CapEff:    0000000000000000
CapBnd:    000001ffffffffff
CapAmb:    0000000000000000
NoNewPrivs:    0
Seccomp:    0
Seccomp_filters:    0
Speculation_Store_Bypass:    thread vulnerable
SpeculationIndirectBranch:    conditional enabled
Cpus_allowed:    ffffffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list:    0-127
Mems_allowed:    00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:    0
voluntary_ctxt_switches:    22
nonvoluntary_ctxt_switches:    1

1.4.4 nice & renice 优先级

nice 按照用户指定的优先级运行进程,普通用户只能指定0~20,管理员可以指定任意优先级

nice -n 2 ./test      运行test并指定其优先级为2

renice 改变正在运行的进程优先级

renice -n 3 pid   用某进程的pid去修改其优先级   

1.4.5 jobs 

用于查看该终端下有哪些后台进程(后台作业)

有2个后台进程,不过已经被我killed!

1.4.6 fg

fg pid    将后台进程转化为前台运行

1.5 进程的创建

NAME
       fork - create a child process

SYNOPSIS
       #include <sys/types.h>
       #include <unistd.h>

       pid_t fork(void);

成功返回子进程的pid,失败返回-1
示例:创建子进程
  
#include <stdio.h>
#include <unistd.h>

int main()
{
  pid_t ret;
  
  ret = fork();
  
  if(ret < 0){ 
    perror("fork\n");
    return -1; 
  }else if(ret == 0){ 
    printf("child pid : %d\n", getpid());
  }else{
    printf("parent pid : %d\n", getpid());
  }
                                                                           
  return 0;
}

父子进程:

  • 子进程继承了父进程的内容
  • 父子进程各自拥有独立的地址空间,互不影响
  • 若父进程先结束
    • 子进程称为孤儿进程,被init进程收养(注:init进程是系统启动之后创建的第一个用户进程,pid==1,系统规定,子进程必须被父进程回收,现在老爹死了只能由init去回收 )
    • 子进程自动变为后台进程
  • 若子进程先结束
    • 父进程没有及时回收,子进程变为僵尸进程(子进程结束后,其相关资源被释放,但是进程的pcb未被释放,因为pcb中存放返回值和结束方式,这些信息系统规定必须由父进程回收,而刚好父进程没有及时回收,那子进程就成了僵尸!)

那么子进程从何处开始运行呢?

答:从fork语句的下一条指令开始运行,所以子进程并没有执行fork!因为子进程继承父进程几乎所有的内容,包括pc(程序计数器,存放下一条指令的地址),而父进程从main函数头部开始运行,pc值也跟着变化,子进程继承的pc值也就不可能是起始位置,而是fork语句的下一条指令的pc值

如何结束进程?

答:一般用exit,该函数结束进程时会刷新(流)缓冲区,函数原型:void exit (int status);

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("hello");
    //print为标准输出流,且为行缓冲类型,这里没有换行符是不会输出的,但是exit会刷新缓冲区,最终正常显示
    exit(0);   
    return 0;
}

1.6 进程回收

父进程调用wait (&status)回收

WIFEXITED(status)                           判断子进程是否正常结束

WEEXITSTATUS(status)                   判断子进程返回值

WIFSIGNALED(status)                      判断子进程是否被信号结束

WTERMSIG(status)                           获取结束子进程的信号类型

AME
       wait, waitpid, waitid - wait for process to change state

SYNOPSIS
       #include <sys/types.h>
       #include <sys/wait.h>

       pid_t wait(int *wstatus);   # 成功返回子进程pid,失败返回-1
                                   # status指定保存子进程返回值和结束方式的地址
                                   # status为NULL表示直接释放子进程pcb,不接受返回值
                                
       pid_t waitpid(pid_t pid, int *wstatus, int options); 
                                  # pid用于指定某个子进程,如果pid为-1表示回收任意子进程
                                  # 成功返回子进程pid或0,失败返回-1;
                                  # pid用于指定回收哪个子线程或任意子进程
                                  # option指定回收方式,0(阻塞方式)或 WNOHANG(非阻塞)
       阻塞方式:子进程未退出,父进程保持阻塞状态,不会返回子进程status
       非阻塞方式:子进程未退出,父进程不会阻塞,而是返回0;如果子进程退出,父进程返回子进程pid                        
  
#include <stdio.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    int status;

    ret = fork();
  
    if(ret < 0){ 
          perror("fork\n");
          return -1; 
    }else if(ret == 0){ 
          sleep(2);    //子进程创建成功,先休眠2s
          exit(1);     //然后退出子进程
    }else{
        wait(&status);    //回收子进程,用status存放子进程的pcb
        printf("%x\n", status);    //打印子进程的pcb地址,十六进制形式
    }   

    return 0;                                                              
}

1.7 exec函数族 

  • 进程调用exec函数族执行某个程序
  • 进程当前内容被指定的程序替换
  • 实现让父子进程执行不同的程序
    • 父进程创建子进程
    • 子进程调用exec函数族
    • 父进程不受干扰

exec函数族包括:

  • execl / execlp
  • execv / execvp
  • system

1.8 守护进程

  • 守护进程(daemon)是Linux三种进程类型之一
  • 通常在系统启动时运行,系统关闭时结束,且始终在后台运行
  • 和交互进程不同,守护进程独立于任何终端
  • Linux中很多服务程序都以守护进程形式运行
  • 守护进程周期性的执行某种任务或等待处理特定事件

会话、控制终端

  • Linux以会话(session)、进程组的方式管理进程
  • 每个进程属于一个进程组
  • 会话是一个或多个进程组的集合,用户打开一个终端时,系统会创建一个会话;所有通过终端运行的进程都属于这个会话
  • 终端关闭时,所有相关进程会被结束

2. 线程

个人理解:线程与进程最大的区别在于是否拥有独立的地址空间!

线程之间共享的资源:可执行指令、静态数据、进程中打开的文件描述符、当前工作目录、用户ID,用户组ID 

线程之间私有的资源:线程ID、PC(程序计数器)和相关寄存器、堆栈、错误号(errno)、优先级、执行状态和属性

2.1 线程的创建 / 回收 / 结束

int pthread_create(pthread_t *thread, const pthread_attr_t &attr,
                     void *(start_routine)(void *), void *arg);
# 成功返回0,失败返回错误码
# thread 线程对象
# attr 线程属性,NULL代表默认属性
# routine 表示线程执行的函数
# arg 传递给routine函数的参数
int pthread_join(pthread_t thread, void **retval);
# 成功返回0,失败返回错误码
# thread 要回收的线程对象
# 调用线程阻塞直到thread结束
# *retval用于接收线程thread的返回值
void pthread_exit(void *retval);
# 结束当前线程,不能用exit,exit用于结束进程!
# retval可被其他线程通过pthread_join获取
# 线程私有数据被释放

2.2 同步机制——信号量的P/V操作

已知线程共享同一进程的地址空间,所以优点就是线程之间可以通过全局变量进行数据交换,线程间通信非常容易,但是多个线程访问共享数据时需要同步或互斥机制。

同步:多个任务按照约定先后次序相互配合完成一件事情

基于信号量的同步机制:由信号量决定线程继续运行还是阻塞等待

信号量代表一类资源,其值表示系统中该资源的数量,其可以通过初始化、P/V操作进行访问

初始化
int sem_init(sem_t *sem,int pshared,unsigned int value);
                                        //pshared 0表示线程  1表示进程
                                        //value 信号量初始值,大于0表示有资源
P/V操作
int sem_wait(sem_t *sem);    //P
int sem_post(sem_t *sem);    //V
sem_t sem;

/* P(S)含义 */
if(sem > 0)
{
    申请资源的任务继续运行;
    sem--;
}else{
    申请资源的任务阻塞
}

/* V(S)含义 */
sem++;
if(有任务在等待资源)
{
    唤醒等待的任务,让其继续运行
}
!!需要注意的是:执行V操纵不会引起阻塞,因为它是在释放资源

2.3 互斥机制——互斥量

临界资源:一次只允许一个进程/线程访问的共享资源

互斥锁初始化
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
这个太麻烦,用这个:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;

互斥量加锁(阻塞)/解锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

3. 进程间通信机制

unix进程间通信方式

  • 无名管道(pipe)
  • 有名管道(fifo)
  • 信号(signal)

System V IPC

  • 共享内存(share memory)
  • 消息队列(message queue)
  • 信号灯集合(semaphore set)

套接字(socket)

3.1 无名管道

  • 无名管道由内核创建
  • 只能用于具有亲缘关系(父子进程)的进程之间的通信(注:无名管道没有路径,在文件系统中是不可见的,仅仅是在内存中存在,并且无名管道是由某个进程创建的,其他进程只能通过继承的方式打开其他进程创建的无名管道;对于普通文件,不同的进程可以通过路径打开同一个文件)
  • 单工通信,具有固定的读端和写端
#include <unitd.h>

int pipe(int pfd[2]);    pfd包含两个元素的整形数组,用于保存文件描述符
                         pfd[0]用于读管道,pfd[1]用于写管道


/* 示例:
 * 子进程1和2分别往管道中写入字符串,父进程读管道内容并打印
 */

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

int main()
{
    pid_t pid1, pid2;
    char buf[32];
    int pfd[2];     //该数组用于创建无名管道

    /* 一定是先创建无名管道,然后创建子进程,不然怎么继承 */
    if(pipe(pfd) < 0){
        perror("pipe");
        return -1;
    }

    if((pid1 = fork()) < 0){
        perror("fork");     //小于0,子进程1创建失败
        return -1;
    }else if(pid1 == 0){
        strcpy(buf, "I'm process1");    //等于0,创建成功,子进程1执行拷贝
        write(pfd[1], buf, 32);         //pfd[1]执行写操作
        return -1;
    }else{
        if((pid2 = fork()) < 0){
            perror("fork");
            return -1;
        }else if(pid2 == 0){
            sleep(2);       //子进程1先写管道,然后2s后子进程2写管道
            strcpy(buf, "I'm process2");    //等于0,创建成功,子进程2执行拷贝
            write(pfd[1], buf, 32);         //pfd[1]执行写操作
        }else{      //父进程
            wait(NULL);             //等待回收任意子进程,这里回收process1
            read(pfd[0], buf, 32);
            printf("%s\n", buf);
            wait(NULL);             //等待回收process2
            read(pfd[0], buf, 32);
            printf("%s\n", buf);
        }
    }

    return 0;
}                                                                                  

 无名管道有多大?

/* 通过循环写操作计算无名管道的大小                                                                                                     
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    int count = 0;
    int pfd[2];
    char buf[1024];

    if(pipe(pfd) < 0){      //创建管道
        perror("pipe");
        return -1;
    }

    while(1){                                   //循化往pipe中写数据直至阻塞
        write(pfd[1], buf, 1024);
        printf("has wrote %dk bytes\n", ++count); //  一般以k作为单位
    }

    return 0;
}



输出结果
has wrote 58k bytes
has wrote 59k bytes
has wrote 60k bytes
has wrote 61k bytes
has wrote 62k bytes
has wrote 63k bytes
has wrote 64k bytes          通过输出结果可以得出无名管道的内存大小为64bytes
^C
yike@yike:~/yike$ vi pipesize.c 


无名管道的读写特性

无名管道的写特性
读端存在   有空间   write返回实际写入的字节数
无空间(空间不足)进程写阻塞
无名管道的读特性
写端存在   有数据   read返回实际读取的字节数
无数据进程读阻塞
写端不存在有数据read返回实际读取的字节数
无数据read返回0

3.2 有名管道

  • 对应管道文件,可用于任意进程之间的通信
  • 打开管道时可指定读写方式
  • 通过文件IO操作,内容存放在内存中
/* 函数原型 */
int mkfifo(const char *path, mode_t mode);
                                path 创建的管道文件路径
                                mode 管道文件的权限



/* 示例代码:
 * 进程A: 循环从键盘输入并写入有名管道myfifo, 输入quit时退出
 * 进程B:循环统计进程A每次写入myfifo的字符串长度  
 * 所以需要3个程序,创建(create_myfifo.c)、读(read_myfifo.c)、写(write_myfifo.c )
 */


/* create_myfifo.c */
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
/* 
 * 创建又名管道myfifo
 */
int main(){
    if(mkfifo("myfifo", 0666) < 0){
        perror("mkfifo");
        return -1;
    }
    printf("myfifo is created\n");    
    return 0;
}


/* write_myfifo.c */
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>

#define N 32

int main(){
    char buf[N];
    int pfd;

    if((pfd = open("myfifo", O_WRONLY)) < 0){
        perror("open");
        return -1;
    }
    printf("myfifo is opened\n");

    while(1){
        fgets(buf, N, stdin);   
        if(strcmp(buf, "quit\n") == 0)
            break;
        write(pfd, buf, N);
    }
    close(pfd);
    return 0;
}


/* read_myfifo.c */
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define N 32

int main(){
    char buf[N];
    int pfd;
    if((pfd = open("myfifo", O_RDONLY)) < 0){
        perror("open");
        return -1;
    }
    printf("myfifo is opened\n");

    while(read(pfd, buf, N) > 0){
        printf("the length of string is %ld\n", strlen(buf));
    }
    close(pfd);
    return 0;
}

/* 输出结果 */
book@100ask:~/yike$ ll
total 56
drwxrwxr-x  2 book book 4096 Sep  5 01:14 ./
drwxr-xr-x 26 book book 4096 Sep  5 01:15 ../
-rwxrwxr-x  1 book book 8400 Sep  5 01:07 create_myfifo*
-rw-rw-r--  1 book book  310 Sep  5 01:07 create_myfifo.c
prw-rw-r--  1 book book    0 Sep  5 01:17 myfifo|    
                            //有名管道文件,大小一直为0,因为其保存在内存中,
-rwxrwxr-x  1 book book 8616 Sep  5 01:12 read_myfifo*
-rw-rw-r--  1 book book  439 Sep  5 01:02 read_myfifo.c
-rwxrwxr-x  1 book book 8664 Sep  5 01:14 write_myfifo*
-rw-rw-r--  1 book book  499 Sep  5 01:14 write_myfifo.c

    

3.3 信号

  • 信号是在软件层上对中断机制的一种模拟,是一种异步通信方式(一个进程在任何情况下都能接收信号)
  • Linux内核通过信号通知用户进程,不同的信号类型代表不同的事件(kill -l  可查看信号类型)
  • 进程对信号有不同的响应方式(缺省方式、忽略信号、捕捉信号)

信号相关指令:

#include <unistd.h>
#include <signal.h>

int kill(pid_t pid, int sig);
                    pid: 进程号,等于0时代表同组进程,等于-1时代表所有进程
                    sig:信号类型

int raise(int sig);
                    自己给自己发信号,所以不需要指定pid

int alarm(unsigned int seconds);
                    创建定时器,时间到了就发送信号SIGSLARM
                    Linux下,一个程序只能有一个定时器,设定了新的,原先的就会失效,返回上个定时器                    
                    的剩余时间

int pause(void);
                    让进程一直阻塞,直到等到信号

sighandler_t signal(int signum, sighandler_t handler);
                    这个函数不会发送任何信号,它只是将一个信号量和信号处理函数关联起来
                    成功返回原先的信号处理函数,失败返回SIG_ERR
                    这个函数具体可以看看韦东山的驱动-异步通知部分

3.4 System V IPC

  • IPC包括:共享内存、消息队列、信号灯集
  • 每个IPC对象有唯一的ID(只有创建该IPC的进程知道,其他进程不知道)
  • IPC对象创建后一直存在,直到被显示的删除
  • 每个IPC对象有一个关联的KEY(允许其他进程通过KEY获取该IPC的ID)
  • yike@yike-virtual-machine:~/yike$ ipcs   查看系统所有IPC对象
    
    ------ Message Queues --------            消息队列
    key        msqid      owner      perms      used-bytes   messages    
    
    ------ Shared Memory Segments --------    共享内存
    key        shmid      owner      perms      bytes      nattch     status      
    0x00000000 6          yike       600        524288     2          dest  
           
    key = 0x00000000表示该IPC为私有属性,不会被其他进程所获得,如果key非零,那其与shmid是一一对应的
    其他进程就可以通过key值获得与其对应的唯一id
    
    ------ Semaphore Arrays --------          信号灯集
    key        semid      owner      perms      nsems  
    
    
    
    yike@yike-virtual-machine:~/yike$ ipcrm   用于删除某IPC对象
    
    

key值通过ftok创建,再由key值创建IPC,然后根据id进行相应的操作。通常要利用IPC进行进程间通信,所以要指定非零的key值。若key值设为IPC_PRIVATE(也就是0x00000000),表明其为私有属性,其他进程不可以访问。


NAME
       ftok - convert a pathname and a project identifier to a System V IPC key

SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>

       key_t ftok(const char *pathname, int proj_id);
                                                  成功返回合法的key值
                                                  path 存在且可访问的文件路径
                                                  proj_id 用于生成key的数字,不能为0

3.4.1 共享内存

  • 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存而不需要任何数据的拷贝
  • 共享内存在内核空间创建,被进程映射到用户空间后可以直接读写操作
  • 由于共享内存可被多个进程同时访问,所以需要配合同步互斥机制
共享内存创建

SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmget(key_t key, size_t size, int shmflg);
                                              成功返回共享内存的id
                                              shmflg 共享内存标志位 IPC_CREAT | 0666


示例1:
int shmid;

if((shmid = shmget(IPC_PRIVATE, 512, 0666)) < 0){
    perror("shmget");        //创建私有共享内存,大小512字节、权限0666
    return -1;
}     

示例2:
/* 创建并打开一个和key关联的共享内存,大小1024字节、权限0666 */
int shmid;
key_t key;

if((key = ftok(".", 'm')) == -1){
    perror("ftok");      //相关的进程里都要调用ftok,并且指定相同的参数,这样才能得到相同的key
    return -1;
}

if((shmid = shmget(key, 1024, IPC_CREAT | 0666)) < 0){
    perror("shmget");
    return -1;
}
共享内存映射

SYNOPSIS
       #include <sys/types.h>
       #include <sys/shm.h>

       void *shmat(int shmid, const void *shmaddr, int shmflg);
                                        成功返回映射后的地址
                                        shmid   要映射的共享内存id
                                        shmaddr 映射后的地址,NULL表示由系统自动映射(推荐)
                                        shmflg  0表示可读写,SHM_RDONLY表示只读


共享内存读写
通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型

示例:
在共享内存中存放键盘输入的字符串
char *addr;
int shmid;
......
if((shmid = (char *)shmat(shmid, NULL, 0)) == (char *)-1){
    perror("shmat");
    return -1;
}

fgets(addr, N, stdin);
......
共享内存撤销映射
SYNOPSIS
       #include <sys/types.h>
       #include <sys/shm.h>
        
       int shmdt(const void *shmaddr);    
                                shmaddr就是前面映射函数中的首地址
                                不使用共享内存时需要撤销映射,及时进程结束时系统会自动撤销


共享内存控制
SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmctl(int shmid, int cmd, struct shmid_ds *buf);
                   成功返回0
                   cmd 需要执行的操作 IPC_STAT           IPC_SET              IPC_RMID
                                    获取共享内存的属性   设置共享内存的属性     删除共享内存id 
                   buf 保存或设置共享内存属性的地址

3.4.2 消息队列

  • 消息队列由消息队列ID唯一标识
  • 消息队列可以按照类型发送/接收消息

消息格式:

首成员类型必须为long(正整数),其他成员都属于消息正文

typedef struct {
    long mtype;
    char mtext[64];
}MSG;

#define MSGLEN (sizeof(MSG) - sizeof(long))        //这才是消息的长度

int main()
{
    ......
    MSG msgBuf;
    msgBuf.mtype = 100;    //填充消息的类型,得是个正整数
    fgets(msgBuf.mtext, 64, stdin);    //直接键盘输入到msgBuf的text中
    msgsnd(msgid,&msgBuf, LEN, 0);    //发送消息到消息队列中
    ......
    return 0;
}
消息队列创建
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

       int msgget(key_t key, int msgflg);


消息队列发送/接收消息
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
                                       msgp 要发送的消息地址
                                       msgsz 消息正文长度
                                       msgflg 0表示发送成功再返回(消息队列满了会等待,直到发                    
       送成功,阻塞方式),IPC_NOWAIT(消息队列满了会返回-1,并设置错误号)

       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
                        返回收到的消息的长度
                        msgp 消息缓冲区(结构体)地址
                        msgtyp 指定接收的消息类型
                        msgflg 0 或者 IPC_NOWAIT



消息队列控制
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

       int msgctl(int msqid, int cmd, struct msqid_ds *buf);
示例:
两进程通过消息队列轮流将键盘输入的字符串发送给对方,接手并打印对方发送的消息

/* clientA.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>                                                                                                                                    

typedef struct {
    long mtype;
    char mtext[64];
}MSG;

#define MSGLEN (sizeof(MSG) - sizeof(long))
#define typeA 100
#define typeB 200


int main()
{
    key_t key;
    int msgid;
    MSG msgBuf;

    if((key = ftok(".", 'q')) == -1){
        perror("ftok");
        return -1;
    }

    if((msgid = msgget(key, IPC_CREAT | 0666)) < 0){
        perror("msgget");
        return 0;
    }

    while(1){
        msgBuf.mtype = typeB;
        printf("input > ");
        fgets(msgBuf.mtext, 64, stdin);
        msgsnd(msgid, &msgBuf, MSGLEN, 0);

        if(strcmp(msgBuf.mtext, "quit\n") == 0) break;

        if(msgrcv(msgid, &msgBuf, MSGLEN, typeA, 0) < 0){
            perror("msgrcv");
            return -1;
        }

        if(strcmp(msgBuf.mtext, "quit\n") == 0){
            msgctl(msgid, IPC_RMID, 0);
            return 0;
        }
        printf("recv from clientB : %s\n", msgBuf.mtext);
    }

    return 0;
}




/* clientB.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>    
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

typedef struct {
    long mtype;
    char mtext[64];
}MSG;

#define MSGLEN (sizeof(MSG) - sizeof(long))
#define typeA 100
#define typeB 200


int main()
{
    key_t key;
    int msgid;
    MSG msgBuf;

    if((key = ftok(".", 'q')) == -1){
        perror("ftok");
        return -1; 
    }   

    if((msgid = msgget(key, IPC_CREAT | 0666)) < 0){ 
        perror("msgget");
        return 0;
    }   

    while(1){
        if(msgrcv(msgid, &msgBuf, MSGLEN, typeB, 0) < 0){ 
            perror("msgrcv");
            return -1; 
        }

        if(strcmp(msgBuf.mtext, "quit\n") == 0){ 
            msgctl(msgid, IPC_RMID, 0); 
            return -1;                                                                                                           
        }
        printf("recv from clientA : %s\n", msgBuf.mtext);

        msgBuf.mtype = typeA;
        printf("input > ");
        fgets(msgBuf.mtext, 64, stdin);
        msgsnd(msgid, &msgBuf, MSGLEN, 0); 

        if(strcmp(msgBuf.mtext, "quit\n") == 0) break;
    }   

    return 0;
}    


改进思路:
通过多线程实现同时收发消息

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值