삽질의 현장/- 네트워크 프로그래밍

#012_Window_Network_클라이언트 서버 통신 (소켓 관점)

shovelman 2015. 10. 5. 17:05


안녕하세요 삽잡이입니다.


이번 시간에는 소켓 관점에서 본 

클라이언트와 서버간 통신에 대해서 살펴보도록 하겠습니다.


참고로, 이전 내용들을 이해하지 않고 오시면 이해하는데 불편함이 있으실 수 있으니

혹여나 처음 접하시는 분들은 이전 글들을 참고해주시길 바랍니다.



 

우리가 클라이언트와 서버의 통신 때 사용하던 send 함수의 예시입니다.

이때 첫번째 인자인 '통신 소켓 핸들'은 

클라이언트와 서버간에 통신을 위해 사용됩니다.

즉, 통신 소켓의 핸들에는 현재 자신의 IP도 있지만, 상대방의 정보들도 알고 있다 이겁니다.

다시말하면, 통신 소켓의 핸들을 통해 

서로 연결되어있는 서버와 클라이언트간에 통신이 가능해지는 것입니다.


send() 함수를 봤으니 이제 잠시 resv() 함수를 볼까요?

recv() 함수에는 통신 소켓과 시작 주소, 최대 받을 바이트를 인자로 받습니다.



recv함수는 나와 접속되어있는 소켓의 데이터를 가져오는 역할을 합니다.

즉, 소켓의 recv 버퍼에서 어플리케이션의 버퍼에 올려보내는 역할을 하는 것이지요...

recv 함수는 정확하게 

'sock에서 어플리케이션 메모리에 세번째 인자만큼 바이트를 가져와!'라는 기능을 가지고 있습니다.

버퍼를 쳐다보다가 어플리케이션 버퍼에 최대 바이트를 가져오라는 뜻이지요...


그리고 블록 함수입니다... 아직까지는 블록함수...

recv 버퍼에 들어오기 전까지는 즉, 상대측에서 데이터를 보내주기 전까지 말입니다...

버퍼에서 꺼내올 때에는 최대 크기를 가져온다는 것이지요...



본론으로 넘어가서 소켓 관점에서 통신 되는 모습에 대해서 생각해보겠습니다.

아래 그림은 하나 그려놓고 주구장창 우려먹는 TCP 서버와 클라이언트간 통신에 대한 그림입니다.



서버가 한대 있다고 가정해보겠습니다.

서버에서 가장 기본적으로 하는 일은 '대기 소켓'을 만드는 행위입니다.

bind() 함수를 성공적으로 수행하게 되면, 

대기 소켓에는 현재 서버로 두고 있는 PC의 IP와 port의 정보가 저장됩니다.


listen() 함수까지 성공하게 되면 대기 소켓이 생성되는 것입니다.

이때 대기 소켓에는 IP와 port번호가 할당이 됩니다.

대기 상태 즉, 클라이언트의 접속이 성공하려면 listen() 함수가 성공적으로 return되야합니다.


자... 이제 클라이언트의 접속을 받아들일 수 있게 된것입니다.

listen() 함수에서 접속 대기 큐를 만들어 내는 것입니다.

클라이언트의 접속 요청을 받아들이기 위해서이지요...

대기 소켓에는 자신의 IP와 자신의 Port 번호는 있지만, 통신하고자하는 상대의 IP와 port번호는 없습니다


지금까지 말을 정리하면,

대기 소켓은 자신의 정보만 가지고 있다는 것입니다. 

즉, 상대방의 정보는 있을 필요가 없다는 것이지요...

그런데, 클라이언트 측에서 접속 요청이 오면 클라이언트는 어떤 정보를 보고 날라오는 것일까요...

즉, 클라이언트는 통신을 위해서 소켓 하나를 만들 것입니다.

그리고 connect() 함수 요청을 하겠지요...

클라이언트 입장에서 connect 전까지는 통신 소켓이 아닙니다.


아무튼...

클라이언트의 통신 소켓은 서버의 대기 소켓에 접속요청을 할 것입니다.

OS 레벨에서 말입니다...




