프로토콜은 스택은 OS단에서 지원하는 네트워크 제어용 소프트웨어라고 보면 된다
어떠한 애플리케이션에서 송신까지의 과정은 이렇다.
1.애플리케이션에서 socket에 요청(리졸버)
2.socket이 프로토콜 스택에 요청
3.프로토콜 스택(TCP,UDP,IP)에서 랜 드라이버로.
4.랜 드라이버에서 랜 어댑터(랜카드)로.
프로토콜 스택엔 TCP와 UDP로 데이터 송수신을 함.
이도 하나의 프로토콜임.
패킷을 통신 상대까지 운반하는데 IP가 쓰임.
IP는 2가지로 이루어짐.
소켓은 개념적인 것. 실제로 존재하는 건 아님(추상화 했따는 거겠지)
소켓의 다양한 정보들(IP, 포트번호, 웹서버 주소 등...)은 메모리에 저장됨.
프로토콜 스택은 결국 소켓의 정보를 기반으로 데이터를 송수신함.
소켓이 작성되면 애플리케이션은 connect를 호출한다. 그러면 프로토콜 스택은 자신의 소켓을 서버측 소켓에 접속함.
여기서 접속이란, 통신 상대와 제어 정보를 주고받아 소켓에 정보를 기록, 데이터를 송수신 가능한 상태로 만드는 것이다.
예를들어 클라이언트측이 제 아이피는 aaa.bbb.ccc 구요. 포트번호는 yyyy입니다!라고 하는것이다.
데이터 송수신동작을 실행할땐 일시적으로 송수신 하는 데이터를 보관해야함. 이를 버퍼 메모리 라고한다.
=> 주기억장치와 I/O사이에 쓰는 그 버퍼메모리와 의미가 일치함.
위에서 말한 제어 정보는 TCP의 헤더에 담긴다. 여기엔 중요한 정보들이 있는데
접속, 송-수신, 연결 끊기. 이런 과정 속에서도 모두 TCP 헤더에 제어정보를 담아 보냄.
또한, IP 에도 제어정보가 있다. 따라서 데이터가 패킷화되어 송수신 하면 이런 그림이 된다.
[[이더넷,IP 제어정보], [TCP 제어정보], [데이터]]
=> 데이터앞에 TCP 제어정보가 들어오고, IP,이더넷 제어정보가 들어온다. 상대측은 반대로 읽는다.
connect(<디스크립터>, <서버측 Ip주소와 포트번호>, ...)
를 호출하면.
=> 이게 바로 3-way handshake
애플리케이션이 connect()
에서 제어권이 넘어왔다면, 이제 데이터를 write()
로 보낼 차례다.
여기서 주의할 점은 프로토콜 스택은 데이터에 무슨내용이 쓰여있는지 모른다. 일정한 길이의 바이너리 데이터라는 것만 알 뿐.
데이터가 크면, 잘게잘게 쪼갠다.
form
같은데서 post로 넘겨주는 데이터는 대게 MSS규격을 초과함. 따라서 송신버퍼의 데이터를 규격대로 잘라서 송신함.
ACK Number을 사용하여 패킷이 도착했는지 확인.
데이터를 잘게 쪼갰다면, 순서를 알아야함. 이때문에 송신측에서 시퀀스 번호를 TCP 헤더에 담아 보냄.
이렇게 시퀀스 번호를 담아 보내고, 서버측에서 잘 받았음을 응답하는 것이 ACK Number.
시퀀스 번호를 계속 더한 값을 응답해준다.(당연히 ACK비트도 1로 만들어 넘겨줌).
클라이언트는 ACK Number를 받아 다음 요청때 시퀀스 번호로 보냄.
시퀀스 번호는 난수이기에, 접속 동작에서 클라이언트가 SYN비트를 1로 만들어 보낼때, 초기 시퀀스 번호값도 보냄. 서버측에선 클라이언트 시퀀스 번호를 토대로 계산한 ACK Number + 서버측 시퀀스 번호를 응답함.
상대가 데이터를 받았다고 할 때까지 송신 버퍼에 데이터 보관. ACK Number가 수신되지 않으면, 패킷 재 송신.
단, 물리적으로 연결이 끊겼다면 재송신x(넽워크 불안정...).
기다리는 시간을 타임아웃이라 함. 기다리는 시간은 ACK Number가 돌아오는 시간에 따라 유동적으로 변함.
윈도우 제어방식으로 ACK Number를 관리함. 지금까지 설명한건 핑-퐁방식.
하나의 요청, 처리, 응답 과정을 거침. 하지만 ACK Number가 돌아올때까지 가만히있으면 자원낭비다.
ACK Number가 돌아오는 동안, 계속해서 패킷을 보냄. 이때 수신측 버퍼메모리의 용량은 한계가 있음.
따라서 수신측이 송신측에게 수신 버퍼메모리용량을 알려줌(TCP 헤더의 윈도우 필드에 담아 보냄. 송신측은 이를 계산해서 데이터를 적절히 보냄.
또한, 수신 가능한 데이터의 최댓값을 윈도우 사이즈(=수신측 버퍼메모리)라고 함.
ACK Number과 윈도우 사이즈를 합승. 각각 따로보내는 건, 자원낭비. 통지 할때 같이 보낸다.
왜냐면 윈도우 사이즈는 수신 버퍼가 비게 됐을때 알려주면됨. 그리고 ACK Number은 데이터를 정상적으로 수신했을 때 보내는 것. 따라서 애플리케이션에서 일어난 의뢰가 끝났을 때 윈도우사이즈+ACK Number를 같이 보내면 됨.
ACK Number도 사실 수신한 데이터의 끝에서 한 번만 보내면 된다
=> 어차피 수신측은 송신측의 시퀀스 번호대로 패킷 순서를 보장함.
HTTP 응답메시지를 수신함.
브라우저가 소켓 라이브러리의 read()
호출. 프로토콜 스택에 제어 넘어감.
데이터 조각 TCP헤더 조사해서 문제 없으면 ACK Number 반송
데이터 조각 수신버퍼에 보관
수신 버퍼에 있는 데이터를 애플리케이션이 정한 메모리 영역에 할당.
윈도우를 송신측에 통지. 이때 송신측은 서버겠지?
데이터 송수신이 끝났다면, 연결을 끊을 차례다. 정확히는 데이터 송신을 완료한 쪽에서.
즉, 서버측에서 close()
를 호출한다.(클라이언트측에서도 가능). 아래는 일련의 과정임
close()
호출.만약 이 과정의 시작이 클라이언트측이라면, 마지막에 서버에서 ACK Number를 받지 못할 수 있음.
따라서 클라이언트측 소켓을 바로 말소한다면, 서버측에서 다시한번 FIN을 보낸다면 충돌이 일어남.
오동작을 막기위해 소켓을 즉시 말소하지 않고 일정시간 기다림. 이를 TIME_WAIT이라함.