TCP 송/수신 원리
PC#1와 Server 한대가 3-Way Handshake로 TCP/IP 연결을 했다고 생각해보자.
클라이언트가 파일을 달라고 요청해서 Server가 송신하게 될 것이다.
서버 상황을 먼저 얘기하자면, 서버에서 Web서버가 작동하게 되면 Socket을 열어서 클라이언트와 통신을 하고 있을 것이다. Socket의 본질은 File이다. 서버는 기본적으로 Process이기에 서버가 File에다가 기본적으로 할 수 있는것은 읽던지 쓰던지 실행하던지, 즉 ‘RWX’이다.
여기서 Read와 Write의 개념을 Socket 통신일 때는 각각 다른말로 부른다. (Read는 Receive, Write는 Send로 부른다.)
또한 하드웨어인 HDD나 SDD나 이 속안에 File이 들어있을 것이다. bmp파일 하나가 있다고 가정하면 이 파일은 File System으로 관리될 것이고 Device driver를 통해 주거니받거니 할것이다.
중요한 것은 보통 User mode에 Memory(Buffer라고도 표현)를 할당하는 구조로 되어있다. 이 때 파일의 크기가 1.4MB로 크다고 가정할 때 Memory의 size는 개발자가 결정하지만 1.4MB보다는 대게 작을 것이다. Memory의 Size를 64kb정도로 생각한다면 파일에서 데이터를 읽어올 때 한방에 읽는 것이 아니라 64kb만큼 끌어와서 읽는다. 그러니까 64kb만큼 읽어와서 Memory에 적재를 한다. 1.4MB의 파일을 64kb 단위로 끊어서 계속해서 읽어서 Memory에 쌓게된다.
64kb만큼 데이터를 파일로부터 끌어올려서 먼저 Read를 한다. 그러면 자바프로그래밍이든 C++프로그래밍이든 Socket이라는 것은 기본적으로 TCP/IP를 추상화 한 것이기 때문에 Socket과 TCP/IP가 맞닿은 지점에서 분해가 일어난다.
앞서 있었던 Memory는 다른 표현으로 Buffer이므로 Buffer#1이라고 하고 Socket과 TCP/IP가 맞닿은 지점에 Buffer가 또 있을 수 있는데 이를 Buffer#2라고 하자.
중요한 것은 64kb가 Buffer#1에서 Buffer#2로 Copy가 일어난다.
따라서 TCP 쪽에서는 Buffer#1의 원본을 Copy한 Buffer#2를 가지고 있다.
이런식의 입출력(I/O)을 Buffered I/O라고 한다.
(왜 이렇게하는지와 이것의 크기에 대한 구체적인 내용은 다음 기회에 찾아보자)
이 TCP가 Copy한 것을 가지고 있다가 IP쪽으로 내려갈 때 Segment로 분해가 일어난다. 또한 분해가 일어날 때 번호를 붙이게 되는데 이 번호별로 Packet에 인캡슐레이션이 된다.
예를들어 Segment 1번을 Packet 하나가 담게 된다. 이 Pakcet이 PC쪽으로 날라가게 되는 경우를 생각해보자. Pakcet이 L2수준인 NIC에 내려올 때 Frame이라고 부르는 것을 앞선 포스팅에서 누누히 말해왔다. 이 Packet을 전달하는게 Frame이다. 그럼 이 Frame의 목적지는 어디인가?
PC가 네트워크 연결이 되어있다고 하면 NIC이 있을 것이다. 그리고 Device Driver와 TCP/IP, Process를 추상화시킨 Socket 등등이 있을 것이다.
역할상 Client지만 Socket의 본질은 아까말했듯이 file이기에 file에 연결된 Buffer 또한 있을 것이다. 이를 File I/O Buffer 라고 하자. 또한 TCP도 Buffer를 가지고 있을 것이다. 이를 TCP Buffer라고 하자.
이때 NIC수준에서 Device Driver 수준으로 올라갈 때 Frame은 없어진다. 또 TCP/IP 수준에서 Packet에서 Segment를 끄집어내는 디캡슐레이션이 일어난다. 즉 Segment 1번이라는게 나오게 된다. 그리고 이 Segment 1번은 TCP Buffer에 가서 붙게 된다. 이 Segment라는게 대략 두번정도 오게되면 잘 수신했다고 서버에 어떤 번호를 알려준다. 이를 ACK라고 한다. ACK#3 을 보낸다고 하면 1,2번은 잘받았으니 3번을 보내! 라는 의미이다.
서버 입장에서 생각해보면 1번이 가고, 2번이 갔을때 그 즉시 3번을 보내는것이 아니라 Wait를 한다. 이는 ACK#3을 기다리는 것이다. 그리고 ACK#3을 받으면 그때되서야 3번을 보내게 된다. 문제는 이것때문에 속도지연이 발생한다. TCP가 UDP보다 느리다고 하는 것은 다른이유도 있지만 이걸 기다리다가 지연이 걸려서 느려지게 되는 이유이다.
TCP Buffer의 크기는 Window Size이다. 즉, 수신측에서 Segment가 날라오면 조립해서 집어넣을 수 있는 공간이다. Window Size라는 것은 정해져있는데 ACK를 보낼 때 중요한 것은 ACK는 Window Size를 포함하고 있다는 것이다.
그래서 서버에서 전송을 할때 수신측의 ‘Window Size’가 ‘MSS’보다 큰지를 따져서 Yes면 보내고 No면 Wait가 걸린다.
이어가자..
TCP buffer에 Segment가 도착하면 TCP가 계속해서 적재하고 TCP buffer에 적재된것을 하루빨리 File I/O buffer에 보내야할 것이다. 이는 Client Process 입장에서도 Socket에다가 Receive(읽는다), Send(쓴다)를 할것인데 여기서 중요한건 Receive 속도를 따져야한다는 것이다.
Receive 속도가 Network 수신속도보다 무조건 빨라야 한다. 그렇지 않으면 TCP buffer의 여유공간이 점점 줄어들게 된다. 그러면 서버에서 수신측의 Window Size에 여유공간이 없기때문에 데이터를 못보내준다. (처리지연에 대한 문제발생)
TCP/IP통신과 MAC주소의 변화
IP Packet을 감싸고 있는 Frame 안에는 Ethernet Header가 붙는다. 이 Ethernet Header란게 최초로 PC에서 Router에 갈때는 출발지에서 목적지가 어디로 바뀌는지 잘봐야한다. Router#1에서 Router#2로 갈때 IP Packet은 달라지지않지만 Header는 “Router#1이 보낸다! Router#2받아라!”로 바뀐다. 이런식으로 바뀌고 바뀌면서 Server까지 간다.
그래서 L2 구간을 통과할 때는 IP주소보다 MAC주소가 중요하다. L2 스위치라는 것 자체가 MAC주소 스위칭을하기 때문이다. 이 MAC주소는 Host하나를 지나갈때마다 바뀐다. 결국 MAC주소는 IP주소랑 체계가 달라서 인터넷에서 Routing같은걸 할수가 없음을 기억해두자.