메뉴

문서정보

목차

IPC

IPC 소개

위의 그림은 2장에서 간단히 설명했던 리눅스 커널 구조그림이다. 이번장에는 이중 IPC 에 대해서 다룰 것이다.

그림에서 처럼 Process는 완전히 독립된 실행객체이다. 서로 독립되어있다는 것은 다른 프로세스(:12)의 영향을 받지 않는다는 장점이 있다. 그러나 독립되어 있으니 만큼 별도의 설비가 없이는 서로간에 통신이 어렵다는 문제가 있다. 이 문제는 이 전장의 쓰레드(:12)와 비교해보면 두드러진다.

이를 위해서 커널영역에서 IPC(:12)라는 내부 프로 세스간 통신 - Inter Process Communication -을 제공한다. 프로세스는 커널(:12)이 제공하는 IPC설비를 이용해서 프로세스간 통신을 할 수 있다.

System V IPC 와 POSIX IPC

IPC에는 두 가지 표준이 있다. 하나는 오래된 버전의 System V IPC이고, 다른 하나는 비교적 최근에 개발된 POSIX IPC다. System V IPC는 오랜 역사를 가진 만큼 이 기종간 코드 호환성을 확실히 보장해 주지만, API가 좀 구식이며 함수명도 명확하지 않다. POSIX IPC는 직관적으로 API가 구성되어 있어서 좀 더 사용하기 쉽다.

우선은 System V IPC를 다루고 나중에 시간이 되면 POSIX IPC를 다루도록 하겠다. 메커니즘은 동일 하므로 어느 것을 배우든 큰 문제는 되지 않을 것이다. 필요하면 System V IPC와 대응하는 POSIX IPC 함수들을 설명하도록 하겠다.

IPC 설비들

현실에서도 필요에 따라 다양한 통신 설비들이 존재하는 것처럼 IPC에도 다양한 설비들이 존재한다. 각각의 필요에 따라 적당한 통신설비들이 준비되어야 하는 것과 마찬가지로 내부 프로세스간 통신에도 그 상황에 맞는 IPC 설비를 선택할 필요가 있다.

상황에 맞는 IPC의 선택은 특히 fork(:12)를 이용해서 만들어진 멀티프로세스의 프로그램에 있어서 중요하다. 잘못된 IPC 설비의 선택은 코딩과정을 어렵게 만들거나 프로그램의 작동을 효율적이지 못하게 만들 수 있기 때문이다.

여기에서는 커널이 제공하는 각각의 IPC 설비들의 사용법과 장점및 단점 어떤 경우에 써야하는지에 대해서 설명할 것이다.

PIPE

pipe는 우리가 생각하는 그 파이프와 비슷하게 작동한다고 보면된다. 즉 수도 파이프와 같은 작동 원리다.

위 그림은 pipe(:12)의 작동원리를 보여주고 있다. 파이프는 두개의 프로세스(:12)를 연결한다. 하나의 프로세스는 단지 데이터를 쓰기만 하고 다른 하나의 프로세스는 단지 읽기만 할 수 있다. 한쪽 방향으로만 통신이 가능한 이러한 파이프의 특징 때문에 Half-duplex 통신 (반이중 통신) 라고 부르기도 한다. 이와 달리 하나의 통신선로를 이용해서 송신과 수신을 모두 할 수 있는 방식을 Full-duplex 통신 (전이중 통신)이라고 부른다.

pipe와 같은 반이중 통신의 경우 하나의 통신선로는 읽기나 쓰기 중 하나만 가능하기 때문에 만약 읽기와 쓰기 즉 송수신을 모두 하길 원한다면 아래처럼 두개의 pipe를 만들어야만 한다.

pipe의 사용

pipe는 pipe(2)라는 함수를 이용해서 만들 있다.
#include <unistd.h>

int pipe(int filedes[2]);
함수의 인자로 int형 배열이 들어가는 점에 유의하자. 배열이 넘어가는 이유는 이 함수가 읽기전용의 파이프와 쓰기전용의 파이프 2개를 리턴해 주기 때문이다. 배열의 0번째에는 읽기를 위한 파이프, 1번째에는 쓰기를 위한 파이프의 지시번호가 들어간다.

