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

#011_Window_Network_클라이언트 서버 통신 (데이터 관점)

shovelman 2015. 10. 4. 02:44


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


이번 시간에는 서버와 클라이언트간에 통신을 

'데이터 관점'에서 바라보는 시간을 가지겠습니다.


즉, 데이터 관점에서 클라이언트와 서버가

어떻게 데이터를 주고 받는지를 확인하겠다는 것입니다.


A와 B라는 두 대의 컴퓨터가 있다고 가정해보겠습니다.

A 는 서버이고 B는 클라이언트입니다.


 


어플리케이션 수준에서는 소켓의 핸들만을 알 수 있습니다.

그리고 OS는 TCP(전송 계층) 수준, IP(인터넷 계층) 수준, 데이터 링크 계층을 관리합니다.

즉, 우리가 어플리케이션을 만들고 통신을 하려고 해도 

어플리케이션 계층의 수준이 아닌 이외의 수준은 OS가 알아서 처리해준다는 것입니다.


소켓을 만드는 동시에 OS는 TCP 계층 수준에서 Send 버퍼와 Recv 버퍼를 같이 만듭니다.




각각 서버와 클라이언트에는 Send 버퍼와 Recv 버퍼가 소켓이 만들어지면서 같이 생성된다는 것입니다.


A와 B의 소켓은 논리적으로 연결되어있습니다.

실제로 그림과 같이 선으로 이어져있다는 뜻이 아니라,

B 소켓은 A에 대한 정보를 자세하게 알고 있고,

A 소켓은 B에 대한 정보를 자세하게 알고 있기 때문에 논리적으로 '연결'되었다고 하는 것입니다.

즉, 프로토콜 정보, IP 정보, 포트 정보를 알고 있기 때문입니다.

따라서 언제든지 상대에게 보내고 받을 수 있다는 것이죠.


A와 B가 연결되어있으면,

보내는 채널과 받는 채널은 과연 동시에 수행될까요 각각 수행이 될까요?

파이프의 경우 주고 받는 작업이 각각이지만, 네트워크는 동시에 주고 받는 것이 가능합니다.

사실 네트워크는 동시 채널 (채널 2개)를 가지고 있다는 소리입니다.


A의 Send 버퍼와 B의 Recv 버퍼가 연결되어있고,

B의 Send 버퍼와 A의 Recv 버퍼가 연결되어있다는 것입니다.



만약 다른 어플리케이션이 생성되면 어떻게 될까요?

소켓이 각각 두개가 되겠지요.... 각각 소켓이 연결되어있는 상태이니 버퍼도 각각 생성될 것이구요...


소켓을 만든다는 소리는 소켓을 통신하기 위한 정보들을 만드는 것입니다.

이 생성의 핵심은 바로 Send, Recv 버퍼를 소켓이 갖는다는 것이구요.

버퍼는 소켓의 개수와 일치되는 사실을 반드시 기억하시길 바랍니다.


정리해보겠습니다.

소켓을 생성하면 OS가 그 소켓의 정보와 함께 버퍼를 만들어줍니다. 따라서 OS가 관리를 하게 됩니다.

TCP 수준에서 관리가 되는 것이고, TCP 프로토콜을 사용하지 않는다면

버퍼가 만들어진다는 보장은 없겠지요... 왜냐, 프로토콜에 맞게 결정이 될 터이니까요...


그렇다면, 소켓은 언제 Send와 Recv 버퍼를 사용할 수 있을까요?

버퍼가 다 만들어졌다는 것은 통신 준비가 끝났다는 것을 의미합니다.

즉, 소켓 통신으로써 모든 준비가 다 되어있다는 것이지요...

따라서, 서버는 accept 함수가 성공하는 시점에서 버퍼를 사용할 수 있게 됩니다.

클라이언트는 connect가 완료된 상태가 되어야

통신 상태가 준비가 됬으니 버퍼들이 준비가 되겠지요...


