C

Message Queue [C]

PON_Z 2023. 12. 16. 16:26

- 메시지큐 IPC기법중 하나이다. Queue를 사용하여 데이터를 선입선출하며, "msgtype"에 따라서 특정 메시지 중 가장 먼저 들어온 메시지를 따로 받아올 수 도 있다. 이 메시지는 kernel에서 보관하기 때문에 프로세스가 종료되어도 사라지지 않는다. 메시지는 큐의 용량이 허용하는 한, 큐에 계속 쌓이며 읽은 메시지는 큐에서 삭제한다. LINUX(UNIX)에서는 POSIX 메시지큐를 사용한다(=System V 메시지큐의 최신버전). 본 글에서는 System V 방식의 메시지 큐를 사용할 것이다.

 

※ 메시지큐는 아래와 같은 협의된 데이터 구조를 활용한다.

struct msgbuf{
	/* 메시지 타입은 반드시 long이면서 0이상의 값 */
	long mType;
	/* 아래의 데이터의 형식과 크기는 변경 가능    */
	char mText[255];
}

 

 

- 메시지큐 관련 시스템 콜 (System V)

- System V 방식의 메시지큐를 사용하기 위해서는 아래 3개의 헤더파일이 필요하다.

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

 

1. msgget : key를 번호로 가지는 큐의 id를 리턴한다.

int msgget(key_t key, int msgflg);
/*
key : 메시지큐를 얻어올 때 사용하는 고유 key값
msgflg :
    IPC_CREATE : key에 해당하는 메시지큐가 없으면 새로 생성 후 msqid, 있으면 이미 존재하는 msqid 반환
    IPC_EXCL :
    1) IPC_EXCL만 사용 : key에 해당하는 메시지큐가 없으면 -1, 있으면 msqid 반환
    2) IPC_CREATE|IPC_EXCL : key에 해당하는 메시지큐가 없으면 새로 생성 후 msqid, 있으면 -1 반환
    => 중복된 메시지큐를 방지, 가장 많이쓰며 보통 "IPC_CREATE|IPC_EXCL|0666" 처럼 권한과 같이 사용 (타프로그램 접근 위함)
*/

 

- 성공하면 msqid ,실패하면 -1을 반환하고 errno을 설정한다.

- ERRORS :
EACCES (Permission denied): 메시지 큐에 대한 읽기 또는 쓰기 권한이 없는 경우 발생합니다.
EEXIST (File exists): IPC_CREAT 플래그와 함께 사용했는데 이미 해당 키에 대한 메시지 큐가 존재하는 경우 발생합니다.
EINVAL (Invalid argument): 잘못된 인자가 전달되었을 때 발생합니다. 예를 들어, IPC_CREATE 플래그가 없는데 식별자가 존재하지 않는 경우 등.
ENOENT (No such file or directory): 주어진 키에 해당하는 메시지 큐가 존재하지 않는 경우 발생합니다.
ENOMEM (Not enough memory): 커널 메모리 부족으로 메시지 큐를 생성할 수 없는 경우 발생합니다.

 

2. msgsnd : 큐의 id(msqid)를 인자로 받아서 큐로 메시지를 보낸다.

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
/*
msqid : msgget으로 얻어온 메시지 큐의 id
msgp : 전송할 자료 의미, 메시지큐 구조체(msgbuf) 사용
msgsz : 전송할 자료의 크기, msgbuf의 mtype를 제외한 실 데이터(mText)의 크기
msgflg:
    IPC_NOWAIT : blocking 하지않고 실패처리(-1 반환)
    => msgsnd는 default로 큐에 공간이 없을 때 block 상태가 되어 큐에 공간이 생길 때 까지 기다림
    MSG_NOERROR : 메시지의 크기가 지정된 크기보다 클 경우, 지정된 크기(msgsz)만큼만 메시지를 전송
    => 만약 이를 사용하지 않고 메시지의 크기가 지정된 크기보다 클 경우 -1 반환
*/

 

- 성공하면 0, 실패하면 -1을 반환하고 errno을 설정한다.

- ERRORS :

