UDP 소켓 특성
- 흐름제어 flow Control이 없음(SEQ, ACK과 같은 메시지 전달 X)
- 연결 설정과 해제 과정 존재 X → connect(), listen(), accept() 필요 X
- 데이터의 분실 및 손실 위험 존재. 빠른 데이터 전송
UDP의 데이터 송수신
- UDP는 연결의 개념 존재 X → 서버 소켓과 클라이언트 소켓의 구분 X
→ connect(), listen(), accept() 불필요
- 하나의 소켓으로 둘 이상의 영역과 데이터 송수신 가능(sendto(), recvfrom())
UDP 기반 소켓 API(시스템 콜 호출) 흐름
※ 서버 소켓: socket() → bind() → recvfrom() → sendto() → close()
※ 클라이언트 소켓: socket() → bind()(생략 가능) → sendto() → recvfrom() → close()
UDP 기반의 데이터 입출력 함수
* 데이터 전송: sendto()
데이터 전송(sendto()) 시 목적지에 대한 정보(struct sockaddr) 전달
#include <sys/socket.h>
// 성공 시 전송된 바이트 수, 실패 시 -1 반환
ssize_t sendto(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
// sock: 데이터 전송에 사용될 UDP 소켓의 파일 디스크립터
// *buff: 전송할 데이터를 저장하고 있는 버퍼의 주소값
// nbytes: 전송할 데이터 크기의 바이트 단위
// flags: 옵션 지정. 지정할 옵션이 없으면 0
// *to: 목적지 주소정보를 담고 있는 sockaddr 구조체 변수의 주소 값
// addrlen: 매개변수 to로 전달된 주소 값의 구조체 변수 크기
* 데이터 수신: recvfrom()
데이터 전송지가 둘 이상이 될 수 있으므로,
데이터 수신(recvfrom()) 이후 전송지(struct sockaddr)가 어디인지 확인 필요()
#include <sys/socket.h>
// 성공 시 수신한 바이트 수, 실패 시 -1 반환
ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
// sock: 데이터 수신에 사용된 UDP 소켓의 파일 디스크립터
// *buff: 데이터 수신에 사용될 버퍼의 주소 값
// nbytes: 수신할 최대 바이트 수
// flags: 옵션지정. 지정할 옵션이 없으면 0
// *from: 발신지 정보를 채워 넣을 sockaddr 구조체 변수의 주소 값
// *addrlen: 매개변수 from으로 전달된 주소 값의 구조체 변수의 크기
UDP 기반 Echo Server
//UDP 소켓 - Server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 300
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); //소켓(UDP) 생성
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);
//클라이언트로부터 문자열 수신(널문자 제외). 수신할 최대 바이트 수는 BUF_SIZE
str_len=recvfrom(serv_sock, message, BUF_SIZE, 0, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
//수신한 데이터의 전송지 정보(clnt_adr)를 참조하여 수신받은 데이터를 에코
sendto(serv_sock, message, str_len, 0, (struct sockaddr*)&clnt_adr, clnt_adr_sz);
printf("[SERVER] received and sending some message %s", message);
}
close(serv_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
UDP 기반 Echo Client
//UDP 소켓 - Client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 300
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); //소켓(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(1)
{
fputs("Insert message(q to quit): ", stdout);
fgets(message, sizeof(message), stdin);
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break;
//입력받은 문자열을 서버로 전송. 전송할때마다 목적지의 주소 정보도 함께 전달(UDP는 연결이 아니므로)
//IP주소와 포트번호 자도으로 할당 → bind() 불필요
sendto(sock, message, strlen(message), 0, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
adr_sz=sizeof(from_adr);
//서버로부터 문자열과 송신지정보(서버 정보) 수신
//UDP는 데이터의 경계가 존재(BUF_SIZE)하기 때문에 한번의 recvfrom()호출을 토해 하나의 메시지를 완전히 읽어들임
str_len=recvfrom(sock, message, BUF_SIZE, 0, (struct sockaddr*)&from_adr, &adr_sz);
message[str_len]=0; //수신한 문자열 뒤 널문자 추가
printf("Messaage from server: %s", message);
}
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
코드(UDP기반) 실행 결과
- server: 클라이언트로부터 문자열 수신 후 echo
- client: 서버로 문자열 송신 후, 서버로부터 echo된 문자열 수신
데이터의 경계가 존재하는 UDP 소켓
- 데이터 송수신 과정에서 입출력 함수의 호출횟수가 의미를 가짐
- 입력 함수와 출력함수의 호출횟수가 완벽히 일치해야 송신된 데이터 전부를 수신 가능
UDP 호스트 간 메시지 전송(Host1)
//5초 간격으로 recvfrom() 3회 호출
//HOST2로부터 데이터 수신
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 300
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len, i;
socklen_t adr_sz;
struct sockaddr_in my_adr, your_adr;
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");
//5초 간격으로 recvfrom()함수를 3번 호출
for(i=0; i<3 <i++)
{
sleep(5) //5초 delay
adr_sz=sizeof(your_adr);
str_len=recvfrom(sock, message, BUF_SIZE, 0, (struct sockaddr*)&youradr, &adr_sz);
printf("[HOST1] Message %d: %s \n", i+1; message);
}
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
UDP 호스트 간 메시지 전송(Host2)
//sendto() 3회 호출
//HOST1로 데이터 송신
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 300
void error_handling(char *message);
int main(int argc, char *argv[])
{
int sock;
char msg1[]="Hello!";
char msg2[]="I'm UDP HOST2";
char msg3[]="Nice to meet you";
socklen_t your_adr_sz;
struct sockaddr_in your_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(&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() 3번 호출
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));
snedto(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);
}
코드(UDP 호스트 간 데이터 전송) 실행 결과
- HOST2의 sendto() 3번 호출로 HOST1로 데이터 전송
- HOST1의 recvfrom()는 5초 단위로 호출되므로 각 메시지가 5초 간격으로 출력됨
공부하면서 학습 목적으로 작성한 포스팅이므로 내용이 완전하지 않습니다ㅠ
계속해서 학습 후 지식이 좀 더 쌓이면 수시로 수정해나갈 예정입니다!
틀린 내용은 둥글게 댓글 달아주시면 빠른 확인 후 수정하겠습니다. :)
코드 출처
윤성우 저 '윤성우의 열혈 TCP/IP 소켓 프로그래밍'
참고
* 윤성우 저 '윤성우의 열혈 TCP/IP 소켓 프로그래밍'
* qteveryday.tistory.com/52?category=921416
'열심히 살기 > Network' 카테고리의 다른 글
소켓 프로그래밍 - Connected UDP (0) | 2020.12.13 |
---|---|
소켓 프로그래밍 - TCP 소켓(입출력 버퍼, Half-close) (0) | 2020.12.13 |
소켓 프로그래밍 - 어플리케이션 프로토콜 (0) | 2020.12.13 |
소켓 프로그래밍 - TCP 기반(서버, 클라이언트) (6) | 2020.12.12 |
소켓 프로그래밍 - 바이트 순서(호스트, 네트워크) (0) | 2020.12.12 |
댓글