2019년 2월 12일 화요일

#7 Network 데이터 송수신 과정

📡 데이터 송수신 과정



    프로토콜 스택에 HTTP 리퀘스트 메시지를 넘긴다

    connect가 실행되고 제어가 돌아오면 애플리케이션은 write를 호출하여 송신데이터를 프로토콜스택에 건네주고 이것을 받은 프로토콜 스택이 송신동작을 실행한다

    프로토콜 스택은 받은 데이터의 내용이 무엇인지 알지 못한다


    프로토콜 스택은 받은 데이터를 송신용 버퍼 메모리에 저장해두는데

    애플리케이션 종류에 따라 프로토콜에 건내주는 데이터의 길이가 다르므로

    바로 보내버리게 된다면 작은 패킷을 많이 보내게 되어 네트워크 이용 효율이 저하된다 따라서 어느정도 데이터를 저장한 뒤에 송 수신을 한다



데이터 송신 방법 <1>


    프로토콜 스택은 이를 패킷이 운반할수 있는 디지털 데이터의 최대 길이MTU(Maximum Transmission Unit)라는 매개변수를 바탕으로 판단한다

    MTU는 패킷의 맨 앞부분에 헤더가 포함되어있으므로 헤더를 제외한 부분이 데이터의 최대 길이인데 이를 MSS(Maximum Segment Size)라 한다

MAC헤더    IP헤더     TCP 헤더      데이터     
             <---이부분의 최대길이 : MTU--->
                (이더넷에서는 1500 byte)

                                                <--MSS-->

    애플리케이션에서 받은 데이터가 MSS를 초과하거나 가까운 길이에 이르게 되면 송신동작을 한다

    (TCP와 IP헤더를 합하면 보통 40byte이므로 MSS의 길이는 1460byte가 되는 식이다
     TCP/IP에는 암호화 등의 프로토콜 옵션이 있어 이를 사용하면 헤더가 더 길어지게 된다)


데이터 송신 방법 <2>


    애플리케이션의 송신속도가 느려지면 MSS에 데이터를 저장하는 시간이 더 걸리므로
송신동작을 실행하고 이는 프로토콜 스택 내부의 타이머가 ms 단위로 체크한다




    1번의 경우 네트워크의 이용효율을 좋게 할수 있지만 송신 동작이 지연될수 있고

    2번의 경우에는 송신 동작은 빠르지만 이용 효율이 떨어진다


    하지만 TCP프로토콜의 사양에는 이 둘을 절충하는게 없고. OS종류나 프로토콜 스택에 따라 이 동작이 달라진다


    대신 애플리케이션에서 송신 타이밍을 제어할수도 있는데 버퍼에 저장하지 않고 바로 송신하는 옵션은 대화형 어플리케이션에서 응답시간을 줄이는 용도로 사용할수 있다.




💬 데이터가 클때는 분할하여 보낸다



    HTTP 리퀘스트 메시지(HTTP헤더 + 메시지 본문)의 길이가 길어지게 되면 송신버퍼에 저장된 데이터를 MSS크기에 맞게 분할하여

    TCP헤더를 부여하고 IP담당(IP 헤더와 이더넷 헤더 부가) 부분에 넘겨주어 송신





💬 ACK 번호를 사용하여 패킷이 도착했는지 확인한다



     TCP는 송신후에 도착확인까지 하여 문제가 발생하면 다시 송신을 한다

    확인 방법은 TCP가 데이터를 조각으로 분할할떄 통신 시작부터 몇번째 바이트인지 확인하여 시퀀스 번호로 저장해두고 패킷을 전달하면 수신측에서는

    시퀀스번호를 보고, 패킷 전체의 길이에서 헤더의 길이를 빼서 데이터의 크기도 계산하여

    ACK번호(시퀀스번호+ 데이터크기)로 응답해준다

    이과정에서 수신측에서는 시퀀스번호 + 데이터크기인 값이 다음 시퀀스번호값으로 와야 수신누락이 없다는것이 확인되며

    결과값을 TCP헤더의 ACK번호에 기록(뿐만아니라 비트의 값도1로 하여 유효함을 전달)하여 송신측에 응답한다( = 수신확인응답 )



    이때 보안을 위해 초기 시퀀스값을 난수로 만들게 되는데 데이터 송수신 전에 접속 부분의 SYN(synchronize)이라는 제어비트1을 설정하여 패킷을 보낼때 상대측에 통지해준다.


    이 과정은 클라이언트 -> 서버 뿐만 아니라 서버 -> 클라이언트 방향으로도 진행된다



    즉 클라이언트에서 서버로 시퀀스번호 초기값을 보내면 서버는 확인했다는 의미의 ACK번호와 서버의 시퀀스번호 초기값을 보내고 클라이언트가 ACK번호를 반송함으로 준비가 완료된다


    TCP는 ACK번호를 확인할때까지 송신한 패킷을 송신용 버퍼 메모리영역에 보관해두고