EACCES (Permission denied): 메시지 큐에 대한 쓰기 권한이 없는 경우 발생합니다.
EAGAIN (Try again): 메시지 큐가 가득 차서 더 이상 메시지를 수용할 수 없는 경우 발생합니다. 비동기(non-blocking) 모드에서 사용됩니다.
EIDRM (Identifier removed): 메시지 큐가 삭제되었지만 해당 식별자로 메시지를 보내려고 시도한 경우 발생합니다.
EINTR (Interrupted system call): 시그널이 도착하여 시스템 호출이 중단된 경우 발생합니다.
EINVAL (Invalid argument): msqid가 유효한 메시지 큐 식별자가 아닌 경우, msgsz가 0인 경우 등 여러 가지 경우에 발생합니다.
ENOMEM (Not enough memory): 커널 메모리 부족 또는 메시지 큐를 위한 자원이 부족한 경우 발생합니다.

 

 

3. msgrcv : 큐의 id를 인자로 받아서 메시지를 읽고, 메시지를 큐에서 제거한다.

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
/*
msqid : msgget으로 얻어온 메시지 큐의 id
msgp : 수신할 자료 의미, 메시지큐 구조체(msgbuf) 사용
msgsz : 수신할 자료의 크기, msgbuf의 mtype를 제외한 실 데이터(mText)의 크기
msgtyp :
    msgtyp > 0 : mType이 같은 첫번째 메시지 리턴
    msgtyp == 0 : mType 상관없이 큐에 첫번째로 들어온 메시지 리턴
    msgtyp < 0 : mType의 절댓값보다 작거나 같은 타입의 첫번째 메시지 리턴
msgflg :
    IPC_NOWAIT : blocking 하지않고 실패처리(-1 반환)
    => msgrcv는 default로 메시지가 비어있을 때 block 상태가 되어 메시지가 올 때까지 기다림
    MSG_NOERROR : 메시지의 크기가 지정된 크기보다 클 경우, 지정된 크기(msgsz)만큼만 메시지를 수신
    => 만약 이를 사용하지 않고 메시지의 크기가 지정된 크기보다 클 경우 -1 반환
*/

 

- 성공하면 0, 실패하면 -1을 반환하고 errno을 설정한다.

- ERRORS :

EACCES (Permission denied): 메시지 큐에 대한 읽기 권한이 없는 경우 발생합니다.
EIDRM (Identifier removed): 메시지 큐가 삭제되었지만 해당 식별자로 메시지를 받으려고 시도한 경우 발생합니다.
EINTR (Interrupted system call): 시그널이 도착하여 시스템 호출이 중단된 경우 발생합니다.
EINVAL (Invalid argument): msqid가 유효한 메시지 큐 식별자가 아닌 경우, msgsz가 음수인 경우 등 여러 가지 경우에 발생합니다.
ENOMSG (No message of the desired type): IPC_NOWAIT 플래그를 사용하였고, 큐에 기다리는 메시지가 없을 때 발생합니다.

 

 

4. msgctl : 큐의 id를 인자로 받아서 큐의 상태정보를 구하거나, 변경하거나, 아예 메시지큐를 삭제할 수 있다.

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
/*
msqid : msgget으로 얻어온 메시지큐의 id
cmd : 제어명령
    IPC_STAT : 메시지큐의 현재상태를 buf에 저장
    IPC_SET : 메시지큐의 상태를 buf값으로 변경(msg_perm, msg_qbytes만 변경 가능)
    IPC_RMID : 메시지큐를 삭제, 이때는 buf가 필요없으므로 buf를 0으로 설정
*buf : 메시지큐 정보를 받을 버퍼

버퍼구조체 :
struct msqid_ds {
       struct ipc_perm msg_perm;     /* Ownership and permissions */
       time_t          msg_stime;    /* Time of last msgsnd(2) */
       time_t          msg_rtime;    /* Time of last msgrcv(2) */
       time_t          msg_ctime;    /* Time of last change */
       unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
       msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
       msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
       pid_t           msg_lspid;    /* PID of last msgsnd(2) */
       pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
};
*/

 

- 성공하면 0, 실패하면 -1을 반환하고 errno을 설정한다.

- ERRORS:

EINVAL (Invalid argument): msqid가 유효한 메시지 큐 식별자가 아닌 경우, 또는 cmd 매개변수가 잘못된 명령을 가리키는 경우에 발생합니다.
EACCES (Permission denied): 메시지 큐에 대한 쓰기 또는 읽기 권한이 없는 경우에 발생합니다.
EPERM (Operation not permitted): 호출자에게 필요한 권한이 없는 경우에 발생합니다.

 

 

 

