Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

socketpair()은 비교적 단순한 내용이지만 기존 IPC에서 빼먹고 다루지 않았음으로 별도로 분리해서 다루기로 했다. 기존에 다루었던 IPC에 대한 내용도 담고 있으므로 복습하는 의미에서 천천히 읽어보기 바란다.

지금까지 비교적 최신이라고 생각되는 SystemV IPC(:12)와 유닉스 초기 부터 사용된 오래된 역사를 가진 FIFO(네임드 파이프), pipe(:12)그리고 소켓(:12)레이어에서 제공하는 Unix:::Domain:::소켓(:12)을 이용한 내부 프로세스간 통신에 대해서 알아보았다.

여기에서는 이들 IPC에서 제외되어있던 socketpair(2)에 대해서 알아보도록 하겠다.

socketpair을 이용한 IPC

이번장에서는 socketpair을 이용하는 방법에 대해서 알아 볼 것이다. socketpair은 pipe와 FIFO와 매우 비슷한 일을 수행하는데, socketpair을 사용할 경우 얻을 수 있는 장점에 대해서도 알아보도록 한다.

마지막에는 간단한 예제를 만들어서 이해를 돕도록 하겠다.

pipe와 FIFO의 단점

pipe와 FIFO는 유닉스 초기에 만들어 져서 지금까지 사용되고 있는 오래된 역사를 자랑하는 IPC(:12)설비다. Simple is beautifule이 유닉스의 철학이 되도록 하는데 가장 큰 역활을 한 설비라고 할 수 있다. 각각의 프로세스(:12)는 필요한 일들만을 담당하며, 복잡한 일은 각각의 프로세스의 데이터를 파이프로 연결해서 해결하였다.

이렇게 해서 유닉스는 필요한 일만담당하는 수많은 "작은" 프로세스들을 통해서 효율적으로 관리 될 수 있게 된다. 각각의 프로세스들은 반드시 필요한 기능만을 가지고 있기 때문에 주어진 일에 대해서는 매우 효율적으로 작동할 수 있으며, 문제가 발생할 확률도 매우 적었다.

pipe와 FIFO는 유닉스의 이러한 파이프를 지원하는 확실하고도 간단한 방법이다. 거의 유일한 단점이라고 생각되는 것은 단방향의 연결만을 지원한다는 점이다. 만약 부모프로세스에서 자식 프로세스를 만들고 이를 pipe를 통해서 연결 했다면 부모는 읽기와 쓰기중 하나만 가능하게 된다. 자식역시 마찬가지로 만약 부모가 쓰기 전용 으로 파이프를 열었다면 읽기 전용으로, 부모가 읽기 전용으로 파이프를 열었다면 쓰기 전용으로 열어야만 한다.

pipe의 이러한 단방향성은 프로세스간 양방향 통신이 지원되어야 하는 경우 문제가 된다. 물론 pipe를 2번 사용함으로써 이러한 문제를 해결할 수 있기는 하지만 코드를 지저분 하게 만든다. FIFO역시 이와 비슷한 문제점을 가지고 있다.

다음은 pipe를 이용해서 자식 프로세스와 대화하는 간단한 예제 프로그램이다. 부모 프로세스는 fork&exec를 통해서 외부 프로그램을 자식 프로세스로 실행시키고 대화하게 될 것이다.

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

int main() 
{ 
   int pp[2]; 
   int pid; 

   if (pipe(pp) < 0) 
   { 
       printf("pipe error:"); 
   } 
   pid = fork(); 
   if (pid < 0) 
   { 
       perror("fork error"); 
       exit(0); 
   } 
   if (pid == 0) 
   { 
       close(pp[1]); 
       dup2(pp[0], 0); 
       if(execl("/home/mycvs/test/pipe_cl", "pipe_cl", 0) < 0) 
       { 
           perror("execl error"); 
       } 
   } 
   else if (pid > 0) 
   { 
       close(pp[0]); 
       while(1) 
       { 
           sleep(1); 
           printf("Write\n"); 
           write(pp[1], (void *)&pid, sizeof(pid)); 
       } 
   } 
} 
이 프로그램은 부모 프로세스로 자식 프로세스로 pipe_cl을 실행시킨다. 실행 시킨 자식 프로세스와의 통신을 위해서 pipe()를 이용해서 파이프를 생성한다. 부모 프로세스에서 자식 프로세스로 데이터를 쓰기 위해서 dup2(2)를 이용해서 읽기 전용 파이프 pp[0]을 0(표준입력)으로 복사했다. 이제 부모 프로세스가 쓰기 전용 파이프 pp[1]로 데이터를 쓰면 이 데이터는 자식 프로세스의 표준입력으로 전달된다. 다음은 자식 프로세스로 실행시킬 pipe_cl.c 프로그램의 소스 코드다.

