이번에는 UDP/IP 소켓 프로그램을 작성하는 방법입니다.
UDP/IP는 TCP/IP 처럼 클라이언트와 서버로 나뉘고 1 대 다수의 통신 방식은 아닙니다. 그냥 목적 시스템으로 자료를 전송하면 되는데, 문제는 UDP/IP를 이용하여 자료를 수신 받아야 하는 쪽은 받을 준비를 해야 합니다.
뭐, 당연한 말 아니냐? 하시겠습니다만 socket 을 만들었다고 socket을 통해 자료를 받을 수 없습니다. 생성한 socket을 커널에 올려 놓아야 커널이 socket 을 이용하여 상대방으로부터 전송된 데이터를 수신할 수 있습니다. 그러므로 처음 통신을 할 때, 자료 수신을 먼저 해야 하는 곳은 받드시 bind()를 사용해야 합니다.
UDP/IP 통신 함수 사용 순서
UDP/IP 통신에서는 TCP/IP와는 달리 read()와 write()를 사용하지 않고 recvform()과 sendto()함수를 이용하여 자료를 송수신합니다. sendto()함수를 대신 사용하는 이유는 전송할 목적지를 지정할 수 있기 때문이며, recvform()함수를 사용하는 이유는 수신되는 자료 외에도 송신지의 정보를 함께 얻을 수 있기 때문입니다.
그러므로 TCP/IP처럼 한번 연결되면 연결된 시스템과 자료를 주고 받지만 UDP/IP는 자유롭게 시스템 주소를 바꾸어 가면서 자료를 송수신 할 수 있습니다.
책에서는 TCP/IP를 전화기로 UDP/IP는 우체통으로 비유하는 경우가 많습니다. 우체통으로 비유한 만큼 UDP/IP는 우체통 하나로 여러 사람과 편지를 교환하듯 소켓 하나로 자유롭게 여러 시스템에게 자료를 송/수신할 수 있습니다.
또한 UDP/IP에서도 connect() 함수를 사용하여 read()와 write() 함수를 사용할 수 있습니다. 그러나 여기서는 다루지 않겠습니다.
UCP/IP 예제 소개
평상 시에도 자료를 수신할 수 있는 프로그램과 자료 전송만을 준비한 프로그램으로 나누어 올려 보겠습니다.
- 두 프로그램 모두 port 4000번을 사용하며
- 자료를 먼저 송신하는 쪽에서 임의의 문자를 전송하면
- 수신하는 쪽은 수신된 자료를 겹쳐서 전송하고
- 다시 송신하는 쪽에서 수신한 후 수신된 자료를 화면에 출력하겠습니다.
자료 수신 프로그램
자료 수신이 가능한 프로그램의 함수 사용 순서는 아래와 같습니다.
우선 socket 부터 만들어야 합니다. TCP/IP에서는 SOCK_STREAM을 UDP/IP에서는 SOCK_DGRAM을 사용하는 것을 참고하여 주십시오.
bind() 함수를 이용하여 커널에 등록합니다.int sock;
sock = socket( PF_INET, SOCK_DGRAM, 0);
if (-1 == sock)
{
printf( "socket 생성 실패");
exit( 1) ;
}
- 만들어진 sock 은 단지 socket 디스크립터일 뿐입니다.
- 이 socket에 주소를 할당하고 port 번호를 할당해서 커널에 등록해야 합니다.
- 커널에 등록하면 클라이언트로부터 자료를 수신할 수 있는 귀를 달아 놓는 상태가 됩니다.
- socket에 주소와 port 를 할당하기 위해 sockaddr_in 구조체를 이용합니다.
- htonl( INADDR_ANY) 는 주소를 지정해 주는 것으로 inet_addr( "내 시스템의 IP ")로도 지정할 수 있습니다. 그러나 프로그램이 실행되는 시스템 마다 IP 가 다를 것이므로 주소 지정을 고정 IP로 하지 않고 htonl( INADDR_ANY) 를 사용하는 것이 편리합니다.
struct sockaddr_in server_addr;
memset( &server_addr, 0, sizeof( server_addr);
server_addr.sin_family = PF_INET; // IPv4 인터넷 프로토롤
server_addr.sin_port = htons( 4000); // 사용할 port 번호는 4000
server_addr.sin_addr.s_addr = htonl( INADDR_ANY); // 32bit IPV4 주소
if( -1 == bind( sock, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
{
printf( "bind() 실행 에러n");
exit( 1);
}
- recvfrom()을 이용하여 송신 측의 주소 정보를 받아 오기 위해 변수를 선언합니다.
- recvfrom()으로 자료를 수신합니다.
- 수신한 자료가 있다면 자료는 buff 에 저장되며, client_addr에 송신측의 주소 정보가 담기게 됩니다.
- client_addr를 이용하여 송신한 측으로 자료를 전송할 수 있습니다.
struct sockaddr_in client_addr;
int client_addr_size;
client_addr_size = sizeof( client_addr);
recvfrom( sock, buff_rcv, BUFF_SIZE, 0 ,
( struct sockaddr*)&client_addr, &client_addr_size);
- 전송 데이터를 준비합니다.
- sendto() 를 이용하여 클라이언트로 자료를 송신합니다.
sendto( sock, buff_snd, strlen( buff_snd)+1, 0,
( struct sockaddr*)&client_addr, sizeof( client_addr)); // +1: NULL까지 포함해서 전송 - 위의 작업을 반복합니다.
sprintf( buff_snd, "%s%s", buff_rcv, buff_rcv);
자료 전송 프로그램
자료를 전송하는 쪽은 bind() 함수를 사용할 필요가 없으며, bind()함수를 사용하지 않았기 때문에 다른 시스템에서 전송하여도 전송 사실 자체를 알 수가 없습니다. 바로 설명 들어갑니다.
socket() 을 이용하여 소켓을 먼저 생성합니다.컨널에 socket 을 등록할 필요 없이 sendto()로 자료를 송신할 수 있습니다.int sock;
sock= socket( PF_INET, SOCK_DGRAM, 0);
if( -1 ==sock)
{
printf( "socket 생성 실패n");
exit( 1);
}
- 상대 시스템이 실행 중인지는 모릅니다. 그러나 전송할 수 있습니다.
- 상대 시스템의 주소 정보를 준비합니다.
struct sockaddr_in server_addr;
memset( &server_addr, 0, sizeof( server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons( 4000);
server_addr.sin_addr.s_addr= inet_addr( "127.0.0.1");
- sendto() 함수로 데이터를 전송합니다.
sendto( sock, argv[1], strlen( argv[1])+1, 0,
( struct sockaddr*)&server_addr, sizeof( server_addr)); // +1: NULL까지 포함해서 전송
- recvfrom()을 이용하여 송신 측의 주소 정보를 받아 오기 위해 변수를 선언합니다.
- recvfrom()으로 자료를 수신합니다.
- 수신한 자료가 있다면 자료는 buff 에 저장됩니다.
struct sockaddr_in server_addr;
int server_addr_size;
server_addr_size= sizeof(server_addr);
recvfrom( sock, buff_rcv, BUFF_SIZE, 0 ,
( struct sockaddr*)&server_addr, &server_addr_size);
- 수신된 자료를 화면에 출력하고 종료합니다.
printf( "receive: %sn", buff_rcv);
close( sock);
return 0;
자료를 수신하는 쪽의 프로그램 소스
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #define BUFF_SIZE 1024 int main( void) { int sock; int client_addr_size; struct sockaddr_in server_addr; struct sockaddr_in client_addr; char buff_rcv[BUFF_SIZE+5]; char buff_snd[BUFF_SIZE+5]; sock = socket( PF_INET, SOCK_DGRAM, 0); if( -1 == sock) { printf( "socket 생성 실패n"); exit( 1); } memset( &server_addr, 0, sizeof( server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons( 4000); server_addr.sin_addr.s_addr= htonl( INADDR_ANY); if( -1 == bind( sock, (struct sockaddr*)&server_addr, sizeof( server_addr) ) ) { printf( "bind() 실행 에러n"); exit( 1); } while( 1) { client_addr_size = sizeof( client_addr); recvfrom( sock, buff_rcv, BUFF_SIZE, 0 , ( struct sockaddr*)&client_addr, &client_addr_size); printf( "receive: %sn", buff_rcv); sprintf( buff_snd, "%s%s", buff_rcv, buff_rcv); sendto( sock, buff_snd, strlen( buff_snd)+1, 0, // +1: NULL까지 포함해서 전송 ( struct sockaddr*)&client_addr, sizeof( client_addr));
} }
자료를 송신하는 쪽의 프로그램 소스
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#define BUFF_SIZE 1024
int main( int argc, char **argv)
{
int sock;
int server_addr_size;
struct sockaddr_in server_addr;
char buff_rcv[BUFF_SIZE+5];
sock = socket( PF_INET, SOCK_DGRAM, 0);
if( -1 == sock)
{
printf( "socket 생성 실패n");
exit( 1);
}
memset( &server_addr, 0, sizeof( server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons( 4000);
server_addr.sin_addr.s_addr= inet_addr( "127.0.0.1");
sendto( sock, argv[1], strlen( argv[1])+1, 0, // +1: NULL까지 포함해서 전송
( struct sockaddr*)&server_addr, sizeof( server_addr));
server_addr_size = sizeof( server_addr);
recvfrom( sock, buff_rcv, BUFF_SIZE, 0 ,
( struct sockaddr*)&server_addr, &server_addr_size);
printf( "receive: %sn", buff_rcv);
close( sock);
return 0;
}