본문 바로가기
열심히 살기/Network

소켓프로그래밍 - UDP 소켓

by yeni03o_o 2020. 12. 13.

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

udp_echo_server.c 실핼 결과

- client: 서버로 문자열 송신 후, 서버로부터 echo된 문자열 수신

udp_echo_client.c 실행 결과

 


 

데이터의 경계가 존재하는 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초 간격으로 출력됨

5초 간격으로 Message출력

 

공부하면서 학습 목적으로 작성한 포스팅이므로 내용이 완전하지 않습니다ㅠ 
계속해서 학습 후 지식이 좀 더 쌓이면 수시로 수정해나갈 예정입니다! 
틀린 내용은 둥글게 댓글 달아주시면 빠른 확인 후 수정하겠습니다. :)


코드 출처

윤성우 저 '윤성우의 열혈 TCP/IP 소켓 프로그래밍'

 

참고

* 윤성우 저 '윤성우의 열혈 TCP/IP 소켓 프로그래밍'

qteveryday.tistory.com/52?category=921416

 

댓글