모든 내용은 [윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어] 를 기반으로 제 나름대로 이해하여 정리한 것입니다. 다소 부정확한 내용이 있을수도 있으니 이를 유념하고 봐주세요!
# UDP 소켓의 특성
1) 장점
- TCP보다 간결한 구조로 설계됨 => 상황에 따라 좋은 성능 발휘
- 프로그래밍 관점에서 구현이 용이함
- 생각만큼 데이터 손실이 자주 발생하지 않음 => 신뢰성보다 성능이 중시되는 상황에서 좋은 선택이 될 수 있음
2) 단점
- 상대방의 수신여부를 알 수 없음
- 전송 도중에 분실될 확률이 있음
- 신뢰할 수 없는 전송방법을 제공함
- 데이터를 전송할 때마다 반드시 목적지의 주소정보를 별도로 추가해야 함
(TCP처럼 연결된 상태가 아니기 때문)
# TCP는 신뢰성 없는 IP를 기반으로 신뢰성 있는 데이터 송수신을 위해
흐름제어(Flow Control)를 하지만, UDP에는 이 흐름제어가 존재하지 않음.
=> "흐름제어 존재의 유무"가 TCP와 UDP의 가장 큰 차이점
# 데이터 송수신 속도
- 일반적으로는 UDP >> TCP
- 하지만 송수신하는 데이터 성격에 따라 TCP는 UDP와 비슷한 속도를 내기도 함
- 한번에 송수신하는 데이터의 양이 크면 클수록 TCP는 UDP 못지않은 전송속도를 냄
# UDP 내부 동작원리
- UDP의 역할중 가장 중요한 것은, 호스트로 수신된 UDP 패킷(호스트까지의 데이터 전달은 IP의 역할)을
PORT정보를 참조하여 최종목적지인 UDP 소켓에 전달하는 것
# UDP의 효율적 사용
- UDP도 나름대로 상당히 신뢰할만함
- 압축파일의 경우에는 그 특성상 반드시 TCP 기반으로 송수신이 이루어져야 하나,
실시간 멀티미디어(영상 및 음성 등) 전송의 경우에는 속도가 상당히 중요한 요소이므로
UDP 기반의 구현을 고려할만함
# TCP가 UDP보다 느린 이유
1) 데이터 송수신 이전, 이후에 거치는 연결설정 및 해제과정이 존재
2) 데이터 송수신과정에서 거치는 신뢰성 보장을 위한 흐름제어가 존재
=> 따라서 송수신하는 데이터의 양이 작으면서, 잦은 연결이 필요한 경우에는 UDP가 훨씬 효율적이고 빠르게 동작
# UDP 서버/클라이언트는 TCP처럼 연결된 상태로 데이터 송수신을 하는 것이 아니기 때문에
연결 설정, 해제과정이 필요가 없다. => listen, accept 함수호출이 불필요
=> 즉, UDP 소켓 생성과 데이터 송수신 과정만 필요함
# TCP에서는 소켓과 소켓의 관계가 일대일 대응(연결되어 있음)
=> 서버가 10개의 클라이언트에 서비스를 제공하려면
서버에 "서버소켓 1개" + 클라이언트와의 연결을 위한 "10개의 추가소켓"이 필요했음.
그러나 UDP에서는 서버건 클라이언트건 하나의 소켓만 있으면 됨.
# UDP 소켓은 "우체통" 역할과 유사하다.
(우체통이 있으면 어디로든 편지를 보낼 수 있는 것처럼 UDP 소켓이 있으면 어디로든 데이터를 보낼 수 있다.
"데이터 수신지의 주소"만 안다면 말이다.)
# UDP 기반의 데이터 입출력 함수
1. 출력함수
#include <sys/socket.h>
ssize_t sendto(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
// -> 성공시 전송된 바이트수, 실패시 -1반환
- sock : 데이터 전송에 사용될 UDP 소켓의 파일 디스크립터
- buff : 전송할 데이터를 저장하고 있는 버퍼의 주소값
- nbytes : 전송할 데이터 크기를 바이트 단위로 전달
- flags : 옵션 지정에 사용되는 매개변수, 지정할 옵션이 없다면 0
- to : 목적지 주소정보를 담고 있는 sockaddr 구조체 변수의 주소값
- addrlen : 매개변수 to로 전달된 주소값의 구조체 변수 크기
- TCP 기반의 출력함수와 가장 비교되는 것은 "목적지 주소정보를 요구"한다는 것
2. 입력함수
ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
// -> 성공시 수신한 바이트수, 실패시 -1 반환
- sock : 데이터 수신에 사용될 UDP 소켓의 파일 디스크립터
- buff : 데이터 수신에 사용될 버퍼의 주소값
- nbytes : 수신할 최대 바이트 수 전달, 때문에 매개변수 buff가 가리키는 버퍼의 크기를 넘을 수 없음
- flags : 옵션 지정에 사용되는 매개변수, 지정할 옵션이 없다면 0
- from : 발신지 정보를 채워 넣을 sockaddr 구조체 변수의 주소값
- 매개변수 from으로 전달된 주소에 해당하는 구조체 변수의 크기정보를 담고있는 변수의 주소값
- 이 함수는 UDP 패킷에 담겨있는 발신지 정보를 함께 반환함(from, addrlen)
# UDP 소켓은 TCP 소켓과 달리
listen(), accept() 함수를 호출하지 않는다. (연결설정이 필요하지 않으므로)
# TCP 클라이언트 소켓의 주소는 connect 함수가 호출될때 IP(호스트IP)와 PORT번호(임의)가 자동으로 할당됨.
그렇다면 UDP 클라이언트 소켓의 주소는 언제 할당될까?
=>
UDP 에서는 sendto 함수호출 이전에 해당 소켓(데이터 발신지)의 주소정보가 할당되어 있어야 함
첫번째 방법 - sendto 함수호출 이전에 bind 함수호출하여 명시적으로 주소정보 할당
두번째 방법 - 첫번째 방법을 사용하지 않았다면, sendto 함수가 처음 호출되는 시점에
해당 소켓(발신지)에 IP와 PORT번호가 자동으로 할당됨 => 보다 일반적 구현방법
(IP는 호스트 IP로, PORT번호는 현재 사용하지 않고있는 번호 하나를 임의로 할당,
프로그램 종료까지 주소정보는 유지)
# 데이터의 경계가 존재하는 UDP 소켓
- 따라서, 데이터 송수신과정에서 호출하는 입출력함수의 호출횟수가 의미를 가짐
- 입력함수의 호출횟수와 출력함수의 호출횟수가 "완벽히 일치"해야 송신된 데이터 전부를 수신할 수 있음
# UDP 데이터그램
- UDP 소켓이 전송하는 패킷을 가리킴
- 데이터그램도 패킷의 일종이나, TCP 패킷과 달리 데이터의 일부가 아닌
그 자체가 하나의 데이터로 의미를 가질 때 이렇게 표현함.
- UDP는 데이터의 경계가 존재하기 때문에 하나의 패킷 = 하나의 데이터로 간주됨
=> 그래서 데이터그램이라고 표현하는 것
# TCP 소켓에는 데이터를 전송할 목적지(수신지)의 IP와 PORT번호를 등록하는 반면,
UDP 소켓에는 데이터를 전송할 목적지(수신지)를 등록하지 않고,
sendto 함수호출을 할 때마다
1) UDP 소켓에 목적지(수신지)의 IP와 PORT번호 등록
2) 해당 목적지(수신지)로 데이터 전송
3) UDP 소켓에 등록된 목적지(수신지) 정보 삭제
위의 과정을 거친다.(단, unconnected 소켓 기준) => 목적지(수신지)의 주소정보가 계속 변경될 수 있기에
하나의 UDP 소켓을 통해 다양한 목적지로 데이터 전송이 가능한 것
# unconnected 소켓(목적지 정보가 등록되어있지 않은 소켓) - UDP 소켓의 DEFAULT
# connected 소켓(목적지 정보가 등록되어 있는 소켓)
- 하나의 호스트와 오랜 시간 데이터 송수신시 이것이 보다 효율적
- UDP 소켓을 대상으로 connect 함수를 호출해주면 됨
- unconnected와 달리 송수신의 대상이 정해졌기 때문에,
sento 대신 write / recvfrom 대신 read 함수를 사용가능하기에 이를 사용하여 이점을 챙겨올 수 있음
# UDP 기반의 에코서버와 에코 클라이언트
uecho_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char *message);
int main(int argc,char *argv[])
{
int serv_sock, clnt_sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr, clnt_adr;
socklen_t clnt_adr_sz;
if(argc!=2) // 실행파일 경로/PORT번호 를 입력으로 받아야 함
{
printf("Usage : %s <port>\n",argv[0]);
exit(EXIT_FAILURE);
}
serv_sock=socket(PF_INET,SOCK_DGRAM,0); // UDP 소켓 생성
if(serv_sock==-1)
error_handling("socket() error");
/* 서버 주소정보 초기화 */
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]));
/* 서버 주소정보 할당 */
if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
while(true)
{
clnt_adr_sz=sizeof(clnt_adr);
/* 클라이언트로부터 널문자를 제외하고 문자열을 수신 */
str_len=recvfrom(serv_sock,message,BUF_SIZE,0,(struct sockaddr*)&clnt_adr,&clnt_adr_sz);
/* 수신받은 데이터를 다시 클라이언트로 송신 */
sendto(serv_sock,message,str_len,0,(struct sockaddr*)&clnt_adr,clnt_adr_sz);
}
close(serv_sock); // UDP 소켓 종료
return 0;
}
void error_handling(char *message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(EXIT_FAILURE);
}
uecho_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char *message);
int main(int argc,char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
socklen_t adr_sz;
struct sockaddr_in serv_adr, from_adr;
if(argc!=3) // 실행파일 경로/IP/PORT번호 입력 받아야 함
{
printf("Usage : %s <IP> <port>\n",argv[0]);
exit(EXIT_FAILURE);
}
sock=socket(PF_INET,SOCK_DGRAM,0); // UDP 소켓 생성
if(sock==-1)
error_handling("socket() error");
/* 서버 주소정보 초기화 */
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
while(true)
{
fputs("Input message(Q to exit): ",stdout);
fgets(message,BUF_SIZE,stdin);
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break;
/*
이때, 클라이언트의 주소가 자동으로 할당됨
입력받은 문자열을 서버로 널문자를 제외하고 송신
*/
sendto(sock,message,strlen(message),0,(struct sockaddr*)&serv_adr,sizeof(serv_adr));
/*
서버로부터 에코받은 문자열을 다시 수신하여 출력
*/
adr_sz=sizeof(from_adr);
str_len=recvfrom(sock,message,BUF_SIZE,0,(struct sockaddr*)&from_adr,&adr_sz);
message[str_len]='\0'; // 수신한 문자열 맨뒤에 널문자 추가
printf("Message from server: %s",message);
}
close(sock); // UDP 소켓 종료
return 0;
}
void error_handling(char *message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(EXIT_FAILURE);
}
# connected UDP 소켓 생성(UDP의 default는 unconnected)
- 클라이언트 역할의 호스트에서 connect 함수를 호출하여 "목적지 주소를 등록함"을 유의해서 보자.
- sendto 대신 write, recvfrom 대신 read 함수를 사용할 수 있음을 유의해서 보자.
- 아래의 코드는 위에서의 uecho.server.c 와 같이 실행이 가능하다.
uecho_con_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
void error_handling(char *message);
int main(int argc,char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr;
if(argc!=3) // 실행파일 경로/IP/PORT번호 입력 받아야 함
{
printf("Usage : %s <IP> <port>\n",argv[0]);
exit(EXIT_FAILURE);
}
sock=socket(PF_INET,SOCK_DGRAM,0); // UDP 소켓 생성
if(sock==-1)
error_handling("socket() error");
/* 서버 주소정보 초기화 */
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
connect(sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr));
// connected UDP이므로 connect 함수를 호출하여 목적지 주소정보를 등록했음을 보자!
while(true)
{
fputs("Input message(Q to exit): ",stdout);
fgets(message,BUF_SIZE,stdin);
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break;
/*
입력받은 문자열을 서버로 널문자를 제외하고 송신
*/
write(sock,message,strlen(message));
/*
서버로부터 에코받은 문자열을 다시 수신하여 출력
*/
str_len=read(sock,message,sizeof(message)-1);
message[str_len]='\0'; // 널문자를 수신한 문자열 맨 뒤에 추가
printf("Message from server: %s",message);
}
close(sock); // UDP 소켓 종료
return 0;
}
void error_handling(char *message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(EXIT_FAILURE);
}
[출처] : 윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어
'Programming > 열혈 TCP, IP 소켓 프로그래밍(저자 윤성우)' 카테고리의 다른 글
Ch 07. 소켓의 우아한 연결종료 (0) | 2020.08.08 |
---|---|
Ch 06. 내용 확인문제 (0) | 2020.08.08 |
Ch 05. 내용 확인문제 (1) | 2020.08.06 |
Ch 05. TCP 기반 서버/클라이언트 2 (0) | 2020.08.06 |
Ch 04. 내용 확인문제 (0) | 2020.08.06 |