ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TCP/IP 소켓 프로그래밍 - 4장 : TCP 기반 서버/클라이언트
    네트워크 2024. 6. 16. 23:23

    4장

    04 - 1. TCP와 UDP에 대한 이해

    TCP는 연결 지향적 프로토콜임을 잊지 말자!

    위 그림은 TCP/IP 프로토콜 스택이다.

     

    인터넷 기반의 효율적인 데이터 전송 문제를 두고 하나의 큰 프로토콜로 해결한 것이 아니라,

    문제를 작게 나눠 계층화했다는 부분에 주목해야 한다.

     

    프로토콜을 계층화해서 얻게 되는 장점 2가지는 아래와 같다.

    1. 프로토콜 설계의 용이성
    2. 개방형 시스템의 설계

    개방형 시스템이란 여러 개의 표준을 근거로 설계된 시스템을 말하며,

    여러 회사가 본인들만의 제품을 만들지만 그 제품이 각 계층의 표준에 기준을 두어 만들기 때문에,

    제품 선택의 다양성빠른 기술 발전을 가능하게 한다.

     

    Link 계층

    물리적인 영역의 표준화를 맡고 있다.

    LAN, WAN, MAN 같은 부분을 정의한다.

    IP 계층

    “목적지로 데이터를 전송하기 위해 어떤 경로를 거쳐야 할까? “ 를 해결하는 계층

    IP자체는 비 연결지향적이며, 신뢰할 수 없는 프로토콜이다.

    경로가 일정하지 않고, 중간에 데이터가 손실되거나 오류가 발생해도 이를 해결하지는 않는다.

    TCP/UDP 계층

    IP 계층이 데이터 전송을 위한 경로를 찾아주었다면, TCP/UDP 계층은 데이터를 전송만 해주면 된다.

    전송을 도맡았기 때문에, Transport(전송) 계층이라고 불린다.

    TCP는 신뢰성 있는 데이터의 전송을 담당한다. TCP는 IP를 기반으로 활동하는 프로토콜이다.

    TCP와 IP의 관계

    IP는 오직 하나의 데이터 패킷이 전송되는 과정에만 초점을 맞춰 설계되었다.

    여러 개의 데이터 패킷을 전송하더라도, 각각의 패킷은 IP에 의해 전송되므로

    전송의 순서도, 전송 그 자체도 신뢰할 수 없다.

     

    TCP 프로토콜은 데이터를 주고받는 과정에서 서로 데이터의 주고 받음을 확인하며,

    분실된 데이터에 대해 재전송해줌으로써 데이터의 전송을 신뢰할 수 있게 만들어준다.

    Application 계층

    소켓을 바탕으로 데이터 송수신에 대한 응답/요청 과정을 신경쓰지 않아도 여러 프로그램을 만들 수 있는 계층이다.

    프로그램 성격에 따라 클라이언트와 서버간의 데이터 송수신에 대한 약속들이 정해지기 마련인데,

    이를 가리켜 APPLICATION 프로토콜이라고 한다.

    대부분의 네트워크 프로그래밍은 application 프로토콜 설계 및 구현으로 이루어져 있다.


    04-2 TCP 기반 서버, 클라이언트 구현

    TCP 서버를 구현하기 위한 기본 함수 호출 순서는 아래와 같다.

    bind 함수호출로 소켓에 주소까지 할당하면

    다음은 listen 함수호출을 통해 ‘연결 요청 대기 상태’로 넘어가야 한다.


    #include <sys/socket.h>
    
    int listen(int sock, int backlog);
    
    //성공시 0, 실패시 -1 반환
    //backlog : 연결요청 대기 큐 크기. 5라고 쓰면, 클라이언트 연결요청을 5개까지 기다릴 수 있다.

    대기 큐의 크기는 실험적 결과에 의존하며,

    웹서버와 같이 잦은 연결 요청을 받는 서버의 경우 최소 15이상을 전달해야 한다.

    클라이언트의 연결 요청 수락 ( accept )

    accpet 함수의 호출 결과로 소켓이 자동을 만들어지고, 연결요청한 클라이언트의 소켓과 자동으로 연결이 된다.

    위 그림을 이해하게 되면 왜 hello_server.c에서 2개의 소켓을 만들었는지 알 수 있다.

    Copy
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    ...
    if(listen(serv_sock, 5)==-1)
    ...
    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    ...
    write(clnt_sock, message, sizeof(message));

    여기까지 오게 되면 이제 남은 것은 데이터를 주고받는 일뿐이다.


    TCP 클라이언트의 기본 함수호출 순서

    클라이언트는 서버가 listen 함수를 호출한 이후부터 연결요청을 할 수 있다.

    클라이언트는 connect( ) 함수를 이용해 연결요청을 시도한다.

     

    #include <sys/socket.h>
    
    int connect(int sock, struct sockaddr * servaddr, socklen_t addrlen);
    
    //성공 시 0, 실패 시 -1 return
    //servaddr : 연결 요청할 서버의 주소정보가 담긴 변수의 주소 값
    //addrlen : 두 번째 매개변수의 변수 크기를 바이트 단위로 나타낸 값

    connect( ) 함수가 성공적이었어도, 연결 요청이 접수되었을 뿐 아직 accept 된 것은 아니다.

    서버의 연결 요청 큐에 등록된 상황일 뿐이다.

    클라이언트 소켓의 주소정보는 따로 기입할 필요가 없다.

    connect 함수가 호출될 때, 운영체제에서(커널에서 ) 자동으로 소켓에 IP와 port가 할당된다.

    따라서 클라이언트 프로그램을 구현할 때에는 bind 함수를 명시적으로 호출할 필요가 없다.


    TCP 기반 서버, 클라이언트의 함수호출 관계

    클라이언트는 서버 소켓의 listen 함수 호출 이후에 connect함수 호출이 가능하고,

    클라이언트가 connect함수를 호출하기 앞서 서버가 accept함수를 먼저 호출할 수 있으며 이때는

    클라이언트가 connect함수를 호출할 때까지 서버는 accept함수가 호출된 위치에서 block된다.


    04-3 Iterative 기반의 서버, 클라이언트 구현

    에코 서버는 클라이언트가 전송하는 문자열을 그대로 재전송하는 서버이다.

    기존에 구현했던 코드는 한 클라이언트의 요청에만 응답을 하고 바로 종료됐었다.

    이번에는 반복문을 삽입해서 accept 함수를 반복 호출해 계속해서 들어오는 클라이언트의 연결요청을 수락해보자.

    여기서 close함수는 서버 소켓을 대상으로 하는 것이 아니라,

    accept함수의 호출 과정에서 새롭게 생성된 소켓을 대상으로 하는 것이다.


    에코 클라이언트의 문제점

    write(sock, message, strlen(message));
    str_len = read(sock, message, BUF_SIZE - 1);
    message[str_len] = 0;
    printf("Message from server : %s", message);

     

    위 코드는

    “read, write 함수가 호출될 때마다, 문자열 단위로 실제 입출력이 이루어진다.”

    라는 잘못된 가정에 기인해 짜여져 있다.

     

    운이 좋게도 한 번의 write로 문자열이 전송되었지만,

    전송할 데이터의 크기가 크다면 내부적으로 여러 조각으로 나눠져 서버가 클라이언트에게 전송할 수도 있다.

     

    이 과정에서, 클라이언트는 모든 조각이 다 도착되지 않았지만, read함수를 호출할 수도 있다.

    이 모든 문제는 TCP의 데이터 전송 특성에 기인한 것이다. ( 전송되는 데이터의 경계가 없다. )



Designed by Tistory.