이 포스트는 널널한 개발자님 강의를 참조하여 작성한 포스트입니다.
파리에서 에펠탑을 택배로 보내려면 어떻게 해야할까? 일단 가능은 할까? 가능은 하다. 일단 첫째로 에펠탑을 분해해가지고 크기를 줄여야하는데 택배박스 크기로 줄여야 한다. 즉, MTU이하로 줄여야 할 것이다. 그렇게 해서 다 분해를 한 다음에 둘째로 운송을 하면 된다. 그 다음 셋째로 조립해야 한다. 과정이 상당히 심플하다. 그런데 여기서 분해라고 하는것은 송신측에서 할 것이다. 조립은 수신측에서 할 것이다. 여기서 택배박스는 패킷일것이다.
인터넷이라는 정보의 물류체계에서 패킷이라는 단위로 정보가 유통된다. 여기서 전체적인 프름이 중요한데 이 흐름이 나왔다는 것은 TCP/IP를 묶어서 설명하겠다는 것이다. PC가 1대가 있는데 이 PC가 L2 Access Switch, L2 distribution Switch, Router를 거쳐서 Internet에 도달할 것이다. 그럼 수신측에서 Router를 거쳐서 L2 distribution Switch, L2 Access Switch를 거쳐서 서버가 있다고 가정해보자.
예를 들어서 위의 그림의 서버가 네이버라고 했을 때 네이버의 파일 하나를 다운로드 한다고 해보자. 이 파일 다운로드라는 것은 사실 네이버 입장에서는 파일이라는 정보를 송신한다는 것이고 PC쪽에서는 수신하는것이다. 근데 중요한것은 인터넷이라는 구간안에서 정보가 유통될때 패킷형태로 전달된다. 그런데 만약에 파일이 1.4MB라고 가정하면 패킷 MTU가 1.4KB이고 차이가 1024배 이상 차이가 나는데 즉, 1000개 이상 패킷으로 바꿔서 날라간다. 그리고 결국 파일을 송신하거나 수신하는것은 PC 혹은 서버라고 애기했지만 정확히 말하면 프로세스가 송수신을 한다. 그래서 이런걸 기반으로 깊이 애기해보자.
파일을 TCP/IP로 보낸다고 가정을 하면 TCP라는 것은 연결지향형 프로토콜임으로 연결이라는 것을 한다는 것은 일종의 전화 통화 연결과 유사하고 이것을 한 다음에 송수신이 이루어진다. 그럼 그렇게 송수신이 잘 되었다고 가정하고 위 그림에 오른쪽의 프로세스를 서버라고 가정하자. 그리고 내려가면 TCP 프로그램을 추상화한 파일형태의 인터페이스인 소켓이 있다.
서버쪽의 HDD 혹은 SSD라는 보조기억장치라는 것이 어딘가 있을 것이고 파일이라고 말하는 것은 이 안에 저장되어 있을 것이다. 이 파일이 1.4MB라고 가정한다 했을 때 여기서 중요한것은 소켓의 본질이 파일이라고 했는데 이 소켓에 대고 I/O가 일어날 것이다. 이런 I/O가 일어날때는 소켓에 attach된 메모리 공간 즉, 버퍼가 있기 마련이다. 이런 버퍼가 있으면 buffered I/O가 일어나는 것이고 이거 없이 I/O를 직접하면 non-buffered I/O를 하게 된다.
근데 문제가 뭐냐면 소켓에도 버퍼가 있지만 이 서버 프로그램 안에도 개발자가 분명히 파일 같은걸 읽는것 때문에 버퍼가 있을 것이다. 이 버퍼의 크기는 개발자가 지정하기 나름인데 암튼 이 버퍼는 프로세스가 관리하는 버퍼이고 그 밑의 버퍼는 소켓 I/O 버퍼이다.
그러면 어떤 일이 일어나냐면 1.4MB정도 파일을 보내야 한다면 프로세스가 관리하는 버퍼의 크기는 1.4MB가 될 수 있지만 이 크기는 개발자가 직접 정한다. 몽땅 메모리에 올릴 수 있고 그 일부를 메모리에 올릴 수도 있다. 그런데 예를 들어 직소퍼즐이 있다고 해보자. 이 직소퍼즐중에 5개의 퍼즐을 블록형태로 짤라온다고 해보자. 이 잘라온 직소퍼즐조각을 프로세스가 관리하는 버퍼에 넣는다. 그러면 직소퍼즐 5개가 버퍼에 넘어올 것이다. 즉, 이 직소퍼즐이 copy가 되는 것이다. 이 읽어온 직소퍼즐을 프로세스가 메모리에 담는다. 그러면 이 버퍼에 있는 데이터를 소켓 I/O가 관리하는 버퍼쪽으로 copy가 일어난다. 이때부터 전송이 일어나는데 user-mode에서 커널 모드로 넘오올때 TCP 스택을 만날때 분해가 일어난다. 즉 전송한 직소퍼즐을 분해해서 짜른다. 이때 user-mode에 버퍼안에 데이터 단위를 스트림이라고 한다. 이 스트림은 파일 전체를 읽어서 보내야 끝이 난다. 문제는 TCP 스택을 만나 Segmentation이 일어나는데 이 중 하나의 조각을 넘버링을 1번붙어 붙여서 세그먼트화 시킨다. 그 다음에 IP쪽으로 내려와 택배상자에 세그먼트를 넣어서 테이핑하여 패킷을 만든다. 이 패킷 헤더에는 송장과 같이 출발지, 목적지 주소가 있다. 그리고 이 택배상자를 택배기사 아저씨한테 전달하여 이 기사 아저씨는 택배 트럭에 상자를 넣는다. 이때 택배 트럭을 프레임이라고 한다.
여기서 중요한것은 이 트럭이 나한테 택배를 전달받자마자 바로 목적지로 갈까? 그렇지 않고 물류센터에 가서 분류하고 다른 택배트럭에 싣고 목적지로 갈 것이다. 즉, 유통과정에서 프레임은 수시로 바뀐다. 이렇게 해서 데이터가 송신된다. 즉, 송신측에 Encapsulation이 일어난다.
그러면 수신쪽에는 어떤 일이 일어날까? 트럭이 목적지 주소까지 가고나면 송신쪽의 정반대 일이 일어난다. 송신쪽 프레임과 다른 프레임이 수신지에 오면 택배박스라는 패킷이 나와 타고 올라갈 것이다. 그리고 IP까지 타고 올라가서 택배상자를 꺼내 내용물을 확인하듯이 패킷에 세그먼트를 꺼내 패킷을 버리고 세그먼트를 TCP 계층까지 올라간다. 이렇게 Decapsulation이 일어나고 이 세그먼트 한 조각을 소켓 I/O 버퍼에 올라간다. 그리고 이 세그먼트 조각을 다시 프로세스 버퍼에 올린다. 이렇게 나머지 세그먼트도 송수신을 하면서 수신쪽 소켓 I/O는 여유공간이라것이 줄어들고 프로세스 버퍼에 올리면 여유공간이 확대된다. 그러면 여기서 문제가 발생할 수 있다.
만약 송수신과정에 세그먼트가 없을 경우가 있다. 이것을 Loss라고 하는데 이것은 네트워크 자체 문제일 경우가 크다. 둘째로 송신측에서 세그먼트를 일정수준 보내면 수신측에서는 잘 받았다고 ASK라고 피드백을 여유공간이 얼마나 남았는지 보내줘야 하는데 응답이 없다면 송신측은 보낸 세그먼트를 재전송(Re-Transmission)을 한다. 그런데 간발의 차로 바로 ASK가 오는 경우가 있는데 이것을 ASK-Duplicated라고 한다. 이것은 네트워크상 문제일수도 있고 end-point간 합이 안 맞아서 일어날 수 있다. 그 다음으로 Out of Order가 있는데 이게 뭐냐면 1,2번 세그먼트가 오고 3번 세그먼트가 와야하는데 3번이 안 오고 4번이 오는 경우라던가 혹은 4번이 오고 3번이 나중에 오는 경우가 있다. 이런것은 거의 네트워크상 이슈다. 마지막으로 Zero Window가 있는데 수신측 여유공간 메모리 크기를 window size라고 하는데 zero라는 것은 process buffer가 빨리 비워줘야 하는데 안 비워준 경우 즉, 네트워크 송수신 속도가 프로세스가 비워내는 처리속도보다 빠른 경우에 발생한다. 이러면 송신은 되도 수신이 안되는 상태가 발생하는데 이런경우 end-point간 application이 문제임으로 이 부분을 확인해봐야 한다.