다음은 pipe를 통해서 통신을 하는 간단한 프로그램이다.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main()
{
	int n, fd[2];
	char buf[255];
	 int pid;

	 if (pipe(fd) < 0)
	{
		perror("pipe error : ");
		exit(0);
	}

	// 파이프를 생성한다. 
	if ((pid = fork()) < 0)
	{
		perror("fork error : ");
		exit(0);
	}

	// 만약 자식프로세스라면 파이프에 자신의 PID 정보를 쓴다.
	else if (pid == 0)
	{
		close(fd[0]);
		while(1)
		{
			memset(buf, 0x00, 255);
			sprintf(buf, "Hello Mother Process. My name is %d\n", getpid());
			write(fd[1], buf, strlen(buf));
			sleep(1);
			}
		}

		// 만약 부모프로세스라면 파이프에서 데이타를 읽어들인다. 
		else
		{
			close(fd[1]);
			while(1)
			{
				memset(buf, 0x00, 255);
				n = read(fd[0], buf, 255);
				fprintf(stderr, "%s", buf);
			}
		}
}
11. pipe(2)함수를 이용해서 파이프를 생성했다.
18. fork(2)함수를 이용해서 자식프로세스를 생성한다. 25~35. pid == 0 즉 자식 프로세스인 경우 실행될 코드다. 자식프로세스는 부모로부터 파일을 물려받는다 - 파이프도 파일이다 -. 여기에서 만들 자식프로세스는 단지 쓰기만 할 것이다. 그러므로 읽기를 위한 파이프는 close(2)를 이용해서 닫아버렸다. 이제 1초 간격으로 write(2)함수를 이용해서 쓰기파이프에 문자열을 쓴다. 38~47 부모프로세스는 단지 읽기만 할 것이다. 그래서 쓰기파이프는 닫아버렸다. 그리고 read(2)함수를 이용해서 읽기 파이프에 읽을 데이터가 있는지를 기다린다.

pipe의 장점과 단점

pipe는 매우 간단하게 사용할 수 있다는 장점이 있다. 만약 한쪽 프로세스가 단지 읽기만 하고 다른 쪽 프로세스는 단지 쓰기만 하는 단순한 데이터 흐름을 가진다면 고민 없이 pipe를 사용하면 된다.

단점은 반이중 통신이라는 점이다. 만약 프로세스가 읽기와 쓰기 통신 모두를 해야 한다면 pipe를 두개 만들어야 하는데, 구현이 꽤나 복잡해 질 수 있다. read(:2)와 write(:2)가 기본적으로 block(봉쇄)로 작동하기 때문으로 프로세스가 read(:2)대기중이라면 read가 끝나기 전에는 write를 할수가 없게 된다. 만약 두개의 프로세스가 모두 read 중이라면 영원히 block되게 될 것이다.

이러한 문제는 fork(:2)를 이용해서 읽기 전용 프로세스와 쓰기 전용 프로세스 2개를 만들거나 혹은 (나중에 다루게 될)입출력 다중화를 이용해서 해결해야 한다. 어떤 방법을 쓰더라도 구현이 복잡해지는 걸 피할 수 없다.

만약 전이중 통신을 고려해야될 상황이라면 pipe 는 좋은 선택이 아니다.

FIFO

FIFO는 First In First Out 의 줄임말이다. 먼저 입력된게 먼저 출력되는 선입선출의 데이터 구조를 의미한다. IPC 설비로써의 FIFO 라면 먼저 입력된 데이터가 먼저 전달되는 내부통신 설비로 해석할 수 있을 것이다. 그러나 이러한 해석은 사용자를 혼란시키는 측면이 있다. 앞서의 pipe 역시 선입선출의 데이터 전달 메커니즘을 따르기 때문이다.

그러니 FIFO는 선입선출이라는 데이터구조로써의 범주가 아닌 named:::pipe(:12)라는 구조적 측면으로써 바라보도록 한다. 이하 FIFO는 named pipe라고 부르기로 하겠다.

pipe 와 named pipe

앞서 이미 배운 pipe와 named pipe의 차이점에 대해서 알아보도록 하자. 먼저 입력된 데이터가 먼저 전달되는 흐름을 가진다는 측면에서 동일한 데이터 흐름 메커니즘을 가진다.

차이점이라면 named pipe 는 사용할 pipe를 명명할 수 있다는 점이 될 것이다. pipe는 사용할 파이프를 명명할 수가 없다. 이런 의미에서 pipe는 때때로 익명 pipe라고 부르기도 한다.

