소켓은 실체가 없다. 제어 정보 또는 제어 정보를 기록한 메모리를 소켓이라 할 수 있다.
프로토콜 스택은 소켓의 제어 정보를 바탕으로 움직인다.
명령 프롬프트 창에 netstat를 치면 소켓의 내용을 볼 수 있다.
옵션으로 -ano를 붙일 수 있는데 각각은 다음의 의미를 가진다.
netstat -ano 라고 치면 프로토콜의 종류(TCP와 UDP 중 하나), 로컬과 통신 상대의 IP주소, 포트 번호 그리고 통신 상태가 나온다.
통신 상태는 두 가지가 있는데, LISTENING은 상대의 접속을 기다리고 있는 상태이고 ESTABLISHED는 데이터를 통신하고 있는 상태라는 것을 의미한다.
PID는 소켓을 어느 프로그램이 사용하는 알 수 있게 해주는데, PID도 보고싶다면 따로 설정해야한다.
디스크립터로 어느 소켓인지 알면 프로토콜 스택에서 필요한 정보를 알 수 있게된다.
접속 동작으로 브라우저가 알고 있는 서버의 IP주소와 포트 번호를 클라이언트의 소켓에게 전달할 수 있다. 그리고 클라이언트에서 클라이언트의 IP주소와 포트 번호를 서버에게 알리면 서버의 소켓에게도 정보가 전달된다. 접속 동작은 이렇게 서로의 정보를 기록하고 데이터 송•수신이 가능한 상태가 되도록 만든다.
또한, 접속 동작에서는 데이터를 잠시 보관하는 버퍼 메모리(메모리 영역)를 확보한다.
제어 정보는 크게 두 가지로 나뉜다.
<헤더>
통신 동작에서 필요한 내용을 TCP 프로토콜로 규정하고 있다.
송신처 포트 번호, 수신처 포트 번호, 시퀀스 번호, ACK 번호, 데이터 오프셋, 컨트롤 비트, 윈도우, 체크섬, 긴급 포인터, 옵션이 규정되어 있다.
이 제어 정보를 데이터인 패킷 맨 앞부분에 배치하는데, 이를 헤더라고 부른다.
TCP 헤더, 이더넷 헤더, IP 헤더가 있다.
<소켓>
애플리케이션에서 통지된 정보, 상대에게 받은 정보, 송•수신 동작의 진행 상황 등이 수시로 기록된다. 상대의 소켓에 있는 제어 정보는 볼 수 없기 때문에 소켓을 바탕으로 헤더를 작성하면 상대쪽도 볼 수 있다.
connect(<디스크립터>, <서버측의 IP 주소와 포트 번호>, …)
접속 동작이 완료되면 데이터를 송•수신할 수 있게된다. 이때 파이프로 연결되었다고 하는데, 실제 무언가로 연결되어 있지는 않지만 연결되어 있다고 생각하는 것이 네트워크 업계의 습관이다.
파이프를 커넥션이라고 한다. 커넥션은 close를 호출하기 전까지 존재한다.
애플리케이션이 write를 호출하면 송신 데이터를 프로토콜 스택에 전달한다.
데이터를 나눠서 전달하는데, 이때 애플리케이션마다 나누는 양이 다르다. 그렇기 때문에 프로토콜 스택은 송신용 버퍼 메모리 영역에 어느 정도까지 저장하고 송신 동작을 한다.
어느 정도까지인지 판단하는 요소는 두 가지가 있다.
첫 번째는, 한 패킷에 저장할 수 있는 데이터의 크기이다. MTU라는 매캐변수를 바탕으로 판단하느데, MTU는 Maximum Transmission Unit으로 헤더와 데이터를 포함한 디지털 데이터의 최대 길이다. 그리고 헤더를 뺀 패킷(데이터)은 MSS(Maximum Segment Size)로 데이터의 최대 길이이다.
두 번째는, 타이밍이다. 애플리케이션의 송신 속도가 느릴 때 MSS까지 기다리고 있으면 송신 동작이 지연된다. 그렇기 때문에 프로토콜 스택은 내부의 타이머로 일정 시간이 경과하면 패킷을 송신한다.
송신 버퍼에 저장된 데이터가 MSS의 길이를 초과하면 MSS의 길이에 맞게 나눠서 한 개씩 패킷에 넣어 송신한다.
TCP 담당 부분은 데이터를 조각으로 나눌 때 몇 번째 바이트에 해당하는지 세고, TCP 헤더의 시퀀스 번호에 기록한다.
위의 그림처럼 1460번째 바이트를 수신 완료했을 때 시퀀스 번호가 1461인 패킷이 오면 누락이 없다는 것을 알 수 있다.
수신측은 이전에 수신한 데이터의 크기와 누적하여 계산하여 TCP 헤더의 ACK 번호에 기록한다. ACK 번호를 보내는 동작을 수신 확인 응답이라고 부른다.
시퀀스 번호는 악의적인 공격이 있을 수 있으므로 난수로 초기값을 시작한다. 그래서 송•수신 전에 SYN에 제어 비트를 1로 설정할 때 시퀀스 번호에도 값을 설정한다.
TCP는 ACK 번호가 상대로부터 돌아오지 않으면 패킷을 다시 보낸다.
ACK 번호가 돌아오는 것을 기다리는 대기 시간을 타임아웃 값이라고 한다.
타임아웃 값이 너무 짧으면 ACK 번호가 돌아오기 전에 다시 보내게 되고, 너무 길면 속도가 저하된다.
타임아웃 값은 서버의 거리에 따라 달라지므로 TCP는 동적으로 변경하도록 하고 있다. ACK 번호가 돌아오는 시간을 기준으로 타임아웃 값을 판단하여 예측한다.
윈도우 제어는 한 개의 패킷을 보낸 후 ACK 번호를 기다리고만 있지 않고 다른 패킷을 보내서 시간의 낭비를 없앤다.
또한, 윈도우 제어 방식으로 수신 버퍼에 데이터가 넘치지 않도록 할 수 있다.
수신측에서 송신 가능한 데이터 양을 TCP 헤더의 윈도우 필드에 기록함으로써 수신측의 능력을 초과하지 않도록 한다.
수신 가능한 데이터 양의 최대값을 윈도우 사이즈라고 한다.
윈도우 통지는 수신 버퍼의 빈 영역이 늘어나서 송신측에 알려야할 때이다.
ACK 번호는 데이터를 받으면 바로 보내야한다.
윈도우와 ACK를 따로따로 보내면 보내는 패킷의 수가 늘어나 효율성이 낮아진다. 그렇기 때문에 수신측은 ACK 번호나 윈도우를 통지할 때 소켓을 바로 보내지 않고 기다려서 둘 다 통지해야하게 되면 한 개의 패킷으로 묶어 보낸다.
브라우저는 서버의 응답 메시지를 받기 위해 read 프로그램을 호출한다.
프로토콜은 응답 메시지가 올 때까지 작업을 보류하고 있다가 도착하면 수신 버퍼에서 수신 데이터를 추출한다. 그리고 문제가 없으면 ACK 를 서버측에 보내고, 데이터 조각을 연결하여 복원해 애플리케이션이 지정한 메모리 영역에 기록한다. 그 후에 윈도우를 서버측에 통지한다.
데이터 보내기를 완료한 쪽에서 연결 끊기 단계에 들어간다.
서버로부터 FIN이 오기 전까지 소켓을 말소하지 않고 기다린다. 그 이유는 소켓을 말소하면 다른 소켓으로 FIN이 가는 상황과 같은 오동작이 생기는 것을 막기 위해서이다.
출처
성공과 실패를 결정하는 1%의 네트워크 원리