Part 01
Ch. 01. Socket Network Programming
Ch. 02. File Descriptor, open(), close(), write(), read()
Ch. 05. Network Byte Order, Big Endian, Little Endian
Part 2
Ch.10.
Ch.11.
Ch.12.
Ch.13.
Ch.14.
Ch.15.
Ch.16.
Ch.17.
Ch.18.
UDP 소켓의 특성
TCP는 신뢰성 없는 IP 프로토콜의 전송을 "ACK와 같은 응답 메시"지와 "SEQ와 같이 패킷 번호"를 사용해 "흐름 제어 (Flow Control)" 한다.
UDP는 데이터를 전송할 때마다 반드시 목적지의 주소정보를 추가한다.
UDP 서버, 클라이언트는 연결된 상태로 데이터를 송수신하지 않는다.
UDP의 효율적 사용
압축파일의 경우, 그 특성상 파일의 일부만 손실되어도 압축의 해제가 어렵기 때문에 반드시 TCP를 기반으로 송수신이 이뤄져야 한다.
실시간 영상 및 음성의 경우, 그 일부가 손실되어도 잠깐의 화면 떨림 또는 아주 작은 잡음 정도는 그냥 넘어갈만하며 실시간으로 서비스이므로 속도가 중요하므로 UDP방식을 사용할 수 있다.
1. 데이터를 전송할 때마다 반드시 목적지의 주소정보를 추가한다.
sendto()
#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 전송할 데이터를 전장하고 있는 버퍼의 주소 값 전달
// nbyles 전송할 데이터 크기를 바이트 단위로 전달
// flags 옵션 지정에 사용되는 매개변수, 지정할 옵션이 없다면 0 전달
// to 목적지 주소정보를 담고 있는 sockaddr 구조체 변수의 주소 값 전달
// addrlen 매개변수 to로 전달된 주소 값의 구조체 변수 크기 전달
recvfrom()
#include <sys/socket.h>
ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags ,
struct sockaddr *from, socklen_t *addrlen);
// 성공시 수신한 바이트 수, 실패시 -1 반환
// sock 데이터 수신에 사용될 UDP 소켓의 파일 디스크립터를 인자로 전달
// buff 데이터 수신에 사용될 버퍼의 주소 값 전달.
// nbyles 수신할 최대 바이트 수 전달, buff가 가리키는 버퍼의 크기를 넘을 수 없다
// flags 옵션 지정에 사용되는 매개변수, 지정할 옵션이 없다면 O 전달
// from 발신지 정보를 채워 넣을 sockaddr 구조체 변수의 주소 값 전달.
// addrlen 매개변수 from으로 전달된 주소에 해당하는 구조체 변수의 크기정보를 담고 있는 변수의 주소값 전달
UDP echo Server, Client
// Server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
int serv_sock;
char message[BUF_SIZE];
int str_len;
socklen_t clnt_adr_sz;
struct sockaddr_in serv_adr, clnt_adr;
if(argc!=2){
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
serv_sock=socket(PF_INET, SOCK_DGRAM, 0);
if(serv_sock==-1)
error_handling("UDP socket creation 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(1)
{
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);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
// Client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
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){
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_DGRAM, 0);
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(1)
{
fputs("Insert message(q to quit): ", stdout);
fgets(message, sizeof(message), 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);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
2. UDP 소켓은 데이터의 경계가 존재한다.
UDP는 하나의 데이터로 의미를 가지는 데이터그램이다.
데이터의 경계가 존재하는 UDP 소켓은 입력 함수와 출력 함수의 호출 횟수가 완벽히 일치해야 한다.
TCP라면, 단 한번의 입력함수 호출을 통해 모든 데이터를 읽어 들일 수 있다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
struct sockaddr_in my_adr, your_adr;
socklen_t adr_sz;
int str_len, i;
if(argc!=2){
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_DGRAM, 0);
if(sock==-1)
error_handling("socket() error");
memset(&my_adr, 0, sizeof(my_adr));
my_adr.sin_family=AF_INET;
my_adr.sin_addr.s_addr=htonl(INADDR_ANY);
my_adr.sin_port=htons(atoi(argv[1]));
if(bind(sock, (struct sockaddr*)&my_adr, sizeof(my_adr))==-1)
error_handling("bind() error");
for(i=0; i<3; i++)
{
sleep(5); // delay 5 sec.
adr_sz=sizeof(your_adr);
str_len=recvfrom(sock, message, BUF_SIZE, 0,
(struct sockaddr*)&your_adr, &adr_sz);
printf("Message %d: %s \n", i+1, message);
}
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
/*
root@my_linux:/home/swyoon/tcpip# gcc bound_host1.c -o host1
root@my_linux:/home/swyoon/tcpip# ./host1
Usage : ./host1 <port>
root@my_linux:/home/swyoon/tcpip# ./host1 9190
Message 1: Hi!
Message 2: I'm another UDP host!
Message 3: Nice to meet you
root@my_linux:/home/swyoon/tcpip#
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
char msg1[]="Hi!";
char msg2[]="I'm another UDP host!";
char msg3[]="Nice to meet you";
struct sockaddr_in your_adr;
socklen_t your_adr_sz;
if(argc!=3){
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_DGRAM, 0);
if(sock==-1)
error_handling("socket() error");
memset(&your_adr, 0, sizeof(your_adr));
your_adr.sin_family=AF_INET;
your_adr.sin_addr.s_addr=inet_addr(argv[1]);
your_adr.sin_port=htons(atoi(argv[2]));
sendto(sock, msg1, sizeof(msg1), 0,
(struct sockaddr*)&your_adr, sizeof(your_adr));
sendto(sock, msg2, sizeof(msg2), 0,
(struct sockaddr*)&your_adr, sizeof(your_adr));
sendto(sock, msg3, sizeof(msg3), 0,
(struct sockaddr*)&your_adr, sizeof(your_adr));
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
/*
root@my_linux:/home/swyoon/tcpip# gcc bound_host2.c -o host2
root@my_linux:/home/swyoon/tcpip# ./host2
Usage : ./host2 <IP> <port>
root@my_linux:/home/swyoon/tcpip# ./host2 127.0.0.1 9190
root@my_linux:/home/swyoon/tcpip#
*/
3.Connected UDP
sendto() 함수는 다음의 3단계 과정을 거친다.
1. UDP 소켓에 목적지의 IP와 PORT번호 등록
2. 데이터 전송
3. UDP 소켓에 등록된 목적지 정보 삭제
장점 : 목적지 주소가 지속적으로 변경되며, 다양한 목적지로 데이터 전송이 가능하다.
= Unconnected UDP
단점 : 같은 IP, PORT 로 여러개의 데이터를 보낼 경우, 1,3번의 반복이 발생한다.
해결 : connect() 함수를 사용해 UPD 소켓을 Connected() 소켓으로 만든다.
= 데이터 전송 과정이 1/3으로 줄어든다.
+ sendto, recvfrom 이 아닌 write, read 함수의 사용이 가능하다.
// ~ 생략
connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
while(1)
{
fputs("Insert message(q to quit): ", stdout);
fgets(message, sizeof(message), stdin);
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break;
/*
sendto(sock, message, strlen(message), 0,
(struct sockaddr*)&serv_adr, sizeof(serv_adr));
*/
write(sock, message, strlen(message));
/*
adr_sz=sizeof(from_adr);
str_len=recvfrom(sock, message, BUF_SIZE, 0,
(struct sockaddr*)&from_adr, &adr_sz);
*/
str_len=read(sock, message, sizeof(message)-1);
message[str_len]=0;
printf("Message from server: %s", message);
}
close(sock);
return 0;
}