ACK번호가 돌아오지않으면 다시 재송신 한다


    이 과정에서 오류발생시 패킷의 회복조치를 취해주기때문에 LAN어댑터,버퍼,라우터,애플리케이션은 오류를 검출하면 버리게된다

    (단 케이블 분리 or 서버 다운등의 이유로 TCP가 데이터를 보낼수없을때는 강제종료후 애플리케이션에 오류 통지)





💬 패킷 평균 왕복 시간으로 ACK번호의 대기시간을 조정한다



    ACK번호가 돌아오는 것을 기다리는 시간을 타임아웃 값이라 한다

    이 타임아웃값이 짧으면 ACK가 돌아오기 전에 다시 송신을 하게되어 혼잡을 야기시키고
타임아웃값이 길게되면 송신이 지연되어 속도저하가 일어난다

    따라서 여러 환경적요인을 생각하여 타임아웃값을 동적으로 설정하는데
    ACK번호가 돌아오는 시간을 기준으로 계측하여 설정한다(컴퓨터의 계측은 정밀도가 낮아 너무 짧으면 계측 못하기때문에 일반적으로 0.5s ~ 1s 의 최솟값을 대기시간으로 가진다)




💬 윈도우 제어방식으로 효율적인 ACK번호관리



    한개의 패킷을 보내고 ACK번호를 기다리면 비효율적이다

    따라서 ACK번호를 기다리지 않고 복수의 패킷을 보내는 방법을 사용하는데 이를 윈도우 제어방식이라 한다

    하지만 이때 복수의 패킷을 보내기에 수신측의 능력을 초과할수 있는데
    (수신 버퍼에 데이터를 임시보관하여 패킷처리를 해 애플리케이션에 주는데 버퍼가 오버되면 데이터가 없어져 오류가 발생)

    이를 막기위해서 수신측은 TCP의 윈도우 필드를 통해 수신버퍼의 빈영역이 얼마나 있는지
    송신측에 알리고 송신측은 데이터를 보낼때마다 수신측의 버퍼용량을 생각하여 넘치지 않게 보낸다.
    수신측이 패킷 처리를 통해 추가 공간이 생기면 TCP헤더를 통해 다시 알려준다

    이렇게 통지해주는 버퍼메모리의 크기를 윈도우 사이즈라 하고 이또한 수신측과 송신측에서 양방향으로 일어난다





💬 ACK번호와 윈도우통지를 합친다



    ACK번호는 데이터가 정상적으로 수신완료되었을때 송신측으로 통지되고

    윈도우는 수신측에서 데이터를 애플리케이션으로 건네주었을때 송신측으로 통지되는데
 
 
    이 과정을 따로 패킷으로 보내게 되면 효율이 떨어지므로 소켓에서 잠시 기다려 다음 통지동작이 일어나면 한개의 패킷으로 묶어서 보낸다

    이와 마찬가지로 ACK번호의 통지가 연속해서 송신측에 전달되야 할경우 마지막 ACK번호를 통지하면 어느부분까지가 수신되었는지 알수있고

    윈도우 또한 최후의 윈도우만 보내면 수신버퍼의 메모리가 얼마나 확보되었는지를 알수있기때문에 이 또한 묶어서 보내준다




💬 HTTP응답메세지의 수신



    브라우저의 의뢰를 통해 프로토콜스택이 HTTP리퀘스트 메세지를 송신하고 나면 브라우저는 read를 호출하여 프로토콜스택에게 수신을 의뢰한다(제어가 프로토콜 스택으로 넘어감)

    송신한지 얼마 안되어 수신버퍼에 아무런 데이터가 없으면 다른 작업을 하며 대기하다가 패킷이 도착했을때 수신동작을 실행한다

    수신동작은 송신에서의 서버와 마찬가지로 이루어지며

    패킷을 조사하여 ACK번호를 반송하고 ➝

    데이터조각을 버퍼에 보관하며 복원한다음 애플리케이션에 건네주고 ➝

    애플리케이션이 지정한 메모리에 옮겨 기록후 제어 반환 ➝윈도우통지


출처 : 성공과 실패를 결정하는 !%의 네트워크 원리

가장 많이 본 글