클라이언트 쪽에 소켓을 만들었으니 웹 서버 쪽에 존재하는 소켓과 연결하는 과정이 필요하다.
이는 Socket 라이브러리의 "connect" 함수를 통해 수행할 수 있다.
그런데 생각해 보자. 과연 이 연결하는 과정이 왜 필요할까?
이미 클라이언트 PC와 웹 서버가 존재하는 컴퓨터는 케이블로써 연결되어 있는 상태이므로 언제든 통신이 가능한 상태이다. 즉, 물리적으로 이미 통신을 보낼 수 있는 상황에서 왜 소켓을 만들고 소켓끼리 연결하는 과정이 필요할까?
이를 알기 위해선 애플리케이션 프로그램 사이에서 데이티 송/수신을 진행할 때 "프로토콜 스택"을 사용한다는 것을 알아야 한다.
소켓을 만든 직후 애플리케이션에서 데이터 송신을 수행할 경우 프로토콜 스택은 어떤 상황일까?
이전에 말했듯 소켓을 처음 만들면 소켓 1개를 위한 메모리 공간을 확보한 뒤 이 소켓이 초기 상태임을 알리는 제어 정보만 저장한 놓는다.
즉, 소켓을 만든 직후에는 서버에 대한 정보가 아무것도 기록되어 있지 않다.
프로토콜 스택은 소켓에 저장되어 있는 제어 정보를 통해 데이터 송/수신을 진행한다.
그런데 정작 소켓은 이 제어 정보를 가지고 있지 않기 때문에 데이터를 어디로 보낼지 모르는 것이다.
이제는 서버 측에서 생각해 보자.
서버 측에는 이미 소켓이 만들어져 있는 상태이지만 서버 측 프로토콜 스택 또한 클라이언트에 대한 정보를 가지고 있지 않다.
심지어 클라이언트는 URL 분석 및 규칙에 의해 정해진 포트 번호를 통해 서버에 대한 정보 파악 자체는 가능하지만 서버 측에서는 이조차도 불가하다.
즉, 서버 측에서 요청(Request)을 받았다고 하더라도 이에 대한 응답(Response)을 어디에 보내줄지 모르는 것이다.
이런 문제를 해결하기 위하여 클라이언트와 웹 서버는 통신 상대에 대한 제어 정보를 소켓에 저장하는 과정이 수행되어야 하며 이 과정이 접속 동작인 것이다.
먼저 통신 상대와 제어 정보를 주고받아 필요한 제어 정보를 소켓에 기록하여 데이터 송/수신이 가능한 상태로 만든다.
대표적인 제어 정보로썬 IP 주소, 포트 번호가 존재한다.
또한 버퍼 메모리를 확보하는 과정도 진행된다.
데이터 송/수신 동작을 수행할 때 바로 애플리케이션으로 데이터를 주는 것이 아닌 송/수신하는 데이터를 일시적으로 저장하는 메모리 영역이 필요한데 이를 "버퍼 메모리"라고 한다.
웹 서버와 클라이언트의 버퍼 메모리를 확보하는 과정 또한 접속 동작에서 수행된다.
참고로 "접속" 동작이라고 명명한 이유는 전화 기술에서 사용하는 단어를 가지고 온 것이다.
전화할 때 전화번호를 입력하고 상대에게 전화를 거는 과정이 필요하다. 이 과정을 영어로 "connect"라고 하는데 네트워크에서도 소켓끼리 연결하는 과정을 이 과정과 동일하다고 간주하여 "connect"라는 명령어를 쓰며, 이를 한국어로 번역하여 "접속"이라고 부르는 것이다.
제어 정보에는 크게 2가지 종류가 있다.
먼저 소켓에 기록되는 제어 정보로써 프로토콜 스택의 동작을 제어하기 위한 정보이다.
여기에는 애플리케이션에서 통지된 정보, 통신 상대로부터 받은 정보 등이 수시로 기록된다.
사람마다 프로토콜 스택에게 필요하다 생각하는 제어 정보가 다를 수 있기 때문에 프로토콜 스택을 만드는 사람에 따라 이 제어 정보는 달라질 수 있다.
소켓에 기록한 제어 정보는 상대측에서 볼 수 없다.
일단 웹 서버 입장에서 볼 때 브라우저 소켓에 기록된 정보는 곧 본인 정보이므로 굳이 볼 필요도 없으며 OS마다 프로토콜 스택을 만드는 방법이 다르기 때문에 필요한 제어 정보도 다르므로 봐도 분석이 힘들 수 있다.
두 번째 제어 정보로는 클라이언트와 서버 사이 연락을 제어하기 위해 주고받는 정보이다.
이 제어 정보는 접속 동작뿐 아니라 데이터 송/수신 과정을 포함한 통신 동작 전체에서 활용되는 제어 정보로써 단계마다 필요한 제어 정보는 TCP 프로토콜 사양으로 규정되어 있다.
이러한 제어 정보를 "헤더"라고 하는데 헤더는 조금 더 자세히 알아보자.
클라이언트와 서버 사이 연락을 제어하기 위해 주고받는 정보인 헤더는 접속, 송/수신, 연결 끊기 각 단계에서 클라이언트와 웹 서버가 대화할 때마다 부가된다.
이러한 제어 정보는 클라이언트와 서버가 주고받는 "패킷"의 맨 앞부분에 부가되므로 "헤더"라고 부르는 것이다.
접속 동작 단계에서는 송/수신할 데이터가 없으므로 헤더만 주고받으며 데이터 송/수신 단계에서는 보내야 할 데이터를 조각낸 뒤 각 데이터 조각마다 헤더를 붙여 패킷을 만들고 이를 웹 서버에 보내는 방식으로 데이터 송/수신을 진행한다.
클라이언트와 서버 사이 연락을 위해서는 많은 단계를 거친다.
이 때문에 필요한 제어 정보 종류도 많은데, 이더넷이나 IP를 위한 제어 정보도 있을 것이고 TCP를 위한 제어 정보도 있을 것이다.
이렇게 많은 종류의 헤더가 존재하므로 무엇의 헤더인지를 표현하기 위하여 TCP 헤더, 이더넷 헤더(MAC 헤더), IP 헤더와 같이 표기한다.
헤더에 기록된 제어 정보는 네트워크 통신을 수행하기 위하여 꼭 필요한 제어 정보로써 이 헤더만 잘 알면 네트워크 통신 동작을 이해할 수 있다. 그만큼 중요하지만 동시에 많기도 하기 때문에 한 번에 알 필요는 없으며 계속해서 이런 부분에 대해 설명할 것이므로 일단 헤더를 통해 제어 정보를 주고받는다 정도로만 알고 있으면 된다.
출처 : http://www.ktword.co.kr/test/view/view.php?m_temp1=1889
접속 동작은 Socket 라이브러리의 "connect" 명령을 실행시킴으로써 시작된다.
connect(<디스크립터>, <서버 측 IP 주소와 포트 번호>, ...);
명령어를 보면 접속 과정에서 어떤 동작이 일어나고 어떤 제어 정보가 저장되는지 알 수 있다.
먼저 connect의 인자로써 디스크립터를 준다.
현재 클라이언트의 소켓 식별자인 디스크립터를 기록함으로써 클라이언트 소켓에 서버 측 제어 정보를 저장하고 동시에 서버 측 소켓에 저장할 클라이언트 측 IP 주소 및 포트 번호를 얻을 수 있다.
또한 서버 측 IP 주소와 포트 번호를 입력하는데 이를 통해 클라이언트 측 소켓에는 서버 측 제어 정보를 저장할 수 있고 저장된 제어 정보를 통해 서버에 접근하여 서버 측 소켓에 클라이언트 소켓 정보를 저장할 수 있다.
즉, 접속 동작을 통해 서버 측 소켓에는 클라이언트 소켓의 정보를, 클라이언트 측 소켓에는 서버 소켓의 정보를 저장하는 동작이 일어나는 것이다.
그렇다면 접속 과정에 대해 조금 더 자세히 알아보자.
먼저 데이터 송/수신 동작을 실행할 것이라는 제어 정보를 기록한 헤더를 만든다.
이 중 가장 중요한 헤더 필드는 "송신처와 수신처의 포트 번호"이다.
이를 통해 각자 서버에 존재하는 소켓에 통신 상대의 소켓 정보를 저장할 수 있게 된다.
이때 컨트롤 비트 중 하나인 SYN 비트를 1로 만든 뒤 헤더를 주고받는다.
TCP 담당 부분이 TCP 헤더를 만들면 이를 IP 담당 부분에게 건네주며 송신을 의뢰한다.
IP 담당 부분은 헤더를 패킷을 만든 뒤 패킷 송신 동작을 실행할 것이고 네트워크를 통해 서버에 패킷이 도착하면 서버 측 IP 담당 부분이 이를 서버 측 TCP 담당에게 전달한다.
서버측 TCP 담당 부분은 TCP 헤더에 존재하는 수신처 포트 번호를 조사하여 웹 서버의 여러 소켓 중 어디에 연결시킬 것인지 찾는다. 이때 TCP 헤더의 수신처 포트 번호와 같은 디스크립터 값을 가진 소켓에 연결시킨다.
이후 찾은 소켓에 필요한 제어 정보를 기록하면 접속 동작이 진행 중이라는 상태가 된다.
"완료"가 아닌 이유는 웹 서버 측에서는 연결되었음을 인지하였지만 아직 클라이언트 측에선 이를 모르기 때문이다.
이제 접속 동작이 "진행 중"으로 바뀌었으니 웹 서버는 클라이언트 측에 정상적으로 제어 정보가 도착하여 연결되었음을 알려야 한다.
클라이언트에서 서버 쪽으로 보낸 것과 마찬가지로 송신처와 수신처 포트번호, SYN 비트 등을 설정한 TCP 헤더를 만든다.
여기에서 추가된 과정은 ACK 비트를 1로 만드는 것이다.
네트워크에서는 패킷이 없어지는 경우가 생길 수 있으므로 이를 감지하기 위하여 패킷이 정상적으로 통신 상대에게 전달되었는지 확인하는 동작이 존재한다.
패킷이 정상적으로 통신 상대에게 전해졌음을 확인시키기 위하여 ACK 비트를 1로 만든다.
이후 동일하게 TCP 헤더를 IP 담당에게 전해주면 패킷을 만들고 클라이언트 측으로 이동시킬 것이다.
클라이언트 측은 응답 패킷을 받을 것이고 이 중 TCP 헤더를 검사할 것이다.
TCP 헤더에 SYN이 1이면 접속이 성공했다는 것이므로 소켓에 서버 IP 주소와 포트 번호 등과 함께 접속이 완료되었음을 알리는 제어 정보를 저장한다.
이 점이 꽤나 중요한데, 클라이언트 측 소켓에 제어 정보를 먼저 저장하는 것이 아닌 서버 측 소켓에 제어 정보를 먼저 저장시킨 뒤 응답이 정상적으로 올 경우에만 클라이언트 측 소켓에 제어 정보를 저장하는 것이다.
클라이언트 측은 마지막 작업을 수행한다.
3번 과정에서 서버 측은 ACK 비트를 1로 만들었다.
클라이언트는 "ACK 비트가 1인" 패킷이 정상적으로 도착했음을 알리기 위하여 다시 ACK 비트를 1로 만든 TCP 헤더를 웹 서버 측으로 반송한다.
그리고 이 반송된 헤더가 서버에 도착하면 접속 동작이 완료된 것이다.
즉, 데이터를 실제로 송/수신할 수 있는 상태가 된 것이다.
이전에 사진에서 표현했듯 네트워크 업계에서는 이를 "소켓 사이가 파이프로 연결되어 있다"라는 개념으로 이해를 많이 한다.
이때 파이프와 같은 것을 "커넥션(connection)"이라고 한다.
커넥션은 데이터 송/수신 동작을 하고 있는 동안 계속 존재하며 close가 호출되어 연결을 끊을 때까지는 계속 살아 있어야 한다.
이렇게 커넥션을 완성시키는 것이 "connect" 함수의 역할이라 보면 될 것이다.
책에는 커넥션에 대해 설명하며 아래와 같은 문구가 있었다.
... '커넥션'이라고 하지 않고 '세션'이라고도 하는데, 의미는 대체로 같습니다....
물론 쉬운 이해를 위해 이렇게 썼을 것이며 역할만 보자면 그다지 틀린 말도 아니다.
하지만 둘 사이에는 엄연한 차이가 존재한다.
커넥션은 "물리적인 연결"을 담당하는 개념이다.
즉, 커넥션이 연결되어 있다는 것은 실제로 데이터를 송/수신할 수 있다는 의미이다.
세션은 "논리적인 연결"을 담당하는 개념이다.
즉, 연결 자체는 되어 있으나 물리적으로 연결이 끊겨 있을 수도 있다는 것이다.
자, 이해가 안 될 것이다. 물리적으로 끊겨 있는데 논리적으로는 연결되어 있을 수 있다?
이를 이해하기 위해선 로그인을 예시로 들어보면 될 것이다.
아무 사이트나 로그인을 해보자. 그리고 브라우저에 아무런 동작을 수행하지 않겠다.
이럼 로그인이 풀려 있을까? 정답은 "아니다"이다.
브라우저에 아무런 동작을 수행하지 않았으므로 데이터 송/수신이 되지 않고 있기 때문에 connection은 끊겨 있을 것이다.
그런데 어떻게 다시 connection을 연결할 때 로그인 정보를 주지 않아도 이 상태가 유지되는 것일까?
이는 물리적인 연결인 connection은 끊겼으나 논리적 연결인 세션이 살아 있기 때문에 가능하다.
세션은 여전히 살아 있다. 그리고 이 세션에 저장되어 있는 정보 또한 유지되고 있다.
이렇게 이전 연결에 대한 세션이 살아 있는 동안 다시 connection을 수행한다고 생각해 보자.
커넥션을 생성하고 싶다는 요청이 서버에 도착하면 서버는 클라이언트 측이 사용할 세션을 연결시켜 줘야 한다.
이때 서버는 이전에 해당 클라이언트가 사용했던 세션이 있다는 것을 감지하고 새로운 세션이 아닌 기존에 사용하던 세션을 클라이언트와 연결시켜 준다.
즉, 클라이언트 측에서 접속 과정 중 데이터를 보내진 않았지만 이전에 세션에 저장해 놨던 상태가 불러와질 것이다.
따라서 이전에 로그인한 상태가 유지되는 것이다.
이런 논리적인 연결인 세션이 영원히 유지될 경우 세션에는 계속해서 데이터가 쌓일 것이며 새로운 사용자가 접속할 때마다 세션 공간이 하나씩 추가되어야 하므로 나중에는 서버 측에 큰 부담이 될 수도 있다.
따라서 서버 측에서는 어느 정도의 시간이 지나면 논리적 연결인 세션까지 끊어버리는데 이것이 바로 "Session Timeout"이다.
은행 같은 사이트에서는 10분 후면 자동 로그아웃이 되는데, 이는 Session Timeout이 되어 로그인한 정보가 저장되어 있는 세션이 없어졌으므로 로그인 상태가 해제되는 것이다.