설명

recvfrom() 함수는 UDP/IP 통신에서 소켓으로부터 데이터를 수신합니다.

헤더 #include <sys/types.h>
#include
<sys/socket.h>
형태 int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
인수
int s : 소켓 디스크립터
void *buf : 자료 수신을 위한 버퍼 포인터
size_t len : 버퍼의 바이트 단위 길이
int flags : 수신을 위한 옵션으로 아래의 값을 사용할 수 있습니다.
flags 옵션 설명
MSG_OOB SOCK_STREAM에만 사용되며 out-of-band 데이터로 전송될 수 있음을 나타냅니다.
MSG_DONTROUTE
데이터는 라우팅될 수 없음으로 지정합니다.
MSG_DONTWAIT NONE BLOCKING 통신이 가능하도록 합니다.
MSG_NOSIGNAL 상대방과 연결이 끊겼을 때, SIGPIPE 시그널을 받지 않도록 합니다.
sockaddr *to : 전송한 곳의 주소 정보
socklen_t tolen : 주조 정보의 크기
반환
-1 이외 : 실제 수신한 바이트 수
-1 : 실패

이번에는 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 예제 소개

평상 시에도 자료를 수신할 수 있는 프로그램과 자료 전송만을 준비한 프로그램으로 나누어 올려 보겠습니다.

  1. 두 프로그램 모두 port 4000번을 사용하며
  2. 자료를 먼저 송신하는 쪽에서 임의의 문자를 전송하면
  3. 수신하는 쪽은 수신된 자료를 겹쳐서 전송하고
  4. 다시 송신하는 쪽에서 수신한 후 수신된 자료를 화면에 출력하겠습니다.

자료 수신 프로그램

자료 수신이 가능한 프로그램의 함수 사용 순서는 아래와 같습니다.

우선 socket 부터 만들어야 합니다. TCP/IP에서는 SOCK_STREAM을 UDP/IP에서는 SOCK_DGRAM을 사용하는 것을 참고하여 주십시오.

int     sock;

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

bind() 함수를 이용하여 커널에 등록합니다.
  1. 만들어진 sock 은 단지 socket 디스크립터일 뿐입니다.
  2. 이 socket에 주소를 할당하고 port 번호를 할당해서 커널에 등록해야 합니다.
  3. 커널에 등록하면 클라이언트로부터 자료를 수신할 수 있는 귀를 달아 놓는 상태가 됩니다.
  4. socket에 주소와 port 를 할당하기 위해 sockaddr_in 구조체를 이용합니다.
  5. 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);
    }

  6. htonl( INADDR_ANY) 는 주소를 지정해 주는 것으로 inet_addr( "내 시스템의 IP ")로도 지정할 수 있습니다. 그러나 프로그램이 실행되는 시스템 마다 IP 가 다를 것이므로 주소 지정을 고정 IP로 하지 않고 htonl( INADDR_ANY) 를 사용하는 것이 편리합니다.

이제 socket 을 커널에 등록했으므로 recvfrom() 또는 sendto()를 이용하여 자료를 송수신할 수 잇습니다. recvfrom() 함수를 이용하여 클라이언트로부터 전송되어 오는 자료를 읽어 들입니다.

  1. recvfrom()을 이용하여 송신 측의 주소 정보를 받아 오기 위해 변수를 선언합니다.
  2. struct sockaddr_in client_addr;
    int                client_addr_size;

  3. recvfrom()으로 자료를 수신합니다.
  4. client_addr_size = sizeof( client_addr);
    recvfrom( sock, buff_rcv, BUFF_SIZE, 0 ,
                ( struct sockaddr*)&client_addr, &client_addr_size);

  5. 수신한 자료가 있다면 자료는 buff 에 저장되며, client_addr에 송신측의 주소 정보가 담기게 됩니다.
  6. client_addr를 이용하여 송신한 측으로 자료를 전송할 수 있습니다.
sendto() 함수를 이용하여 데이터를 전송합니다.

  1. 전송 데이터를 준비합니다.
  2. sprintf( buff_snd, "%s%s", buff_rcv, buff_rcv);

  3. sendto() 를 이용하여 클라이언트로 자료를 송신합니다.

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

  4. 위의 작업을 반복합니다.

자료 전송 프로그램

자료를 전송하는 쪽은 bind() 함수를 사용할 필요가 없으며, bind()함수를 사용하지 않았기 때문에 다른 시스템에서 전송하여도 전송 사실 자체를 알 수가 없습니다. 바로 설명 들어갑니다.

socket() 을 이용하여 소켓을 먼저 생성합니다.

int    sock;

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

컨널에 socket 을 등록할 필요 없이 sendto()로 자료를 송신할 수 있습니다.

  1. 상대 시스템이 실행 중인지는 모릅니다. 그러나 전송할 수 있습니다.
  2. 상대 시스템의 주소 정보를 준비합니다.
  3. 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");

  1. sendto() 함수로 데이터를 전송합니다.

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

sendto() 함수를 사용하여 커널에 소켓이 등록되므로, 이번에는 recvfrom() 함수를 이용하여 상대로부터 자료를 읽어 들일 수 있습니다.
  1. recvfrom()을 이용하여 송신 측의 주소 정보를 받아 오기 위해 변수를 선언합니다.
  2. struct sockaddr_in server_addr;
    int                server_addr_size;

  3. recvfrom()으로 자료를 수신합니다.
  4. server_addr_size= sizeof(server_addr);
    recvfrom( sock, buff_rcv, BUFF_SIZE, 0 ,
                ( struct sockaddr*)&server_addr, &server_addr_size);

  5. 수신한 자료가 있다면 자료는 buff 에 저장됩니다.
  1. 수신된 자료를 화면에 출력하고 종료합니다.

    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;
}