'2012/04/12'에 해당되는 글 1건

  1. 2012.04.12 UDP서버-클라이언트 by 초코송송이

1.UDP 프로토콜개요

TCP와 UDP의 공통점: 전송 계층 프로토콜이라는 점에서 다음과 같은 공통점이 있다.

- 포트번호를 이용해 주소를 지정한다.

- 데이터 오류를 체크한다. : TCP와 UDP는 IP의 패킷 전송 기능을 기반으로 동작한다. 전송 중 여러 원인으로 오류가 발생할 수 있는데, IP는 프로토콜 동작에 필수적인 IP 헤더에 대해서만 오류를 체크하고 데이터는 체크하지 않는다. 반면 TCP와 UDP는 헤더는 물론이고 데이터에 대한 오류도 체크한다.

TCP와 UDP의 차이점 

항목 

TCP 

UDP 

 1)

연결형(connection-oriented) 프로토콜

- 연결설정 후 통신 가능

비연결형(connectionless) 프로토콜

- 연결설정 없이 통신 가능 

 2)

신뢰성 있는 데이터 전송  신뢰성 없는 데이터 전송 
 3) 일대일 통신(unicast) 

일대일 통신(unicast), 일대다 통신(broadcast, multicast) 

 4)

데이터 경계 구분 안함

- 바이트 스트림(byte-stream) 서비스

데이터 경계 구분함

- 데이터그램(datagram) 서비스 

항목을 UDP 위주로 설명하면,

1) 연결 설정을 하지 않았으므로 connect() 함수를 사용하지 않는다. (몇가지 이유로 connect() 함수를 사용하는 경우가 있지만, UDP 프로토콜에는 연결 설정 개념이 없으므로 connect() 함수를 호출하더라도 특별한 패킷 교환이 일어나지 않는다.

2) 프로토콜 수준에서 신뢰성 있는 데이터 전송을 보장하지 않으므로, 필요하다면 응용 프로그램 수준에서 신뢰성 있는 데이터 전송 기능을 구현해야 한다.

3) 간단한 소켓 함수 호출 절차만 따르면 다자 간 통신을 쉽게 구현할 수 있다.

4) TCP와 달리 응용 프로그램이 데이터 경계 구분을 위한 작업을 별도로 할 필요가 없다.

 

※ UDP에 대한 오해

UDP는 신뢰성 없는 데이터 전송을 하므로 데이터 오류를 체크하지 않을 것이라고 생각하면 안된다. UDP는 TCP처럼 체크섬(checksum)을 이용해 데이터 오류를 체크한다. 그렇다면 TCP는 신뢰성 있는 데이터 전송을 하는데 왜 UDP는 그렇지 못 할까? 답은 UDP에서 데이터 재전송과 데이터 순서 유지 작업을 하지 않기 때문이다. UDP는 도착한 데이터에 오류가 있다고 판단하면 이 데이터를 응용 프로그램에 전달하지 않고 그대로 삭제해 버린다. 따라서 응용 프로그램은 데이터에 오류가 있어 버려졌다는 사실을 알지 못한다. 또한 TCP는 데이터 순서 유지를 위해 각 바이트마다 번호를 부여하지만 UDP는 비슷한 기능을 제공하지 않는다.

 

2. UDP 서버-클라이언트 동작 원리

UDP서버는 TCP 서버와 달리 멀티스레드 등의 프로그래밍 기법을 사용하지 않고도 한 소켓으로 여러 클라이언트를 처리할 수있다.

 (a) 서버는 소켓을 생성하고 클라이언트가 데이터를 보내기를 기다린다. 이 때 서버가 사용하는 소켓은 특정 포트 번호와 결합되어 이 포트번호로 도착한 데이터만 수신한다.

(b) 첫 번째 클라이언트는 연결 설정 없이 서버와 데이터를 바로 주고 받는다.

