7.4.2 응용 프로토콜 스택 구현 [TCP/IP 소켓 프로그래밍 with 윈도우즈]

응용 프로토콜 스택은 설계 단계에서 구현할게요. 구현 단계에서는 이들을 이용하여 Peer와 각 서비스를 구현하는 부분을 다룰 것입니다.

제일 먼저 EH 메신저의 메시지 종류에 관계없이 패킷을 만들고 이를 전송하거나 수신하는 클래스를 정의합시다. 여기에서는 이를 동적 라이브러리 EHPacketLib이름의 컴포넌트에 정의할게요.

먼저 EHPacketLib를 생성합니다. 이 때 솔루션 이름은 EH 메신저라 정의할게요.

[그림 7.12] EH 메신저 솔루션 EHPacketLib 프로젝트 만들기
[그림 7.12] EH 메신저 솔루션 EHPacketLib 프로젝트 만들기(개발 도구 버전에 따라 화면은 다를 수 있습니다.)
 EHPacketLib 프로젝트의 유형을 DLL을 선택하세요. 이 책에서는 Win32 API의 DLL로 만들 것입니다. 여러분께서 아직 Win32 API를 학습한 적이 없으면 필요한 부분을 위한 추가적인 학습을 요구합니다. 이 책에서는 Win32 API에 관한 사항은 자세히 설명하지 않습니다.(개발도구 버전에 따라 화면은 다를 수 있습니다.)

[그림 7.13] EHPacketLib를 DLL로 만들기
[그림 7.13] EHPacketLib를 DLL로 만들기
 동적 라이브러리를 제작하면서 약속 부분을 정의한 헤더 파일은 동적 라이브러리를 사용하는 곳에서도 필요합니다. 이에 공통으로 사용할 헤더 파일들만 추가할 빈 프로젝트를 생성할게요. 여기에서는 빈 프로젝트 이름을 Common으로 정할게요.

[그림 7.14] 빈 프로젝트 생성
[그림 7.14] 빈 프로젝트 생성
 EHPacketLib에는 EH 메신저에서 주고 받을 패킷을 생성하고 전송 및 수신하는 역할을 제공할 것입니다.

먼저 Common 프로젝트에 EHPacket.h 파일을 추가하세요.

윈속 헤더 파일을 포함합니다.

EHPacketLib에서는 __declspec(dllexport)로 이를 사용하는 곳에서는 __declspec(dllimport)로 나타낼 수 있도록 매크로 EH_DLL을 정의합니다. __declspec은 동적 링크 라이브러리에서 함수나 형식을 사용하는 곳에 노출하기 위해 사용하는 구문입니다. 자세한 내용은 Win32 API 관련 자료를 참고하세요.

EH 메시지 패킷의 최대 크기를 정의합시다.

클래스 EHPacket을 정의합니다.

패킷 데이터를 저장할 버퍼를 선언합니다.

패킷을 수신한 후 목적에 맞게 데이터를 디캡슐할 위치를 기억하기 위한 멤버를 선언합니다.

메시지를 패킷으로 만들 때 사용할 생성자를 선언합니다.

소켓에서 데이터 수신하여 패킷을 만드는 생성자를 선언합니다.

패킷의 메시지 아이디를 반환하는 메서드를 선언합니다.

데이터를 패킷의 데이터로 캡슐화하는 메서드를 선언합니다.

패킷의 데이터를 디캡슐화하는 메서드를 선언합니다.

패킷을 소켓으로 전송하는 메서드를 선언합니다.

EHPacket 클래스 내부에서 메시지 바디 위치를 구하는 메서드를 선언합니다.

이제 EHPacketLib 프로젝트에 EHPacket.cpp 소스 파일을 추가하세요.

EHPacket.h 파일에서 EHPacket 라이브러리 내부와 라이브러리를 사용하는 곳에 EH_DLL 매크로의 의미를 다루게 정의하기 위해 사용한 매트로 상수를 정의한 후에 EHPacket.h 파일을 포함합니다. EHPacket.h 파일 위치는 이전 폴더에서 common 폴더 내부에 있습니다.

윈속을 사용하기 위해 필요한 파일을 추가합니다.

EH 메신저에서는 패킷을 헤더와 바디로 구성하기로 하였습니다. 바디는 메시지 종류에 따라 별도로 정의하지만 메시지 헤더는 아이디와 바디 길이로 같습니다. 먼저 메시지 헤더를 정의합시다.

EHPacket 클래스에 선언한 메서드를 하나씩 정의합시다.

먼저 메시지를 보내는 곳에서 사용하는 EHPacket 생성자를 구현합시다.

먼저 패킷 버퍼의 시작 위치에 메시지 헤더 내용을 담을 것입니다. 패킷 버퍼를 MsgHead 포인터로 형변환합시다.

입력 인자로 받은 메시지 아이디를 설정하고 바디 길이를 0으로 설정합니다.

그리고 바디가 시작하는 위치로 ptr을 설정합니다.

소켓에서 데이터를 수신하여 EHPacket을 생성하는 생성자를 구현합시다.

메시지 헤더와 바디 위치를 구합니다.

먼저 메시지 헤더를 수신한 후에 바디 위치에 바디 길이 만큼 수신합니다.

메시지 아이디를 반환하는 메서드를 구현합시다.

패킷 버퍼의 시작 위치에 있는 메시지 헤더의 멤버 아이드를 반환합니다.

데이터를 패킷에 캡슐화하는 메서드를 구현합시다.

메시지 헤더와 바디 위치를 구합니다.

만약 바디 길이와 추가할 데이터 길이와 메시지 헤더의 길이의 합이 최대 패킷 크기보다 크면 캡슐화할 수 없습니다.

바디 위치에서 바디 길이를 더한 위치에 데이터를 캡슐화합니다.

바디 길이를 변경합니다.

패킷의 데이터를 디캡슐화하는 메서드를 구현합시다.

메시지 헤더와 바디 위치를 구합니다.

현재까지 디캡슐화한 위치에서 바디 위치의 상대적 거리를 구합니다. 만약 바디 길이가 상대적 거리와 디캡슐화할 크기보다 작으면 디캡슐화할 수 없습니다.

디캡슐화할 위치의 내용으로 버퍼에 내용을 복사합니다.

디캡슐화할 위치를 변경합니다.

패킷을 전송하는 메서드를 구현합시다.

먼저 메시지 헤더와 바디 위치를 구합니다.

바디 길이와 메시지 헤더의 크기를 더한 전송할 패킷 길이를 구합니다.

소켓에 패킷을 전송합니다.

바디 위치를 구하는 메서드를 구현합시다.

바디 위치는 패킷 버퍼에서 메시지 헤더 크기를 더한 위치입니다.

실제 이와 같은 라이브러리를 만들 때는 먼저 라이브러리로 만들 로직을 간단한 콘솔 응용 프로젝트에서 테스트한 후에 안정성과 신뢰성 등을 테스트 한 후에 라이브러리로 제작하는 것이 전체 비용을 줄이는데 기여합니다. 이미 충분히 숙련 상태에 도달하여 굳이 테스트 과정을 거칠 필요가 없다고 생각하고 바로 라이브러리를 만들어도 논리적 버그가 발생합니다. 라이브러리로 만들었을 때 이를 사용하는 프로젝트에 버그가 발생했을 때 사용하는 라이브러리 때문에 발생한 버그를 판단하지 못할 때가 많습니다.

따라서 직접 라이브러리로 만들기 전에 만들려고 하는 로직이나 형식을 간단한 형태의 콘솔 응용 프로젝트에서 구현하고 테스트해 본 후에 라이브러리로 제작하길 권합니다.