내부 프로세스들 끼리의 통신을 위한 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; }
질문 부탁드립니다.
UDS 이용하여 프로그램을 작성하였는데,,,아래와 같이 컴파일 에러가 나는데 원인을 모르겠네요,,
고수님의 답변좀 부탁드립니다.
현재 컴파일 서버는 HP-UX evtdb1 B.11.31 U ia64 4294967295 unlimited-user license에서 하였구요
에러는 아래와 같이 나오네요
"UdsServer.c", line 30: error #2020: identifier "PF_FILE" is undefined
server_socket = socket( PF_FILE, SOCK_STREAM, 0 );
^
1 error detected in the compilation of "UdsServer.c".
PF_FILE이 정의가 안되었다구 하는데,,, sys/socket.h에 정의 되어 있는걸로 알고 있어 include를 당근 했죠~
그외 아래와 같이 헤더 파일을 include하였씁니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/un.h>
원인이 뭘까요~?