II : TCP/IP의 데이터를 전기 신호로 만들어 보낸다
1% 네트워크 원리 을 읽고 정리한 문서입니다 ;)
소켓을 작성한다
TCP/IP 소프트웨어의 계층을 보면 다음과 같다.
- 네트워크 어플리케이션이 최상위에 존재한다.
- 웹 브라우저, 메일러, 웹 서버, 메일 서버 등
- 소켓 라이브러리가 어플리케이션과 OS 사이에 위치한다.
- 이 단계에 DNS 리졸버 또한 존재한다.
- OS 단계, 즉 프로토콜 스택은 두 계층으로 나뉜다.
- TCP / UDP 계층에서는 커넥션을 사용하여 통신할 것인지에 따라 캡슐화가 달라진다.
- IP 계층에서는 위 계층을 타고 온 데이터를 IP 주소를 통해 경로를 결정하여 패킷을 보낸다.
- 드라이버 소프트웨어 계층에서 LAN 드라이버가 LAN 어댑터를 제어한다.
- 기기에 하드웨어로 존재하는 LAN 어댑터가 실질적인 통신 송수신을 수행한다.
브라우저나 메일 등의 일반적인 어플리케이션이 데이터를 송수신할 경우에는 TCP를 사용한다.
DNS 서버에 대한 조회 등 짧은 제어용 데이터를 송수신할 경우에는 UDP를 사용한다.
IP 프로토콜 층에서는 데이터를 패킷이라는 단위로 작게 나누어 통신 상대까지 운반하며, 이를 위해 ICMP 라는 제어용 프로토콜과 ARP 라는 MAC 주소 조회 프로토콜을 사용한다.
따라서 프로토콜 스택에 존재하는 통신 상대의 IP 주소, 포트 번호, 통신 동작의 상태 등 제어 정보를 다루는 메모리 공간이 소켓의 실체라고 볼 수 있다.
윈도우의 경우 netstat
이라는 명령어로 소켓의 내용을 화면에 표시할 수 있다.
소켓을 만든다는 것은, 소켓 한 개 분량의 메모리를 확보하고, 그곳에 IPv4 사용여부나, TCP/UDP 사용 등 제어 정보를 기록한다. 이후 이 소켓(의 메모리 공간)을 나타낼 수 있는 디스크립터 를 어플리케이션에게 리턴한다.
정리
- 소켓을 만든다는 것은, 프로토콜 스택에 존재하는 소켓 라이브러리를 통해 제어 정보를 가지는 메모리 공간을 할당받아 제어받는 것을 의미한다.
- 이 제어 공간에는 주소 체계나 프로토콜, 상태 등이 존재하며, 이후 동작은 항상 이 제어 정보를 기반으로 동작하게 된다.
- 어플리케이션은 이 소켓을 나타낼 수 있는 디스크립터를 받아 사용한다.
서버에 접속한다
소켓을 만든 직후에는, 데이터 송신 요청이 들어온다 하더라도 연결 되어있는 상태가 아니기 때문에, 상대가 누구인지, 상태가 어떠한지를 알 수 없다. 또한, 서버측도 클라이언트의 주소뿐만 아니라 상태와 소켓이 사용하는 포트 번호를 알 수 없기에, 서로 통신이 불가능하다.
따라서 소켓의 접속 동작 이라는 것은, 서버의 IP 주소와 포트를 내 소켓에 알리는 것일 뿐만 아니라, 클라이언트가 서버 측에 내 정보를 전달하여 통신 동작의 개시를 알리는 것이다.
또한, 데이터를 송수신 할 때는 일시적으로 데이터를 다루고 보관할 메모리가 필요한데, 이 버퍼 메모리의 확보 또한 접속 동작을 할 때 이루어진다.
이 제어 정보 는 두 가지로 나뉠 수 있다.
- 서로 주고받기 위해 필요한 제어 정보로서, 프로토콜의 헤더로서 서로 송수신되는 정보
- 이 경우 계층마다 제어에 필요한 정보가 다르기에, 헤더가 여러 종류 존재한다.
- 소켓(메모리 공간)에 기록하여 프로토콜 스택의 동작을 제어하기 위한 정보
- 이는 각자의 OS 프로토콜 스택이 내부적으로 관리하는 것이다.
소켓의 접속 동작은 다음 명령어로 이루어진다.
connect(<디스크립터>, <서버측의 IP주소와 포트 번호>, ...)
여기에 서버측의 IP 주소와 포트 번호를 쓰면 명령이 프로토콜 스택의 TCP 담당 부분에 전달되어, 접속을 나타내는 제어 정보 (SYN : 1) 를 기록한 TCP 헤더를 만드는 것이다.
이런 TCP 헤더를 만들게 되면, 이를 IP 계층에 전달하게 되고, IP계층은 서버와 이 정보를 기반으로 통신하여, 서버 측에서는 이 패킷을 받으면 요청받은 포트에 해당하는 소켓을 찾아 연결 작업을 수행한다.
이후 서버는 접속이 가능한 상태가 맞다면 SYN 비트를 설정하고, 접속 불능이라면 RST 비트를 설정한 뒤, ACK 비트를 1로 만들어 응답을 회신한다.
클라이언트가 이 패킷을 받게 되면 연결이 정상 수립된 것이므로 소켓에 서버측 정보와 연결 상태를 기록하며, 다시 ACK 비트가 1인 TCP 헤더를 반송함으로서 연결 절차가 마무리된다.
이렇게 해서 커넥션이 이루어지면 close
를 호출하여 연결을 끊을 때 까지 연결이 유지되게 된다.
정리
- 서버에 접속한다는 것은, 서버의 IP 주소와 포트를 내 소켓에 알리는 것 뿐만이 아니라, 클라이언트가 서버 측에 내 정보를 전달하고 통신 동작의 개시를 알리는 것이다.
- 이러한 제어 정보들을 다룰 때, TCP 헤더를 통해 서로 공유할 제어 정보를 다루고, 프로토콜 스택 내부 메모리 공간에 또한 내부적으로 동작할 제어 정보들이 존재하게 된다.
- 클라이언트가 SYN 비트의 TCP 헤더를 보내고, 이를 받은 서버의 소켓이 SYN+ACK 나 RST+ACK 를 설정하여 반송하며, 응답 받은 클라이언트가 다시 ACK를 반송함으로서 연결이 이루어진다.
데이터를 송수신한다
프로토콜 스택은 받은 데이터를 곧바로 송신하지 않고, 네트워크 송신의 효율을 극대화하기 위해 내부 버퍼에 우선 저장해 놓는 것을 기본 동작으로 한다.
MTU (한 패킷으로 보낼 수 있는 데이터의 최대 길이) 라는 크기가 기준이 되어, 여기서 IP 헤더와 TCP 헤더 크기를 뺀 MSS (한 패킷에 담기는 최대 세그먼트 길이) 만큼 내부 버퍼가 차게 되면 송신이 이루어지게 된다. 이는 이더넷 기준 약 (1500 - 20 - 20)바이트이다.
혹은 이만큼 차지 않더라도, 어느 정도의 내부 타이머가 지나게 되면 데이터를 송신한다. 만일 실시간 통신이 중요한 경우라면 지연 없이 곧바로 송신하도록 설정할 수 있다.
- Windows에서는 내부 타이머로 데이터를 전송하지 않는다. ACK나 MSS도달시 보낸다.
데이터가 클 경우 MSS 의 크기에 맞게 이를 분할하고, 각각을 패킷에 넣어 송신하게 된다.
TCP에는 송신한 패킷이 올바르게 도착했는지 확인하고, 이상이 있다면 재전송하는 기능이 있으므로 송신 후에는 이를 확인하는 작업으로 넘어간다.
TCP에는 시퀀스 번호 라는 개념이 존재하는데, 이는 내가 보내는 데이터가 통신 개시부터 따져서 몇 번째 바이트인지를 기록한 번호이다. 이 값은 통신을 서로 연결할 때 난수로 설정하여 상대한테 알려주는 값이고, SYN 비트를 1로 설정하고 시퀀스 번호를 전송하게 된다.
이 시퀀스 번호 덕분에 수신측에서는 패킷이 누락되었는지를 알 수 있다. 정상적으로 패킷을 수신했다면 송신측에 ACK 비트를 1로 설정하고 ACK 번호에 다음 받을 시퀀스 번호를 기록하여 전송한다. 이를 수신 확인 응답이라고 부른다.
이전 3-Way 핸드셰이크에서 서로가 서로에게 SYN을 보내고 ACK를 한 번씩 보내게 되는데, 이 때 자신이 설정한 시퀀스 번호를 상대에게 알려주고 이를 받았음을 ACK로 응답하는 것이다.
TCP가 송신한 패킷을 ACK가 돌아오지 않을 시 재전송하기 위해 송신 버퍼에 패킷을 보관해 둔다.
이렇게 TCP가 패킷에 대한 확인 작업과 재전송 작업까지 확실히 진행해주기 때문에, 다른 계층에서는 회복 조치를 취할 이유가 전혀 없고, 그저 이상이 있다면 해당 패킷을 버리기만 하면 된다.
이 수신 확인 응답 또한 네트워크가 혼잡한 등의 이유로 지연되거나 버려지게 된다면, 송신 측에서는 이 응답을 무한정 기다릴 수는 없기에 미리 설정한 타임아웃 값 만큼만 기다리게 된다. 재전송을 무턱대고 시도했다가는 네트워크 혼잡만 가중시킬 수 있다.
이 타임아웃 값은 고정된 값이 아니라, 통신을 진행하며 받은 ACK 응답 속도에 비례하여 자동으로 조절되게 된다.
단, 하나 하나의 패킷에 대해 ACK 확인 작업을 거치게 된다면 대기 시간이 매우 길어져 비효율적이므로, 여러 패킷을 병렬적으로 다루도록 슬라이딩 윈도우 방식을 사용한다.
단, 이 윈도우 사용에 있어서 수신 측의 버퍼가 얼마나 여유 공간이 있는지를 송신 측이 알고 이에 맞춰 데이터를 보내야 하기에, TCP 헤더의 윈도우 필드 를 통해 이 크기 정보를 전달한다.
이 수신 버퍼가 늘어나는, 윈도우 크기의 변경 통지는 보통 ACK 수신 확인 응답 이후 메시지 전달 처리 시간 이후 동작되게 된다. 하지만 이 둘 사이의 간격은 네트워크 패킷의 속도와 비교했을 때 매우 짧기도 하며 패킷이 과도하게 늘어나는 것을 방지하고자, ACK 수신 응답과 윈도우 통지를 합쳐서 응답하게 된다.
이 과정에서 만일 보낼 ACK 수신 응답이 여러 개 쌓였다던가, 윈도우 통지 또한 여러 개 대기해 있다면 마지막 상태만 응답함으로서 효율을 높일 수 있다.
정리
- 데이터를 송신할 때와 수신할 때 모두 버퍼링을 사용하여 TCP의 기능을 구현한다.
- SYN 신호를 통해 시퀀스 번호를 전달하고, ACK를 통해 이를 받았음을 확인하며, 내 윈도우 사이즈를 보낸다. ACK를 기다리는 데에는 네트워크 상황에 따라 조절되는 타임아웃 값이 존재한다.
- 이후 MSS에 맞추어 패킷이 나뉘어져 전송되고, 이 패킷마다 ACK 수신 확인 응답을 보낸다.
- 수신측의 버퍼가 쌓이고 비워지는 것을 윈도우 필드로 공유함으로서 송신 측은 여러 패킷을 병렬적으로 송신할 수 있다.
- 수신 확인 응답과 윈도우 통지를 모두 개별적으로 하지 않고, 적당히 합승시킴으로서 효율성 높은 통신을 구현한다.
서버에서 연결을 끊어 소켓을 말소한다
서버와 클라이언트 중 누가 먼저 연결 끊기를 시도해야 하는지 정해진 것은 아니고, 어플리케이션의 구현마다 다를 수 있다.
끊으려고 하는 측이 먼저 소켓 라이브러리의 close
를 호출하게 되고, 이는 FIN 비트가 1로 설정된 TCP 헤더를 만들어 전송하는 것이다.
이후 상대방은 이 FIN 패킷을 받게 되면 ACK 로 응답하고, 어플리케이션의 다음 read
에 통신이 완료되었음을 알린다. 이는 그 어플리케이션이 close
를 호출하게 만들 것이다.
따라서 상대방도 다시 먼저 연결을 끊으려 했던 측에 FIN 패킷을 전송하고, ACK 를 회신받는다.
이것으로 연결은 종료되었지만, 소켓은 바로 말소되지 않고 몇 분 정도 대기시간을 가지고 말소된다.
이는 마지막 FIN 에 대한 ACK 패킷이 유실되어 다시 FIN 을 보냈을 때의 오동작이 있을 수 있는 등 여러 이상 현상을 막기 위함이다.
정리
- 연결의 해제는 FIN 패킷 송신, ACK 응답 수신 이후, 상대방측도 어플리케이션도 연결을 하여 FIN 패킷 송신, ACK 응답 수신을 거치며 이루어진다.
- 소켓은 연결이 해제된 이후에 오동작을 막기 위해 대기시간을 거친 이후 말소된다.
IP와 이더넷의 패킷 송수신 동작
UDP 프로토콜을 이용한 송수신 동작
UDP를 사용하는 경우도 크게 다음 세 종류가 있기에, 종류와 이유를 알아두어야 한다.
- 수정 송신이 필요없는 간단한 데이터의 송신은 UDP가 효율적이다.
- 데이터의 길이가 짧아서 하나의 패킷에 담길 수 있는 경우
- 제어용 짧은 데이터
- 제어용으로 실행하는 정보 교환은 한 개의 패킷으로 끝나는 경우가 많다.
- 오류가 발생하면 회답이 돌아오지 않기에, 이 때 요청을 다시 보내면 된다.
- 음성 및 동영상 데이터
- 실시간으로 전달받지 못하면 회복하여 데이터를 전달받아도 의미가 없는 경우
- UDP로 약간의 오류를 허용하는 것이 더욱 효율적이다.
'공부한 이야기 > 네트워크' 카테고리의 다른 글
1% 네트워크 III : 케이블의 앞은 LAN 기기였다 (0) | 2023.04.29 |
---|---|
1% 네트워크 II-I IP와 이더넷의 패킷 송수신 동작 (0) | 2023.04.29 |
1% 네트워크 I : 웹 브라우저가 메시지를 만든다 (0) | 2023.04.29 |
모두의 네트워크 IX : 무선 랜 이해하기 (0) | 2023.04.29 |
모두의 네트워크 VIII : 네트워크의 전체 흐름 살펴보기 (0) | 2023.04.29 |