IP(Internet Protocol)는 인터넷을 통해 패킷을 전달해준다. 하지만 IP는 동일한 컴퓨터에서 실행되는 여러 프로세스(응용 프로그램)을 구분할 수 없다. IP datagram 영역은 오직 컴퓨터 안에서만 식별된다. 전송 프로토콜은 end-to-end(양 끝단)의 사용자들이 안전하게 데이터를 주고 받을 수 있도록 해준다. TCP/IP 프로토콜은 2가지 전송 프로토콜(TCP/UDP)을 제공한다.
TCP/IP에서 안정성을 제공하는 전송 프로토콜이다.
응용 프로그램 관점에서 TCP는 다음과 같은 특징을 갖는다.
TCP는 header와 body 부분으로 나뉠 수 있고, header 부분은 위 그림과 같은 Segment Structure를 갖는다.
source port
와 dest port
, internet checksum
은 UDP와 동일하다. source port와 dest port는 길이가 16비트이다. sequence number
와 acknowledgement number
는 안정적인 전송을 구현하는 데 사용되며, 각각 32비트 길이를 갖는다. 송신하는 쪽에서 sequence number를 보내주면 수신하는 쪽에서 데이터를 잘 받았으면 acknowledgement number를 같이 보내준다. URG, ACK, PSH, RST, SYN, FIN
는 TCP flag들(code bits in TCP header)이다. 각각 1비트씩 길이를 가져 총 6비트의 길이가 된다. 말 그대로 TCP가 데이터를 주고 받으면서, 어떤 flag를 1로 만드느냐를 갖고 송신측과 수신측이 의사소통을 한다.Receive window
는 들어오는 데이터에 얼마나 많은 uffer space를 사용할 수 있는지 식별해준다. 흐름 제어에 사용된다. TCP에서는 기본적으로 three-way handshake을 통해 연결을 설정한다.
예를 들어, 클라이언트에서 서버로 데이터를 보내는 상황이라면
이렇게 하면,
데이터가 얼만큼 안정적으로 전송되었는지 명확히 확인할 수 있다. 예를 들어 TCP는 일정 단위로 데이터를 분할해 전송하는데, sequence number는 해당 데이터가 몇번째 데이터인지를 알려준다(해당 패킷 데이터의 첫번째 번호, 여기에 데이터 길이를 더해 계산한다). 그리고 수신 쪽에서 여기에 +1을 더해서 acknowledgement number로 다시 돌려주면, 해당 번호 전까지 잘 받았다는 걸 확인하고 다음 번호를 sequence number로 설정해서 데이터를 또 보내는 것이다.
패킷 분석 프로그램을 사용하면 직접 확인해볼 수 있다.
아래 그림을 보면, ISN에 +1을 해서 acknowledgement number로 넘겨주는 걸 볼 수 있다.
TCP 연결을 해제할 때는 FIN flag를 사용한다.
이렇게 4번을 주고 받는다고 해서 four-way handshake이라고 한다. 하지만 수신 쪽에서 FIN, ACK flag를 함께 보내서 three-way handshake로 구현할 수 있다.
데이터를 너무 많이 보내면 부하가 걸린다. 그 과정에서 데이터가 유실될 수도 있다. 데이터 흐름을 제어하는 가장 기본적인 방법은 stop and wait이다. 데이터를 보내면 잘 받았는지를 매번 확인하는 과정을 거치는 방법이다. 하지만 데이터 패킷을 보낼 때마다 확인 절차를 거치므로 비효율적이다. 한번에 가능한 최대한 보내는 게 효율적일 것이다.
이를 위해 TCP는 window mechanism(sliding window)을 사용해 데이터 흐름을 제어한다. (3-way handshake 과정에서 윈도우 사이즈를 주고 받는다)
수신측에서 수용 가능한 윈도우 사이즈(버퍼 공간)을 advertise해주면, 송신측에는 윈도우 사이즈가 2개가 있게 된다. 하나는 수신측으로부터 받은 Advertised Window(혹은 Receiver Window)가 있고, 다른 하나는 네트워크 혼잡 제어를 위한 Congestion Window가 있게 된다. 이때 송신측에서는 이 2가지 윈도우 중에서 작은 윈도우 값을 최종 윈도우 사이즈로 결정하게 된다.
MaxWindow = MIN(CongestionWindow, AdvertisedWindow)
EffectiveWindow = MaxWindow - (LastByteSent - LastByteAcked)
congestionWindow의 초기값은 1(MSS)로 되어 있다. 이 MSS는 최대 전송 가능 단위(MTU, Maximum Transmission Unit)에서 IP헤더길이와 TCP헤더길이를 뺀 값을 말한다.
초기값 1에서 시작해서, ACK가 잘 도착하면 congestionWindow에 +=1을 해준다. 반대로 ACK가 일정 시간 동안 도착하지 않으면 congestionWindow를 나눠준다(윈도우 사이즈가 너무 크다고 판단해 줄이는 것이다).
Increment = MSS x (MSS/CongestionWindow)
CongestionWindow += Increment
그러다 보면 sawtooth 패턴이 나타나게 된다. 즉, 일정하게 증가하다가 뚝 떨어지고 ...를 반복한다.
AIMD는 네트워크 가능한 용량에 가깝게 작동할 수 있는 적절한 접근 방식이지만 처음부터 시작할 때 1씩 증가시키므로 가능한 용량까지 도달하는 데 있어 너무 오래 걸린다. 이를 해결해주는 게 slow start이다. slow start는 선형이 아니라 기하급수적으로 증가시킨다. 대신 ack가 도착하지 않으면 1로 다시 초기화한다.
여기서 congestionWindow가 일정 크기에 도달하면 slow start처럼 바로 1로 초기화하는 게 아니라 이 지점부터 AIMD처럼 선형적으로 동작하도록 해서 과격한 동작을 개선할 수도 있다.
HTTP 등 대부분 데이터 송수신은 TCP를 사용한다. 하지만 스트리밍이나 일부 네트워크 프로그램에서는 UDP를 사용한다. UDP는 클라이언트가 서버에 짧은 요청을 보내고 짧은 응답을 기대하는(데이터 유실이 있을 수 있어도 빠른 전송이 필요하거나) 클라이언트-서버 상황에서 특히 유용하다. DNS 같은.
UDP는 TCP와 달리,
연결 설정/해제도 없고 데이터 신뢰성도 보장하지 않는다. 데이터 효율성을 중요시하며 세그먼트 구조도 단순하다.