그럼 파이프를 익명으로 만드는 것과 명명 - named - 해서 사용하는 것의 차이점에 대해서 알아보도록 하자.

익명 파이프는 데이터 통신을 할 프로세스가 명확하게 알 수 있을 때 사용한다. 송신할 프로세스는 수신할 프로세스를 알고, 수신할 프로세스는 송신할 프로세스를 아는 경우다. 가장 대표적인 예는 부모프로세스와 자식프로세스간에 데이터 통신을 하고자 하는 경우다. 자식 프로세스와 부모 프로세스는 서로를 명확히 알고 있으므로 굳이 파이프에 이름을 줄필요가 없을 것이다. 부모와 자식간이니 굳이 이름이 없어도 대화가 통하지 않겠는가 ?

반면, 자식과 부모프로세스가 아닌 즉 전혀 모르는 프로세스들 사이에서 pipe를 이용해서 통신을 해야하는 경우를 생각해보자. 생판 모르는 사람과 대화를 하려면 이름을 알고 있어야 하듯이, 전혀 관련없는 프로세스들 사이에서 pipe를 이용해서 통신을 하려면 pipe에 이름이 주어져야 한다. named pipe 를 만든 이유다.

이제 named pipe 가 존재하는 방식을 알아보도록 하자. 복잡하게 생각할 것 없다. 리눅스에서 모든 것은 파일로 통한다고 한 것을 기억하고 있을 것이다. named pipe도 파일로 존재한다. pipe 파일이 존재하고, 이 파일의 이름이 바로 pipe의 name이 된다. 파일은 시스템 전역적으로 관리하는 객체이니, 이름만 안다면 어떤 프로세스라도 접근이 가능하다. 이제 서로 관련없는 프로세스들도 named pipe를 이용해서 통신을 할 수 있게 되었다.

시스템 명령을 이용한 named pipe의 생성

named pipe는 파일의 형태로 존재하며 프로세스와 전혀 관련이 없기 때문에, 프로세스와 관련없이 독자적인 파일의 형태로 존재하게 할 수 있다. 해서 반드시 프로세스 생성후에 통신선로가 개설되는 pipe와 달리 named pipe는 미리 파일(:12)로 만들어 둘 수 있다. 일단 파일로 만들어 둔 다음에, 쓰고 싶은 프로세스가 파이프를 열어서 통신을 하는 방식이다. 공중전화와 비슷하다고나 할 수 있을 것 같다.

리눅스는 mkfifo(:1) 라는 시스템 명령어를 이용해서 named pipe를 생성할 수 있다.
# mkfifo mypipe
ls 를 이용해서 파일의 특성을 알아보도록 하자.
# ls -al mypipe
prw-r--r-- 1 yundream yundream 0 2009-04-16 19:23 mypipe
가장 앞의 p는 이 파일이 pipe 임을 의미한다. 성공적으로 mypipe라는 이름을 가지는 pipe가 생성되었음을 확인할 수 있다.

리눅스 표준라이브러리 함수를 이용한 named pipe의 생성

시스템 명령을 이용해서 named pipe (이하 파이프)를 생성할 수 있지만 이걸로는 좀 부족하다. 우리는 시스템 프로그래머 이니, 프로그램내에서 파이프를 생성할 수 있어야 한다. 리눅스는 표준 라이브러리(:12)에서 mkfifo(3)이라는 파이프 생성 함수를 제공한다.
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo ( const char *pathname, mode_t mode );

다음은 인자로 주어진 문자열을 이름으로 가지는 파이프를 생성하는 간단한 프로그램이다.
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    int state;
    if (argc !=2 )
    {
        fprintf(stderr,"Usage : %s [filename]\n", argv[0]);
        return 1;
    }
    state = mkfifo("/tmp/myfifo", S_IRUSR|S_IWUSR);
    if (state < 0)
    {
        perror("mkfifo error \n");
        return 1;
    }
    return 0;
}

mknod 시스템 호출을 이용한 파이프 생성

앞서 mkfifo(3)을 이용해서 간단하게 파이프를 생성해 보았다. 그러나 왠지 기분이 석연치 않다. 표준 라이브러리에서 제공하는 함수를 이용했기 때문이다. 그래도 시스템 프로그래밍이니 이왕이면, 시스템콜(:12)을 이용해서 저수준에서 접근을 해보아야만 할 것 같은 기분이 든다.

