2011년7월12일_UDP프로토콜 소켓통신시 connect()를 사용해 서버와의 연결을 유지하기, 소켓옵션

 

● TCP와 UDP의 큰 차이점: TCP는 연결필요하나 UDP는 연결이 불 필요하다.

● UDP통신의 흐름

image

UDP통신은 TCP프로토콜과 다르게 listen( )로 소켓대기모드와 클라이언트 최대접속 갯수 설정과,
클라이언트에서 connect( )로 연결요청을 하고 accept( )로 연결요청한 클라이언트와 통신할 송수신소켓을 따로 만들지 않는다.
연결을 하지 않고 sendto( )와 recvfrom( )에 도착지의 주소와 데이터를 같이 넣어 보낸다.

Q. 그럼 UDP통신시 클라이언트에 connect( )로 연결요청을 하면 어떻게 되는 것일까?
A. 소켓을 통해 앞으로 전송 할 데이터그램의 목적지 주소를 고정하여 목적지를 지정할 필요가 없어 sendto( )대신 send( )를..
   사용하여 데이터그램을 전송할 수 있고, 마찬가지로 연결된 UDP소켓은 connect( ) 때 사용한 외부 주소와 포트를 가지는..
   단 하나의 목적지로 부터만 데이터그램 수신이 가능
하므로 recvfrom( )대신 recv( )를 사용할 수 있다.
   따라서 connect( )를 호출한 이후에는 들어오는 데이터의 송신지를 알 수가 있는 것이다.
   사실상 연결이 이루어진 후에는 connect( ) 때 지정한 주소로만 데이터의 송수신이 가능하다.
   UDP소켓의 연결이 이루어진 후에 사용한 send( )와 recv( )는 UDP의 내부 행동에 아무런 영향을 미치지 않는다.
   메시지경계는 여전히 보존되고, 데이터그램도 여전히 유실이 가능하다.
  ※ AF_UNSPEC를 주소집합체로 하여 connect( )를 호출 하면 ‘연결을 종료(disconnect)’할 수 있다.

  UDP소켓 상에서 connect( )을 호출했을 때 지나치기 쉬운 또 하나의 장점은 그 소켓에서 일어날 수 있는 이전 행위에 대한..
  에러를 확인할 수 있다는 것이다.
대표적인 예로 실제 존재하지 않는 서버나 포트에 데이터를 보낼 때 send( )를 호출하면,
  결과적으로는 에러를 발생시키지만, 에러를 반환하지 않는다. 조금 시간이 지나면 보낸 데이터그램에 문제가 생겼다는 것을..
  알리는 에러 메시지가 송신자로 도착한다. 이 데이터그램은 보통의 UDP데이터그램이 아닌 일종의 제어(control)메시지이다.
  연결이 되지 않은 소켓의 경우 원격지에 대한 주소와 포트 정보 자체가 없으므로, 시스템은 에러가 발생해도,
  어디로 에러 메세지를 보내야 할지 모르게 된다. 그러나 일단 소켓이 연결되면 시스템은 소켓 내부에에서 연관된 상대방의..
  주소와 외부 포트 정보를 확인하는 것이 가능해진다.
  여기서 주목할 것은 본인의 소켓으로 전달되는 제어 에러 메시지는 send( ) 때문이 아니라 응답을 받기 위해 호출하는 recv( )와,
  같은 그 뒤에 이어지는 시스템 함수의 호출 결과라는 점이다.

[출처] TCP/IP 소켓프로그래밍C page.91 4.4 UDP 소켓의 연결



image

UDP소켓 통신에서 connect( )를 사용하면 TCP와 다르게 연결 요청을 하지 않지만 소켓에 IP와 Port값을 할당하여,
서버와 클라이언트의 연결을 유지할 수 있다.
만약, UDP소켓 통신에서 sendto( )로 100번 전송하면 소켓과 연결을 100번 한다는 뜻이다.
       connect( )를 사용하면 100번 전송하더라도 소켓과의 연결은 1번이면 족하니 연결시간을 절약할 수 있다.(?)
      (아직 이해가 완벽히 되지 않았습니다.)