#include <unistd.h> 

int main() 
{ 
   int buf; 
   while(1) 
   { 
       read(0, (void *)&buf, sizeof(buf)); 
       printf("read %d\n", buf); 
   } 
} 

부모 프로세스는 자신의 PID를 자식에게 전달하고 자식 프로세스는 이 PID를 출력한다.

만약 위의 프로세스들이 쌍방향으로 - 예를 들어 자식 프로세스에서 부모 프로세스의 PID를 받고 자신의 PID를 부모 프로세스에게 전달 하는 - 통신하기를 원한다면 pipe()를 두번 호출해서 읽기전용과 쓰기전용의 두개의 파이프를 생성해야 할것이다. 뭐 어떻게 보면 그리 복잡하지 않은 작업이라고 생각 될 수 있지만 애매모호한 변수명과 명확하지 않은 입출력 관계 때문에 지저분한 코드가 되기 쉽다.

socketpair의 사용

socketpair()를 이용하면 연결된 소켓의 쌍을 생성한다. 생성된 소켓쌍 중 하나를 부모가 가지고 다른 하나를 자식에게 넘기는 방식으로 연결을 만들 수 있다. 소켓이므로 당연히 전이중 통신을 지원하게 된다.
#include <sys/types.h> 
#include <sys/socket.h> 

int socketpair(int d, int type, int protocol, int sv[2]); 
  • d : 소켓의 도메인(영역)을 지정하기 위해서 사용한다.
  • type 과 protocol 를 이용해서 소켓의 부가적인 특징을 결정할 수 있다. 만들어진 소켓쌍은 sv를 통해서 넘어온다. 만들어진 소켓쌍은 구분할 수 없다.
pipe.c와 pipe_cl.c의 socketpair 버젼으로 읽기/쓰기가 모두 가능한 것을 확인할 수 있다. 예제에 대한 모든 설명은 주석으로 대신한다.

#include <sys/types.h> 
#include <sys/socket.h> 

int main() 
{ 
   int sv[2]; 
   int pid; 
   int mynum = 1; 

   // 소켓쌍을 생성한다. 
   if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) 
   { 
       perror("socketpair error"); 
       exit(0); 
   } 

   pid = fork(); 
   if (pid < 0) 
   { 
       perror("fork error"); 
       exit(0); 
   } 

   // exec를 이용해서 자식프로세스를 생성한다. 
   if( pid == 0) 
   { 
       // 소켓쌍중 하나를 표준입력으로 복사한다. 
       dup2(sv[0], 0); 
       // 사용되지 않는 소켓을 닫는다. 
       close(sv[1]); 
       close(sv[0]); 
       execl("/home/mycvs/test/pipe_cl", "pipe_cl", 0); 
   } 
   else if (pid > 0) 
   { 
       // 쏘켓쌍중 사용하지 않는 소켓은 닫는다.  
       close(sv[0]); 
       while(1) 
       { 
           write(sv[1], (void *)&mynum, sizeof(mynum)); 
           sleep(1); 
           read(sv[1], (void *)&mynum, sizeof(mynum)); 
           printf("num is %d\n", mynum); 
       } 
   } 
} 
           </screen> 
       </para> 
       <para> 
           <emphasis>예제 : socketpair_cl.c</emphasis> 
           <screen> 
#include <unistd.h> 

int main() 
{ 
   int buf; 
   while(1) 
   { 
       // 표준입력으로 부터 값을 읽어 들이고  
       // ++한뒤 쓴다. 
       read(0, (void *)&buf, sizeof(buf)); 
       buf++; 
       write(0, (void *)&buf, sizeof(buf)); 
   } 
}