TCP/IP
1. 소켓을 작성한다.
[프로토콜 스택 내부 구성]
애플리케이션에서 데이터 송신을 시작한다.
소켓라이브러리를 사용해 리졸버로 DNS 서버를 조회한다
OS내부에 있는 프로토콜 스택이 그 다음 작업을 의뢰 받는다.
TCP혹은 UDP로 데이터를 송수신한다.
IP프로토콜로 패킷 송수신 동작을 제어한다.
ICMP : 패킷을 운반할 때 발생하는 오류를 통지하거나 제어용 메시지를 통지할 때 동작
ARP : IP주소에 대응하는 이더넷의 MAC주소를 조사할 때 사용합니다.
LAN 드라이버는 LAN 어댑터의 하드웨어를 제어합니다.
그 아래에 있는 LAN어댑터가 실제 송수신 동작. 즉 케이블에 대해 송수신하는 동작을 실행합니다.
[소켓은 통신 제어용(제어정보 기록)]
‘netstat’를 터미널에 입력하면 네트워크 상태를 확인할 수 있음.
통신 상대의 IP주소, 포트번호, 통신 동작 진행 상태 등의 소켓의 ‘통신 제어정보’ 확인 가능
[소켓을 호출할 때 동작]
‘socket’ 메서드를 호출, 프로토콜 스택에 의뢰해 소켓을 생성
소켓 한개가 사용하는 메모리 영역을 확보 (통신 이전이면 초기 상태를 메모리에 기록)
프로토콜 스택은 소켓에 대한 디스크립터를 어플리케이션에 알려줌
디스크립터를 통해 프로토콜 스택에 의뢰
디스크립터만 있으면 프로토콜 스탯이 소켓의 통신상태, 상대 소켓등 기타 정보를 알 수 있음.
2. 서버에 접속한다.
접속동작 : → 데이터 송수신이 가능한 상태고 만드는 것
초기상태 : 정보가 없음. 접속하려는 소켓에 대한 IP주소와 포트번호가 필요함
connect메소드는 애플리케이션이 알고 있는 상대 소켓의 IP주소 및 포트 번호를 프로토콜 스택에 알려저 socket에 기록하는 일을 수행함
소켓에 접속하는 connect 수행시 데이터 송수신 메모리 버퍼도 확보함
[맨 앞에 헤더를 배치(제어 정보 기록)]
소켓의 제어정보
이더넷/IP헤더 + TCP헤더 : 클라이언트와 서버가 통신하기 위해서 필요한 정보. 소켓의 헤더에 포함되어 보내짐
소켓(프로토콜 스택 메모리 영역)에 기록되는 정보
소켓의 제어 정보에 따라서 프로토콜 스택의 동작 대부분이 결정지어 지기 때문에 결합도가 매우 높다.****
[접속동작] 3 way handshake
‘connect’를 호출하여 상대 IP와 포트 번호를 함께 쓴다.
프로토콜 스택의 TCP담당이 상대와 정보를 주고받은 정보를 기록하는 다음 과정을 거친다(송신처와 수신처 포트 번호등의 중요한 정보)
상대 소켓(IP주소와 포트번호)지정과 헤더 설정이 끝나면 SYN 비트(컨트롤 비트)를 1로 설정한다
TCP헤더 생성 이후 프로토콜 스택 내부의 IP담당에게 넘겨주어 패킷 송신 동작을 실행하도록 한다. 여기서 송신한 패킷은 상대 서버의 IP담당이 받아 TCP담당에게 넘겨줌)
상대 소켓의 TCP담당은 받은 패킷의 수신처 포트번호에 적힌 소켓을 찾는다. 소켓을 지정하여 필요한 정보를 기록하고 응답을 보낸다.
응답을 보낼 때, SYN을 1(시퀀스 초기번호)로 설정하고 TCP 헤더에 필요 정보를 설정한다. 추가로 패킷을 성공적으로 받았다고 알리는 ACK 비트를 1로 설정한다.
생성된 헤더 정보를 IP 담당에게 넘겨 다시 응답한다.
응답을 받은 소켓은 SYN 비트를 확인하여 접속이 성공했는지 보고 성공이라면 접속 완료를 기록한다. 그리고 상대 소켓에 패킷을 잘 받았다는 ACK 비트를 1로 만든 패킷으로 응답한다.
3. 데이터를 송수신 한다.
write 메서드를 호출. 송신하고자 하는 데이터를 프로토콜 스택에 넘긴다
어플리케이션에서 받은 데이터를 프로토콜 스택 내부의 버퍼 메모리 영역에 우선 저장한다.
이유는, 어플리케이션에 건네주는 데이터의 크기는 프로토콜 스택이 제어할 수 없기 때문에 받은 데이터를 곧바로 보내면 데이터 송수신 동작이 지나치게 많이 일어나서 네트워크 효율이 떨어진다.
패킷의 최대크기인 MTU에서 헤더를 제외한 MSS 만큼의 최대 데이터를 보낼 수 있다. (이것보다 작은 데이터를 보내는 경우 패킷이 예상치 못하게 나누어지지 않는다)
하지만 항상 버퍼를 꽉 채워서 데이터를 보내는 경우 대기 시간이 길어지므로 송신 동작이 지연된다.
네트워크 이용 효율을 중시하는지, 송신 동작 시간을 중시하는지 잘 절충해야한다. (프로토콜 스택을 구현한 OS에서 담당하며 어플리케이션 레벨에서 어느 정도 설정을 할 수도 있다)
데이터가 크면 분할해서 보냄
ACK 번호를 사용해 패킷이 도착했는지 확인
ACK 번호 = 수신을 완료한 바이트 + 시퀀스번호
최초 3way handshake를 할 때 초기 시퀀스 번호를 함께 주고받는다.
초기 시퀀스 번호를 악용할 수 있기 때문에 난수로 설정하여 미리 주고받는다.
이후
최초 시퀀스 번호 + 데이터의 크기
만큼의 데이터를 수신했다면 그것에 대한 확인으로지금까지 수신한 바이트 + 시퀀스번호
숫자를 ACK로 지정하여 응답한다.데이터의 크기는 어떻게 알 수 있을까? 보낸 패킷에 헤더길이를 빼면 수신한 데이터의 크기를 유추할 수 있기 때문에 따로 기재하지 않는다.
이후 송신할 데이터를 시퀀스 번호로 지정하고 송신하고, 동일하게 수신한 마지막 바이트 + 시퀀스번호를 ACK로 응답한다.
시퀀스 번호와 ACK 번호로 누락된 패킷 여부를 알 수 있다. 만일 누락되었으면 송신 버퍼 메모리에 저장되어 있는 데이터를 재송신한다.
TCP는 누락을 검출하고 회복 처리를 한다
LAN 어댑터, 버퍼, 라우터는 회복 조치를 취하지 않는다. 오류가 검출되면 패킷을 버린다.
TCP 여러번 패킷을 재송신해도 오류가 난다면 동작을 중지하고 애플리케이션에 오류를 통지한다.
패킷 평균 왕복 시간으로 ACK 번호의 대기 시간을 조정한다
ACK가 오지않는 것으로 패킷 유실을 판단하는데 ACK를 평생 기다릴 순 없으니 타임아웃 값 만큼 기다린다.
슬라이딩 윈도우(Sliding window) 제어 방식으로 ACK번호를 관리한다.
ACK번호를 기다리지 않고 (핑퐁 방식이 아닌) 연속해서 복수의 패킷을 보내는 방식
장점 : ACK번호를 기다리는 시간이 낭비되지 않음.
단점 : 수신측의 총 용량(능력)을 초과해서 패킷을 보내는 사태가 일어날 수 있음
해결방법 : 이것을 해결하기 위해 슬라이딩 윈도우 방식에서 수신 측에 빈 버퍼 최대 사이즈(윈도우 사이즈)를 TCP 헤더의 윈도우 필드에 기록하여 송신측에 알려준다.
ACK번호와 윈도우를 합승한다.
ACK번호와 윈도우 사이즈를 각각 다른 패킷에 송신하면 주고받는 패킷이 너무 많기 때문에 효율적이지 않다.
둘 중 하나만 생성되었을 때 기다리다가 두 개가 모두 일어나면 함께 하나의 패킷으로 송신한다.
복수개의 ACK가 생겼을 때도 최후의 것만 송신한다.
복수 윈도우 통지가 발생해도 최후 윈도우 사이즈만 보낸다.
HTTP 응답 메시지를 수신한다.
프로토콜 스택이 HTTP 요청 메세지를 모두 보면 응답 메세지를 수신해야한다.
read
메서드를 호출해 프로토콜 수택이 수신 버퍼에 응답 메세지를 수신한다.응답 메세지가 일정 시간 후 도착해 수신 버퍼에 담기면 프로토콜 스택은 그것을 추출해 어플리케이션에 넘겨준다.
수신 데이터에 TCP 헤더 정보를 통해 누락된 데이터가 없는지 확인하고 ACK를 응답한다. 데이터 조각을 버퍼에 보관하고 원래 데이터로 복원하여 어플리케이션에 보낸다.
어플리케이션에 데이터를 추출한 타이밍에 윈도우 사이즈를 상대에 통지한다.
4. 서버에서 연결을 끊어 소켓을 말소한다.
TIME-WAIT란?
서버와 클라이언트가 연결된 상태에서 서버를 강제 종료를 하더라도 4 way-handshake 과정을 거침 (time-wait를 100% 거침)
time-wait 상태에 있으면 ip와 port 정보를 유지하는 상태임. 때문에 같은 ip와 port를 사용해 서버를 새로 띄우면 bind exception이 발생함 →
**코드**
time-wait 상태가 생각보다 더 길어질 수가 있음.
**
serverSocket.close();
**를 호출하지 않고도 4-way handshake가 진행되는 이유는 운영체제가 자원 관리를 담당하기 때문입니다.
CLOSE-WAIT란?
→ 서버로부터 FIN패킷을 받은 시점부터 FIN 패킷을 보낼 때까지의 이 과정이
CLOSE_WAIT
상태TCP의
CLOSE_WAIT
상태는 일반적으로 클라이언트에서 발생하는 상태입니다. 이 상태는 클라이언트가 서버로부터 FIN 패킷을 받았지만, 클라이언트가 아직 모든 데이터를 소비하지 않았거나 처리하지 않은 경우에 발생합니다. 클라이언트가 서버로부터 FIN 패킷을 받으면 해당 연결의 리소스가 정리되고 데이터 송수신을 중단합니다. 그러나 클라이언트가 아직 소비하지 않은 데이터가 있는 경우, 이 데이터를 처리한 후에 해당 소켓을 닫아야 합니다.간단히 말해서,
CLOSE_WAIT
상태는 클라이언트가 서버로부터 연결 종료 요청을 받았지만, 아직 클라이언트 측에서 종료되지 않은 상태를 나타냅니다. 이 상태에서 클라이언트가 해당 소켓을 닫으면CLOSE_WAIT
상태가 해소됩니다.
[FIN+ACK] 패킷을 함께 보내는 이유?
위의 사진을 보면 server→client로 FIN패킷을 보낼때와 그 아래 client→server로 FIN패킷을 보낼 때 ACK 패킷이 함께 보내짐을 알 수 있음
FIN과 ACK를 함께 보냄으로써 만약 FIN패킷이 유실되더라도 수신측에서 ACK패킷을 받음으로써 송신측의 패킷 전송상태를 확인할 수가 있음. 따라서 패킷의 손실이나 지연이 발생하더라도 연결종료를 안전하게 수행할 수가 있다는 장점.
만약 ACK패킷을 같이 보내지 않은 상황에서 FIN패킷 유실이 일어나게되면 연결 종료가 지연될 수가 있음. ACK 패킷을 함께 보내는 것은 실제로 더 안전한 방식이며, 데이터의 정확한 전달과 연결의 안전한 종료를 보장한다고 볼 수가 있음.
5. IP와 이더넷의 패킷 송.수신 동작 (IP 프로토콜)
이더넷의 패킷 : MAC헤더, IP헤더, TCP헤더, 데이터조각
IP에서의 패킷 : IP헤더, TCP헤더, 데이터조각
패킷의 송신처에서 패킷 생성
header에 제어정보를 입력
가까운 중계 장치에 송신
패킷을 중계장치에 계속 송신하면서 수신처에 패킷 도착
→ IP가 목적지를 확인 후 다음 IP의 중계장치인 라우터를 가르킴
→ 서브넷 안에 있는 이더넷이 중계 장치까지 패킷을 운반
MAC 헤더 : 이더넷용 헤더
IP용 헤더 : IP용 헤더
라우터(Router)(외부망) - 네트워크 계층
서브넷(Subnet)(내부망) - 네트워크 계층
이더넷 - 데이터 링크 계층
이더넷을 기반으로 하는 랜 (Local Area Network)은 허브, 스위치 등을 사용하여 장치들 간의 통신을 관리.
허브(Hub) - 데이터 링크 계층
모든 포트로 브로드캐스팅 → 효율 떨어짐
스위치 - 허브에서 발전된 형태
TCP이후 IP layer에서
IP헤더
를 만들어 TCP의 헤더 앞에 붙임.IP 헤더에서의 수신처 IP주소에는 통신 상대의 주소를 설정함
송신처 IP주소는 송신처가 되는 LAN어댑터를 판단해서 주소를 설정함
6. UDP 프로토콜을 이용한 송.수신 동작
수정 송신이 필요 없는 데이터의 경우
TCP의 경우 데이터 손실시 3way-handshake를 통해서 재전송을 해줌
손실된 패킷을 확인하고 그 패킷을 다시 보내야 하기 때문에 복잡함 오버헤드가 여기에서 발생함
UDP의 경우 재전송이 오히려 불필요한 데이터들에 사용하면 적합한 프로토콜
패킷하나에 데이터를 수용할 수 있을만큼 길이가 짧은 경우 데이터를 전부 다시보냄.
수신 확인 응답 패킷이 아닌, **
회신 자체
**가 수신 확인 응답을 대신하는 형태이기 때문에 TCP의 수신확인처럼 번거로운 데이터들이 필요가 없음.ex : DNS 응답 데이터에는 요청한 도메인 이름에 대한 IP 주소 정보와 관련된 정보
제어용 짧은 데이터(DNS 서버 조회)
DNS 에서 일정시간 회신이 없을 경우 데이터를 재전송 해야하는데, 이건 구현하는 것
음성 및 동영상 데이터
실시간성이 중요한 데이터
영상은 되돌릴 수 없기 때문에 데이터가 늦게 도착하면 쓸모가 없음
그렇다고 고속 회선을 쓰기에는 적정기술 측면에서 낭비가 큼
이처럼 다시 보낼 필요가 없는 데이터, 보내도 사용이 불간으한 데이터를 다룰 때 UDP를 사용해 데이터를 보내는게 더 효율적임.
다만 HTTP 3.0에 들어서며 TCP의 장점과 UDP의 장점을 뽑아낸 QUIC라는 UDP 프로토콜을 google에서 만들어 보편화 됨.
Last updated