정보공간_1

[7기 대구 유용수] IO Completion Port 2장.WinSock 이해하기 본문

IT 놀이터/Elite Member Tech & Talk

[7기 대구 유용수] IO Completion Port 2장.WinSock 이해하기

알 수 없는 사용자 2015. 4. 14. 15:24

소켓이란?

 앞장에서 네트워크 프로그래밍(연결지향적)을 위한 TCP/IP에 대해서 설명 했습니다. 앞장에서 보신바와 같이 네트워크 프로그래밍을 편리하게 하기위해 OSI 7계층이라는걸 확립하고, 그중 연결 지향적 네트워크 프로그래밍을 위해서 TCP/IP 4계층이라는걸 확립했습니다. 하지만 이마저도 너무 복잡한 매커니즘일 수 밖에 없습니다.

 Raw레벨의 네트워크 프로그래밍을 하는 분이 아니라면 이런 선행 개발은 네트워크개발자들에게 너무 많은 분량의 반복작업이 될수 밖에 없습니다.

 이때문에 개발자들의 원활한 네트워크 개발을 위해서 어플리케이션 레벨에서 몇개의 간단한 함수 호출 만으로 커널레벨의 매커니즘에 간접적 접근이 되도록 도와주는 함수집합, 바로 
소켓이 등장했습니다.

소켓 레이어(Socket Layer)

 소켓에 대해서 공부를 하다보면 Socket Layer라는 단어를 접하게 됩니다. 전화에서 수신자와 송신자의 통신을 위해서 전화기 전화번호를 사용 하는 것 처럼, 네트워크 에서는 수신자와 송신자는 소켓레이어 IP Address로 각 대상을 연결 합니다.

 연결 지향형 TCP/IP에서 소켓이 IP Address를 찾아가기 위해 IP프로토콜위에 있는 TCP프로토콜에도 접근하며, 최종적으로 브라우저나 서버를 통해서 사용자에게 보여지게 됩니다. 
이 때, Socket Layer는 TCP/IP 4계층중 응용계층(Application layer)과 전송계층(Transmission layer) 중간에 존재하게 되어 Layer라는 단어를 붙여, Socket Layer라고 지칭 합니다.

소켓레이어와 소켓은 엄연히 다릅니다. Layer는 계층을 나타내는 것으로, 즉 Socket을 다루기 위한 계층 이라고 생각하시면 됩니다. 이런 개념은 TCP프로토콜 자체가 전송계층이 아닌것과 같은 의미입니다. 결과적으로 소켓 프로그래밍 시에 우리는 소켓레이어에서 제공하는 다양한 API를 통해서 소켓을 제어하게되는 것 입니다.

서버 & 클라이언트

 네트워크 프로그래밍에서 가장 대표적으로 나누는 방식은 다들 바로 서버(Server)와 클라이언트(Client)입니다.

 이 둘을 나누는 가장 대표적인 차이점은 바로 서비스를 제공하는쪽(서버)이냐, 서비스를 제공 받는 쪽(클라이언트)이냐 입니다.

 두 프로그램의 흐름들을 대표적인 함수로 나타내 보겠습니다.

 다들 굉장히 익숙한 함수일 것이라 생각합니다. 윈도우 기반으로 함수를 표현 했지만, BSD계열의 유닉스소켓을 기반으로 만들어진 윈도우 소켓이기에, 리눅스, 윈도우에서 사용하는 함수들은 그 함수명과 매개변수까지 비슷합니다. 자세한 내용은 아래쪽에서 설명하겠습니다.

윈도우즈 자료형

 윈도우즈 기반으로 만들어진 윈도우 소켓(이하 윈속이라 지칭)은 일반적인 자료형이 아닌, 윈도우즈 자료형을 많이 사용합니다. 새로운 자료형이 아닌 변환형이기에 제가 만들어 놓은 표로 한번 살펴 봐주시면 될 것 같습니다.

 WORD나 DWORD를 빼고는 대부분 원형의 대표철자를 명시하기에 알아보시는데 불편함을 없을 거라 생각합니다.

 윈속의 주요 함수들


초기화(윈속dll 불러오기)

 가장 먼저 윈속을 사용하기 위한 초기화 함수 입니다. 윈속의 대부분의 함수는 WSA라는 접두어가 붙어있는데, 이것은 Windows Sockets Asynchronous를 의미합니다. 또한, 첫번째 인자에 MAKEWORD라는 매크로가 사용되는데, 이 함수는 WORD형으로 변환해 주는 매크로 입니다.

예) (MAKEWORD(2,2) == 0x0202) = TRUE 

MAKEWORD
 - 첫번째 인자 : 하위(low) 바이트
 - 두번째 인자 : 상위(high) 바이트 

원형 : #define MAKEWORD(a, b) \
                       ((WORD) (((BYTE) (a)) | ((WORD) ((BYTE) (b))) << 8))

 실패시에 에러코드 반환이라고 WSAGetLastError()함수를 호출하게 되는데, 이 또한 윈속에서 윈속 함수릐 반환값을 에러코드로 확인 할 수 있게 만들어 놓은 함수 입니다. 에러코드표는 MSDN에러코드표에서 참고하시면 될 것 같습니다.


해제(윈속dll 해제하기)

윈속을 사용한 후에는 사용된 dll을 반납하기위한 함수를 호출 합니다.


소켓생성

 소켓생성에서는 주소체계와, 연결타입, 프로토콜타입을 설정합니다. 아래는 표현 체계 상수값 목록 입니다.

[주소 체계]