accept() 함수가 성공하면 통신 소켓이 만들어집니다.

그리고 데이터를 주고 받기 위해 recv() 함수와 send() 함수가 호출이 될 것입니다.


TCP 수준에서 버퍼는 OS가 다루는데 해당 버퍼는 통신을 위해 OS가 사용하는 버퍼입니다. 

즉, 어플리케이션 수준에서 사용자가 데이터를 어플리케이션 수준의 버퍼에 담아줘야한다는 것입니다.


어플리케이션에 버퍼가 하나 있다고 해보겠습니다.

우선 버퍼란, '임시적으로 사용하는 메모리 공간'을 말합니다.

대부분 버퍼는 많은 메모리를 갖기 때문에 배열이나 동적 메모리를 많이 사용합니다.


아무튼... 어플리케이션에도 사용자의 입력과 출력을 위해 어플리케이션 버퍼가 만들어집니다.

OS에서 만든 버퍼는 TCP 버퍼입니다. 

TCP 버퍼와 어플리케이션 버퍼는 엄연히 다릅니다. 구분하시길 바랍니다.


자... 우선 Send 함수부터 시작해보겠습니다...

우리가 생각할 때 데이터가 보내지는 과정은 단순히 데이터를 입력하고 엔터를 치면 끝입니다.

자세하게 들여다보도록 하죠...


사용자로부터 만들어진 데이터는 어플리케이션 버퍼에 담겨지게됩니다.

Send 함수는 사용자에게 어플리케이션 버퍼에 데이터를 담겨져 있는지를 확인하다가

어플리케이션의 버퍼가 채워지면 데이터가 담겨져 있는 버퍼의 길이를 구하고 

버퍼에 있는 내용물들을 TCP 수준의 버퍼에 복사를 합니다.




'sock 의 버퍼에다가 어플리케이션 버퍼의 시작주소를 줄테니까 strlen(buf)의 길이만큼 복사하라!'

는 뜻입니다...


그렇다면 Send 함수의 return 시점은 언제일까요? 즉, 언제 성공했다고 알 수 있을까요?

OS는 우리가 건들 수 없습니다.

그런데 OS 수준의 버퍼에 있는 내용물은 OS가 알아서 처리할 일입니다.

왜냐, 내가 구현한게 아니니깐요.... 처리는 OS가 할 일이라는 것입니다.

따라서 Send 함수의 return 시점은 'TCP 수준의 버퍼에 복사가 끝나는 순간'입니다.


어떤 일에 의해서 음... 예를 들어 TCP 버퍼가 꽉 차있다면 데이터를 더이상 복사해올 수 없을 것입니다.

이럴 때에는 send 함수가 block상태에 빠지게됩니다. 아하... send 함수도 block 함수였지요...


send 함수는 TCP 버퍼까지만 나르고 return 한다고 했습니다. 

send 함수는 그래서 많은 시간을 사용하지 않습니다. 복사만 하면 끝이니깐요...

그런데 recv 함수는 시간이 좀 더 걸립니다.

recv 함수의 역할은 recv 버퍼의 데이터를 어플리케이션 버퍼에 복사하는 일입니다.


헷갈릴 수 있으니 정리를 해보도록 하지요...

send 함수는 어플리케이션 버퍼로 부터 내용물을 TCP 버퍼로 복사시키는 것,

recv 함수는 TCP 버퍼에 있는 내용물을 어플리케이션 버퍼에 복사하는 것입니다.


그래서 recv 함수는 어플리케이션 버퍼의 최대 길이만큼을 어플리케이션 버퍼에 가져올 수 있습니다.

당연하지 않습니까... 어플리케이션 버퍼의 사이즈를 초과하여 데이터를 가져오면 소용이 없으니깐 말입니다...



recv 함수는 굉장히 많은 시간이 필요하고 blocking 상태에 걸려있을 확률이 높습니다.

왜냐, TCP recv 버퍼를 계속해서 쳐다보고 있지 않겠습니까...