리눅스는 mknod(2)라는 시스템콜을 제공한다. mknod는 특수파일의 생성을 위해서 커널에서 제공하는 함수로 사용방법은 다음과 같다.
include <sys/stat.h>

int mknod(const char *path, mode_t mode, dev_t dev);

아래는 mknod를 이용해서 named pipe를 생성하는 간단한 프로그램이다.
#include <sys/stat.h>

int main(int argc, char **argv)
{
  mknod(argv[1], S_IFIFO, 0);
  return 0;
}

mknod는 named pipe 외에도 문자장치파일, 블럭장치파일, Unix Domain Socket(:12)등의 파일들도 생성할 수 있다. 자세한 내용은 mknod(2) 메뉴얼 문서를 참고하기 바란다.

named pipe를 이용한 데이터 통신

named pipe를 이용하면 전형적인 서버/클라이언트 모델을 따르는 프로그램의 작성이 가능하다. 클라이언트는 요청을 하는 프로그램이고 서버는 요청을 받아서 처리하고 그 결과를 되돌려주는 프로그램이다. 서버/클라이언트 모델은 일반적으로 인터넷 서비스에 매우 익숙한 방식이다. apache와 같은 웹서버와 firefox(:12), ie 와 같은 클라이언트 프로그램이 대표적인 예이다.

시스템 프로그래밍에서도 이러한 서버/클라이언트 모델은 매우자주 사용된다. 인터넷에서의 서버/클라이언트 보다 그 역사도 오래되었다고 볼 수 있다.

여기에서는 간단한 계산 프로그램을 named pipe를 이용해서 만들어 보도록 하겠다. 이들 프로그램은 서버/클라이언트 모델을 따르도록 할 것이다. 서버프로그램의 이름은 calc_server.c 이고 클라이언트 프로그램의 이름은 calc.c 이다. calc.c 가 두개의 숫자를 named pipe를 이용해서 calc_server에 넘기면, calc_server 는 계산을 해서 calc로 넘겨주도록 할 것이다.

Message Queue

Queue(큐)는 선입선출의 자료구조를 가지는 통신설비로 커널에서 관리한다. 입출력 방식으로 보자면 named pipe와 동일하다고 볼 수 있을 것이다. named pipe와 다른 점이라면 name pipe가 데이터의 흐름이라면 메시지큐는 메모리공간이라는 점이다. 파이프가 아닌, 어디에서나 물건을 꺼낼수 있는 컨테이너 벨트라고 보면 될 것이다.

메시지큐의 장점은 컨테이너 벨트가 가지는 장점을 그대로 가진다. 컨테이너 벨트에 올라올 물건에 라벨을 붙이면 동시에 다양한 물건을 다룰 수 있는 것과 같이, 메시지큐에 쓸 데이터에 번호를 붙임으로써, 여러개의 프로세스가 동시에 데이터를 쉽게 다룰 수 있다.

메시지 큐의 생성

메시큐의 생성과 접근은 msgget(2) 함수를 통해서 이루어진다.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
메시지큐는 커널에서 관리하는 자원으로 여러개의 메시지큐를 가질 수 있다. 그러므로 메시지큐를 실벽할 수 있는 key가 필요하다. 메시지큐는 이 key를 유일한 번호로 가지고 생성된다. IPC_CREAT, IPC_EXEL와 권한을 설정하기 위해서 사용한다. IPC_CREAT는 메시지큐를 생성할 때 사용한다. IPC_CREATIPC_EXEL을 함께 사용하면, key를 가진 메시지큐가 존재 할때 에러를 리턴한다.

데이터 쓰기와 읽기

리눅스 커널은 쓰기와 읽기를 위해서 각각 msgsnd(2) 와 msgrcv(2) 함수를 제공한다.
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 
 
int msgsnd (int msqid, (void *)msgp, size_t msgsz, int msgflg) 
ssize_t msgrcv (int msqid, struct  msgbuf  *msgp,  size_t msgsz,  
                long msgtyp, int msgflg)  
보내고자 하는 데이터는 msgp를 이용해서 보내는데, 데이터 구조는 다음과 같다.
struct msgbuf 
{ 
    long mtype;             // 메시지 타입 
    char mtext[BUFF_SIZE];  // 보내고자 하는 데이터 
} 
보내고자 하는 데이터는 어느정도 정형화 되어 있는 측면이 있다. 즉 mtype을 가져야 하는데, 이것을 이용해서 자기가 읽고 싶은 메시지 만을 읽어올 수 있다. mtype을 이용하면, 여러개의 데이터 큐가 있는 것처럼 사용할 수 있다.

