내부 프로세스들 끼리의 통신을 위한 IPC 방법 중에 UDS 를 이용하여 프로그램을 작성하는 방법에 대해 알아 봅니다.

UDS

UDS(Unix Domain Socket)은 내부 프로세스들 끼리 TCP 또는 UDP 프로토콜을 이용하여 통신할 수 있도록 도와주는 소켓입니다. 이 소켓을 이용하시면 기존의 TCP/IP, UDP/IP에서 사용하던 함수로 같은 방법을 사용하여 프로세스들끼리 통신할 수 있어 매우 편리하며, 이점이 장점입니다.

말씀 드린 바와 같이 TCP/IP, UDP/IP와 같은 함수를 사용하고 다만 필요한 변수값만 바꾸어 주면 됩니다. 예를 들어 소켓을 생성해 보겠습니다.

TCP/IP와 UDP/IP에서 처럼 socket() 함수를 이용합니다. 우선 #include <sys/socket.h> 대신에 #include <sys/un.h>를 사용합니다.

자, socket() 함수를 호출해 보겠습니다.

int     sock;

sock = socket( PF_FILE, SOCK_DGRAM, 0);

지금 UDP 프로토콜을 이용하기 위한 UDS 소켓 하나를 만들었습니다. 기존의 UDP/IP와는 달리 PF_INET 대신에 PF_FILE을 사용했습니다. UDS는 데이터를 전송하기 위해 파일을 이용하기 때문입니다. UDS의 사용은 이와 같습니다. 기존의 TCP/IP, UDP/IP 통신에서 사용하는 함수를 그대로 사용하면서 다만 필요한 정보를 몇 가지 바꾸어 주기만 하면 됩니다. socket()에 대한 더 자세한 말씀은 "Unix C Reference의 11장 7절 소켓 열고 닫기"를 참고하십시오.

이번에는 bind() 함수를 이용하여 소켓에 통신 케이블을 연결해 보겠습니다. bind() 함수를 이용하기 위해서는 주소정보가 필요합니다. 이를 위해서는 struct sockaddr_in 대신에 struct sockaddr_un을 사용합니다.

struct sockaddr_un server_addr;

memset( &server_addr, 0, sizeof( server_addr));
server_addr.sun_family     = AF_UNIX;
strcpy( server_addr.sun_path, "/tmp/test_server.dat");

역시 TCP/IP, UDP/IP에서 사용했던 루틴이죠? 다만 sun_family 속성에 AF_UNIX 또는 AF_FILE로 대입해 주고, sun_path에 통신에 사용할 파일을 지정해 주시면 됩니다.

이후로는 기존의 TCP/IP, UDP/IP 프로그램하고 똑 같습니다!! 너무 멋지지 않습니까? 다만 소켓을 close() 해도 에 지정한 파일이 삭제되지 않아서 실행하기 전에 미리 삭제해 주어야 한다는 것 외에는 기존 루틴을 그대로 사용할 수 있습니다. 이런 귀찮은 것도 아래 코드로 간단히 해결할 수 있습니다.

if ( 0 == access( "/tmp/test_server.dat", F_OK))
    unlink( "/tmp/test_server.dat");

이제 UDS를 이용한 TCP와 UDP 프로토콜 구현하는 프로그램을 보도록 하겠습니다.

UDS를 이용한 TCP

아래의 헤더파일을 꼭 포함합니다.

#include <unistd.h>
#include <arpa/inet.h>
#include <sys/un.h>

서버 소켓에 사용할 파일의 이름을 상수로 정의하겠습니다. 이 파일은 서버 프로그램이나 클라이언트나 모두 같이 사용되는 파일 이름입니다.

#define FILE_SERVER "/tmp/test_server"

예제 프로그램에서는 클라이언트 프로그램이 서버 프로그램에게 문자를 전송하면 서버가 문자열의 길이와 전송한 문자열을 반송하는 프로그램을 작성해 보겠습니다.

TCP 서버 프로그램

소켓 변수를 선언하고 소켓을 생성합니다. PF_INET가 아닌 PF_FILE입니다.

int     server_socket;

server_socket = socket( PF_FILE, SOCK_STREAM, 0);

이제 리슨 대기 모드에 사용할 서버 소켓으로 등록하기 위해 먼저 bind() 로 소켓에 주소 정보를 지정합니다. struct sockaddr_un 구조체 변수를 선언부터 하겠습니다.

struct sockaddr_un server_addr;

memset( &server_addr, 0, sizeof( server_addr));
server_addr.sun_family        = AF_UNIX;
strcpy( server_addr.sun_path, FILE_SERVER);   // 통신에 사용할 파일 이름을 복사해 줍니다.

