Linux 下 IPC 的運作方式

IPC溝通的有許多方式,例如:Shared Memory, Message Queue, PIPE, FIFO, Unix Socket 等等。 下面將會整理 Linux 下常見的 IPC 的運作方式舉例。

一、Shared Memory

主要有四個 API:

  • int shmget(key_t key, size_t size, int shmflg);
  • int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • void shmat(int shmid, const void shmaddr, int shmflg);
  • int shmdt(const void *shmaddr);

用法說明:

  • 使用函數 key_t ftok(char *name, int id) 取得一個在此係統內唯一的值,假設為 vKey。
  • 使用函數 vShmId = shmget(vKey, 1024, 0600)取得一個大小為1024bytes, 權限為 0600(read+write) 的Shared Memory 。
  • 使用函數 pShmPtr = shmat(vShmId,0,0) 取得 Shared Memory 對應的指標,以供後續操作。
  • 當不需要使用時,使用 shmctl(vShmId, IPC_RMID, 0) 移除此 Shared Memory 。

舉例: 請參考 http://www.cs.cf.ac.uk/Dave/C/node27.html

  • shm_server.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

#define SHMSZ     27

main()
{
    char c;
    int shmid;
    key_t key;
    char* shm, *s;

    /*
     * We'll name our shared memory segment
     * "5678".
     */
    key = 5678;

    /*
     * Create the segment.
     */
    if ((shmid = shmget(key, SHMSZ, IPC_CREAT | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }

    /*
     * Now we attach the segment to our data space.
     */
    if ((shm = shmat(shmid, NULL, 0)) == (char*) - 1) {
        perror("shmat");
        exit(1);
    }

    /*
     * Now put some things into the memory for the
     * other process to read.
     */
    s = shm;

    for (c = 'a'; c <= 'z'; c++) {
        *s++ = c;
    }

    *s = NULL;

    /*
     * Finally, we wait until the other process
     * changes the first character of our memory
     * to '*', indicating that it has read what
     * we put there.
     */
    while (*shm != '*') {
        sleep(1);
    }

    exit(0);
}
  • shm_client.c
/*
 * shm-client - client program to demonstrate shared memory.
 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

#define SHMSZ     27

main()
{
    int shmid;
    key_t key;
    char* shm, *s;

    /*
     * We need to get the segment named
     * "5678", created by the server.
     */
    key = 5678;

    /*
     * Locate the segment.
     */
    if ((shmid = shmget(key, SHMSZ, 0666)) < 0) {
        perror("shmget");
        exit(1);
    }

    /*
     * Now we attach the segment to our data space.
     */
    if ((shm = shmat(shmid, NULL, 0)) == (char*) - 1) {
        perror("shmat");
        exit(1);
    }

    /*
     * Now read what the server put in the memory.
     */
    for (s = shm; *s != NULL; s++) {
        putchar(*s);
    }

    putchar('\n');

    /*
     * Finally, change the first character of the
     * segment to '*', indicating we have read
     * the segment.
     */
    *shm = '*';

    exit(0);
}

二、Message Queue 的基本用法

參考 http://tldp.org/LDP/lpg/node27.html,主要有四個API

  • int msgget ( key_t key, int msgflg );
  • int msgsnd ( int msqid, struct msgbuf *msgp, int msgsz, int msgflg );
  • int msgrcv ( int msqid, struct msgbuf *msgp, int msgsz, long mtype, int msgflg );
  • int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );

用法說明:

  • 使用函數 key_t ftok(char *name, int id) 取得一個在此係統內唯一的值,假設為 vKey。當然這個值也可以自行指定。
  • 使用函數 vQueueId = msgget( vKey, IPC_CREAT | 0660 ) 取得此 message queue 對應的 ID。
  • 定義將傳送的 message 資料結構,假設是 tMsgBuf vxMsgBuf。此處需注意的是第一個欄位需定義為 long mtype, 舉例如下:
struct msgbuf
{
  long mtype;
  char mtext[1];
};
  • 使用函數 msgsnd(vQueueId, &vxMsgBuf, sizeof(tMsgBuf), 0) 將資料送往 message queue。
  • 使用函數 msgrcv(vQueueId, &vxMsgBuf, sizeof(tMsgBuf), mtype, 0)) 取得 message queue 內, 定義為 mtype 的資料。

舉例:

#define MSG_KEY 9999
#define MSG_SIZE 1024
struct tMsgBuf
{
     long mtype;
     char mtext[MSG_SIZE];
};

typedef struct tMsgBuf tMessageBuffer; 

main()
{
     tMessageBuffer  sndmsg={0} ;
     tMessageBuffer  recvmsg={0}  ;
     msqid = msgget(MSG_KEY , PERMS | IPC_CREAT));
     msgsnd(msqid, &sndmsg, MSG_SIZE, 0);
     msgrcv(msqid, &recvmsg, MSG_SIZE, 0, 0)
}

三、PIPE

只需要一個 API,但是其只適用於 parent process 與 child process int pipe(int fd[2]); 用法說明:

  • 使用 pipe() 建立兩個 file descripter,第一個 FD 用來從PIPE讀取資料,第二個FD 用來作為寫入資料至PIPE,如下例:
