흔히 TCP와 UDP를 비교할 때 TCP는 연결 지향이며 신뢰성을 보장하고, UDP는 그렇지 않다고들 말한다.
그래서 연결 지향이란 무슨 뜻일까?
TCP는 송신자와 수신자가 논리적으로 연결되었다고 말한다. 여기서 연결 지향을 하는 이유는 데이터를 신뢰성 있게 보내기 위함이다. TCP는 패킷 교환 방식을 사용하는데, 패킷 교환 방식은 회선 교환 방식과는 다르게 데이터를 일괄적으로 보내지 않고 여럿으로 분할해서 보내는 것이다. 그래서 언제 누구에게서 무슨 패킷이 올지 모르고 송신한 순서대로 수신처가 받는다고 말할 수가 없다.
그런데 TCP는 그걸 해낸다...! 가상 회선 방식으로.
TCP는 데이터를 전송하기 전에 각각의 기기마다 논리적 연결을 수립하고, 이를 가상 회선이라고 한다. 가상 회선 방식에선 패킷이 도착하는 순서와 보낸 순서가 같다.
각 패킷에는 가상 회선을 식별하는 번호가 포함되고 패킷의 전송이 끝나면 연결을 종료하는 과정 역시 거친다. 이 연결 수립과 종료 과정을 Handshaking이라고 말한다.
반면 UDP는 가상 회선 방식이 아니라 데이터그램 방식이다. 이는 비연결형 서비스로 연결을 설정하는 과정이 없으므로 패킷이 전송되는 경로들이 다 다르다. 그래서 도착 순서가 바뀔 수도 있다. 그래서 TCP를 연결지향 방식, UDP를 비연결지향 방식이라고 하는 것이다.
이 과정에서 송수신자가 가질 수 있는 상태들을 알아보자.
CLOSED: 연결 요청을 받지 않고 닫힌 상태
LISTEN: 포트가 열린 상태로 연결 요청을 대기함
SYN_SENT: SYN 요청을 한 상태
SYN_RECEIVED: SYN 요청을 받고 응답을 기다리는 중
ESTABLISHED: 연결이 확립된 상태
A가 클라이언트, B가 서버라고 가정하자.
3-way Handshaking이기에 말 그대로 3번의 과정을 거친다.
STEP 1.
A가 "나 너랑 연결하고 싶어, 이건 내 랜덤 숫자야." 한다.
A가 B에게 접속을 요청하는 SYN(Synchronize Sequence Number) 플래그가 설정되었으며 Sequence Number를 담은 세그먼트를 전송한다. 이때 A는 SYN_SENT 상태가 된다.
여기서 Sequence Number는 A가 랜덤으로 정한 ISN이다.
STEP 2.
B가 "그래 알았어, 네가 보낸 랜덤 숫자 잘 받았고, 여기 또 새로운 랜덤 숫자가 있어." 한다.
B는 LISTEN 상태로 해당 포트를 개방해둔 상태여야만 한다. B는 A에게 ACK과 SYN Flag가 설정된 세그먼트를 전송하고 SYN_RECEIVED 상태가 된다. 그리고 다시 A가 ACK을 보내기를 기다린다.
여기서 Acknowlegment Number는 A가 이전에 보낸 Sequence Number에 +1을 한 값이다.
Sequence Number는 B가 위 ISN과 다른, 또다시 랜덤으로 정한 ISN이다.
STEP 3.
A가 "너가 다시 보낸 랜덤 숫자 잘 받았어. 이제 연결 됐다." 한다.
A가 B에게 ACK을 최종적으로 전송한다. 이후로는 연결이 이루어지고 데이터가 오가게 된다. 이때 B는 ESTABLISHED 상태가 된다.
여기서도 Acknowlegment Number는 STEP 2에서 전송받은 SYN에 +1을 한 값이다.
이전 글에서 설명했듯이 여기서 Sequence Number는 수신 받은 패킷을 조립할 때 순서를 알려주는 역할을 한다. ACK을 보낼 때 Sequence Number에다가 +1을 하는 이유는, 이 패킷이 바로 직전에 수신한 패킷의 다음 패킷임을 알리기 위함이다.
FIN_WAIT_1: 연결 종료되고 있지만 응답은 받을 수 있는 상태
FIN_WAIT_2: 상대의 FIN을 기다리는 상태
CLOSE_WAIT: 연결 종료를 기다리는 상태
TIME_WAIT: 연결은 종료되었으나 상대가 데이터를 더 보낼 수도 있으니 기다리는 상태
LAST_ACK: 연결이 종료되었고 승인을 기다리는 상태
CLOSED: 완전히 연결이 연결이 종료된 상태
A가 연결 종료 요청자, B가 그 요청을 듣는 사람이라고 가정한다.
STEP 1.
A가 "나 너랑 연결 끊고 싶어." 라고 FIN을 보내 연결 종료 의사를 밝힌다.
A가 B에게 FIN 플래그가 설정된 세그먼트를 전송한다. A는 FIN_WAIT_1 상태가 된다.
STEP 2.
B가 "응 알았어. 근데..." 라는 의미의 ACK을 보낸다.
B가 FIN을 받으면 바로 ACK이 설정된 세그먼트를 보내지만, 연결이 종료된 것은 아니다. 만약 A에게 아직 미처보내지 못한 통신이 있다면 그걸 먼저 처리해야 되기 때문이다. 그래서 이때 B는 CLOSE_WAIT 상태가 된다. A는 FIN_WAIT_2 상태가 된다.
STEP 3.
B가 미처 남은 통신을 먼저 처리한 뒤 "이제 다 됐으니 진짜 연결 종료하자." 라는 의미의 FIN을 보낸다.
B가 아까 처리하지 못한 통신이 있다면 그걸 먼저 보낸 뒤에, 클라이언트에게 FIN이 설정된 세그먼트를 보낸다. 이때 B는 LAST_ACK 상태가 된다.
STEP 4.
A가 "확인 했어." 라는 의미의 ACK을 보낸다.
A는 마지막으로 ACK이 설정된 세그먼트를 보낸다. A는 혹시나 ACK이 분실된 경우를 대비해서 TIME_WAIT 상태가 된다. 그리고 일정 시간이 지나면 진짜로 CLOSED 상태가 된다.
Thanks to...
http://wiki.hash.kr/index.php/패킷
https://www.geeksforgeeks.org/tcp-connection-termination/
https://daeun28.github.io/%EC%BB%B4%ED%93%A8%ED%84%B0%EA%B3%B5%ED%95%99-%EC%8A%A4%ED%84%B0%EB%94%94/post20/
https://sleepyeyes.tistory.com/4
https://mindnet.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-22%ED%8E%B8-TCP-3-WayHandshake-4-WayHandshake
https://evan-moon.github.io/2019/11/17/tcp-handshake/
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=senrjql44&logNo=40052293245
https://kosaf04pyh.tistory.com/164