HTTP는 TCP에 기반한 프로토콜이므로 웹 서버-클라이언트는 대표적인 TCP 서버-클라이언트 응용 프로그램입니다. 웹 브라우저는 사용자가 입력한 주소를 해석하여 접속 대기 중인 웹 서버에 접속합니다. 그런 다음 HTTP를 이용하여 웹 페이지 경로명과 같은 요청 메시지를 보냅니다. 웹 서버는 웹 브라우저에서 받은 메시지를 분석한 후 HTTP를 이용하여 웹 페이지 데이터와 같은 응답 메시지를 보내줍니다.
이처럼 TCP 서버-클라이언트 핵심 동작은 다음과 같습니다.
1. 서버는 먼저 실행하여 클라이언트가 접속하기를 기다립니다(listen).
2. 클라이언트는 서버에 접속(connect)하여 데이터를 보냅니다(send).
3. 서버는 클라이언트 접속을 수용하고(accept), 클라이언트가 보낸 데이터를 받아서(recv) 처리합니다.
4. 서버는 처리한 데이터를 클라이언트에 보냅니다(send).
5. 클라이언트는 서버가 보낸 메시지를 받아서(recv) 처리합니다.
6. 데이터를 주고받는 과정을 모두 마치면 접속을 끊습니다(윈도우: closesocket / 리눅스: close).
TCP 서버와 클라이언트가 연결되어 통신을 수행하는 과정의 각 단계를 요약하면 다음과 같습니다.
1. 서버는 소켓을 생성한 후 클라이언트가 접속하기를 기다립니다(대기 소켓 생성). 이때 서버가 사용하는 소켓은 특정 포트 번호와 결합되어 있어 이 포트 번호로 접속하는 클라이언트만 수용할 수 있습니다.
2. 클라이언트가 서버에 접속합니다. 이때 TCP 프로토콜 수준에서 연결 설정을 위한 패킷 교환이 일어납니다. (TCP 3-way handshake: TCP/IP 프로토콜을 이용해서 통신을 하는 응용 프로그램이 데이터를 전송하기 전에 먼저 정확한 전송을 보장하기 위해 상대방 컴퓨터와 사전에 세션을 수립하는 과정입니다. SYN(클라이언트->서버) -> SYN+ACK(서버->클라이언트) -> ACK(클라이언트->서버) 순.)
3. TCP 프로토콜 수준의 연결 절차가 끝나면, 서버는 접속한 클라이언트와 통신할 수 있는 새로운 소켓을 생성합니다. 이 소켓을 이용하여 서버와 클라이언트는 데이터를 주고받고, 기존 소켓은 새로운 클라이언트 접속을 수용하는 데 계속 사용합니다.
서버 측 소켓과 클라이언트 측 소켓은 일대일로 대응하며, 하나의 클라이언트가 둘 이상의 소켓을 사용하여 서버에 접속할 수도 있습니다. 또한 서버 측에는 항상 연결 대기 중인 소켓이 하나 더 존재합니다.
교재 96쪽은 받은 데이터를 그대로 다시 보낸다는 뜻의 에코 서버와 에코 서버와 통신한다는 의미의 에코 클라이언트 예제입니다.
'netstat -a -n -p tcp | findstr 9000' 명령이 있습니다. netstat 명령은 네트워크 연결과 통계 정보를 표시하는 유틸리티로, 윈도우에서 기본으로 제공합니다. -a 옵션은 모든 연결과 연결 대기 포트를 표시하며, -n 옵션은 주소와 포트 번호를 숫자 형식으로 표시합니다. -p tcp 옵션은 TCP 상태만 골라서 볼 수 있습니다. findstr 명령은 특정 패턴을 찾을 때 사용하는데, 여기서는 포트 번호 9000번을 포함하는 줄만 골라내는 기능입니다. |(파이프)는 두 명령을 연결하여 앞쪽 명령의 출력을 다음 명령의 입력으로 넘겨주는 역할을 합니다. 즉, 'netstat 명령의 출력에서 9000번을 포함하는 줄만 골라내어 출력하라'라는 뜻이 됩니다.
서버 측에 존재하는 소켓은 모두 같은 포트 번호를 사용합니다.
클라이언트 접속을 수용하는 서버 측의 TCP 소켓은 LISTENING 상태에 계속 머물러 있습니다.
클라이언트가 접속하면 서버 측과 클라이언트 측에 ESTABLISHED(확립된) 상태의 TCP 소켓이 각각 존재합니다. 서버 측의, ESTABLISHED 소켓은 새로운 클라이언트가 접속할 때마다 새로 만들어지지만, 클라이언트 측의 ESTABLISHED 상태 소켓은 클라이언트당 한 개만 존재합니다.
클라이언트가 접속을 끊으면 서버 측의 ESTABLISHED 상태 소켓은 사라지고, 클라이언트 측의 ESTABLISHED 상태 소켓은 TIME_WAIT 상태가 됩니다. TIME_WAIT 상태의 TCP 소켓은 일정 시간(운영체제의 TCP/IP 구현에 따라 다르지만 대개 5분 이내)이 지나면 사라집니다. TIME_WAIT 상태는 같은 포트 번호를 가진 소켓 생성을 일정 시간 금지하여, 이전 TCP 연결에서 발생했다가 뒤늦게 도착한 데이터를 잘못 수신하는 상황을 막아줍니다.
응용 프로그램 통신을 위해 결정해야 할 요소에는 통신 규약으로 소켓을 생성할 때 결정하는 프로토콜, 서버 또는 클라이언트 자신의 주소인 지역 IP 주소와 지역 포트 번호, 서버 또는 클라이언트가 통신하는 상대의 주소인 원격 IP 주소와 원격 포트 번호가 있습니다. 이번에 설명할 소켓 함수는 지역 주소와 원격 주소를 결정하고 TCP 상태(LISTENING, ESTABLISHED 등)를 변경하기 위한 일련의 절차라고 할 수 있습니다.
TCP 서버가 소켓 함수를 호출하는 순서입니다.
1. socket() 함수로 소켓을 생성함으로써 사용할 프로로콜을 결정
2. bind() 함수로 소켓의 지역 IP 주소와 지역 포트 번호를 결정
3. listen() 함수로 소켓의 TCP 상태를 LISTENING으로 변경
4. accept() 함수로 클라이언트 접속을 수용하고, 접속한 클라이언트와 통신할 수 있는 새로운 소켓을 생성합니다. 이때 원격 IP 주소와 원격 포트 번호가 결정됩니다.
5. send() 함수와 recv() 함수 등의 데이터 전송 함수로 클라이언트와 통신을 수행한 후 closesocket()/close() 함수로 소켓을 닫습니다.
6. 새로운 클라이언트 접속이 들어올 때마다 4~5번의 과정을 반복합니다.
socket() 함수와 accept() 함수는 반환값이 SOCKET이며, 나머지는 반환값이 int입니다.
bind() 함수는 소켓의 지역 IP 주소와 지역 포트 번호를 결정합니다.
listen() 함수는 소켓의 TCP 상태를 LISTENING으로 변경합니다.
accept() 함수는 클라이언트 접속을 수용하고, 접속한 클라이언트와 통신할 수 있는 새로운 소켓을 생성하여 리턴합니다. 이때 원격 IP 주소와 원격 포트 번호가 결정되고, 기존 소켓은 새로운 클라이언트 접속을 수용하는 데 계속 사용합니다.
connect() 함수는 TCP 프로토콜 수준에서 서버와 논리적 연결을 설정합니다.
기본이 되는 함수는 send() 함수, recv() 함수입니다. 또한 sendto() 함수, recvfrom() 함수는 UDP에서 주로 사용합니다.
윈도우 전용 함수로는 WSASend*(), WSARecv*() 확장 함수, 리눅스 전용 함수로는 write() 함수, read() 함수가 존재합니다.
send() 함수는 응용 프로그램의 데이터 전송을 위해 운영체제의 송신 버퍼에 데이터를 복사하고 리턴합니다.
recv() 함수는 운영체제의 수신 버퍼에 도착한 데이터를 응용 프로그램 버퍼에 복사하고 리턴합니다. 수신 버퍼에 데이터가 도달한 경우와 접속이 정상 종료한 경우에 recv() 함수는 성공적인 리턴을 할 수 있습니다.
아래는 IPv4 코드를 IPv6 코드로 변환하는 규칙이며, 이런 규칙을 따라 변경한 코드는 IPv6만을 지원합니다.
소켓은 네트워크 통신의 끝점(End Point)입니다. 소켓은 호스트(컴퓨터나 서버)에서 네트워크 서비스를 구별하기 위해 사용됩니다. 예를 들어, 웹 서버와 메일 서버는 서로 다른 소켓을 사용합니다.
이렇듯 소켓은 end-to-end 구조에서 host에서 네트워크 서비스 구별을 위해 구성되는 개념이고, TCP/IP는 end-to-end 구조에서 중간에서 서비스 데이터를 전송하는 라우터들을 위해 제공되는 규칙입니다.
end-to-end 사이의 라우터들 사이에 TCP 3-Way Handshake 구성이 이루어져야 합니다. 만약 google까지 30개의 라우터를 거쳐서 간다면 30개의 라우터들 사이에 30번의 TCP 3-Way Handshake 구성이 이루어져야 합니다.