우당탕탕 개발일지
[C언어] IPC(2) (feat. 메세지 큐) 본문
[C언어] IPC(1) (feat. 공유 메모리)
1. IPC 프로세스는 완전히 독립된 실행 객체로 다른 프로세스에 문제가 발생하여도 영향을 받지 않는다는 장점이 있다. 그러나 독립되어 있는 만큼 서로간에 통신이 어렵다는 문제가 있다. 이를
uj0791.tistory.com
1. Pipe

파이프는 두 프로세스 사이에서 한 방향으로 통신할 수 있도록 인터페이스를 제공한다.
파이프는 이름 없는 파이프(익명 파이프, annoymous pipe)와 이름 있는 파이프(named pipe)로 구분된다. 특별한 수식어 없이 그냥 파이프라고 하면 일반적으로 이름 없는 파이프를 의미한다. 이름 없는 파이프는 부모-자식 프로세스 간에 통신할 수 있게 해준다.
✅ pipe()
#include <unistd.h>
int pipe(int fildes[2]);
- fildes[2] : 파이프로 사용할 파일 기술자(2개)
- fides[0]은 읽기 전용으로 열고, fildes[1]은 쓰기 전용으로 연다.
- 반환값: 성공시 0을, 실패시 -1을 반환하고 errno 값을 설정한다.
- 파이프를 생성하고 나면 일반적으로 fork() 함수를 호출해 자식 프로세스를 생성한다.
- 자식 프로세스는 부모 프로세스가 pipe() 함수로 생성한 파일 기술자들도 복사한다.
- 파이프는 단방향 통신이므로 통신 방향을 결정한다.
만약 파이프의 쓰기 부분이 닫혀 있다면 파이프에서 읽으려고 할 때 0이나 EOF가 리턴된다. 파이프의 읽기 부분이 닫혀 있다면 파이프에 쓰려고 할 때 SIGPIPE 시그널이 발생한다.
✅ pipe2 ()
#include <unistd.h>
struct fd_pair {
long fd[2];
};
struct fd_pair pipe();
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int pipe2(int pipefd[2], int flags);
- pipefd : 파이프의 끝을 가리키는 두 개의 파일 기술자
- flags가 0이면 pipe2()는 pipe()와 동일하다.
- pipefd[0]은 파이프의 읽기 끝, pipefd[1]는 파이프의 쓰기 끝을 나타낸다.
| FLAG | 설명 |
| O_NONBLOCK | fnctl()과 동일한 결과를 얻을 수 있다. |
| O_CLOEXEC | 두 파일 기술자에 실행 종료 FD_CLOEXEC 플래그를 설정한다. |
| O_DIRECT |
|
2. Message Queue
Queue 를 사용하기 때문에 기본적으로 FIFO 방식이다. 메세지 큐와 관련해서 메세지 큐 생성, 메세지 전송, 메세지 수신, 메세지 큐 제어 함수가 제공된다.
Message Queue vs Message Passing
Message Passing은 구현보다는 개념적으로 사용되는 용어이다. 프로세스 간 통신을 하기 위해 메세지를 어떻게 전송할지에 대한 방법론이고, 그 구체적인 방법 중 하나가 Message Queue 구현 방법이다.
Message Queue 구현을 위해서는 아래 3개의 헤더 파일이 필요하다.
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>

