TCP는 연결 지향 프로토콜로서 전송되는 데이터의 경계(boundary)가 없다. 그래서 한번의 write
함수 호출을 통해서 "ABCD"라는 문자열을 전송할지라도 그 데이터들이 반드시 하나의 패킷으로
구성되어서 전송된다고 보장할 수 없다. 상황에 따라서 "AB" 문자열이 먼저 하나의
패킷으로 전송되고, 그 다음에 "C" 가 전송되고, 마지막으로 "D" 가 전송될
수도 있다.
그래서 이전에 구현한 에코 클라이언트에서 위와 같은 상황이
발생한다면 문자열의 일부분만을 수신하는 결과가 있을 수 있게 된다.
그러므로
write 함수 호출을 통해서 데이터를 전송하는 경우, 반드시 하나의 패킷으로 구성되어서
데이터가 전달되어야만 제대로 동작할 것이다. TCP는 절대로 이것을 보장해 주지 않는다.
다음은 이를 보완한 코드이다.
이전의 에코 클라이언트와 차이가 나는 부분은 66부터 71까지이다. 코드를 보면, 전송된
데이터가 에코되어 완전히 돌아올 때까지, 계속해서 read 함수를 호출해서 수신한 데이터를
배열에 저장해 나가고 있다. 따라서 정확히 전송한 바이트 크기만큼의 데이터를 수신할
수 있다.
하지만 이러한 방법은 수신해야 할 데이터의 크기를 미리
알고 있는 경우가 아니라면 적용하기 어렵다. TCP 기반의 에코 클라이언트가 구현하기
쉬운 이유가 바로 이것이다. 수신해야 할 데이터의 크기를 미리 알고 있다는
것이다.
그러나 다른 소켓 프로그램의 경우에서는 이런 상황이 드물게 발생한다.
즉 데이터를 수신하는 측은 몇 바이트를 수신해야 하는지 알지 못한다. 이러한
경우에는 프로그램을 구현하기가 조금 더 복잡해 질 것이다.
5-2. 경계(Boundary)가 없는 TCP기반의 데이터 전송
이번에는 경계(Boundary)가 없다는
사실을 확인해 보자.
시나리오는 다음과 같다. 클라이언트는 두번의 메시지를 서버로
전송한다. 하지만 서버는 전송되어 오는 메시지를 한번의 read를 통해서 모두 읽어
들인다. 그리고 나서 이 메시지를 한 번의 write 함수를 호출해서 클라이언트로
전송한다. 마지막으로 클라이언트는 네 번의 read 함수를 호출해서 모든 메시지를 수신한다.
만약 시나리오대로 프로그램이 정상적으로 실행된다면 TCP 에서 경계(Boundary)가 없다는 것이
확인된다.
서버쪽 코드이다.
다음은 클라이언트쪽
코드이다.
실행
화면
5-3. TCP의
내부 구조
1) 첫 번째 단계 : 연결 설정 소켓은
전 이중(full-duplex)모드이므로 데이터를 양방향으로 주고 받을 수 있다. 따라서 데이터 송,수신을
하기 전에 준비 과정이 필요하다.
송신 호스트가 SYN
Flag값을 1로 설정한 TCP Packet과 임의의 Sequence Number를 수신 호스트로 보낸다. Ex) 송신 --------------------------> 수신
SYN=1, SEQ=J
수신 호스트가 Session성립을
원하면 SYN Flag를 1로 설정하고 Ack를 송신 호스트가 보낸 SEQ번호의 다음
번호로 정하고 수신 호스트에 따로 설정한 SEQ번호를 보낸다. Ex)
송신 <-------------------------- 수신 SYN=1, ACK=J+1,
SEQ=K
송신 호스트는 ACK 값을 수신지 호스트가 보낸 SEQ번호의 다음
번호로 정하여 수신 호스트에 보낸다. Ex) 송신 --------------------------> 수신
ACK=K+1
서로간에 데이터를 주고 받을 수 있다는 것을 완벽히
확인하는데 세 번의 패킷 전송이 있었다. 따라서 이것을 가리켜 three-way handshaking이라
한다.
2) 두 번째 단계 : 데이터 송,수신 송,수신 단계에서의
흐름 제어(flow control)은 첫 번째 단계보다 훨씬 복잡하다. 여러가지 흐름 제어
기법들이 사용되는데 여기서는 기본적인 개념만 소개한다.
Sender 와 Receiver 가
서로 데이터를 주고 받으면서 SEQ를 받은 데이터의 수만큼 증가 시키고 ACK
를 보낸다. 만약 ACK가 시간내에 수신되지 않는다면 패킷을 보낸측은 해당 패킷을
재전송한다.
3) 세 번째 단계 : 연결 종료
위의 그림에서 먼저 initialtor 가 Responder 에게 종료 요청의 메시지를
담은 패킷을 전송한다.
Responder 는 종료 메시지를 잘 받았다는 ACK
를 전송한다. 이것은 아직 Responder가 아직 종료할 상황이 아니라는 것을 의미한다.
예를 들어 Responder는 아직도 전송해야 할 데이터가 남아 있는 상황을 생각
해 볼 수 있다.
다음 Responder 도 Initialtor 에게 종료
요청 메시지를 보낸다.
마지막으로 initialtor가 최종적인 수신 응답 메시지 ACK를
전송하며 연결은 종료된다.
아마도 한번에 이해하기가 힘들 것이다. 좀
더 자세히 알고 싶다면 전문적인 네트워킹 자료를 찾아보길 바란다.