(c) 두 번째 클라이언트도 연결 설정 없이 서버와 데이터를 바로 주고 받는다. (TCP 서버와 달리 UDP서버는 소켓을 한개만 사용한다.)

(d) UDP 서버-클라이언트가 통신하는 일반적인 상황이다. 'UDP 클라이언트 #n' 처럼 한 클라이언트가 소켓을 두개이상 사용해 서버와 통신할 수 도 있다.

 

※ UDP서버-클라이언트모델(1)

 

주의 사항

- 블로킹 소켓을 사용할 경우, 송수신 함수의 호출 순서가 맞지 않으면 교착상태가 발생할 수 있다.

- 클라이언트는 데이터를 받은 후 송신자의 주소(IP주소,포트번호)를 확인해야 한다. recvfrom() 함수는 UDP 서버가 보낸 데이터는 물론, 전혀 다른 UDP 응용 프로그램이 보낸 데이터도 수신할 수 있기 때문이다.

 

※ UDP 서버-클라이언트모델(2)

=> UDP 클라이언트를 위와 같이 작성할 수도 있다. UDP 소켓에 대해 connect() 함수를 호출하면 통신할 상대의 주소 정보가 내부적으로 기억되므로 sendto()/recvfrom() 함수 대신 send()/recv() 함수를 사용해 특정 UDP 서버와 통신할 수있다.

장점

- sendto() 함수를 사용한 경우보다 효율적이다. connect() 함수로 서버주소를 한 번만 설정해 두면, send() 함수가 이 정보를 재사용하기 때문이다.

- 데이터를 받은 후 송신자의 주소(IP, 포트)를 확인하지 않아도 된다. recvfrom() 함수와 달리 recv() 함수는 connect() 함수로 설정한 대상을 제외한 다른 UDP 응용 프로그램이 보낸 데이터는 수신하지 않기 때문인다.

주의사항

- 블로킹 소켓을 사용할 경우, 송수신 함수의 호출 순서가 맞지 않으면 교착 상태가 발생할 수 있다.

 

※ UDP 데이터 전송 함수

1. sendto() 함수

1) 함수 원형

int sendto(

 SOCKET s, //통신에 사용할 소켓

 const char *buf, //보낼 데이터를 담고 있는 버퍼의 주소

 int len, // 보낼 데이터의 크기

 int flags, // sendto()함수의 옵션. 대부분 0을 사용. MSG_DONTROUTE(윈속에서는 사용하더라도 무시), MSG_OOB(UDP에서는 의미가 없음)가 있음

 const struct sockaddr *to, // 목적지 주소를 담고 있는 소켓 주소 구조체(UDP의 경우, 특정 호스트나 라우터 주소, 브로드캐스트나 멀티캐스트 주소를 사용할 수 있음)

 int tolen // 목적지 주소를 담고 있는 소켓 주소 구조체의 크기

);

2) sendto() 함수의 특징

sendto() 함수는 UDP 소켓은 물론, TCP 소켓에도 사용가능 하다. TCP 일때는 to와 tolen인자는 무시된다. 그리고 TCP 소켓에 사용할 때만, flags 인자에 MSG_OOB를 사용할 수 있다.

sendto() 함수로 보낸 데이터는 독립적인 UDP 데이터그램(패킷)으로 만들어져 전송되며, 수신측에서는 recvfrom() 함수 호출 한번으로 이 데이터를 읽을 수 있다. 따라서 UDP를 사용할 경우에는 TCP와 달리 응용 프로그램 수준에서 메시지 경계를 구분하는 작업을 할 필요가 없다.

UDP 소켓에 대해 sendto()함수를 호출할 경우, 한번에 보낼수 있는 데이터의 크기에 제한이 있다. 최소 0~최대 65507(65535-20(IP헤더크기)-8(UDP헤더크기)) 바이트이다. 실제로는 최대값보다 훨씬 작은 크기를 사용하는것이 바람직하다. 특히 UDP를 이용해 브로드캐스트 패킷을 보낼 경우, 512바이트보다 작은 크기를 사용할 것을 권고한다.(비주얼스트디오 설명서)