- Create Message
int masgget (key_t key, int msgflg);
- Send Message
int msgsnd(int msqid, struct msgbuf * msgp, size_t msgsize, int msgflg);
- Receive Message
ssize_t msgrcv(int msgid, struct msgbuf * msgp, size_t msgsize, long msgtype, int msgflg);
- Control Message
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
메세지 큐의 기본적인 흐름은 다음과 같다.
- 메세지 큐 생성
- msgget() 함수를 사용하여 큐를 생성하거나 기존 큐에 접근한다.
- 메세지 전송
- msgsnd() 함수를 사용하여 데이터를 큐에 전송한다.
- 메세지 수신
- msgrcv() 함수를 사용하여 큐에서 데이터를 수신한다.
- 메세지 큐 삭제
- 작업이 끝나면 msgctl() 함수를 사용하여 큐를 삭제하여 자원을 해제한다.
✅ msgget()
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
>> 메세지 큐 식별자를 생성한다.
- key : 메세지 큐를 구별하는 키
- IPC_PRIVATE나 ftok() 함수로 생성한 키를 지정한다.
- msgflg : 메세지 큐의 속성을 설정하는 플래그
- IPC_CREAT(0001000) : 새로운 키면 식별자를 새로 생성한다.
- IPC_EXCL(0002000) : 이미 존재하는 키면 오류가 발생한다.
- IPC_CREAT | IPC_EXCL | 0666 : 없으면 새로 생성하고, 있으면 오류를 발생시킨다.
- IPC_CREAT | 0666 : 없으면 새로 생성하고, 이미 생성되어 있으면 이미 생성된 메세지 큐 ID를 리턴한다.
- 메세지 큐 식별자 관련 메세지 큐와 IPC 구조체가 새로 생성되는 경우는 다음 두가지 이다.
- key가 IPC_PRIVATE이다.
- key가 0이 아니며 다른 식별자와 관련되어 있지 않고, 플래그에 IPC_CREAT가 설정되어 있다.
- 이 두 가지 경우가 아니면 기존 메세지 큐의 식별자를 리턴한다.
✅ 반환값
- 성공시, 메세지 큐 식별자를 음수가 아닌 정수로 리턴한다.
- 실패시, -1을 리턴한다.
| ERROR | 설명 |
| EACCES | 프로세스의 user가 접근 권한이 없습니다. |
| EEXIST | msgflg IPC_CREAT | IPC_EXCL option이 추가되었으며, key로 이미 생성된 message queue가 존재합니다. |
| ENOENT | IPC_CREAT가 msgflg에 설정되지 않았고, key로 생성된 message queue가 존재하지 않습니다.] |
| ENOMEM | 메모리가 부족합니다. |
| ENOSPC | System에서 정의한 최대 message queues 갯수(SHMMNI)에 도달하였습니다. |
✅ 메세지 큐 구조체
/* <sys/msg.h> */
struct msqid_ds {
struct ipc_perm msg_perm; /* 권한 */
struct msg* msg_first; /* 마지막으로 msgsnd(2)를 호출한 시간 */
struct msg* msg_last; /* ptr to last message on queue */
msglen_t msg_cbytes; /* current #bytes on queue */
msgqnum_t msg_qnum; /* 큐에 있는 메시지의 갯수 */
msglen_t msg_qbytes; /* 큐의 최대 허용 크기 - byte(:12) */
pid_t msg_lspid; /* 마지막으로 msgsnd()를 호출한 프로세스(:12)의 PID */
pid_t msg_lrpid; /* 마지막으로 msgrcv()를 호출한 프로세스의 PID */
time_t msg_stime; /* 마지막으로 msgsnd(2)를 호출한 시간 */
time_t msg_rtime; /* 마지막으로 msgrcv(2)를 호출한 시간 */
time_t msg_ctime; /* 마지막으로 변경된 시간 */
};
✅ msgsnd()
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
>> 메세지 큐로 데이터를 전송한다.
- msqid : msgget 함수로 생성한 메세지 큐 식별자
- msgp : 메세지를 담고 있는 메세지 버퍼의 주소
- msgsz : 메세지의 크기 (0 ~ 시스템이 정한 최댓값)
- msgflg : 블록 모드(0)/비블록 모드(IPC_NOWAIT)
- 메세지 큐가 가득 찼을 때의 동작을 지정한다.
- 0이면 메세지 큐에 메세지가 올 때까지 기다린다.
- IPC_NOWAIT 이면 메세지 큐가 비었을 때 기다리지 않고 즉시 오류를 리턴한다.
msgsnd() 함수의 수행이 성공하면 msgid_ds 구조체 항목에서 msg_qnum 값이 1 감소하고, msg_lrpid는 msgrcv() 함수를 호출한 프로세스의 ID로 설정된다. msg_rtime은 현재 시간으로 설정된다.
- msgget() 함수가 리턴한 메세지 큐 msgid를 통해 크기가 msgsz인 메세지를 메세지 버퍼 msgp에 담아 전송한다.
하나의 메세지 큐 크기는 struct msqid_ds의 멤버 변수인 msg_qbytes 바이트이다. msg_qbytes 값은 default로 MSGMNB byte로 설정되며, root 권한이 있는 경우에는 msgctl()을 이용하여 변경 가능하다.
✅ 반환값
- 성공시, 읽어온 메세지의 바이트 수를 리턴한다.
- 실패시, -1을 리턴하고 메세지를 읽어오지 않는다.
| ERROR | 설명 |
| EACCES | 프로세스의 user가 접근 권한이 없습니다. |
| EEXIST | msgflg IPC_CREAT | IPC_EXCL option이 추가되었으며, key로 이미 생성된 message queue가 존재합니다. |
| ENOENT | IPC_CREAT가 msgflg에 설정되지 않았고, key로 생성된 message queue가 존재하지 않습니다.] |
| ENOMEM | 메모리가 부족합니다. |
| ENOSPC | System에서 정의한 최대 message queues 갯수(SHMMNI)에 도달하였습니다. |
✅ msgbuf 구조체
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[]; /* message data */
};
- 전송/수신하는 데이터는 위와 같은 형태로 구성해야 한다.
- 구조체 이름 및 형식은 마음대로 정의를 할 수 있다.
- mtype : 메세지 유형으로, 양수를 지정한다.
- mtext : msgsnd() 함수의 msgsz로 지정한 크기의 버퍼로, 메세지 내용이 저장된다.
첫 번째 필드는 반드시 long 타입의 변수여야 한다.
✅ msgrcv ()
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
>> 메세지 큐로 메세지를 수신하는 데 사용한다.
- msgid : msgget() 함수로 생성한 메세지 큐 식별자
- msgp : 메세지를 담고 있는 메세지 버퍼의 주소
- msgsz : 메세지 버퍼의 크기
- msgtyp : 읽어올 메세지의 유형으로 지정할 수 있는 값은 다음과 같다.
- 0 : 메세지 큐의 다음 메세지를 읽어온다.
- 양수 : 메세지 큐에서 msgtyp으로 지정한 유형과 같은 메세지를 읽어온다.
- 음수 : 메세지의 유형이 msgtyp으로 지정한 값의 절댓값과 같거나 작은 메세지를 읽어온다.
- msgflg : 블록 모드(0)/비블록 모드(IPC_NOWAIT)
- 0이면 메세지 큐에 메세지가 올 때까지 기다린다.
- IPC_NOWAIT 이면 메세지 큐가 비었을 때 기다리지 않고 즉시 오류를 리턴한다.
msgrcv() 함수의 수행이 성공하면 msgid_ds 구조체 항목에서 msg_qnum 값이 1 감소하고, msg_lrpid는 msgrcv() 함수를 호출한 프로세스의 ID로 설정된다. msg_rtime은 현재 시간으로 설정된다.
✅ 반환값
- 성공시, 읽어온 메세지의 바이트 수를 리턴한다.
- 실패시, -1을 리턴하고 메세지를 읽어오지 않는다.
| ERROR | 설명 |
| EAGAIN | msgflg가 IPC_NOWAIT이면서 Message Queue에 충분한 공간이 없어서 재시도하시오. |
| EFAULT | msgp가 유효하지 않은 메모리 번지입니다. |
| EIDRM | msqid가 이미 삭제된 ID입니다. |
| EINTR | message queue로 전달중 signal이 발생하여 전달에 실패하였습니다. |
| EINVAL | msqid가 유효하지 않거나, mtype이 양수가 아니거나, msgsz가 0보다 작거나 MSGMAX보다 큽니다. |
✅ msgctl ()
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
>> 메세지 큐를 제거하거나 상태 정보를 설정하고 읽어오는 메세지 큐에 대한 제어 기능을 수행한다.
- msqid 로 지정한 메세지 큐에서 cmd에 지정한 제어를 수행한다.
- msqid : msgget() 함수로 생성한 메세지 큐 식별자
- cmd : 수행할 제어 기능으로, 다음 중 하나를 지정한다.
- IPC_RMID
- msqid로 생성된 메세지 큐를 제거하고, 관련 데이터 구조체를 제거한다.
- 삭제는 삭제 권한이 있는 user가 실행했을 때만 삭제된다.
- 메세지 큐를 읽기 또는 쓰기로 블록된 프로세스에서는 EIDRM 오류가 발생한다.
- IPC_SET
- msqid로 생성된 메세지 큐 정보 중에서 msg_perm.uid, msg_perm.gid, msg_perm.mode, msg_ctime 값을 세 번째 인자로 지정한 값으로 바꾼다.
- 이 명령은 root 권한이 있거나 유효 사용자 ID인 경우만 사용할 수 있다.
- msg_qbytes는 root 권한이 있어야 변경할 수 있다.
- IPC_STAT
- 현재 메세지 큐의 정보를 buf로 지정한 메모리에 저장한다.
- IPC_RMID
- buf : 제어 기능에 사용되는 메세지 큐 구조체의 주소
- cmd의 종류에 따라 제어값을 지정하거나 읽어오는 데 사용한다.
✅ 반환값
- 성공시 0을, 실패시 -1을 리턴하고 errno 값이 설정된다.
- 실패시, -1을 리턴하고 메세지를 읽어오지 않는다.
| ERROR | 설명 |
| E2BIG | msgflg가 MSG_NOERROR로 설정되지 않았으며, 읽으려는 message가 msgsz)보다 큽니다. |
| EACCES | Message queue에 대한 읽기 권한이 없습니다. |
| EAGAIN | msgflg가 IPC_NOWAIT이면서 Message Queue에 쌓인 Message가 없습니다. |
| EFAULT | msgp가 유효하지 않은 메모리 번지입니다. |
| EIDRM | msqid가 이미 삭제된 ID입니다. |
| EINTR | message queue로 전달중 signal이 발생하여 전달에 실패하였습니다. |
| EINVAL | msqid가 유효하지 않거나, msgsz가 0보다 작습니다. |
| ENOMSG | msgflg가 IPC_NOWAIT로 설정되어 있으며 요청한 message type의 message가 없습니다. |
🔷 주소록 프로그램
- 기존 주소록 프로그램에서 멀티 스레드 → 멀티 프로세스로 변경
- 두 프로세스간 통신을 위해 Message Queue를 사용하도록 변경
- 주소록 프로그램은 다음과 같은 형태로 실행을 한다.
- 프로그램명 [display]
- 프로그램명만 입력된 경우, 사용자로부터 주소록 정보를 받아서 파일에 저장하도록 함
- display가 옵션으로 주어진 경우 주소록 파일 정보를 읽어서 화면에 출력하도록 해야 함
- 주소록 프로그램이 주소록 정보를 저장하는 경우에는 사용자로 부터 주소록 정보를 입력받는 메인 스레드와 파일에 주소록 정보를 쓰는 파일 Write 스레드 2개로 구성되어야 하고 주소록 프그램이 주소록 정보를 출력하는 경우에는 파일에서 읽는 메인 스레드와 화면에 정보를 출력하는 Display 스레드 2개로 구성되어야 한다.
- 두 스레드간에는 Queue 자료 구조를 통해 데이터를 주고 받는다. Queue 자료 구조는 직접 코딩한다.
- Thread간 데이터를 송수신하기 위한 Queue는 세마포어와 mutex lock을 사용하여 구현
- 프로그램명 [display]
🔹 입력
1. 주소록 정보로 입력받는 데이터는 이름, 전화번호, 주소이다.
- 이름은 한글로 최대 4글자
- 전화번호는 xxx-xxxx-xxxx 형태로 입력받는다.
- 주소는 한글로 최대 50자 입력받는다.
2.이름에 exit를 입력할 때까지 주소록 입력을 반복한다.
3.입력한 정보가 잘못된 경우, 에러 문구를 출력하고 이름부터 다시 입력받는다.
🔹 출력
- 주소 정보는 다음과 같이 출력하도록 한다.
——————————————————————————
1 - 김민
——————————————————————————-
전화번호: 010-8821-1234
주소: 서울 서초구 법원로3길 20-7
——————————————————————————-
2 - 이지민
——————————————————————————-
전화번호: 010-8821-1234
주소: 서울 서초구 법원로3길 20-7
——————————————————————————-
전체: 2명
——————————————————————————-
[C언어] 메모리 (feat. 주소록 프로그램)
리눅스 가상 메모리 구조 가상 메모리는 실제 각 프로세스마다 충분한 메모리를 할당하기에는 메모리 크기의 한계가 있어, 디스크(ROM)의 일부를 확장된 RAM처럼 사용하기 위한 개념이다.실제 물
uj0791.tistory.com
[ receiver_message.c ]
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>
#define MAX_NAME 13
#define MAX_PHONE_NUMBER 14
#define MAX_ADDRESS 151
#define KEY_FILE "mq_keyfile"
#define ADDRESS_DATA "address_data"
#define PROJ_ID 1234
#define MESSAGE_TYPE 1
typedef struct address_info_t {
char name[MAX_NAME];
char phone_number[MAX_PHONE_NUMBER];
char address[MAX_ADDRESS];
} address_info_t;
typedef struct message_t {
long mtype;
address_info_t address_info;
} message_t;
volatile int rcv_terminate_flag = 0;
#define CLOSE_FILE(fp) \
do { \
if ((fp) != NULL) { \
if (fclose(fp) == EOF) { \
printf("[ERROR] fclose: %s\n", strerror(errno)); \
} \
(fp) = NULL; \
} \
} while (0)
int init_mqid(int *mqid);
int receive_data (int mqid, message_t *mesg);
int write_data(address_info_t *address_info);
int main(void) {
int mqid;
if(init_mqid(&mqid) < 0) {
printf("[ERROR] failed init mqid\n");
return -1;
}
while(!rcv_terminate_flag) {
message_t mesg;
printf("수신 대기중 . . .\n");
if(receive_data(mqid, &mesg) < 0) {
printf("[ERROR] failed receive data\n");
return -1;
}
printf("수신 메세지 처리중 . . .\n");
if(strcmp(mesg.address_info.name, "exit") == 0) {
break;
}
if(write_data(&mesg.address_info) < 0) {
printf("[ERROR] failed write data\n");
return -1;
}
}
if(msgctl(mqid, IPC_RMID, NULL) < 0) {
perror("[ERROR] msgctl");
return -1;
}
return 0;
}
int init_mqid(int *mqid) {
if(mqid == NULL) {
printf("[ERROR] init_mqid: mqid is NULL\n");
return -1;
}
key_t key;
if((key = ftok(KEY_FILE, PROJ_ID)) == -1) {
perror("[ERROR] ftok");
return -1;
}
if((*mqid = msgget(key, IPC_CREAT | 0666)) == -1) {
perror("[ERROR msgget");
return -1;
}
return 0;
}
int receive_data (int mqid, message_t *mesg) {
if(mqid < 0) {
printf("[ERROR] receive_data: mqid is wrong\n");
return -1;
}
if(mesg == NULL) {
printf("[ERROR] receive_data: mesg is NULL\n");
return -1;
}
if(msgrcv(mqid, mesg, sizeof(mesg->address_info), MESSAGE_TYPE, 0) == -1) {
perror("[ERROR] msgrcv");
return -1;
}
return 0;
}
int write_data(address_info_t *address_info) {
FILE *wfp;
if((wfp = fopen(ADDRESS_DATA, "a")) == NULL) {
printf("[ERROR] failed: %s\n");
return -1;
}
if(strcmp(address_info->name, "exit") == 0) {
return 0;
}
if(fwrite(address_info, sizeof(address_info_t), 1, wfp) != 1) {
printf("[ERROR] %s\n", strerror(errno));
CLOSE_FILE(wfp);
return -1;
}
CLOSE_FILE(wfp);
return 0;
}
[ sender_message.c ]
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include "line.h"
#include "is_korean.h"
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>
#define MAX_NAME 13
#define MAX_PHONE_NUMBER 14
#define MAX_ADDRESS 151
#define KEY_FILE "mq_keyfile"
#define ADDRESS_DATA "address_data"
#define PROJ_ID 1234
#define MESSAGE_TYPE 1
#define CLOSE_FILE(fp) \
do { \
if ((fp) != NULL) { \
if (fclose(fp) == EOF) { \
printf("[ERROR] fclose: %s\n", strerror(errno)); \
} \
(fp) = NULL; \
} \
} while (0)
typedef struct address_info_t {
char name[MAX_NAME];
char phone_number[MAX_PHONE_NUMBER];
char address[MAX_ADDRESS];
} address_info_t;
typedef struct message_t {
long mtype;
address_info_t address_info;
} message_t;
volatile int send_terminate_flag = 0;
int init_mqid(int *mqid);
int send_data(address_info_t *address_info, int mqid);
int input_address(address_info_t *address_info);
int check_phone_number(char *number);
int reduce_space(char *address);
int main() {
int mqid;
if(init_mqid(&mqid) < 0){
printf("[ERROR] failed init mqid\n");
return -1;
}
while(!send_terminate_flag) {
address_info_t address_info;
if(input_address(&address_info) < 0) {
break;
}
if(strcmp(address_info.name, "exit") == 0) {
if(send_data(&address_info, mqid) < 0) {
return -1;
}
break;
}
if(send_data(&address_info, mqid) < 0) {
break;
}
}
return 0;
}
int init_mqid(int *mqid) {
if(mqid == NULL) {
printf("[ERROR] init_mqid: mqid is NULL\n");
return -1;
}
FILE *fp = fopen(KEY_FILE, "a+");
if(fp == NULL) {
perror("[ERROR] fopen");
return -1;
}
CLOSE_FILE(fp);
key_t key;
if((key = ftok(KEY_FILE, PROJ_ID)) == -1) {
perror("[ERROR] ftok");
return -1;
}
if((*mqid = msgget(key, IPC_CREAT|0666)) == -1) {
perror("[ERROR msgget");
return -1;
}
return 0;
}
int send_data(address_info_t *address_info, int mqid) {
if(address_info == NULL) {
printf("[ERROR] send_data: address_info is NULL\n");
return -1;
}
if(mqid < 0) {
printf("[ERROR] send_data: mqid is out of range\n");
return -1;
}
if(sizeof(address_info) > sizeof(address_info_t)) {
printf("[ERROR] message size is too large\n");
return -1;
}
message_t mesg;
mesg.mtype = MESSAGE_TYPE;
mesg.address_info = *address_info;
if(msgsnd(mqid, &mesg, sizeof(mesg.address_info), 0) == -1) {
perror("[ERROR] msgrcv");
return -1;
}
return 0;
}
#define CHECK_DASH(buf, idx) \
if(buf[idx] != '-') { \
printf("[%d]%c is not '-'\n", (idx)+1, buf[(idx)]) ; \
return -1; \
}
#define CHECK_NUM(buf, idx) \
if ( (buf[(idx)]) < '0' || (buf[(idx)]) > '9' ) { \
printf("[%d]%c is not number\n", (idx)+1, buf[(idx)]) ; \
return -1 ;\
}
int input_address(address_info_t *info) {
while(!send_terminate_flag) {
printf("Name: ");
char name[MAX_NAME];
if(line(name, sizeof(name)) == -1) {
printf("[ERROR] failed input name\n");
continue;
}
if(strcmp(name, "exit") == 0) {
strcpy(info->name, name);
return 0;
}
if(is_korean(name) < 0) {
printf("[ERROR] failed check if name is korean\n");
continue;
}
printf("Phone Num: ");
char number[MAX_PHONE_NUMBER];
if(line(number, sizeof(number)) == -1) {
printf("[ERROR] failed input phone number\n");
continue;
}
if(check_phone_number(number) < 0) {
printf("[ERROR] failed check phone number format\n");
continue;
}
printf("Address: ");
char address[MAX_ADDRESS];
if(line(address, sizeof(address)) == -1) {
printf("[ERROR] failed input address\n");
continue;
}
if(reduce_space(address) < 0) {
printf("[ERROR] failed reduce address space\n");
continue;
}
strcpy(info->name, name);
strcpy(info->phone_number, number);
strcpy(info->address, address);
return 0;
}
}
int check_phone_number(char *number) {
if(number == NULL) {
printf("[ERROR] phone number must not be NULL\n");
return -1;
}
if(strncmp(number, "010", 3) != 0) {
printf("[ERROR] phone number must start with 010\n");
return -1;
}
int idx = 3;
CHECK_DASH(number, idx);
for(idx += 1; idx < 8; idx++) {
CHECK_NUM(number, idx);
}
CHECK_DASH(number, idx);
for(idx += 1; idx < 13; idx++) {
CHECK_NUM(number, idx);
}
return 0;
}
int reduce_space(char *address) {
if(address == NULL) {
printf("[ERROR] address must not be NULL\n");
return -1;
}
int space = 0, i, idx = 0;
int len = strlen(address);
char temp[len];
for(i = 0; i < len; i++) {
if(address[i] == ' ') {
if(!space) {
temp[idx] = address[i];
space = 1;
}
}
else {
if(space) space = 0;
temp[idx++] = address[i];
}
}
temp[idx] = 0;
strcpy(address, temp);
return 0;
}
[ line.c ]
#include <stdio.h>
#include <string.h>
int line(char *buf, int buf_len) {
char ch;
int len = 0;
if(buf == NULL) {
printf("buf is NULL\n");
return -1;
}
if(buf_len < 1) {
puts("buf_len is too small\n");
return -1;
}
while(1) {
ch = getchar();
if(ch == EOF) {
return -1;
}
if(ch == '\n') {
break;
}
if(len < buf_len - 1) {
buf[len++] = ch;
}
}
buf[len] = '\0';
return len;
}
[ korean.c ]
#include "is_korean.h"
int is_korean(char *name) {
if(name == NULL) {
printf("이름을 입력해주세요.\n");
return -1;
}
int i = 0, len = 0;
while(name[i] != '\0') {
int first = (unsigned char)name[i];
int second = (unsigned char)name[i + 1];
int third = (unsigned char)name[i + 2];
// UTF-8
if(first >= 0xEA && first <= 0xED) { // ㄱ - ㅎ
if(second < 0x80 || second > 0xBF || third < 0x80 || third > 0xBF) {
printf("이름은 한글만 입력해주세요.\n");
return -1;
}
i += 3;
len++;
if(len >= NAME_BUF) {
printf("이름은 4글자 이내로 입력해주세요.\n");
return -1;
}
} else {
printf("이름은 한글만 입력해주세요.\n");
return -1;
}
}
return 0;
}
헤더 파일은 생략하였다.
[ Makefile ]
C = gcc
TARGETS = receiver_message sender_message receiver_shared sender_shared
RCV_MESSENGER = receiver_message.o
SEND_MESSENGER = is_korean.o line.o sender_message.o
RCV_SHARED = receiver_shared.o
SEND_SHARED = is_korean.o line.o sender_shared.o
all: $(TARGETS)
receiver_shared: $(RCV_SHARED)
$(CC) -o receiver_shared $(RCV_SHARED)
sender_shared: $(SEND_SHARED)
$(CC) -o sender_shared $(SEND_SHARED)
receiver_message: $(RCV_MESSENGER)
$(CC) -o receiver_message $(RCV_MESSENGER)
sender_message: $(SEND_MESSENGER)
$(CC) -o sender_message $(SEND_MESSENGER)
is_korean.o : is_korean.c is_korean.h
$(CC) -c is_korean.c
line.o : line.c line.h
$(CC) -c line.c
receiver_message.o : receiver_message.c
$(CC) -c receiver_message.c
sender_message.o : sender_message.c is_korean.h line.h
$(CC) -c sender_message.c
receiver_shared.o : receiver_shared.c
$(CC) -c receiver_shared.c
sender_shared.o : sender_shared.c is_korean.h line.h
$(CC) -c sender_shared.c
clean:
rm -f *.o $(TARGETS)'Server > Linux, C' 카테고리의 다른 글
| [C언어] IPC(1) (feat. 공유 메모리) (0) | 2025.10.19 |
|---|---|
| [C언어] 시그널 (feat. Sigaction) (0) | 2025.09.30 |
| [C언어] 메모리 (feat. 주소록 프로그램) (0) | 2025.09.17 |
| [C언어] 동기화 API (feat. Rwlock API 사용해 은행 잔고 프로그램 구현하기) (2) | 2025.09.12 |
| [C언어] 동기화 (feat. Mutex API 사용해 은행 잔고 프로그램 구현하기) (0) | 2025.09.12 |