六种进程间通信方式

管道

        管道pipe分为匿名管道和命名管道(FIFO),匿名管道用于父子进程间通信,没有实体文件;父进程fork()一个子进程,子进程可获得父进程的文件描述符fd;命名管道可用于不同进程间通信,有设备文件;匿名管道是单工通信,命名管道是双工通信;遵循数据先进先出原则。

        创建匿名管道系统调用:int pipe(int fd[2]); fd[1]用于写端,fd[0]用于读端。

        创建命名管道系统指令:mkfifo 管道名

信号

    Linux系统中可用kill -l命令查看所有信号,是异步通信机制。

$ 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

        通常用的Ctrl + c 发送的信号时(2)SIGINT,停止信号;Ctrl +  z 发送的是(20)SIGTSTP,终止信号。

        可通过kill -9 进程号 命令来终止运行中的进程。

信号量

        信号量是一个整形计数器,可用于生产者消费者模型来保护一段共享资源。

        信号量相关操作创建、打开、P操作(计数减一)、V操作(计数加一)、设值、取值,计数下限为-1进程处于等待状态。

        

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
                                (Linux-specific) */
};

int sem_create(int key)//创建信号量
{
    int semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL);
    if (semid == -1)
        ERR_EXIT("semid");
    return semid;
}

int sem_open(int key)
{
    int semid = semget(key, 0, 0);
    if (semid == -1)
        ERR_EXIT("semid");
    return semid;
}

int sem_p(int semid)
{
    struct sembuf sops = { 0,-1,0 };
    int ret = semop(semid, &sops, 1);
    if (ret == -1)
        ERR_EXIT("semop");
    return ret;
}

int sem_v(int semid)
{
    struct sembuf sops = { 0,1,0 };
    int ret = semop(semid, &sops, 1);
    if (ret == -1)
        ERR_EXIT("semop");
    return ret;
}

int sem_getval(int semid)
{
    int ret = semctl(semid, 0, GETVAL, 0);
    if (ret == -1)
        ERR_EXIT("semctl");
    return ret;
}

int sem_setval(int semid, int val)
{
    union semun su;
    su.val = val;
    int ret = semctl(semid, 0, SETVAL, su);
    if (ret == -1)
        ERR_EXIT("semctl");
    return ret;
}

        信号量与共享内存实现生产者消费者模型:

        1、进程A将数据写入共享内存后进行V操作,代表生产了一份可用资源;

        2、进程B进行V操作后从共享内存读取数据,代表消费了一份可用资源;

        设置信号量初值为0来保证进数据由进程A流入进程B,如果没有数据(资源)进程B执行P操作,信号量的值为-1,进程B进入等待状态;生产着消费者模型可很好的保护一段共享资源。

共享内存

        共享内存是物理内存的映射,不同进程操作同一块共享内存实际操作的是同一块物理内存,不是数据的拷贝,当进程A写数据到共享内存中,其他进程能立马得知,通信速度快。

        可将一块共享内存进行设计,用于不同进程间进行通信,可以很好的传输数据,并且是当数据量很大的时候。与信号量配合使用作为生产着消费者模型中的组成部分。

实现思路:把一段共享内存进行分段处理,比如说分成10段 ,首段存储头部信息不用于存储实际数据,首段作为共享内存的起始地址,首段后面的作为数据地址,用于真正的数据存储。

头部信息可包含:段数、每段的大小、写数据进段的位置(生产者)、读数据出段的位置(消费者);写和读的位置使用信号量来控制,把共享内存设计成10段,就把生产者的信号量设置初值为10,表示生产者可使用的资源有10份。每当写数据到共享内存,生产者可使用的资源(段)减一(P操作),然后进行memcpy()把数据存入共享内存,地址=初始数据地址(开始地址+1)+当前写位置*每段大小,接着更新写的位置=当前位置+1%段数,最后消费者可消费的资源(段)+1;从共享内存读数据,把生产者和消费者兑换位置即可。

 共享内存的创建:shm_id = shmget(key, blocks * block_size, IPC_CREAT | IPC_EXCL | 0666);

class CreateMyMem
{
public:
	typedef struct head
	{
		int size;
		int blocks;
		int block_size;
		int r_index;
		int w_index;
	}ShmHead_T;

