Part1_UDP 기반 서버 클라이언트

·2023년 11월 7일
0

[UDP에 대한 이해]

  • UDP는 IP를 기반으로 흐름 제어를 해 주지
    프로토콜 자체가 상당히 간단하며, 전송 속도에 있어 TCP 보다 빠름
  • Port 정보를 통해서 최종 목적지를 구분해 줌
  • 인터넷 기반 실시간 영상 및 음성 전송에 주로 사용됨

[UDP 기반 서버/클라이언트의 구현]

  • UDP 클라이언트와 서버는 연결 상태가 존재하지 않음
    즉, listen(), accept(), connect() 함수의 호출이 필요하지 않음 -> 오로지 소켓의 생성과 주소 할당만 하면 됨
  • 서버의 소켓은 오로지 하나면 됨
  • 연결 상태를 유지하지 않으므로 데이터를 전송하는 함수 호출 시 보내고자 하는 곳의 주소 정보를 포함해야 함

[데이터 전송 함수]

#include <sys/types.h>
#include <sys/socket.h>

int sendto(int sock, const void* msg, int len, unsigned flags,
			     const struct sockaddr* addr, int addlen);

// 성공 시 전송된 바이트 수, 실패시 -1 리턴
// sock : 데이터를 전송할 때 사용할 소켓의 파일 디스크립터
// msg : 전송하고자 하는 데이터를 저장해 놓은 버퍼를 가리키는 포인터
// len : msg 포인터가 가리키는 위치에서부터 몇 바이트를 전송할 것인지 그 크기를 인자로 넘겨줌
// flags : 옵션을 설정하는데 필요한 인자 (일반적으로 0)
// addr : 전송하고자 하는 곳의 주소 정보 (초기화 후 인자로 넘겨줌)
// addrlen : addr포인터가 가리키고 있는 구조체 변수의 크기

[데이터 수신 함수]

연결된 상태로 수신하는 것이 아니기 때문에 어디에서 데이터가 전송되었는지 알 수 없음 → 함수 자체적으로 데이터가 전송된 위치 정보를 얻을 수 있는 기능을 제공함

(UDP 패킷이 주소 정보를 지니고 있고, 데이터 수신 함수는 이 주소 정보를 반환함)

#include <sys/types.h>
#include <sys/socket.h>

int recvfrom(int sock, int* buf, int len, unsigned flags,
	           struct sockaddr* addr, int* addrlen);

// 성공 시 수신한 바이트 수, 실패시 -1 리턴
// sock : 데이터를 수신할 때 사용할 소켓의 파일 디스크립터
// buf : 수신할 데이터를 저장할 버퍼를 가리키는 포인터
// len : 수신할 수 있는 최대 바이트 수 (일반적으로 buf가 가리키는 저장소의 크기를 넘지 않음)
// flags : 옵션을 설정하는 데 필요한 인자
// addr : 주소 정보 구조체 변수으 포인터 (호출이 끝나면 데이터를 전송한 호스트의 주소 정보로 채워짐)
// addrlen : addr 포인터가 가리키는 주소 정보 구조체 변수 크기

[UDP 기반의 에코 클라이언트]

// uecho_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define BUFSIZE 30

void error_handling(char* message);