if( -1 == bind( server_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
{
   printf( "bind() 실행 에러n");
   exit( 1);
}

bind() 실행에 에러가 없다면 이제 리슨 대기 모드로 진입합니다.

if ( -1 == listen(server_socket, 5))
{
    printf( "대기상태 모드 설정 실패n");
    exit( 1);
}

listen()에서 에러 없이 복귀했다면 클라이언트로부터 연결이 요청된 것입니다. accept() 로 접속을 수락합니다. 클라이언트 주소 정보를 위해 struct sockaddr_un 구조체 변수를 선언하겠습니다.

struct sockaddr_un client_addr;
int     client_socket;
int     client_addr_size;

client_addr_size = sizeof( client_addr);
client_socket = accept( server_socket, (struct sockaddr*)&client_addr, &client_addr_size);
if ( -1 == client_socket)
{
   printf( "클라이언트 연결 수락 실패n");
   exit( 1);
}

accept() 까지 해서 접속이 이루어 졌습니다. 이제 read()/write()를 이용하여 통신을 하면 되겠습니다. 클라이언트로부터 수신한 문자열의 길이를 구한 후 클라이언트로 전송합니다.

read ( client_socket, buff_rcv, BUFF_SIZE);
sprintf( buff_snd, "%d : %s", strlen( buff_rcv), buff_rcv);
write( client_socket, buff_snd, strlen( buff_snd)+1); // +1: NULL까지 포함해서 전송

그리고 close()를 이용하여 클라이언트와의 연결을 종료합니다.

close( client_socket);

TCP 클라이언트 프로그램

역시 소켓부터 생성합니다.

int client_socket;

client_socket = socket( PF_FILE, SOCK_STREAM, 0);

목적지인 서버 시스템의 주소 정보를 준비합니다.

struct sockaddr_un server_addr;

memset( &server_addr, 0, sizeof( server_addr));
server_addr.sun_family = AF_UNIX;
strcpy( server_addr.sun_path, FILE_SERVER);    // 역시 서버 프로그램에서 사용하는 파일을 지정했습니다.

이제 서버와 접속을 시도합니다.

if( -1 == connect( client_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
{
   printf( "접속 실패n");
   exit( 1);
}

connect() 함수에서 에러 없이 복귀했다면 접속이 된 것입니다. 이제 read()/write() 를 이용하여 서버와 통신합니다. 프로그램을 실행할 때 인수로 받은 문자열을 전송합니다.

write( client_socket, argv[1], strlen( argv[1])+1); // +1: NULL까지 포함해서 전송

서버에서는 전송된 문자열에 대한 문자열 길이를 계산해서 다시 보내 줍니다. 서버가 보내준 데이터를 받아서 화면에 출력한 후 close() 함수로 연결을 종료합니다.

read ( client_socket, buff, BUFF_SIZE);
printf( "%sn", buff);
close( client_socket);

TCP 서버 프로그램 소스

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/un.h>

#define  BUFF_SIZE   1024
#define  FILE_SERVER "/tmp/test_server"

int   main( void)
{
   int   server_socket;
   int   client_socket;
   int   client_addr_size;
   int   option;

   struct sockaddr_un   server_addr;
   struct sockaddr_un   client_addr;

   char   buff_rcv[BUFF_SIZE+5];
   char   buff_snd[BUFF_SIZE+5];

   if ( 0 == access( FILE_SERVER, F_OK))
      unlink( FILE_SERVER);

   server_socket  = socket( PF_FILE, SOCK_STREAM, 0);
   
   if( -1 == server_socket)
   {
      printf( "server socket 생성 실패n");
      exit( 1);
   }

   memset( &server_addr, 0, sizeof( server_addr));
   server_addr.sun_family  = AF_UNIX;
   strcpy( server_addr.sun_path, FILE_SERVER);

   if( -1 == bind( server_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
   {
      printf( "bind() 실행 에러n");
      exit( 1);
   }

   while( 1)
   {
      if( -1 == listen(server_socket, 5))
      {
         printf( "대기상태 모드 설정 실패n");
         exit( 1);
      }

      client_addr_size  = sizeof( client_addr);
      client_socket     = accept( server_socket, (struct sockaddr*)&client_addr, &client_addr_size);

      if ( -1 == client_socket)
      {
         printf( "클라이언트 연결 수락 실패n");
         exit( 1);
      }

      read ( client_socket, buff_rcv, BUFF_SIZE);
      printf( "receive: %sn", buff_rcv);
      
      sprintf( buff_snd, "%d : %s", strlen( buff_rcv), buff_rcv);
      write( client_socket, buff_snd, strlen( buff_snd)+1); // +1: NULL까지 포함해서 전송
      close( client_socket);
   }
   return 0;      
}

TCP 클라이언트 프로그램 소스

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/un.h>

#include "sample.h"

#define  BUFF_SIZE   1024
#define  FILE_SERVER "/tmp/test_server"

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

   struct sockaddr_un   server_addr;

   char   buff[BUFF_SIZE+5];

   client_socket  = socket( PF_FILE, SOCK_STREAM, 0);
   if( -1 == client_socket)
   {
      printf( "socket 생성 실패n");
      exit( 1);
   }

   memset( &server_addr, 0, sizeof( server_addr));
   server_addr.sun_family  = AF_UNIX;
   strcpy( server_addr.sun_path, FILE_SERVER);

   if( -1 == connect( client_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
   {
      printf( "접속 실패n");
      exit( 1);
   }
   write( client_socket, argv[1], strlen( argv[1])+1);      // +1: NULL까지 포함해서 전송
   read ( client_socket, buff, BUFF_SIZE);
   printf( "%sn", buff);
   close( client_socket);
   
   return 0;
}

UDS를 이용한 UDP 프로그램

  앞서 UDS를 이용한 TCP 프로그램을 이해하셨다면 UDP는 별반 다를 것이 없습니다. 예제에서는 두 개의 프로그램을 작성하며, 한쪽에서 문자열을 전송하면 받는 쪽은 문자열을 겹쳐서 반송해 줍니다. 이렇게 자료를 주고 받을 수 있도록 하기 위해 각각의 프로그램에 파일을 따로 따로 두도록 하겠습니다.

  예제 프로그램 1은 /tmp/process_a 를 프로그램 2는 /tmp/process_b 파일을 사용하도록 하겠습니다.

UDS를 이용한 UDP 프로그램 1

  프로그램 1에서 사용할 소켓용 파일은 /tmp/process_a 입니다.

#define SOCK_LOCALFILE "/tmp/process_a"

  이전 실행에서 생성한 소켓용 파일을 삭제하겠습니다.

if ( 0 == access( SOCK_LOCALFILE, F_OK))
   unlink( SOCK_LOCALFILE);

  역시 소켓부터 만들겠습니다.

int sock;

sock = socket( PF_FILE, SOCK_DGRAM, 0);

  상대로부터의 자료를 수신하기 위해 주소정보를 준비하고 bind()함수로 소켓에 주소정보를 할당하고 커널에 올립니다.

struct sockaddr_un local_addr;

memset( &local_addr, 0, sizeof( local_addr));
local_addr.sun_family = AF_UNIX;
strcpy( local_addr.sun_path, SOCK_LOCALFILE);

if( -1 == bind( sock, (struct sockaddr*)&local_addr, sizeof( local_addr)) )

  에러없이 bind()가 실행되었다면 이제 recvfrom() 함수와 sendto() 함수를 이용하여 통신할 수 있습니다. recvfrom() 함수를 이용하여 상대로부터 전송되어온 전문을 수신합니다.

addr_size = sizeof( guest_addr);
recvfrom( sock, buff_rcv, BUFF_SIZE, 0 , ( struct sockaddr*)&guest_addr, &addr_size);

  buff_rcv에는 수신한 데이터가 guest_addr 에는 전송지의 주소 정보가 있습니다. 예제인만큼 주소지 정보를 출력해 보겠습니다. 출력해 보면 두번째 프로그램의 소켓 파일인 /tmp/process_b 가 출력될 것입니다.

printf( "%sn", guest_addr.sun_path); // 게스트 프로세스의 소켓용 파일 이름을 출력해 봅니다.

  이제 수신한 자료를 중복하여 겹친 후 전송지로 다시 보내 줍니다.

sprintf( buff_snd, "%s%s", buff_rcv, buff_rcv);
sendto( sock, buff_snd, strlen( buff_snd)+1, 0,
         ( struct sockaddr*)&guest_addr, sizeof( guest_addr)); // +1: NULL까지 포함해서 전송

UDS를 이용한 UDP 프로그램 2

  프로그램 2은 실행 시 입력받은 인자를 전송하고 다시 프로그램 1에서 전송한 데이터를 출력해 보겠습니다. 우선 소켓용 파일을 정리합니다. 이번에는 SOCK_LOCALFILE 은 /tmp/process_b 임을 확인하여 주십시오.

#define SOCK_LOCALFILE "/tmp/process_b"
#define SOCK_TARGETFILE "/tmp/process_a"

  이전 실행으로 생성된 소켓용 파일을 삭제합니다.

if ( 0 == access( SOCK_LOCALFILE, F_OK))
   unlink( SOCK_LOCALFILE);

  자, 소켓을 만듭니다.

int sock;

sock = socket( PF_FILE, SOCK_DGRAM, 0);

  상대편이 제대로된 주소 정보를 받을 수 있고, 또한 자신도 다른 프로세스로부터 자료를 수신 받기 위해 소켓에 주소정보를 할당해 줍니다.

struct sockaddr_un local_addr;

memset( &local_addr, 0, sizeof( local_addr));
local_addr.sun_family = AF_UNIX;
strcpy( local_addr.sun_path, SOCK_LOCALFILE);

if( -1 == bind( sock, (struct sockaddr*)&local_addr, sizeof( local_addr)))

  이제 데이터를 전송할 목적지 주소를 구성합니다.

struct sockaddr_un target_addr;

memset( &target_addr, 0, sizeof( target_addr));
target_addr.sun_family = AF_UNIX;
strcpy( target_addr.sun_path, SOCK_TARGETFILE);

  목적지인 프로그램 1로 자료를 전송합니다.

sendto( sock, argv[1], strlen( argv[1])+1, 0,
          ( struct sockaddr*)&target_addr, sizeof( target_addr)); // +1: NULL까지 포함해서 전송

  다시 프로그램 1로부터 recvfrom()을 이용하여 자료를 수신합니다. 이때 프로그램 1 에서 처럼 recvfrom()에서 송신지의 주소 정보를 구할 수 있으나, 이미 알고 있으므로 인수에 NULL로 간편화 하겠습니다.

recvfrom( sock, buff_rcv, BUFF_SIZE, 0 , NULL, 0);

  수신한 자료를 출력하고 close()로 소켓을 닫아 통신을 종료하겠습니다.

printf( "%sn", buff_rcv);
close( sock);

UDP 프로그램 A

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/un.h>

#define  BUFF_SIZE   1024
#define  SOCK_LOCALFILE   "/tmp/process_a"

int   main( void)
{
   int    sock;
   size_t addr_size;
   struct sockaddr_un   local_addr;
   struct sockaddr_un   guest_addr;
   char   buff_rcv[BUFF_SIZE+5];
   char   buff_snd[BUFF_SIZE+5];

   if ( 0 == access( SOCK_LOCALFILE, F_OK))
      unlink( SOCK_LOCALFILE);

   sock  = socket( PF_FILE, SOCK_DGRAM, 0);

   if( -1 == sock)
   {
      printf( "socket 생성 실패n");
      exit( 1);
   }

   memset( &local_addr, 0, sizeof( local_addr));
   local_addr.sun_family = AF_UNIX;
   strcpy( local_addr.sun_path, SOCK_LOCALFILE);

   if( -1 == bind( sock, (struct sockaddr*)&local_addr, sizeof( local_addr)) )
   {
      printf( "bind() 실행 에러n");
      exit( 1);
   }

   while( 1)
   {
      addr_size  = sizeof( guest_addr);
      recvfrom( sock, buff_rcv, BUFF_SIZE, 0 ,
                     ( struct sockaddr*)&guest_addr, &addr_size);
      printf( "receive: %sn", buff_rcv);
      printf( "%sn", guest_addr.sun_path);     // 게스트 프로세스의 소켓용 파일 이름을 출력해 봅니다.

      sprintf( buff_snd, "%s%s", buff_rcv, buff_rcv);
      printf( "%sn", buff_snd);

      sendto( sock, buff_snd, strlen( buff_snd)+1, 0,
                     ( struct sockaddr*)&guest_addr, sizeof( guest_addr));// +1: NULL까지 포함해서 전송
   }
}

UDP 프로그램 B

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/un.h> 

#define  BUFF_SIZE   1024
#define  SOCK_LOCALFILE   "/tmp/process_b"
#define  SOCK_TARGETFILE  "/tmp/process_a"

int   main( int argc, char **argv)
{
   int    sock;
   struct sockaddr_un   local_addr;
   struct sockaddr_un   target_addr;
   char   buff_rcv[BUFF_SIZE+5];


   if ( 0 == access( SOCK_LOCALFILE, F_OK))
      unlink( SOCK_LOCALFILE);

   sock  = socket( PF_FILE, SOCK_DGRAM, 0);
   
   if( -1 == sock)
   {
      printf( "socket 생성 실패n");
      exit( 1);
   }

   memset( &local_addr, 0, sizeof( local_addr));
   local_addr.sun_family        = AF_UNIX;
   strcpy( local_addr.sun_path, SOCK_LOCALFILE);

   if( -1 == bind( sock, (struct sockaddr*)&local_addr, sizeof( local_addr)))
   {
      printf( "bind() 실행 에러n");
      exit( 1);
   }

   memset( &target_addr, 0, sizeof( target_addr));
   target_addr.sun_family        = AF_UNIX;
   strcpy( target_addr.sun_path, SOCK_TARGETFILE);

   sendto( sock, argv[1], strlen( argv[1])+1, 0, 
            ( struct sockaddr*)&target_addr, sizeof( target_addr));          // +1: NULL까지 포함해서 전송

   recvfrom( sock, buff_rcv, BUFF_SIZE, 0 , NULL, 0);

   printf( "%sn", buff_rcv);
   close( sock);
   
   return 0;
}