sendto()함수로 보낸 응용 프로그램 데이터는 커널(운영체제)영역에 복사되어 전송된 후 곧바로 버려진다. sendto() 함수가 리턴됐다고 실제 데이터 전송이 완료된 것은 아니며, 데이터 전송이 끝났어도 상대방이 받았는지 확인할 수 없다.

블로킹 소켓을 사용할 경우, 커널 영역에 데이터를 복사할 공간이 부족하면, sendto()함수는 호출 시 블록된다.

2. recvfrom() 함수

1) 함수 원형

int recvfrom(

 SOCKET s, // 통신에 사용할 소켓. sendto()함수에 사용하는 소켓과 달리, 이 소켓은 반드시 지역주소(IP, 포트)가 미리 결정되어 있어야 한다.

 char *buf, // 받은 데이터를 저장할 버퍼의 주소

 int len, // 버퍼의 크기. 도착한 UDP 패킷 데이터가 len보다 크면 len 만큼만 복사하고 나머지는 버린다. 이때 SOCKET_ERROR를 리턴한다.

 int flags, // 대부분 0을 사용. 사용가능한 값으로 MSG_PEEK, MSG_OOB(UDP에서는 의미가 없음)가 있다. recvfrom() 함수의 기본 동작은 수신버퍼의 데이터를 응용프로그램 버퍼에 복사한 후 해당 데이터를 수신버퍼에서 삭제하는 것인데, MSG_PEEK 옵션을 사용하면 수신 버퍼에 데이터가 계속 남는다.

 struct sockaddr *from, // 소켓 주소 구조체를 전달하면 송신자의 주소 정보로 채워진다.

 int *fromlen // 함수가 채워넣은 주소정보 구조체의 크기를 갖게된다.

);

2) recvfrom() 함수의 특징

recvfrom() 함수는 UDP 소켓은 물론이고, TCP 소켓에도 사용할 수 있으며, 이 경우, from과 fromlen 인자는 무시된다. TCP 소켓에 사용할 때만 flags 인자에 MSG_OOB를 사용할 수 있다.

sendto() 함수로 보낸 데이터는 독립적인 UDP 데이터그램(패킷)으로 만들어져 전송되며, 송신측에서는 recvfrom() 함수 호출 한번으로 이 데이터를 읽을 수 있다. 따라서 응용프로그램 수준에서 메시지 경계를 구분하지 않아도 된다.

UDP 소켓에 대해 recvfrom() 함수를 호출할 경우 리턴 값이 0이 될 수 있는데, 이는 상대방이 sendto() 함수 호출시 데이터 크기를 최소값 0으로 설정했다는 뜻이다. UDP 프로토콜에는 연결 설정과 종료 개념이 없으므로 recvfrom() 함수의 리턴 값이 0이라고 해서 특별한 의미가 있는 것은 아니다. 반면, TCP 소켓에서 recvfrom() 함수를 호출할 경우 리턴 값이 0이면, 정상종료를 의미한다.

블로킹 소켓을 사용할 경우, 소켓 수신 버퍼에 도착한 데이터가 없으면 recvfrom()함수는 호출 시 블록된다.

 

3. 브로드캐스팅

 

- 유니캐스팅(unicasting: one-to-one): 한 개체가 다른 한 개체에 데이터를 보내는 모델. IPv4, IPv6에서 지원

- 브로드캐스팅(broadcasting: one-to-many): 한 개체가 특정 네트워크(자신과 동일 네트워크도 가능)에 속한 모든 개체에 데이터를 보내는 모델. IPv4에서만 지원