#include<unistd.h>
int fds[2];
int pipe(fds);

四、FIFO (Named Pipe)

使用以下幾個 API

int mkfifo(const char *pathname, mode_t mode);
int open(const char *pathname, int flags);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
int close(int fd);
int unlink(const char *pathname);

用法說明:

1. 定義 FIFO 對應的檔案

int client_to_server;
char *myfifo = "/tmp/client_to_server_fifo";
int server_to_client;
char *myfifo2 = "/tmp/server_to_client_fifo";

2. 使用 mknod 或 mkfifo 建立 FIFO 對應的檔案

mkfifo(myfifo, 0666);
mkfifo(myfifo2, 0666);

3. 開啟檔案以供讀取或寫入

client_to_server = open(myfifo, O_RDONLY);
server_to_client = open(myfifo2, O_WRONLY);

4. 寫入資料,或讀取資料

read(client_to_server, buf, BUFSIZ);
write(server_to_client,buf,BUFSIZ);

5. 關閉檔案,並刪除 FIFO 檔案

close(client_to_server);
close(server_to_client);
unlink(myfifo);
unlink(myfifo2);

舉例: 請參考 http://stackoverflow.com/questions/8611035/proper-fifo-client-server-connection

五、Unix Socket

此處socket應用與一般撰寫網路應用相同,只是此時改成使用AF_UNIX address family,設定的資訊由 sockaddr_in 的 port, address 改為 sockaddr_un 的 path 可能會用到的 API 列舉如下:

sockfd = socket(int socket_family, int socket_type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int send(int s, const char *msg, int len, int flags),
int recv(int s, char *buf, int len, int flags)
int close(int fd);
  • server.c
/* server.c */

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>

int port = 8111;

int main(void)
{
    struct sockaddr_in    sin;
    struct sockaddr_in    pin;
    int   mysock;
    int   tempsock;
    int   addrsize;
    char  str[100], str1[20], str2[20], str3[20];
    char  buf[100];
    int   i, len1, len2;
    float c;

    mysock = socket(AF_INET, SOCK_STREAM, 0);

    if (mysock == -1) {
        perror("call to socket");
        exit(1);
    }

    bzero(&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    sin.sin_port = htons(port);

    if (bind(mysock, (struct sockaddr*)&sin, sizeof(sin)) == -1) {
        perror("call to bind");
        exit(1);
    }

    if (listen(mysock, 20) == -1) {
        perror("call to listen");
        exit(1);
    }

    printf("Accepting connections ...\n");

    while (1) {
        tempsock = accept(mysock, (struct sockaddr*)&pin, &addrsize);

        if (tempsock == -1) {
            perror("call to accept");
            exit(1);
        }

        len1 = recv(tempsock, str, 100, 0);
        printf("\n收到字元數: %d\n", len1);
        str[len1] = 0;

        printf("received from client: %s\n", str);

        if (len1 > 0) {
            strcpy(str1, strtok(str, " "));
            printf("第 1 個字串為: %s\n", str1);
            strcpy(str2, strtok(NULL, " "));
            printf("第 2 個字串為: %s\n", str2);
            strcpy(str3, strtok(NULL, " "));
            printf("第 3 個字串為: %s\n", str3);
            c = atof(str3) * 1.05;
            sprintf(buf, "品號為 %s\n品名為 %s\n含稅價為: %.2f\n", str1, str2, c);
        }

        len2 = strlen(buf);

        if (send(tempsock, buf, len2, 0) == -1) {
            perror("call to send");
            exit(1);
        }

        close(tempsock);
    }

    return 0;
}
  • client.c
/* client.c */

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

int port = 8111;

int main(int argc, char* argv[])
{
    struct sockaddr_in    pin;
    int   mysock;
    char  buf[8192];
    char*  str = "A001 電視機 20000.00 ";

    if (argc < 2) {
        printf("使用方法: client 字串\n");
        printf("使用預設字串\n");
    } else {
        str = argv[1];
    }

    bzero(&pin, sizeof(pin));
    pin.sin_family = AF_INET;
    pin.sin_addr.s_addr = inet_addr("192.168.2.163");
    pin.sin_port = htons(port);

    mysock = socket(AF_INET, SOCK_STREAM, 0);

    if (mysock == -1) {
        perror("call to socket");
        exit(1);
    }

    if (connect(mysock, (void*)&pin, sizeof(pin)) == -1) {
        perror("call to connect");
        exit(1);
    }

    printf("Sending message %s to server ...\n", str);

    if (send(mysock, str, strlen(str), 0) == -1) {
        perror("Error in send\n");
        exit(1);
    }


    if (recv(mysock, buf, 8192, 0) == -1) {
        perror("Error in receiving\n");
        exit(1);
    }

    printf("\nResponse from server: \n\n%s\n", buf);

    close(mysock);
    return 0;
}

法說明:

請直接參考 http://learn.akae.cn/media/ch37s04.html 舉例:

請參考

http://learn.akae.cn/media/ch37s04.htmlhttp://www.cs.cf.ac.uk/Dave/C/node28.html

參考資料: