Unix Domain Socket - UDP

윤 상배

dreamyun@yahoo.co.kr

   

   

1절. 소개

우리는 이미 이전에 Unix Domain Socket를 이용한 IPC 에서 Unix Domain Socket 에 대한 기본적인 내용을 다루었었다.

그때는 SOCK_STREAM 을 이용한 연결지향의 Socket 를 사용했었는데, internet 소켓과 마찬가지로 UDP 특성 Socket 를 사용할수도 있다. 이번 글 에서는 UDP 를 이용한 Unix Domain Socket 에 대해서 알아보도록 하겠다.

   

2절. Unix Domain Socket (UDP)

2.1절. 특징

internet socket 상에서 UDP 를 다룰때, 관심을 가지는 특성이 비연결지향성이며, 그런이유로 데이타를 유실할수도 있으며, 데이타의 순서가 바뀔수 있다. 그러므로 중요 데이타 통신을 위한 프로토콜로는 UDP 가 적합하지 않다. 라는 점이 될것이다.

그러나 Unix Domain 영역의 UDP 는 internet 영역과는 특성이 좀 다르다고 할수 있다. Unix Domain 에서의 UDP 는 일종의 PIPE 와 같은 형태로 다루어진다. 게다가 동일한 같은 시스템 영역에서의 데이타 교환이므로 패킷이 유실되거나, 순서가 뒤바뀌는 등의 문제는 전혀 발생하지 않을거라고 장담할수 있다.

   

2.2절. 메시지 전달 방식

internet socket 에 전달되는 패킷들이 헤더 + 바디 로 이루어지는 것과 마찬가지로, Domain 영역에서의 데이타도 헤더 + 바디 로 이루어진다. 그러나 헤더의 구성요소는 매우 다르다.

Domain 영역에서의 데이타 통신은 알겠지만 "지정된 파일" 을 통해서 이루어지게 된다. 이 파일로 데이타를 쓰고/읽는 작업을 하며, 이것은 마치 FIFO 와 같은 pipe 형태로 전달된다.

그러므로 헤더영역에는 다른 기타의 정보가 들어가는게 아닌 통신을 위한 파일정보 가 들어가게 된다. /usr/include/sys/un.h 의 sockaddr_un 구조체를 보면 확실히 이해가 가능할 것이다.

struct sockaddr_un
{
__SOCKADDR_COMMON (sun_);
char sun_path[108]; /* Path name. */
};
                        

위의 구조체를 보면, sockaddr_in 과는 달리 단지 파일의 위치 지정을 위한 108바이트 크기의 배열변수만 있는것을 알수 있다.

   

2.3절. 서버/클라이언트 구성방식

internet socket 을 이용한 서버/클라이언트 구성과는 달리 Domain 영역에서 udp 를 이용한 서버/클라이언트의 구성은 주의해야 될 몇가지 점이 있다.

가장 기본적인 문제는 Domain 영역에서의 udp 는 Internet 영역에서의 udp 와는 달리 경로를 지정 할수 없다는 단점을 가진다. 이것은 다중의 클라이언트를 받아들이는 서버의 경우 문제가 될수 있다.

다음은 Internet 영역의 udp 서버/클라이언트 모델의 경우이다.

+----------+
+------- | Client 1 | address 1
| +----------+
+------+ | +----------+
| 서버 |-+------- | Client 2 | address 2
+------+ | +----------+
| +----------+
+------- | Client 3 | address 3
+----------+
                        

Internet 영역에서의 서버는 단지 하나의 소켓지정번호(endpoint) 만을 가지고도 다중의 클라이언트와의 연결이 가능하다. 그 이유는 Internet 영역에서 통신을 할때 UDPIP 상위 프로토콜이므로 IP 프로토콜의 헤더를 통해서 해당 메시지가 어디에서 출발했는지 알수 있기 때문이다. 그러므로 굳이 select(2) 나 poll(2) 같은 작업없이 단지 하나의 소켓지정번호로 통신이 가능해진다.( UDP 프로그래밍의 기초를 참고하라.)

그러나 Unix Domain 영역의 udp 소켓이라면 이야기가 달라진다. 기본적으로 가질수 있는 정보가 "파일 경로" 뿐이므로 다중의 클라이 언트로 부터 메시지를 받았을때 어떤 클라이언트로 부터 메시지가 전달되었는지를 알수가 없는 것이다. 물론 이것은 데이타 영역에 클라이언트를 식별할수 있는 어떤 정보를 보냄으로써 해결할수 있긴 하겠지만, 설사 그렇다 하더라도 서버는 다중의 클라이언트중 원하는 클라이언트(client)로 메시지를 보낼방법이 없다. 만약 다중의 클라이언 트가 떠있는 상태에서 메시지를 보낸다면 누가 먼저 읽을지 예측할수가 없게된다.

이문제를 해결하기 위해서는 각 클라이언트 마다 통신을 위한 각각의 소켓지정파일 을 생성할수밖에 없다. 이것은 FIFO 에서 서버/다중클라이언트 를 구성하기 위한 방법과 마찬가지이다.

+----------+
+---| Client 1 |
+------+ | +----------+
| |-----+ +----------+
| 서버 |---------| Client 2 |
| |-----+ +----------+
+------+ | +----------+
+---| Client 3 |
+----------+
                        

결국에는 Domain 영역에서 udp 소켓을 사용하기 위해서는 select, poll 등을 써야만 한다는 결론에 도달한다.

여기에서 한가지 궁금한점이 문득 생길것이다. Domain 영역 UDP 소켓 에서의 다중클라이언트를 받아들이기 위한 서버의 제작을 위해서는 그럼 클라이언트의 숫자만큼 소켓파일을 생성시켜야한다는 뜻인데, 그렇다면 서버는 어떻게 클라이언트가 접근할줄 알고 클라이언트와 서버 모두가 알수 있는 소켓파일을 생성할것인가 ?

이문제의 해결방법은 여러가지가 될것이다. 마치 internet 의 TCP 소켓이 다중의 연결을 받아들이기 위해서 accept(2) 하는 하나의 endpoint 소켓을 두는 것처럼, Domain 영역에서도 하나의 endpoint 파일을 두는 것이다. 물론 이 endpoint 파일의 이름은 서버와 클라이언트간에 약속이 되어 있어야 할것이다.

그러먼 서버는 최초에 endpoint 를 위한 소켓파일을 하나 생성하고 이것을 통해서 클라이언트가 접근하면 적당한 이름으로 새로운 소켓파일을 만든다음에 이 파일이름을 클라이언트에게 전달해서 클라이언트가 전달받은 파일이름으로 쓰기를 하도록 하면 된다.

   

2.3.1절. 예상과는 달리 사용하는게 좀 번거롭군요 ?

예상과는 달리 다중의 클라이언트를 받아들이도록 Domain 영역 udp 소켓을 구성하는게 좀 까다롭다라는 생각이 들수도 있을 것이다.

그러나 어차피 내부 시스템에서의 통신용이라면 대부분의 경우 여러개의 클라이언트가 붙는다고 하더라도, 그 클라이언트가 정해져 있는 경우가 대부분이다. 그러므로 그냥 각 클라이언트 마다 어떤 파일을 통신에 쓰라고 아예 지정해주면 된다. 그러면 단지 select 나 poll 만을 돌림으로써, 간단하게 서버/클라이언트 프로그래밍이 가능하다.

또한 Domain 영역에서의 TCP 소켓과는 달리 다소 복잡한 연결과 연결대기를 위한 작업이 필요가 없으므로 코딩하기도 간편하다는 장점도 가지고 있다. 또한 패킷단위로 주고 받음으로 직관적으로 이해하기 쉬운 코드를 만들수 있다는 장점도 가진다.

   

2.4절. 예제 코드

그럼 예제 프로그램을 만들어 보도록 하겠다. UDP 프로그래밍의 기초 에서 다루었던 덧셈 프로그램인 sum_server.c 와 sum_client.c 를 Domain 영역에서 동작하도록 변경시킬 것이다. 위의 프로그램들을 참조하면서 코딩하도록 하자.

프로그램자체는 일반적인 Internet UDP 소켓 프로그램과 거의 동일함으로 이해하는데 이해가 없을것이다.

   

2.4.1절. 서버프로그램

예제 : sum_server.c

#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>
#include <fcntl.h>

struct data
{
int a;
int b;
int sum;
};
int main(int argc, char **argv)
{
int sockfd;
int clilen;
struct data mydata;
struct sockaddr_un clientaddr, serveraddr;

sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket error : ");
exit(0);
}
unlink(argv[1]);

bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path, argv[1]);

if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
perror("bind error : ");
exit(0);
}
clilen = sizeof(clientaddr);

while(1)
{
recvfrom(sockfd, (void *)&mydata, sizeof(mydata), 0, (struct sockaddr *)&clientaddr, &clilen);
printf("%d + %d = %d\n", mydata.a, mydata.b, mydata.a + mydata.b);
sendto(sockfd, (void *)&mydata, sizeof(mydata), 0, (struct sockaddr *)&clientaddr, clilen);
}
close(sockfd);
exit(0);
}
                                

   

2.4.2절. 클라이언트 프로그램

예제 : sum_client.c


// 클라이언트 아래와 같이 하면 안됩니다.
// 서버에서 보낼 때, 클라이언트가 바인드해야 정보를 받을 수 있습니다.
// 이 글을 보시는 분은 적절한 코드로 수정해 주시기 바랍니다.
// 그외 기타 간단한 오타 및 타입 오류는 제가 수정하고 갑니다. - 용운

#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>
#include <fcntl.h>

struct data
{
int a;
int b;
int sum;
};
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un serveraddr;
int clilen;
struct data mydata;

sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("exit : ");
exit(0);
}

bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path, argv[1]);
clilen = sizeof(serveraddr);

mydata.a = atoi(argv[2]);
mydata.b = atoi(argv[3]);
mydata.sum =0;

if (sendto(sockfd, (void *)&mydata, sizeof(mydata), 0,
(struct sockaddr *)&serveraddr, clilen) < 0)
{
perror("send error : ");
exit(0);
}
if (recvfrom(sockfd, (void *)&mydata, sizeof(mydata), 0,
(struct sockaddr *)&serveraddr, (socklen_t *)&clilen) < 0)
{
perror("recv error : ");
exit(0);
}
printf("result is : %d\n", mydata.sum);

close(sockfd);
exit(0);
}
                                

   

2.4.3절. 테스트

먼저 서버를 띄운다음에, 클라이언트를 통해서 더하고 싶은 2개의 숫자를 인자를 통해서 넘기면 된다.

[root@localhost src]# ./unix_domain_udp_server /tmp/test_udp
5 + 6 = 11
7 + 1024 = 1031
...
                                

[root@localhost src]# ./unix_domain_udp_client /tmp/test_udp 5 6
[root@localhost src]# ./unix_domain_udp_client /tmp/test_udp 7 1024
                                

   

3절. 결론

다른 Domain 영역 소켓과 마찬가지로, IPC 대용으로 사용가능하다. TCP 소켓이 IPC 대용으로 사용하기가 비교적 복잡하다는 단점과, 오늘 알아본 UDP 도메인 소켓과 아주 비슷한 FIFO 가 범용성이 좀더 떨어지는 단점을 보완한 적당하고 편리하게 사용가능한 IPC 로써의 용도를 제공해 줄것이다.

   [출처]<http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/system_programing/IPC/Unix_Domain_Socket_UDP>에서 삽입

반응형

+ Recent posts