클라이언트에서 send 버퍼에 데이터가 채워지지 않는 이상 

서버의 recv함수는 계속해서 recv 버퍼만을 쳐다보고 있을 것입니다.


TCP 프로토콜은 신뢰성 있는 프로토콜로써, 데이터의 신뢰성을 보장합니다.

즉, 신뢰성 있는 프로토콜로써 데이터의 전송이 완료됬다는 것을 보장해주는 프로토콜입니다.

아무튼... 이 의리있는 프로토콜을 사용하여 통신을 수행하다보니까

데이터는 서버의 recv 버퍼에 데이터가 도착하면 클라이언트의 Send 버퍼에 내용물이 비워지게 됩니다.


이 모든 일은 OS 수준에서 해주는 것입니다. 정확히는 Socket lib에서 해주는 것이구요..

따라서 우리가 걱정할 일은 아닙니다.


자... 그 다음... 계속 recv 버퍼에 데이터가 오기까지 대기하고 있는데...

recv 함수는 그러면 언제 return 할까요?

어플리케이션 버퍼에 데이터를 가져오는 순간 

recv 버퍼를 비우고 recv 함수는 return을 하게 됩니다.


즉, recv 함수는 TCP 버퍼에서 

어플리케이션의 버퍼에 데이터를 복사하는 역할을 하는것인데,

복사를 완료하면 return을 하고 

또 recv함수를 실행하게 될 때에는 TCP recv 버퍼가 비어있다면 계속해서 대기를 하고 있게 됩니다.

결론적으로 recv 함수도 block 함수이군요...



자... 지금부터는 서버와 클라이언트간에 통신 중에 일어날 수 있는 몇가지의 경우를 제시해보겠습니다.

어떤 결과가 나타날지 생각해보시길 바랍니다.


1. 통신 중 삽잡이가 서버 혹은 클라이언트 PC의 네트워크 선을 자른 경우

2. 통신 중 삽잡이가 서버 혹은 클라이언트 PC의 전원을 꺼버린 경우

3. 통신을 하다가 삽잡이가 클라이언트 프로세스를 죽여버린(?) 경우


우선 첫번째 경우에 대해서 알아보겠습니다.

통신 중에 네트워크 선을 자른 경우입니다... 잔인하군요... 


각 서버와 클라이언트는 이 사실을 모릅니다.

그래서 TCP 통신을 할 때에는 사실 heart 비트라고 해서 

일정 시간마다 살아있는지 checking을 하는 데이터를 날립니다.

그래서 죽은 것 즉, 통신이 안됨을 확인했을 때 통신을 끊겠지요...


두번째 경우를 살펴봅시다. 클라이언트의 전원을 꺼버렸습니다... 이런...

서버 입장에서는 클라이언트의 PC가 종료된 사실을 모릅니다...

결과는 첫번째 경우와 같습니다.


마지막의 경우에는 프로세스를 꺼벼렸습니다.

이때 죽여버렸다는 표현을 사용한 이유는 비정상 종료를 말하기 위해서였습니다... 허허...

아무튼... 서버는 역시 클라이언트가 죽은지를 모르고 있다가... 그런데 바로 알게 됩니다...

왜일까요?

OS는 살아있지 않습니까? OS가 바로 비정상 종료임을 알려줍니다.

즉, reset 정보라고 해서 OS가 서버의 TCP 버퍼에 오류 정보를 보내는 것입니다.


OS가 알면 에러 정보를 상대 Recv 버퍼에 보내고,

OS가 죽어있는 상태라면 

계속해서 Recv 버퍼를 기다리던 OS측에서 살아있는지 정보를 보내보는 것입니다...



지금까지 데이터 관점에서 클라이언트와 서버간에 통신을 공부해봤습니다.

지금까지 배운 내용은 기초중에 기초이지요...

훗날 고급진 네트워크 프로그래밍을 위해! 열심히 공부합시다!


이상 삽잡이였습니다!