Windows API로 시작해보는 소켓 프로그래밍

아직 소켓에 대한 개념을 모르시겠다구요?

아래의 버튼을 클릭하여 Socket 포스트를 미리 읽어보시기 바랍니다.


네트워크 프로그래밍의 기초 - Socket


안녕하세요. 여기는 Windows 개발자를 위한 소켓 프로그래밍 페이지입니다. 위 Socket의 개념 포스트와 더불어, Windows API를 사용하기 때문에 Windows API에 대한 기본 개념과 같이 정확하게 이해하셨다고 판단하고, 글을 써 나가보도록 하겠습니다.


WinSock2

Windows Socket은 BSD Unix 호환을 위해 그 개념은 굉장히 비슷하지만, Windows 운영체제에 맞춰있기 때문에, 몇 가지 다른 부분이 있습니다. 또, 소켓은 특성상 커널에 요청하여 생성하는 방식이기 때문에, 파일로 구성되어 있는 유닉스와 달리 Windows 에서는 사용 방법이 조금 까다로울 수도 있습니다.


유닉스에서 소켓은 파일로 다루고, 그에 관한 함수는 signed int 형의 파일 디스크립터를 이용해서 파일을 제어합니다. 하지만 윈도우는 유닉스와 달리 파일이 아닌 제가 적은 Windows API 기본 문서에도 적혀있듯이 Kernel Object, 즉 커널 객체로 인식합니다. 이들 객체는 유닉스처럼 signed int 형태의 파일 디스크립터가 아닌, HANDLE을 이용해 제어한다는 점을 알고 계셔야 합니다.


하지만 WinSock2는 BSD Unix Socket이 signed int 형태를 그대로 따라가고자(호환을 위해) unsigned int 형태의 재정의한 소켓 디스크립터로 소켓을 다룹니다. 결론은 Unix Socket과는 많이 차이가 없다는 것이지요. 결국 다른 Kernel Object와 달리 Socket을 다른 객체로 인식한다는 것입니다.




그렇다면, Unix와의 이식성을 고려해 signed int를 써도 무방하다는 얘기겠지요? 물론 그렇지만, Visual C++ 컴파일러에서는 일부 경고 메시지가 표시될 수 있다는 점을 참고하시고 코딩하시면 되겠습니다 ^^;



위 이미지는 실제 Visual Studio에서 SOCKET 지정자가 나타내는 자료형을 나타낸 것입니다. 실제로 unsigned int 형태를 사용하고 있으며 코딩을 해 본 결과, signed int 형태를 사용해도 무방하다는 점은 알 수 있었습니다. 



WinSock 생성 준비

Windows Socket도 유닉스 소켓과 같이 필요한 요소가 똑같습니다. 각 호스트에는 호스트를 구별하는 식별자인 IP 주소가 존재합니다. 하지만 한 호스트에는 여러 네트워크 응용 프로그램을 사용할 수 있죠. 그럼 한 호스트에서 여러 네트워크 응용 프로그램을 구동하기 위해서는 각 프로그램의 식별자 또한 필요합니다. 로컬에서는 이를 PID(프로세스 식별자ID)가 존재하지만, 네트워크에서는 이를 PID로 구분하지 않고, 포트 번호로 구분합니다.


포트번호는 TCP/UDP 패킷에 16bit 형태로 같이 저장되어 전송하게 됩니다. 1 ~ 65535번 번호를 가지고 있고, TCP와 UDP가 각각 같은 번호로 동작할 수도 있습니다. 이 얘기는 한 응용 프로그램에서 TCP와 UDP를 동시에 사용할 수 있다는 이야기지요.


자 그럼 이제 소켓을 만들기 위해 필요한 것을 전체적으로 보자면 다음과 같습니다.


- 통신에 사용할 프로토콜 (TCP, UDP)

- 클라이언트 IP 주소

- 클라이언트 포트 번호

- 서버 IP 주소

- 서버 포트 번호


유닉스 소켓과 마찬가지로, 실제 프로그래밍 할 때는 클라이언트 IP 주소와 포트 번호는 코드에 적지 않습니다. 이는 운영체제에서 직접 포트 주소를 할당하고, IP 주소를 할당해주기 때문입니다. 이는 유닉스와 윈도우 모두 똑같이 적용되는 사항입니다.



WinSock 생성

이제 소켓을 만들기 위해 필요한 준비물은 모두 갖춰졌습니다. 우리는 이제 소켓을 어떻게 만드는지만 알면 됩니다. 그럼 이제 시작해보겠습니다.


네트워크 프로그램을 개발하려면, 두 프로그램을 개발 해야 합니다. 하나는 서버 프로그램, 또 하나는 클라이언트 프로그램입니다. 둘이 서로 통신을 할 것이기 때문에 두 프로그램 모두 소켓을 사용해야 합니다. 


또, Windows Socket을 사용하기 위해서는 초기에 winsock.dll 이라는 동적 라이브러리를 불러와야 합니다. 또 더 이상 Windows Socket을 사용하지 않고, 소멸하고자 하는 경우, 단순히 Socket을 close할 뿐 아니라, Windows Socket Library를 Cleanup 해주는 작업까지 모두 해줘야 합니다.


먼저 Visual Studio를 열고, 프로젝트 속성에 들어갑니다.