[연결 체계]


[프로토콜 타입]

 ★여기서 앞에 표현된 코드에서는 PF라는 접두어는 붙였지만 표에는 AF라는 접두어가 붙습니다. 내부적으로 AF_INET과 PF_NET은 상수값 2로 동일하지만, 소켓생성시에는 주소체계전달이 아닌 프로토콜 체계를 전달하는 개념이기에 PF_INET을 사용했지만, AF_INET을 사용하여도 무방합니다. 개념상 상수 이름을 나누었을 뿐입니다.

 3번째 인자에는 이미 2번째 인자에 TCP/IP연결체계를 사용한다고 명시 하였기 때문에, 0을 넣던 IPPROTO_TCP를 넣던 상관이 없습니다.


바인드

 바인드 함수를 사용하기 위해서는 SOCKADDR_IN 구조체의 초기화가 선행 되어야 합니다. 해당 구조체는 주소정보를 표현한 구조체로써, 구조체 내부의 내용은 다음과 같습니다.

 첫번째 변수는 socket()함수를 초기화 할때 봤던 주소체계를 설정하는 변수입니다. 두번째부터는 포트번호, IP주소이며, 마지막 인자는 예전 SOCKADDR이었던 과거의 구조체와 동일한 사이즈를 만들기 위한 패딩값 입니다.
 여기서 주소쪽 변수를 보시면 in_addr이라는 구조체가 s_addr이라는 변수를 또 다시 호출하게 됩니다. s_addr은 32비트로 표현한 IPv4버전의 주소값을 담을 수 있는 값입니다.

 이 변수들을 사용할때 보시면, IP주소나 포트번호에 또 다른 함수나 상수가 호출되어 있는 것 을 볼 수 있으실 겁니다. (htonl, htons, INADDR_ANY등..)

 먼저 이 함수들의 호출 이유를 위해서는 바이트순서(Byte Order)를 알아야 합니다.

운영체제나 언어에따라서 바이트 순서가 다릅니다. 하지만, 네트워크상 데이터 전송시에는 무조건 빅엔디안으로 고정되어 전송하기로 Network Byte Order 규약이 체결되어 있습니다.

 따라서, 주소값이나 포트값에는 우리가 바이트순서를 변환해서 사용해야 하기 때문에 htonl들의 함수를 호출 하였으며, 해당 함수들의 표현은 다음과 같습니다.

 윈속 또한 네트워크 바이트순서 규약에 입각하여 설계되었기 때문에 해당 값들은 모두 빅엔디안 방식으로 변환 해서 사용 하여야 합니다.

 그리고 다음에 등장하는 상수 INADDR_ANY는 무었일까요?

 해당 함수에는 inet_addr()이라는 함수를 이용해서, 문자열로 된 IP주소를 long형태로 변환하여 사용 할 수 있지만(예 inet_addr("127.0.0.1)), 이러한 초기화는 호스트의 IP가 특정한 고정 IP를 꼭 사용해야 할 때만 사용하시고 이외의 상황에서는 INADDR_ANY를 사용하시는 것이 훨씬 편리합니다.

 INADDR_ANY를 사용하는 이유는 다음과 같습니다.


연결대기(listen)

 이제 bind()함수를 이용해서 해당 호스트의 주소와 해당 포트번호에 서버의 뿌리를 내렸다면, 클라이언트의 연결대기모드로 돌입해야 합니다. 이 때 호출하는 함수가 바로 listen()함수 입니다.

 첫번째 인자로는 위에서 socket()함수로 초기화 해 주었던 SOCKET구조체를 넘겨주고, 두번째 인자는 몇개의 큐에 연결을 대기할지 연결대기 큐 갯수를 설정하는 인자입니다.(해당 큐는 클라이언트가 접속될때, 즉 뒤에나올 서버의 접속 승인인 accept()함수가 호출 되기 전 까지의 클라이언트 대기 큐 입니다. 즉 구현시 accept()함수를 구현하지 않으면, listen()에서 대기큐에 계속 클라이언트가 쌓이게되는데 설정한 큐의 갯수 이상으로는 쌓이지 않습니다. listen(), accept()그리고 클라이언트의 connect()는 소켓레이어상 밀접한 관계에 놓여 있기에 흐름을 잘 이용 하셔야 동접에서의 예외상황을 잘 제어할 수 있습니다.)


연결수락(accept)

listen()에서 언급했던 연결 수락 함수 accept()입니다.

클라이언트에 연결을 수락시에도 SOCKADDR_IN 구조체를 넘겨주는데, 위에서의 초기화된 구조체는 서버정보를 담은 구조체이고, 현재 사용된 구조체는 접속된 클라이언트의 정보를 저장하는 구조체 입니다.


소켓 닫기(closesocket)

 서버가 종료될때는 항상 사용한 소켓을 닫아 주어야 합니다. 해당함수는 아래가 같습니다.

매개변수는 오픈한 SOCKET 객체를 넘겨 주시면 됩니다. 소켓을 닫아 주지 않으면, 소켓을 열었던 해당포트가 계속 활성화 중이기 때문에(컴퓨터를 껏다가 키면 되겠지만) 반드시 닫아주도록 합시다.


 여기까지 윈도우 소켓(윈속)에 대해서 살펴보았습니다. 뒤에 마지막으로 설명해 드릴 IOCP를 이해하기 위해선 앞서배운 TCP/IP개념과 윈속의 흐름을 잘 알고 있어야 하기때문에, 꼭 숙지 하시고 IOCP 글을 보시기 바랍니다.