본문 바로가기
Programming/열혈 TCP, IP 소켓 프로그래밍(저자 윤성우)

Ch 06. 내용 확인문제

by minjunkim.dev 2020. 8. 8.

    모든 내용은 [윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어] 를 기반으로 제 나름대로 이해하여 정리한 것입니다. 다소 부정확한 내용이 있을수도 있으니 이를 유념하고 봐주세요!


01. TCP보다 UDP가 빠른 이유는 무엇인가? 그리고 TCP는 데이터의 전송을 신뢰할 수 있지만 UDP는 신뢰할 수 없는 이유는 또 무엇인가?

 

    TCP와 UDP의 가장 큰 차이점은 바로 흐름제어(Flow Control)의 유무이다. 흐름제어는 TCP의 연결설정 과정, 데이터 송수신 과정, 연결해제 과정 전반에 걸쳐 발생한다. 이 과정에서 TCP는 데이터 전송의 신뢰성을 확보하나 그만큼 속도가 느려지게 된다. 반면에, UDP는 이러한 흐름제어의 과정이 필요 없으므로(TCP와 다르게 소켓이 연결되어 데이터 송수신을 하는 것이 아니기 때문) 비교적 속도가 빠르지만, 그만큼 데이터 전송의 신뢰성이 확보되지는 않는다.


02. 다음 중 UDP의 특성인 아닌 것을 모두 고르면?

 

b. UDP 기반으로 데이터를 전송할 목적지가 두 군데라면, 총 두개의 소켓을 생성해야 한다.

=> UDP 소켓은 하나 있다면 어디건(수신지의 주소를 알고있다는 가정 하에) 데이터를 전송할 수 있다.

 

c. UDP 소켓은 TCP 소켓이 할당한 동일한 번호의 PORT에 재할당이 불가능하다. => TCP와 UDP는 PORT번호를 공유하지 않으므로 재할당이 가능하다.

 

e. UDP 소켓을 대상으로도 connect 함수를 호출할 수 있는데, 이러한 경우 UDP 소켓도 TCP 소켓과 마찬가지로 Three-way handshaking 과정을 거치게 된다.

=> UDP 소켓을 대상으로 connect 함수를 호출하는 것은 목적지 주소를 등록하는 것(connected UDP)이다.

목적지의 UDP 소켓과 연결설정 과정을 거친다거나 하지는 않는다.(UDP에서 흐름제어 과정은 없음)

 

# UDP의 특성이 맞는 것은?

 

    a. UDP는 TCP와 달리 연결의 개념이 존재하지 않는다. 따라서 반드시 TCP에서 보인 것처럼 1대 1의 형태로 데이터를 송수신 하지 않을 수 있다.

 

    d. UDP 소켓과 TCP 소켓은 공존할 수 있다. 따라서 필요하다면 한 호스트 상에서 TCP 방식과 UDP 방식의 데이터 송수신을 모두 진행할 수 있다.


03. UDP 데이터그램이 최종 목적지인 상대 호스트의 UDP 소켓에 전달되는데 있어서 IP가 담당하는 부분과 UDP가 담당하는 부분을 구분지어 설명해보자

 

# UDP 내부 동작원리

- UDP의 역할중 가장 중요한 것은, 호스트로 수신된 UDP 패킷(호스트까지의 데이터 전달은 IP의 역할)을

PORT정보를 참조하여 최종목적지인 UDP 소켓에 전달하는 것


04. UDP는 일반적으로 TCP보다 빠르다. 그러나 송수신하는 데이터의 성격에 따라서 그 차이는 미미할 수도 있고, 반대로 매우 클 수도 있다. 그렇다면 어떠한 상황에서 UDP는 TCP보다 매우 좋은 성능을 보이는지 설명해보자

 

# 데이터 송수신 속도

- 일반적으로는 UDP >> TCP

- 하지만 송수신하는 데이터 성격에 따라 TCP는 UDP와 비슷한 속도를 내기도 함

- 한번에 송수신하는 데이터의 양이 크면 클수록 TCP는 UDP 못지않은 전송속도를 냄

 