- 오류코드들은 "<errno.h>" 헤더 파일에 정의되어 있다. 아래처럼 perror 함수나 strerror(errno)를 사용하여 실패한 이유를 출력할 수 있다.

/* error print */
perror("msgget");
fprintf(stderr, "Error: %s\n", strerror(errno));

 

 

- 예제 프로그램

/* msgcast : 메시지큐를 생성하고, 데이터를 큐에 쓰는 프로그램 */
#include <sys/types.h>  
#include <sys/ipc.h>  
#include <sys/msg.h>  
#include <sys/stat.h>  
//구조체도 헤더파일에서 선언된 것과 겹치는 현상이 일어나서 컴파일이 되질 않습니다. 구조체 이름도 바꾸어야합니다. 
struct msgbuf 
{ 
    long msgtype; 
    char mtext[256]; 
    char myname[16]; 
    int  seq; 
}; 
 
int main() 
{ 
    key_t key_id; 
    int i; 
    struct msgbuf mybuf, rcvbuf; 
 
    key_id = msgget((key_t)1234, IPC_CREATE|IPC_EXCL|0666); 
    if (key_id == -1) 
    { 
        perror("msgget error : "); 
        exit(0); 
    } 
 
    printf("Key is %d\n", key_id); 
 
    memset(mybuf.mtext, 0x00, 256);  
    memset(mybuf.myname, 0x00, 16);  
    memcpy(mybuf.mtext, "hello world 4", 13); 
    memcpy(mybuf.myname, "yundream", 8); 
    mybuf.seq = 0; 
    i = 0; 
 
    while(1) 
    { 
        // 짝수일경우 메시지 타입이 4 
        // 홀수일경우에는 메시지 타입이 3 
        if (i % 2 == 0) 
            mybuf.msgtype = 4; 
        else  
            mybuf.msgtype = 3; 
        	mybuf.seq = i; 
 
        // 메시지를 전송한다.  
        if (msgsnd( key_id, (void *)&mybuf, sizeof(struct msgbuf), MSG_NOERROR|IPC_NOWAIT) == -1) 
        { 
            perror("msgsnd error : "); 
            exit(0); 
        }  
        printf("send %d\n", i); 
        i++; 
        sleep(1); 
    } 
 
    printf("%d \n", rcvbuf.msgtype); 
    printf("%s \n", rcvbuf.mtext); 
    printf("%s \n", rcvbuf.myname); 
    exit(0); 
}
/* msgrecv : 메시지큐에 쓴 데이터를 읽어들이는 프로그램 */
#include <sys/types.h>  
#include <sys/ipc.h>  
#include <sys/msg.h>  
#include <sys/stat.h>
//구조체도 헤더파일에서 선언된 것과 겹치는 현상이 일어나서 컴파일이 되질 않습니다. 구조체 이름도 바꾸어야합니다. 
struct msgbuf  
{ 
    long msgtype; 
    char mtext[256]; 
    char myname[16]; 
    int  seq; 
}; 
 
int main(int argc, char **argv) 
{ 
    key_t key_id; 
    struct msgbuf mybuf; 
    int msgtype;//전역변수로 빼야 됩니다. 
 
    // 아규먼트가 있을경우 msgtype 가 3인 메시지를 받아오고(홀수)  
    // 아규먼트가 없을경우 msgtype 가 4인 메시지를 받아온다(짝수)   
    if (argc == 2) 
        msgtype = 3; 
    else  
        msgtype = 4; 
 
    key_id = msgget(1234, IPC_CREATE|IPC_EXCL|0666); 
    if (key_id < 0) 
    { 
        perror("msgget error : "); 
        exit(0); 
    } 
    while(1) 
    { 
        if (msgrcv( key_id, (void *)&mybuf, sizeof(struct msgbuf), msgtype, 0) == -1) 
        { 
            perror("msgrcv error : "); 
            exit(0);     
        } 
        printf("%d\n", mybuf.seq); 
    } 
    exit(0); 
}

 

 

 

 

 

 

 

 

ref)

https://reakwon.tistory.com/97

https://www.joinc.co.kr/w/man/2/msgget

728x90

'C' 카테고리의 다른 글

시스템 프로그래밍[C] #1 파일 입출력  (0) 2023.07.31