이와 같은 과정은 'send'와 'recv'함수를 통해 처리되는 작업이 아닙니다.

TCP 레벨에서 일어나는 과정임을 아셔야합니다.

즉, 3-way Handshaking을 통해 통신 연결을 확인하는 것이이지요...


listen() 함수가 수행되고 대기큐가 만들어진다고 했습니다.

이때 클라이언트 측에서 connect() 함수를 통해 서버와 연결 요청을 승낙 받게되면,

클라이언트의 통신 정보가 들어오게 됩니다.



통신용 소켓이 만들어진 것은 local과 remote의 IP, port 번호가 모두 저장된다는 것입니다.

서버에서 accept() 함수를 아직 호출하지 않은 상태라면

클라이언트는 계속해서 접속 대기 큐에 들어올 수 있게 됩니다.

즉, 다른 클라이언트가 똑같이 소켓을 생성하고, connect() 함수 요청을 하게 되면

서버의 대기 소켓에게 연결 요청을 하는 것입니다.

서버에서 수용을 하면 접속 대기큐에 소켓과 관련된 정보들이 들어가게 되는 것이구요...


소켓이 있다는 소리, 연결이 되었다는 소리는 

서버와 클라이언트의 정보를 '둘 다' 가지고 있다는 뜻이며, 보낼 준비가 되어있다는 뜻입니다.

즉, listen() 상태까지 성공하고 클라이언트가 접속 대기 큐에 들어오면

통신할 수 있는 상태가 되었다는 뜻입니다.


통신을 하려면 자신의 IP 번호와 port 번호, 상대방의 IP와 포트번호가 있어야합니다.

각 클라이언트들과 서버는 통신을 수행해야하는데 통신 소켓의 포트번호는 각각 달라야할 것입니다.

따라서, 포트 번호는 접속이 되는 순간 자동으로 결정이 됩니다.

즉, 포트번호는 대기 큐에 들어가면서 클라이언트의 connect()요청이 되면

자신의 포트번호가 결정된다는 것입니다.


이 포트번호는 대기 소켓의 포트번호가 아닌

접속 되어있는 통신 소켓의 포트번호이니 혼돈하시면 안됩니다.


여담으로 말씀드리자면, 윈도우 계열에서는 Remote의 IP, port 번호, Local IP, port번호중 하나만 달라도

다른 소켓으로 인식하기 때문에 포트번호가 같아도 상관이 없을 것입니다.

리눅스에서는 아니지만요...


아무튼...

대기 큐 들어와 생성되어 있는 통신 소켓에는 recv와 send 버퍼가 생성되있을 것입니다.

따라서 클라이언트 입장에서는 대기큐에만 접근이 성공하더라도

서버에서 accept()를 하지 않았음에도 불구하고 데이터가 보내질 수 있음을 확인하실 수 있습니다.

물론, 클라이언트에서 데이터를 계속 서버측에 보내더라도

소켓의 recv 버퍼가 가득차면 보내는 것이 계속 실패가 되겠지만요...


통신 소켓은 나와 연결되어있는 놈의 정보를 다알고 

보내고 받기 위한 send, resv 버퍼가 다 만들어져있습니다.


서버라는 개념은 클라이언트에 접속 요청을 받아들이고

클라이언트의 서비스를 응답하는 놈을 말하기 때문에 PC나 프로세스를 말하는 것입니다.



아무튼... 이제 서버에서 accept()를 호출하게 되면 

반환하는 값은 대기큐에 있던 소켓의 핸들입니다.


대기 소켓에는 결과적으로 통신 소켓이 하나 빠지게 될 것이고,

accept() 함수가 return이 되면 다시 accept()를 위해 루프를 돌 것입니다.


따라서 서버 소켓은

'대기 소켓' + '지금 통신 중인 소켓' + '접속 대기 큐에 접속 되어있는 통신 소켓'을 말하면 됩니다.


부족한 설명이지만...

꼼꼼한 개념을 구글님과 함께 보완하며 실력을 개발합시다!

뻔뻔하군요! 푸하하...


이상 삽잡이였습니다!