	CreateMyMem(int key, int blocks, int block_size);
	char* shm_data_addr;
	int sem_pro_id;
	int sem_cus_id;
	int shm_id;
	ShmHead_T* shm_start_addr;
	void read(char* buf);
	void write(char *buf);
};

消息队列

        消息队列是保存在内核中的消息链表,用户可自定义消息的类型。发送方和接收方需要使用相同的消息类型才能正常通信,消息队列在Linux内核中,消息存放在消息队列中,遵循先进先出原则,如果消息没有及时取出来,消息就会在消息队列中累积,单个消息的最大容量为2的16次方8192字节,一个消息队列中的所有消息最多占16384字节,最多可以创建256个消息队列。消息队列中的数据需要在用户空间和内核空间来回拷贝,速度比共享内存慢。

1. 创建或打开一个队列

    int msgget(key_t key, int flag);

    key: 键 由ftok()生成

        key_t ftok(const char* path, int id);

    flag: IPC_CREAT 或 IPC_EXCL 

2. 发送消息

    int msgsnd(int msgid, const void* ptr, size_t nbyte, int flag);

    msgid: 消息队列标识

    ptr: 发送的消息,一般格式为:

        struct _msg{

            long mtype;

            char _msg[512];

            . . . 

        };

    nbyte:消息大小,一般为sizeof(struct _msg)-sizeof(long)

    flag: 0                     默认阻塞方式

            IPC_NOWAIT  非阻塞

3. 接收消息

    int msgrcv(int msgid, void* ptr, size_t nbyte, long mtype, int flag);

Socket

        socket用于不同主机之间的通信,系统调用:int socket(int domain, int type, int protocal)

        domain 参数用来指定协议族,AF_INET 用于 IPV4、AF_INET6 用于 IPV6、AF_LOCAL/AF_UNIX 用于本机;
        type 参数用来指定通信特性,SOCK_STREAM 表示的是字节流,对应 TCP;SOCK_DGRAM 表示的是数据报,对应 UDP;SOCK_RAW 表示的是原始套接字;
        protocal 参数原本是用来指定通信协议的,但现在基本废弃。因为协议已经通过前面两个参数指定完成,protocol 目前一般写成 0 即可;

        实现 TCP 字节流通信: socket 类型是 AF_INET 和 SOCK_STREAM;
        实现 UDP 数据报通信:socket 类型是 AF_INET 和 SOCK_DGRAM;
        实现本地进程间通信: 「本地字节流 socket 」类型是 AF_LOCAL 和 SOCK_STREAM,「本地数据报 socket 」类型是 AF_LOCAL 和 SOCK_DGRAM。另外,AF_UNIX 和         AF_LOCAL 是等价的,所以 AF_UNIX 也属于本地 socket;

        服务端:

        创建socket(TCP协议)之后还需要进行bind(绑定)、listen(监听)、

        进阶:epoll_create()epoll_ctl()epoll_wait()(管理socket—fd)、accept()(等待客户端连接)、epoll_ctl()(管理客户端fd)

        一般:accept()(等待客户端连接)

        之后就可对客户端fd进行读写操作;

        客户端:socket()、connect()、读写操作。

fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1)
    {
        perror("socket");
        exit(1);

    }
    //cout << "pid == " << getpid() << endl;
    //绑定端口和ip
    // int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(8888);
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);

    //让端口能重复使用(服务器关闭之后,这个端口立即使用,不需要等待
    int on = 1;
    int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    if (ret == 0)
    {
        cout << "setsockopt success!" << endl;
    }
    ret = bind(fd, (struct sockaddr*)&seraddr, sizeof(seraddr));
    if (ret == 0)
    {
        cout << "bind success!" << endl;
    }

    //监听:维护两个队列:1、已经完成三次握手的客户端的数据 2、没有完成三次握手的客户端的数组
    ret = listen(fd, SOMAXCONN);
    if (ret == 0)
    {
        cout << "listen success!" << endl;
    }

三次握手:

 1、第一次握手:客户端给服务器发送一个 SYN 报文。

 2、第二次握手:服务器收到 SYN 报文之后,会应答一个 SYN+ACK 报文。

 3、第三次握手:客户端收到 SYN+ACK 报文之后,会回应一个 ACK 报文。

 4、服务器收到 ACK 报文之后,三次握手建立完成。

      作用是为了确认双方的接收与发送能力是否正常。
四次挥手:

  1、第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。

  2、第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。

  3、第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。

  4、第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态

  5、服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。
 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小道道.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值