공유메모리 - Shared Memory

데이터를 공유하는 방법에는 크게 두가지가 있다. 하나는 통신을 이용해서 데이터를 주고 받는 것이고, 다른 하나는 데이터를 아예 공유 w즉 함께 사용하는 것이다. pipe, named pipe, message queue(:12)가 통신을 이용한 설비라면 공유메모리가 데이터 자체를 공유하도록 지원하는 설비다.

프로세스는 자신만의 메모리영역을 가지고 있다. 이 메모리 영역은 다른 프로세스가 접근해서 함부로 데이터를 읽거나 쓰지 못하도록 커널에 의해서 보호가 된다. 만약 다른 다른 프로세스의 메모리 영역을 침범하려고 하면 커널은 침범 프로세스에 SIGSEGV 경고 시그널을 보내게 된다.

다수의 프로세스가 동시에 작동하는 linux(:12) 운영체제의 특성상 프로세스의 메모리영역은 반드시 보호되어야 할것이다. 그렇지만 메모리영역에 있는 데이터를 다른 프로세스도 사용할 수 있도록 해야할 경우도 있을 것이다. 물론 pipe등을 이용해서 데이터 통신을 이용해서 데이터를 전달하는 방법도 있겠지만 thread(:12)에서 처럼 메모리영역을 공유한다면 더 편하게 데이터를 함께 사용할 수 있을 것이다.

공유메모리는 프로세스간 메모리 영역을 공유해서 사용할 수 있도록 허용한다. 프로세스가 공유메모리 할당을 커널에 요청하면 커널은 해당 프로세스에 메모리 공간을 할당한다. 이후 어떤 프로세스든지 해당 메모리영역에 접근할 수 있다.

공유메모리는 중개자가 없이 곧바로 메모리에 접근할 수 있기 때문에 다른 모든 IPC들 중에서 가장 빠르게 작동한다.

공유메모리 함수들

공유메모리는 공간은 커널이 관리한다. 그러므로 프로세스가 종료되면 반환되는 메모리영역과는 달리 생성한 프로세스가 종료하더라도 공유메모리는 그대로 남아있게 된다.

공유메모리는 다음과 같은 과정을 거쳐서 사용한다.
  1. 공유메모리 영역을 생성을 요청한다.
  2. 공유메모리를 자신의 프로세스에서 맵핑해서 사용할 수 있도록 요청한다.
  3. 공유메모리를 사용한다.
이를 위해서 linux(:12)는 다음과 같은 함수를 제공한다.
#include <sys/types.h>
#include <sys/shm.h>

int shmget(key_t key, int size, int shmflg)
void *shmat( int shmid, const void *shmaddr, int shmflg )
int shmdt( const void *shmaddr)
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
shmget : 이 함수를 이용해서 공유메모리공의 생성을 요청할 수 있다.

세마포어의 사용

세마포어는 3개의 함수(:12)를 제공한다. 즉
  1. 세마포어의 생성 : 자원을 사용하기 위해서 알림판을 만드는 거라고 보면 된다.
  2. 세마포어의 획득 및 되돌려주기 : 알림판을 사용가능/불가능으로 만드는 작업
  3. 세마포어의 세부값 제어
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h> 
 
int semget(key_t key, int nsems, int semflg); 
int semop (int semid, struct sembuf *sops, unsigned nsops); 
int semctl(int semid, int semnum, int cmd, union semun arg); 

세마포어는 커널이 관리한다. semget을 이용해서 커널에 세마포어의 생성 혹은 기존에 만들어진 세마포어의 접근을 요청할 수 있다. semget의 인자는 다음과 같다. || IPC_CREATE || key에 해당하는 세마포어가 없다면 새로 생성한다. ||
IPC_EXCL 세마포어가 이미 있다면, 에러를 리턴해서 세마포어에 접근하지 못한다. 이미 열려진 세마포어에 접근하려면 이 옵션이 없어야 한다.

세마포어 예제 프로그램

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <stdio.h>

#define MAX_THREAD  2

int count = 0;

void *myThreadFunc(void *data);