● cecho_client.c

   1:  // cecho_client.c
   2:   
   3:  #include <stdio.h>
   4:  #include <stdlib.h>
   5:  #include <string.h>
   6:  #include <unistd.h>
   7:  #include <arpa/inet.h>
   8:  #include <sys/types.h>
   9:  #include <sys/socket.h>
  10:   
  11:  int main(int argc, char *argv[])
  12:  {
  13:      int sock;
  14:      char message[100];
  15:      char message1[100];
  16:      int str_len;
  17:   
  18:      struct sockaddr_in serv_addr;
  19:   
  20:      if(3 != argc)
  21:      {
  22:          fprintf(stderr, "How to use: ./%s [IP주소] [Port번호]\n", argv[0]);
  23:          return -1;
  24:      }
  25:      
  26:      sock = socket(PF_INET, SOCK_DGRAM, 0);
  27:   
  28:      memset(&serv_addr, 0, sizeof(serv_addr));
  29:      serv_addr.sin_family = AF_INET;
  30:      serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
  31:      serv_addr.sin_port = htons(atoi(argv[2]));
  32:   
  33:      //커널과 소켓간의 연결상태 유지위해
  34:      connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
  35:      
  36:      while(1)
  37:      {
  38:          fputs("전송할 메시지를 입력하세요 (q to quit) : ", stdout);
  39:          fgets(message, sizeof(message), stdin);
  40:          
  41:          if(!strcmp(message, "q\n"))
  42:          {
  43:              break;
  44:          }
  45:   
  46:          write(sock, message, strlen(message));
  47:   
  48:          str_len = read(sock, message1, sizeof(message1) - 1);
  49:          message1[str_len] = 0;
  50:          printf("서버로부터 전송된 메시지: %s ", message1);
  51:      }
  52:   
  53:      close(sock);
  54:   
  55:      return 0;
  56:  }


● read( )의 예외처리

   1:  //read( )리턴값 예외처리
   2:  if(-1 == str_len)
   3:  {
   4:      fprintf(stderr, "수신에러!\n");
   5:      return -1;      //수신에러시 main( )의 리턴값
   6:  }

상기와 같이 read( )에 예외처리를 하면 버퍼를 두 개 선언할 필요가 없다.

<실행결과>

image <-서버

image <- 클라이언트


//분석…

 

 

 

● 소켓옵션을 알아보는 함수 getsockopt( )

   1:  #include <sys/types.h>
   2:  #include <sys/socket.h>
   3:  //성공시 0, 실패시 -1을 리턴
   4:  //소켓디스크립터, 프로토콜레벨, 확인할 옵션이름, 옵션값 저장버퍼, 옵션에 따른 버퍼크기(?)
   5:  int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);



image



● 소켓 생성 후 타입정보를 읽어오는 예제 sock_type.c

   1:  // sock_type.c
   2:   
   3:  #include <stdio.h>
   4:  #include <stdlib.h>
   5:  #include <unistd.h>
   6:  #include <arpa/inet.h>
   7:  #include <sys/types.h>
   8:  #include <sys/socket.h>
   9:   
  10:  void error_handling(char *);
  11:   
  12:  int main()
  13:  {
  14:      int tcp_sock, udp_sock;
  15:      int sock_type = -1;        //확인결과를 저장할 버퍼 초기화
  16:      socklen_t optlen;
  17:      int state;
  18:   
  19:      optlen = sizeof(sock_type);
  20:      tcp_sock = socket(PF_INET, SOCK_STREAM, 0);
  21:      udp_sock = socket(PF_INET, SOCK_DGRAM, 0);
  22:      
  23:      printf("SOCK_STREAM : %d \n", SOCK_STREAM);
  24:      printf("SOCK_DGRAM : %d \n", SOCK_DGRAM);
  25:   
  26:      state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, &sock_type, &optlen);
  27:   
  28:      if(state)
  29:      {
  30:          error_handling("getsockopt() error ");
  31:      }
  32:   
  33:      printf("첫 번째 소켓이 타입은 %d \n", sock_type);
  34:      
  35:      state = getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, &sock_type, &optlen);
  36:   
  37:      if(state)
  38:      {
  39:          error_handling("getsockopt() error ");
  40:      }
  41:   
  42:      printf("두 번째 소켓이 타입은 %d \n", sock_type);
  43:   
  44:      return 0;
  45:  }
  46:   
  47:  void error_handling(char *message)
  48:  {
  49:      fputs(message, stderr);
  50:      fputc('\n', stderr);
  51:      exit(1);
  52:  }



<실행결과>

image