링커로 들어가시게 되면, 입력 메뉴가 보이십니다. 입력 메뉴에는 의존 라이브러리를 추가할 수 있는 메뉴가 맨 첫 부분에 있습니다. 이 부분을 수정할 것입니다.



ws2_sock.lib 파일을 추가해줍니다.


자 이제 코드를 작성해보겠습니다.


argument는 총 3개를 담았고, WSAStartup 함수를 이용하여 ws2_sock.lib 라이브러리를 로드 할 수 있습니다. Unix Socket에서는 이런 작업 과정이 필요없지만, Windows 에서는 라이브러리 사용을 위해서, 위 함수를 동반사용해야 합니다.


이 코드는 유니코드와의 호환을 위해 t 계열 함수를 사용했습니다.
만약, 멀티바이트(ASCII) 코드를 사용하고자 하는 경우, cstdio 헤더 파일을 include 하셔야 합니다.


Windows Socket을 생성할 떄도, Unix Socket과 마찬가지로 socket() 함수를 사용해 생성할 수 있습니다. WinSock2.h 헤더 파일만 include하면 됩니다.

첫 인자인 PF_INET은 서비스 타입입니다. 이는 프로토콜 체계로 인터넷을 사용하겠다는 이야기이며 보통 IPv4 체계를 사용할 때 사용합니다. IPv6를 사용하고자 할 경우, PF_INET6 매크로 상수를 사용하시면 됩니다.


또, 유닉스 소켓에서는 마지막 인자가 두 번째 인자값에 따라 SOCK_STREAM 매크로 상수이면, 0을 써도 무관했지만 Windows Socket에서는 IPPROTO_TCP 매크로 상수를 별도로 매개변수에 넣어주셔야 합니다.


sockaddr_in 구조체는 3개의 멤버 변수가 존재합니다. sin_family는 우리가 위에서 만든 소켓에 대한 주소 체계입니다. 나머지는 포트번호와 IP 주소인데, 여기서 저는 IP 주소를 따로 지정하지 않고, 운영체제에서 주어진 IP 주소를 사용하기 위해 INADDR_ANY라 하는 미리 정의된 매크로 상수를 사용하였습니다




WinSock2 통신 절차
유닉스 소켓과 달리, 윈도우 소켓은 커널 객체의 요소로 동작하지만, 유닉스와의 호환을 위해 파일로 다뤄진다는 점을 알았으며, 이식성을 위해 unsigned int로 재정의한 것을 signed int 형으로 사용해도 무관하다는 점도 알 수 있었습니다.

이런 호환성과 똑같이 윈도우 소켓도 통신 절차는 유닉스 소켓과 100% 똑같습니다. 우리는 여기서 socket() 함수를 사용해 소켓 함수 생성까지는 마쳤으니 그 다음 과정으로 진행하면 네트워크 프로그램 중 서버 프로그램이 완성됩니다. Let's Coding !


소켓번호와 소켓주소를 묶어놓기 위해, 소켓번호를 첫 번쨰 인자값으로 주고, 인터넷 소켓 주소는 구조체이므로 포인터로 넘깁니다. 마지막으로 소켓 주소 구조체의 크기까지 넣어주면 bind가 완성됩니다. 오류 처리는 필수입니다


bind 함수까지 마쳤으면, 이제 모든 준비가 끝났습니다. 서버는 이제 listen 함수를 이용해 대기모드로 들어갑니다. 대기모드는 클라이언트가 신호를 줄 때 까지 기다리는 함수로, 마치 손님이 오기를 기다리는 것과 같다고 생각하시면 됩니다. listen 함수에서 마지막 인자 값은 동시 접속 허용 클라이언트 수입니다.


클라이언트와 연결에 성공하면, 서버는 accept() 함수를 호출하고, 클라이언트와 통신을 하기 위한 소켓을 반환하게 됩니다. 고로 서버 프로그램에는 2개의 소켓이 필요한 것이죠. Listen을 위한 소켓과 클라이언트 연결을 받기 위한 Accept 소켓.


accept 소켓은 클라이언트와 직접 데이터를 주고 받기 위한 연결 고리로, listen socket은 계속 다음 연결을 받기 위한 용도로 사용됩니다.


Windows에서도 문제 없이 소켓 프로그래밍에 성공하였습니다!


오잉? 어느새 보니, Windows에서도 grep 커맨드를 지원하는군요. Good !


같은 방법으로 통신 절차에 따라 클라이언트 프로그램도 코딩해보시면 됩니다.



마치며.....

여기까지 윈도우 소켓 프로그래밍 작성도 마무리되었습니다. 아무래도 저는 Windows와는 잘 안맞는 듯 ㄱ-; 리눅스와 번갈아가며 사용하다보니, 여러모로 헷갈리는 부분도 있고, Windows 만의 커널 다루는 방법이 또 별도로 존재해서, 여러모로 코딩할 때 많이 까다로운 점이 존재했습니다.


특히 Visual C++를 사용할 때, 유니코드 프로그래밍에 심여를 기울여서 한 편인데, 이번 소켓 프로그래밍에서 argument를 유니코드로 보내는 것에 문제가 있어 약간의 가라를 조금 이용했습니다만, 다음 코딩할 때는 반드시 이 문제에 대한 해법을 찾아보도록 하겠습니다 ^^;


여기까지 네트워크 프로그래밍의 기초였습니다.

TAGS.
comments powered by Disqus

Tistory Comments 0