데이터를 송·수신
1%의 네트워크 원리 (09) - TCP/IP의 데이터를 전기 신호로 만들어 보냄 3
03 데이터를 송·수신
1 프로토콜 스택에 HTTP 리퀘스트 메시지를 넘김
애플리케이션
connect()
에서 앱에 제어가 되돌아오면 데이터 송·수신 동작에 진입- 앱이
write()
를 호출해 송신 데이터를 프로토콜 스택에 건넴
프로토콜 스택
- 송신 데이터를 받은 프로토콜 스택이 송신 동작을 실행
- 프로토콜 스택은 받은 데이터 내용 모름. 나열된 바이너리 데이터로 인식
- 자체의 내부 송신용 버퍼 메모리 영역에 저장
- 앱마다 주는 데이터의 길이가 다르기 때문(한번의 송신 의뢰에서)
- 작은 패킷을 많이 보내면 네트워크 이용 효율 저하. 어느 정도 저장하고 송·수신 동작함
판단 요소
- 한 패킷에 저장할 수 있는 데이터의 크기
- 프로토콜 스택은
MTU
라는 매개변수를 바탕으로 판단
- 프로토콜 스택은
- 타이밍
- 프로토콜 스택은 내부에 타이머가 있어 일정 시간 이상 경과하면 패킷 송신
- ms 단위의 시간
TCP 프로토콜 사양에는 절충에 관한 규정이 없음 (1번 vs 2번)
MTU(Maximum Transmission Unit)
- 한 패킷으로 운반할 수 있는 디지털 데이터의 최대 길이
- 이더넷에서 보통 1500바이트
- 패킷 맨 앞 헤더도 포함
MSS(Maximum Segment Size)
- 헤더를 제외한 하나의 패킷으로 운반할 수 있는 TCP의 데이터의 최대 길이
1
2
3
4
<- MSS ->
프리앰블(preamble) / MAC_헤더 IP_헤더 TCP_헤더 데__이__터 FCS
스타트_프레임_딜리미터
<------------ MTU ----------->
2 데이터가 클 때는 분할해서 보냄
1
2
3
4
앱의 데이터: HTTP_헤더|메시지_본문_메시지_본문...
<--- 조각_1 ---><--- 조각_2 --->
TCP 조각_1 TCP 조각_2
MAC IP TCP 조각1 MAC IP TCP 조각2
3 ACK 번호로 패킷이 도착했는지 확인
TCP
- 송신한 패킷이 상대에게 올바르게 도착했는지 확인하고, 도착하지 않았으면 다시 송신하는 기능
- 패킷을 송신한 후에는 확인 동작으로 넘어감
- TCP 담당 부분은 데이터를 조각으로 분할할 때 조각이 통신 개시부터 따져서 몇 번째 바이트에 해당하는지 셈
- 데이터의 조각을 송신할 때 세어둔 값을 TCP 헤더에 기록 (시퀀스 번호)
송신 데이터 길이
- 헤더에 작성하지 않음
- 수신측에서 계산 가능
패킷 전체 길이 - 헤더 길이 = 데이터 크기
1
2
3
4
5
6
7
8
송신측
| | | ...
1 1461 2921
수신측
| | | | ...
1460 2920 4380
위 값에 +1한 값을 ACK 번호로
송신측
- 시퀀스 번호 1 (크기 1400바이트)
- 시퀀스 번호 1461 (크기 1400바이트)
- 시퀀스 번호 2921 (크기 1400바이트)
수신측
- ACK 번호 1461
- ACK 번호 2921
- ACK 번호 4381
수신 확인 응답
- ACK 번호 되돌려주는 동작
- 이 방식으로 수신측에서 누락 여부 확인 가능
- TCP 헤더의 ACK 번호에 기록해 송신측에 알림
시퀀스 번호
- 1부터가 아닌 난수를 바탕으로한 초깃값으로 시작
- 시퀀스 번호를 항상 1부터 시작한다고 예측할 수 있으면 악의적 공격을 할 우려가 있음
- 데이터 송·수신 전에 상대에게 초깃값을 알려야 함
접속 동작
- SYN 제어 비트 1로 서버에 전송
- 이것이 초깃값을 통지하는 것을 나타냄
- SYN에 1을 설정할 때 시퀀스 번호에도 값을 설정하게 돼 있음
- 시퀀스 번호의 값이 초깃값을 나타냄
- SYN을 1로 만든 후 시퀀스 번호의 초깃값을 나타내는 것은 접속 동작인 경우뿐 (Synchronize)
1
2
3
4
5
6
7
8
9
1 <- 시퀀스 번호 초깃값 --
2 -- ACK 번호, 시퀀스 번호 초깃값 ->
3 <- ACK 번호 --
4 <- 시퀀스 번호, 데이터 --
5 -- ACK 번호 ->
6 -- 시퀀스 번호, 데이터 ->
7 <- ACK 번호 --
- 상대가 데이터 받았는지 확인할 때까지 송신한 패킷을 송신 버퍼 메모리 영역에 보관
- 송신한 데이터에 대응하는 ACK 번호가 상대로부터 돌아오지 않으면 패킷 재전송
- 수신측에 패킷이 올바르게 도착한 것을 확인. 도착하지 않았다면 재전송
- 네트워크의 어디에서 오류가 발생했더라도 그것을 전부 검출해 회복처리가 가능
- 회복처리: 패킷을 다시 보내는 것
- 다른 곳에서 회복처리할 필요가 없음
- LAN 어댑터, 버퍼, 라우터 모두 회복처리 하지 않음
- 오류 검출시 해당 패킷을 버리기만 함
TCP가 아무리 다시 보내도 데이터가 도착하지 않는 경우
- 도중에 케이블이 분리되거나 서버가 다운되는 등의 이유
- TCP는 몇 번을 다시 보낸 후 회복의 전망이 없는 것으로 보고 데이터 송신 동작을 강제로 종료하고 앱에 오류 통지
TCP의 데이터 송·수신 동작은 양방향
1
2
3
4
5
6
7
8
9
10
<- 클라이언트측의 시퀀스 번호 초깃값 --
-- 서버측의 시퀀스 번호 초깃값 ->
<- 클라이언트측의 시퀀스 번호 --
-- 서버측의 ACK 번호 ->
...
-- 서버측의 시퀀스 번호 ->
<- 클라이언트측의 ACK 번호 --
4 패킷 평균 왕복 시간으로 ACK 번호의 대기 시간을 조정
타임아웃 값
- ACK 번호가 돌아오는 것을 기다리는 시간
- 대기 시간이 작으면 네트워크 혼잡 악화(ACK 번호 돌아오기 전에 패킷 재전송)
- 대기 시간이 크면 패킷을 다시 보내는 동작이 지연되어 속도 저하
TCP는 대기 시간을 동적으로 변경
- ACK 번호가 돌아오는 시간을 기준으로 대기시간을 판단
- 곧바로 돌아오면 대기시간 짧게. 지연되면 대기시간 늘림
- 데이터 송신 동작 중에 항상 ACK 번호가 돌아오는 시간 계측
5 윈도우 제어 방식으로 효율적으로 ACK 번호를 관리
핑퐁 방식
- 한 개의 패킷을 보내고 ACK 번호를 기다리며 아무 일도 하지 않음
- 시간 낭비
윈도우 제어 방식
- 한 개의 패킷을 보낸 후 ACK 번호를 기다리지 않고 차례대로 연속해서 복수의 패킷을 보내는 방법
- 수신용 버퍼 메모리에 데이터 일시 보관
- TCP 헤더의 윈도우 필드에서 송신측에 알림
- 수신측의 능력을 초과해 데이터를 보내는 일 없음
윈도우 사이즈
- 수신 가능한 데이터 양의 최댓값
- 보통 수신측의 버퍼 메모리의 크기와 같은 크기
- TCP를 정밀 조정하는 매개변수
6 ACK 번호와 윈도우를 합승
윈도우의 값은 송신측이 스스로 산출 가능
윈도우 통지의 타이밍
- 수신측에서 앱에 데이터를 건네주고 수신 버퍼의 빈 영역이 늘어났을 때 이것을 송신측에 통지해야 함
ACK 번호
- 수신측에서 데이터를 받았을 때 내용을 조사하여 정상 수신을 확인할 수 있는 경우에만 송신측에 보냄
수신측의 통지
- 수신측은 ACK 번호나 윈도우를 통지할 때 소켓을 바로 보내지 않고 잠시 기다림
- 이렇게 기다리는 사이 다음 통지 동작이 일어나면 양쪽을 상승시켜서 한 개의 패킷으로 묶어 보냄
- 복수의 ACK 번호 통지가 연속해서 일어난 경우 최후의 것만 통지하고 도중의 것은 생략
- 윈도우 통지가 연속된 경우도 마찬가지로 도중을 생략하고 최후의 것만 통지
7 HTTP 응답 메시지를 수신
read()
- 서버에서 돌아오는 응답 메시지를 받기 위해 호출
- 프로토콜 스택은 다수의 앱에서 의뢰받은 작업을 수행
- 다른 진행 가능한 작업을 실행
프로토콜 스택이 데이터를 수신하는 동작
- 수신한 데이터 조각, TCP 헤더 내용 조사. 데이터 누락 검사
- 문제 없으면 ACK 번호 반송
- 데이터 조각 수신 버퍼에 일시 보관
- 조각 연결해 데이터 복원. 앱에 전달
- 수신 데이터를 앱이 지정한 메모리 영역에 옮겨 기록한 후 앱에 제어를 되돌려줌
- 앱에 데이터를 건네주고 나서 타이밍을 가늠해 윈도우를 송신측에 통지
참고
성공과 실패를 결정하는 1%의 네트워크 원리