struct sembuf mysem_open = {0, -1, SEM_UNDO};
struct sembuf mysem_close = {0, 1, SEM_UNDO};

union snum
{
	int val;
};

static int semid;

int main(int argc, char **argv)
{
	int thr_id;
	int status;
	int i;
	union snum s_union;
	pthread_t pt[MAX_THREAD];

	semid = semget(2345, 1, 0600|IPC_CREAT);

	if(semid == -1)
	{
		perror("semget error");
		return 1;
	}

	s_union.val = 1;
	if(semctl(semid, 0, SETVAL, s_union) == -1)
	{
		return 1;
	}

	for(i = 0; i < MAX_THREAD; i++)
	{
		thr_id = pthread_create(&pt[i], NULL, myThreadFunc, (void *)&i);
		if(thr_id < 0)
		{
			perror("Thread Create Error");
			return 1;
		}
		sleep(1);
	}	
	for(i = 0; i < MAX_THREAD; i++)
	{
		pthread_join(pt[i], NULL);
	}	
}

void *myThreadFunc(void *data)
{
	int thread_num = *(int *)data;
	int lnum;
	printf("Thread Create %d\n", thread_num);
	while(1)
	{
		semop(semid, &mysem_open, 1);
		lnum = count;
		sleep(1);
		lnum = lnum+1; 
		count = lnum;
		printf("[%d] count : %d\n", thread_num, count);
		semop(semid, &mysem_close, 1);
	}
}
이 프로그램은 카운팅 프로그램이다. 두 개의 스레드가 하나의 count 변수에 접근하는데, 세마포어를 이용해서 접근을 제어하고 있다. 스레드 함수인 myThreadFunc에서 semop 부분을 주석 처리한 후 어떤 결과가 나오는지 확인해 보도록 하자.

세마포어의 장점과 단점

세마포어는 변수의 값을 확인해서 임계영역으로의 진입을 제어한다. 그렇다면 프로그램변수로도 임계영역의 진입을 제어할 수 있을 것이다. 대략 다음과 같다.

P(S) 
{
    while (S <= 0)
        continue;
    S--;
}
/* 임계영역 */

V(S)
{
    S++;
}
매우 간단한 구현으로 보이지만, 이방법을 이용하게 될 경우 프로세스가 busy wait 상태에 놓이기 된다. 또한 다른 프로세스들간에는 사용하기 힘들다는 문제점도 가진다. 반면 세마포어는
  1. busy wait 상태에 놓이지 않는다.
  2. 커널에서 관리되기 때문에 다른 프로세스들간에도 사용할 수 있다.
는 장점을 가진다.

세마포어를 제어하기 위해서는 P함수와 V함수를 사용해야 하는데, 이 두개의 함수는 독립적이다. 때문에 잘못사용하게 될경우 deadlock(:12)에 빠질 수 있다.

Socket

Socket는 물리적으로 멀리 떨어져 있는 컴퓨터끼리의 통신을 도와주기 위한 통신계층이다. Socket(:12)은 원격통신을 위한 여러가지 함수들을 제공하는데, 이들을 이용해서 물리적으로 떨어진 컴퓨터 간의 통신이 가능하게 된다. 인터넷을 이용한 통신 프로그램의 대부분이 socket 기반으로 작성된다고 보면 틀림없을 것이다.

컴퓨터와 컴퓨터간의 통신이라고는 하지만 실질적으로는 이쪽 컴퓨터의 프로세스와 저쪽 컴퓨터의 프로세스가 통신을 하는 것다. 이렇게 프로세스간 통신을 한다는 점에서 봤을 때 IPC(:12)설비와 근본적으로 다른점은 없다고 볼 수 있다. 차이점이라면 통신이 이루어지는 영역이 컴퓨터 내부인지 아니면 온라인인지 하는 것이될 것이다.

비록 socket이 원격의 컴퓨터 프로세스들간의 통신을 위한 도구이지만 이러한 프로세스간 통신이라는 비슷한 특성으로 내부 프로세스간 통신을 위한 방법을 제공한다. 즉 외부 프로세스간 통신으로 사용할건지 내부 프로세스간 통신으로 사용할 건지를 선택할 수 있게끔 하고 있다. socket(:12)는 단일 주제만으로도 책한두권은 만들어낼 수 있는 방대한 양이다. 여기에서는 socket의 기능중 내부 프로세스간 통신지원 기능에 대해서만 알아보도록 할 것이다.