- 멀티캐스팅(multicasting: ont-to-many): 동일 그룹에 가입한 모든 개체(물리적으로는 서로 다른 네트워크에 속할 수 있음)에 데이터를 보내는 모델. 개념적으로 브로드캐스팅은 멀티캐스팅의 특수한 경우로 볼수 있다. IPv4, IPv6에서 지원)

- 애니캐스팅(anycasting: one-to-one-of-many): 한 개체가 동일 그룹에 가입한 개체 중 가장 가까운 하나에만 데이터를 보내면, 데이터를 받은 개체가 그룹에 속한 나머지 개체에 데이터를 보내는 모델. IPv6에서만 지원)

 

※ 소켓응용 프로그램에서 브로드캐스트 데이터를 보내는 방법

① 브로드캐스팅을 활성화 한다.

BOOL bEnable = TRUE;

retval = setsocketopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&bEnable, sizeof(bEnable));

=> socket() 함수로 생성한 소켓은 기본적으로 유니캐스팅만 지원하지만, setsocketopt() 함수의 첫번째 인자로 해당 소켓, 두번째 SOL_SOCKET과 세번째 SO_BROADCAST 옵션, 네번째 TRUE 값을 사용하면 브로드캐스팅이 활성화된다.

② 브로드캐스트 주소를 목적지로 해서 데이터를 보낸다.

SOCKETADDR_IN remoteaddr;

ZeroMemory(&remoteaddr, sizeof(remoteaddr));

remoteaddr.sin_family = AF_INET;

remoteaddr.sin_addr.s_addr = inet_addr("255.255.255.255");

remoteaddr.sin_port = htons(9000);

char buf[BUFSIZE]; //송신용 버퍼를 선언하고 테이터를 넣는다.

...

retval = sendto(sock, buf, strlen(buf), 0, (SOCKET *) &remoteaddr, sizeof(remoteaddr));

if(retval == SOCKET_ERROR) 오류처리;

printf("%d바이트를 보냈습니다.\n", retval);

=>브로드캐스트 데이터를 보내는 코드는 IP 주소 부분을 제외하면 유니캐스트 데이터를 보내는것과 동일

 

※ 브로드캐스트 주소의 종류

IPv4 주소는 크게 네트워크 ID와 호스트 ID로 나눌수 있다. 서브넷을 사용하는 경우, 호스트 ID의 일부는 서브넷 ID로 사용된다. 브로드캐스트용으로 예약되어 있는 IPv4 주소는 아래와 같다. 

 

- 네트워크 브로드캐스트(net-directed broadcast)

호스트 ID 비트가 모두 1인 경우로 특정 네트워크에 대한 브로드캐스트를 의미한다. 브로드캐스트 데이터가 라우터를 거쳐야 하므로 라우터 설정에 따라 브로드캐스팅이 불가능할 수도 있다. 따라서 실용적으로 브로드캐스팅 주소로 사용하기는 어렵다.

 

- 서브넷 브로드캐스트(subnet-directed broadcast)

서브넷 ID를 제외한 호스트 ID비트가 모두 1인 경우로, 특정 서브넷에 대한 브로드캐스트를 의미한다. 서브넷 브로드캐스트도 라우터를 통과하지 못할 수 있으므로, 일반적으로 외부 서브넷에 대한 브로드캐스팅을 목적으로 사용하기는 어렵다. 하지만 송신자 자신이 속한 서브넷에 대한 브로드캐스팅은 항상 가능하다.

 

- 지역 브로드캐스트(local broadcast 또는 limited broadcast)

송신자 자신이 속한 네트워크에 대한 브로드캐스트를 의미한다. 항상 브로드캐스팅이 가능하며, 브로드캐스트 데이터가 라우터 경계를 넘어가지 않는다. 지역 브로드캐스트 주소는  ws2def.h파일에 INADDR_BROADCAST(0xffffffff; 255.255.255.255) 값으로 정의되어 있다.

 

"TCP/IP 윈도우 소켓 프로그래밍"책 에서 발췌

Posted by 초코송송이
l