문서 정보
  • Archive-name: unix-faq/socket
  • Posting-Frequency: monthly
  • 최근 수정일: 1997/12/21
  • URL: http://www.auroraonline.com/sock-faq/
  • 한글판작성자: 안창선(csan@coresw.co.kr, http://genesis.yonsei.ac.kr/~kabin)
  1. UDP/SOCK_DGRAM 어플리케이션 작성
    1. TCP대신에 UDP는 언제 사용하는가?
    2. "connected" 소켓과 "unconnected"소켓의 차이는 무엇인가?
    3. connect() 호출이 소켓의 수신에 영향을 주는가?
    4. "connected" UPD 소켓으로부터 어떻게 하면 ICMP error를 읽을 수 있는가?
    5. 어떻게 UDP메시지가 수신 되었다는 것을 확신 할 수 있는가?
    6. UDP 메시지가 순서대로 도착했음을 확신할 수 있는가?
    7. 얼마나 자주 un-ack된 메시지에대해서 재전송 해야 하는가?
    8. How come only the first part of my datagram is getting
      through?

5. UDP/SOCK_DGRAM 어플리케이션 작성

5.1. 언제 TCP대신 UDP를 사용해야 하는가?

UDP는 다른 시스템으로 가게 되는 메시지의 순서가 중요하지 않을 경우에 메시지를 한 시스템에서 다른 시스템으로 전송하는 좋은 방법이다. 이것은 이것은 faq에서 내가 예제 소소코드에서 UDP는 한 번만 사용한 이유이다. 보통 TCP가 더 좋은 솔루션이다. 그것은 메시지가 다른 호스트로 전달 된 것에 대한 신뢰를 줄 수 있고 순서에도 확신을 할 수 있게 전달 되었는지를 확인하는 소스코드의 추가 작성을 줄여 준다. 꼭 알고 있어야 하는 것은 소스코드의 길이가 늘어나면 늘어날수록 버그는 증가하게 된다는 것이다.

만약 TCP이 여러분이 바라는 것보다 무척 드리다는 것을 발견하게 되면 UDP를 사용하면 좀 더 나은 성능을 얻을 수 있다. 물론 메시지 순서와 신뢰성은 좀 희생 될 것이다.

UDP는 동시에 한 시스템 이상에게 메시지를 전달하는 멀티캐스트 메시지로 사용될 수 있다. TCP에서 어플리케이션은 목적지 시스템 각각에 대해 연결을 설정해야 하고 각각의 시스템에 메시지를 보내야 한다. 이것은 여러분의 어플리케이션이 오직 여러분이 알고 있는 시스템에게만 메시지를 전달 할 수 있다는 것을 의미한다.

5.2. "connected" 소켓과 "unconnected"소켓의 차이는 무엇인가?

Andrew Gierth (andrew@erlenstar.demon.co.uk):

만약 이용가능한 목적지 주소가 없어서, bind()호출 후에 일반적인 상태인 UDP소켓이 unconnect된 상태는 send()또는 write()가 허용되지 않고 오직 sendto()호출에 의해서만 데이터를 전송할 수 있다.

소켓에서 connect()를 호출하는 것은 지정된 주소 레코드와 포트번호를 원하는 통신 파트너로 변환된다. 그것은 send() 또는 write()가 허용되지 않는다는 것을 의미한다. 그들은 connect호출에 의해서 주어진 목적 주소지와 포트를 사용한다.

5.3. connect() 호출이 소켓의 수신에 영향을 주는가?

Richard Stevens (rstevens@noao.edu):

그렇다 두가지다. 첫 번째, 당신의 "connected peer"로부터 오는 데이터그램은 리턴된다. 모든 당신의 포트에 다른 도착하는 것들 모두는 당신에게 전달되지 않는다.

그러나 가장 중요한 것은, UDP소켓은 ICMP 에러를 받기 위해 연결되어야만 한다는 것이다. "TCP/IP Illustrated, Volume 2"의 Pp. 748-749에 보면 왜 이것이 이런지 설명하고 있다.

5.4. "connected" UPD 소켓으로부터 어떻게 하면 ICMP error를 읽을 수 있는가?

만약 상대 기계가 메시지를 버리게 되면(요청된 포트 번호를 읽는 프로세스가 없어서) 그 서버는 당신의 서버에 ICMP메시지를 보내서 그 소켓에서 동작하는 다음 시스템 호출이 ECONNREFUSED를 리턴하도록 만든다.

기억할 것은 당신의 소켓은 ICMP에러를 받기 위해서는 "connected"되어 있어야 한다는 것이다. 예전에 얘기 했지만, Alan Cox는 리눅스에서는 "unconnected"된 소켓에서도 리턴 하는 것을 확인 했다는데, 이것은 당신의 어플리케이션에 포팅 문제를 일으킬 수 있다. 따라서 그런 어플리케이션에는 리눅스 커널 2.0.0 이후 버전을 위해 그것에 SO_BSDCOMPAT flag를 설정하도록 하고 있다.

5.5. 어떻게 UDP메시지가 수신 되었다는 것을 확신 할 수 있는가?

당신은 상대방이 메시지를 수신했음에 대한 confirm을 받을 수 있도록 당신의 프로토콜을 디자인 해야 한다. 물론 confirm은 UDP에 의해서 전송된다. 그리고 그것도 신뢰성이 없는 것이고 송신자로 부터 수신자에게 제대로 전달된다는 보장을 못한다. 만약 전송자가 일정시간 동안 confirm을 받지 못하면 최소 한 번 이상 그 메시지를 재전송해야 한다. 그리고 재전송에 따른 복제된 데이터를 원래 데이터 다음에 다시 받게되는 경우 그 복제된 데이터는 무시 할 수 있도록 구현 되어야 한다. 대부분의 프로토콜은 이미 메시지를 받았는지를 알아내기 위해 메시지 넘버링 체계를 가지고 있다. confirmation은 또한 전송자가 어느 메시지가 confirm되었는지 알 수 있기 위해 그 메시지 번호를 참조해야 한다. 정신 없다고? 이래서 TCP를 쓰는 거다.

5.6. UDP 메시지가 순서대로 도착했음을 확신할 수 있는가?

할 수 없다. 할 수 있는건 메시지가 순서대로 처리 되었는지만을 알 수가 있다. (5.5를 보라) 만약 메시지가 순서대로 도착하는 것을 확신할 수 있어야 한다면 TCP를 쓰라. 그게 훨씬 시간도 절약해 주고 프로그램을 신뢰성 있게 할 수가 있다.

5.7. 얼마나 자주 un-ack된 메시지에대해서 재전송 해야 하는가?

해야할 가장 간단한 것은 적당하게 작은 지연시간(약 1초 정도)를 갖는 것이다. 이 문제는 이것이 여러분의 네트웍이 랜 또는 다른 기계 등의 문제가 있을 경우에 더 큰 트래픽 문제를 일으킬 수 있다.

더 좋은 기법은 "UNIX Network Programming"책에 기술되어 있는데, 적당한 timeout를 잠재적인 backoff(포기)로 사용하는 것이다. 이 기법은 호스트에 도달고자 하는 메시지와 그에 따른 타임아웃 조정을 획득하는 시간의 통계정보를 유지시켜준다. 그것은 또한 불필요한 데이타그램으로 네트웍이 넘치지 않도록 하기위한 각 타임아웃 시간을 두배로 만든다. Richard는 친절하게도 이것에 관한 소스코드를 다음의 웹사이트에 등록해 놓았다.

http://www.noao.edu/~rstevens.

5.8. 왜 데이터 그램의 첫 부분만 들어오고 있나?

이것은 두 연관된 기계에서 최대 데이터그램 크기를 가지고 해봐야 한다. 이것은 관련된 시스템과 MTU(Maximum Transmission Unit)에 의존되는 것이다. "Unix Network Programming"책에 의하면 MTU의 크기에 상관 없이 모든 TCP/IP 구현들은 데이터그램 사이즈가 최소 576바이트이어야 한다고 되어 있다. 20바이트의 IP헤더 그리고 8바이트의 UDP헤더, 그리고 나머지 548바이트는 UDP메시지의 안전한 최대 크기이다. 최대 크기는 65516바이트이다. 몇몇 플랫폼에서는 IP 조각을 허용하여 이것에 의하여 데이터 그램이 조각나고(MTU값 때문에) 그리고나서 상대쪽에서 가서 다시 재조립된다. 그러나 모든 implementation들이 이것을 지원하는 것은 아니다.

Andrew는 다음의 큰 UDP메시지에 관해서 눈을 돌리고 있다.

다른 이슈는 조각(fragmentation)이다. 만약 데이터그램이 네트웍 인터페이스(랜카드)를 통해 전송하기에는 너무 크면 전송 호스트는 이것을 조각내서 상대쪽 호스트에서 재조립하도록 한다.

그리고 만약 그 호스트 사이에 라우터가 있다고 하면 그들은 또한 하나 또는 그 이상의 조각들을 잃게 될 수 있다.(이렇게 되면 전체 데이터그램이 상실된다.) 그러므로 인터넷의 여러 라우터를 거칠 가능성이 있는 어플리케이션에서는 큰 UDP메시지는 왠만하면 피해야 한다.

5.9. 기대했던 것 보다 빨리 소켓의 버퍼가 꽉 차는데 왜 그런가?

Paul W. Nelson (nelson@thursby.com):

전통적인 BSD소켓시스템에서 UDP처럼 작은 소켓은 수신된 데이타를 mbufs의 리스트에 저장한다. mbuf는 다양한 프로토콜 스택에 의해 공유된 고정된 크기의 버퍼이다. 수신 버퍼 크기를 설정할때, 프로토콜 스택은 실제 바이트수의 수가 아닌 mbuf공간의 바이트를 수신 버퍼에 유지하고 있다. 이 방법은 정말 여러분이 제어하고 있는 자원들이 소켓 버퍼에 얼마나 많이 포함되어 있는지가 아니라 얼마나 많은 mbuf가 사용 되는지를 제어함을 위한 것이다. (소켓 버퍼는 전통적인 의미의 버퍼가 아니다. 이것은 mbuf의 리스트이다.)

예를 들어: 여러분의 유닉스에 mbuf 크기가 256바이트 의 작은 크기라고 하자. 만약 수신 소켓버퍼가 4096으로 설정되어 있다면, 여러분은 소켓 버퍼위에 16 mubf를 고정시킬 수 있다. 만약 각각이 10바이트인 16개의 UDP 패킷을 받았다고 하면 여러분의 소켓 버퍼는 full이 될 것이다. 그리고 여러분은 160바이트의 데이터를 갖게 될 것이다. 만약 각각 200바이트인 16개의 UDP패킷을 받았다고 하면 여러분의 소켓 버퍼는 역시 full이 될 것이지만 3200바이트의 데이터를 갖게된다. FIONREAD는 메시지 또는 mbuf의 바이트 수가 아닌 총 바이트 수를 리턴한다. 이렇기 때문에 이것은 여러분의 수신버퍼가 어느정도 차 있는지를 측정하는 좋은 지시자는 못된다.

게다가, 만약 여러분이 260바이트의 UDP메시지를 받으면, 여러분은 두개까지 mbuf를 사용할 수 있고 버퍼가 가득차기 전에 오직 8개의 패킷을 받을 수 있다. 이 경우 4096 바이트 중에서 2080바이트만이 소켓 버퍼에 들어가게 된다.

이 예는 아주 간단한 것이다. 그리고 실제 소켓 버퍼 알고리즘은 또한 몇가지 다른 파라메터를 포함하고 있다. 예전의 몇몇 소켓 시스템은 128바이트의 mbuf를 사용했음을 주지하라.