Unix Domain Socket 의 장점

Unix Domain Socket(이하 소켓)의 가장 큰 장점은 네트워크 통신에 사용하던 기술을 그대로 사용할 수 있다는 점일 것이다. 코딩의 일관성을 유지할 수 있도록 도와준다. 얻을 수 있는 장점은 단지 함수(:12)를 그대로 사용할 수 있다는 점 이상이다. 다수의 클라이언트를 동시에 처리하기 위한 방법, 메시지 관리를 위한 방법 파이프로도 충분한 간단한 프로그램에 소켓을 사용하는 건 비효율적이긴 하지만 어느정도 이상의 규모가 된다면 소켓을 이용하는 걸 추천한다.

Unix Domain Socket 예제

이 프로그램은 에코서버/클라이언트의 IPC 버전이다.

먼저 서버 프로그램이다.
#include <sys/types.h> 
#include <sys/stat.h> 
#include <sys/socket.h> 
#include <sys/un.h> 
#include <unistd.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define MAXLINE 1024
int main(int argc, char **argv)
{
    int server_sockfd, client_sockfd;
    int state, client_len;
    pid_t pid;

    FILE *fp;
    struct sockaddr_un clientaddr, serveraddr;

    char buf[MAXLINE];

    if (argc != 2)
    {
        printf("Usage : %s [socket file name]\n", argv[0]);
        printf("예    : %s /tmp/mysocket\n", argv[0]); 
        exit(0);
    }

    if (access(argv[1], F_OK) == 0)
    {
        unlink(argv[1]);
    }

    // internet 기반의 스트림 소켓을 만들도록 한다. 
    client_len = sizeof(clientaddr);
    if ((server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    {
        perror("socket error : ");
        exit(0);
    }
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sun_family = AF_UNIX;
    strcpy(serveraddr.sun_path, argv[1]);

    state = bind(server_sockfd , (struct sockaddr *)&serveraddr, 
            sizeof(serveraddr));
    if (state == -1)
    {
        perror("bind error : ");
        exit(0);
    }

    state = listen(server_sockfd, 5);
    if (state == -1)
    {
        perror("listen error : ");
        exit(0);
    }

    while(1)
    {
        client_sockfd = accept(server_sockfd, (struct sockaddr *)&clientaddr, 
                               &client_len);
        pid = fork();
        if (pid == 0)
        {
            if (client_sockfd == -1)
            {
                perror("Accept error : ");
                exit(0);
            }
            while(1)
            {
                memset(buf, 0x00, MAXLINE);
                if (read(client_sockfd, buf, MAXLINE) <= 0)
                {
                    close(client_sockfd);
                    exit(0);
                }
                write(client_sockfd, buf, strlen(buf));
            }
        }
    }
    close(client_sockfd);
}

다음은 클라이언트 프로그램이다.
#include <sys/types.h> 
#include <sys/stat.h> 
#include <sys/socket.h> 
#include <unistd.h> 
#include <sys/un.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define MAXLINE 1024

int main(int argc, char **argv)
{

    int client_len;
    int client_sockfd;

    FILE *fp_in;
    char buf_in[MAXLINE];
    char buf_get[MAXLINE];

    struct sockaddr_un clientaddr;

    if (argc != 2)
    {       
        printf("Usage : %s [file_name]\n", argv[0]);
        printf("예    : %s /tmp/mysocket\n", argv[0]);
        exit(0);
    }       

    client_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (client_sockfd == -1)
    {
        perror("error : ");
        exit(0);
    }
    bzero(&clientaddr, sizeof(clientaddr));
    clientaddr.sun_family = AF_UNIX;
    strcpy(clientaddr.sun_path, argv[1]);
    client_len = sizeof(clientaddr);

    if (connect(client_sockfd, (struct sockaddr *)&clientaddr, client_len) < 0)
    {
        perror("Connect error: ");
        exit(0);
    }
    while(1)
    {
	memset(buf_in, 0x00, MAXLINE);
	memset(buf_get, 0x00, MAXLINE);
	printf("> ");
        fgets(buf_in, MAXLINE, stdin);
        write(client_sockfd, buf_in, strlen(buf_in));
        read(client_sockfd, buf_get, MAXLINE); 
        printf("-> %s", buf_get);
    }

    close(client_sockfd);
    exit(0);
}