# UDP의 효율적 사용
- UDP도 나름대로 상당히 신뢰할만함
- 압축파일의 경우에는 그 특성상 반드시 TCP 기반으로 송수신이 이루어져야 하나,
실시간 멀티미디어(영상 및 음성 등) 전송의 경우에는 속도가 상당히 중요한 요소이므로

UDP 기반의 구현을 고려할만함

# TCP가 UDP보다 느린 이유
1) 데이터 송수신 이전, 이후에 거치는 연결설정 및 해제과정이 존재
2) 데이터 송수신과정에서 거치는 신뢰성 보장을 위한 흐름제어가 존재

=> 따라서 송수신하는 데이터의 양이 작으면서, 잦은 연결이 필요한 경우에는 UDP가 훨씬 효율적이고 빠르게 동작


05. 클라이언트의 TCP 소켓은 connect 함수를 호출할 때 자동으로 IP와 PORT가 할당된다. 그렇다면 bind 함수를 호출하지 않는 UDP 소켓은 언제 IP와 PORT가 할당되는가?

 

    UDP 에서는 sendto 함수호출 이전에 해당 소켓(데이터 발신지)의 주소정보가 할당되어 있어야 함
첫번째 방법 - sendto 함수호출 이전에 bind 함수호출하여 명시적으로 주소정보 할당
두번째 방법 - 첫번째 방법을 사용하지 않았다면, sendto 함수가 처음 호출되는 시점에

해당 소켓(발신지)에 IP와 PORT번호가 자동으로 할당됨 => 보다 일반적 구현방법
(IP는 호스트 IP로, PORT번호는 현재 사용하지 않고있는 번호 하나를 임의로 할당,

프로그램 종료까지 주소정보는 유지)


06. connect 함수의 호출문장은 TCP 클라이언트의 경우 반드시 삽입해야 하는 문장이다. 그러나 UDP의 경우는 선택적으로 삽입이 가능하다. 그렇다면 UDP에서 connect 함수를 호출하면 어떠한 이점이 있는가?

 

    UDP 소켓에는 데이터를 전송할 목적지(수신지)를 등록하지 않고, sendto 함수호출을 할 때마다
1) UDP 소켓에 목적지(수신지)의 IP와 PORT번호 등록
2) 해당 목적지(수신지)로 데이터 전송
3) UDP 소켓에 등록된 목적지(수신지) 정보 삭제
위의 과정을 거친다.(단, unconnected 소켓 기준) => 목적지(수신지)의 주소정보가 계속 변경될 수 있기에

하나의 UDP 소켓을 통해 다양한 목적지로 데이터 전송이 가능한 것

 

    그러나 connect 함수를 호출한 connected UDP 소켓의 경우, 1단계와 3단계의 과정을 생략할 수 있다.(이로 인한 성능향상이 존재함) connect 함수를 호출했다고 해서 TCP처럼 연결설정과정을 거치는 것은 아니며, 데이터 목적지(수신지)의 IP, PORT번호를 등록하는 과정이다. 이런 경우에는 sendto 대신 write, recvfrom 대신 read 함수를 이용해서도 데이터 송수신이 가능해진다.


7. 본문에서 보인 예제 uecho_server.c와 uecho_client.c를 참고해서 서버와 클라이언트 상호간에 한번씩 메세지를 주고받는 형태와 대화를 진행하는 예제를 작성해보자. 단, 주고받는 대화는 콘솔상에 출력되어야 한다.

 

# 리눅스 기반으로만 구현해보았습니다.

 

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);
        message[str_len]='\0';
        printf("Message from Client : %s",message);

        fputs("Input message(Q to exit): ",stdout);
        fgets(message,BUF_SIZE,stdin);

        /* 클라이언트로 서버의 메시지를 널문자를 제외하고 송신 */
        sendto(serv_sock,message,strlen(message),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);
}

 

# 실행 결과


[출처] : 윤성우 저, "열혈강의 TCP/IP 소켓 프로그래밍", 오렌지미디어