int main(int argc, char** argv)
{
    int serv_sock;
    char message[BUFSIZE];
    int str_len;

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    int clnt_addr_size;

    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 소켓 생성 오류");
    
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
        error_handling("bind() error");
    
    while(1)
    {
        clnt_addr_size = sizeof(clnt_addr);
        str_len = recvfrom(serv_sock, message, BUFSIZE, 0,
                           (struct sockaddr*)&clnt_addr, &clnt_addr_size);
        sendto(serv_sock, message, str_len, 0,
               (struct sockaddr*)&clnt_addr, sizeof(clnt_addr));
    }

    close(serv_sock);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
// uecho_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define BUFSIZE 30
void error_handling(char* message);

int main(int argc, char** argv)
{
    int sock;
    char message[BUFSIZE];
    int str_len, addr_size;

    struct sockaddr_in serv_addr;
    struct sockaddr_in from_addr;

    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_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_addr.sin_port=htons(atoi(argv[2]));

    while(1)
    {
        fputs("전송할 메시지를 입력하세요 (q to quit) : ", stdout);
        fgets(message, sizeof(message), stdin);

        if(!strcmp(message, "q\n")) break;

        sendto(sock, message, strlen(message), 0,
               (struct sockaddr*) &serv_addr, sizeof(serv_addr));
        addr_size=sizeof(from_addr);

        str_len=recvfrom(sock, message, BUFSIZE, 0,
                         (struct sockaddr*)&from_addr, &addr_size);
        
        message[str_len] = 0;
        printf("서버로부터 전송된 메시지 : %s", message);
    }

    close(sock);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

[실행 결과]

[UDP 기반 클라이언트의 IP와 Port 정보의 할당]

  • TCP는 connect() 호출 시 자동적으로 할당됨
  • UDP는 sendto()가 제일 처음 호출되는 시점에 소켓에 IP와 Port가 할당됨

[데이터의 경계(Boundary)가 존재하는 UDP 소켓]

  • TCP와 달리 데이터의 경계가 존재하므로 입/출력 함수 호출 수를 반드시 일치시켜 줘야 함
// becho_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define BUFSIZE 30

void error_handling(char* message);

int main(int argc, char** argv)
{
    int serv_sock;
    char message[BUFSIZE];
    int str_len, num = 0;

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    int clnt_addr_size;

    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 소켓 생성 오류");
    
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_addr.sin_port=htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
        error_handling("bind() error");
    
    sleep(5);

    while(1)
    {
        clnt_addr_size = sizeof(clnt_addr);
        sleep(1);
        str_len = recvfrom(serv_sock, message, BUFSIZE, 0,
                           (struct sockaddr*)&clnt_addr, &clnt_addr_size);
        printf("수신 번호 : %d \n", num++);
        sendto(serv_sock, message, str_len, 0,
               (struct sockaddr*)&clnt_addr, sizeof(clnt_addr));
    }

    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}
// becho_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define BUFSIZE 30
void error_handling(char* message);

int main(int argc, char** argv)
{
    int sock;
    char message[BUFSIZE];
    int str_len, addr_size, i;

    char MSG1[] = "Good ";
    char MSG2[] = "Evening ";
    char MSG3[] = "Everybody!";

    struct sockaddr_in serv_addr;
    struct sockaddr_in from_addr;

    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_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_addr.sin_port=htons(atoi(argv[2]));

    sendto(sock, MSG1, strlen(MSG1), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    sendto(sock, MSG2, strlen(MSG2), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    sendto(sock, MSG3, strlen(MSG3), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    for(i = 0; i < 3; i++)
    {
        addr_size = sizeof(from_addr);
        str_len = recvfrom(sock, message, BUFSIZE, 0,
                           (struct sockaddr*)&from_addr, &addr_size);
        message[str_len] = 0;
        printf("서버로부터 수신된 %d차 메시지 : %s \n", i, message);
    }

    close(sock);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

[실행 결과]

클라이언트와 서버의 메시지 송/수신 수가 완전히 일치함

[connect 함수 호출을 통한 성능의 향상]

UDP 클라이언트를 구현할 때 connect 함수의 호출은 불필요 (연결 요청할 필요가 없음)
하지만 connect 함수는 UDP 클라이언트에서 여전히 유용함 (적절히 사용하면 성능 향상을 가져옴)

[TCP 소켓을 가지고 connect 함수를 호출하는 경우]

  • 소켓에 호스트의 IP와 임의의 Port 할당
  • 서버로 연결 요청함

[UDP 소켓을 가지고 connect 함수를 호출하는 경우]

  • 소켓에 호스트의 IP와 임의의 Port 할당

[connect 함수를 통해 얻어지는 이점]

UDP 기반 호스트의 경우 sendto, recvfrom 함수를 호출하기 전에는 커널이 소켓에 연결되어 있지 않다가, 함수 호출 시에 연결을 설정함
그리고 나서 함수 호출이 끝나는 경우에 연결을 종료함
그렇다면 어떻게 연결 상태를 유지시켜 놓을 수 있을까?

→ connect 함수를 호출해서 IP와 Port를 소켓에 할당해 놓으면 커널은 그 소켓과의 연결 상태를 계속 유지함

뿐만 아니라, sendto, recvfrom 함수가 아닌 read, write와 같은 TCP 소켓이 통신하는데 사용하는 함수를 그대로 사용할 수 있게 됨

[connect 함수 호출을 하는 UDP 기반의 에코 클라이언트]

// cecho_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

int main(int argc, char** argv)
{
    int sock;
    char message[30];
    int str_len;

    struct sockaddr_in serv_addr;

    if(argc!=3)
    {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock=socket(PF_INET, SOCK_DGRAM, 0);
    
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_addr.sin_port=htons(atoi(argv[2]));

    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    while(1)
    {
        fputc("전송할 메시지를 입력하세요 (q to quit) : ", stdout);
        fgets(message, sizeof(message), stdin);

        if(!strcmp(message, "q\n")) break;
        write(sock, message, strlen(message));

        str_len=read(sock, message, sizeof(message)-1);
        message[str_len] = 0;
        printf("서버로부터 전송된 메시지 : %s", message);
    }

    close(sock);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

0개의 댓글