7.4.10 EH 소켓 래핑 라이브러리 설계 및 구현 [TCP/IP 소켓 프로그래밍 with 윈도우즈]

소켓 통신 프로그래밍을 작성하다 보면 TCP 서버를 설정하는 코드나 자신의 디폴트 IP 주소를 얻어내는 부분, 클라이언트에서 서버에 연결하는 부분 등은 알고리즘에 차이가 없고 몇 개의 인자만 다릅니다. 이러한 부분을 EH 소켓 래핑(Wrapping, 감싸는) 라이브러리로 만듭시다.

EH 메신저 솔루션에 EHWrapSocketLib 이름으로 Win32 프로젝트를 추가하고 DLL 형태로 만드세요.

EH 소켓 래핑 라이브러리와 이를 사용하는 곳에서 포함할 EHWrapSocket.h 파일을 Common 프로젝트에 추가합니다.

#pragma once
#include <WinSock2.h>
#ifdef EHWRAP037DIECUEMFHCUEKUEJDI
#define EHWRAP_DLL __declspec(dllexport)
#else
#define EHWRAP_DLL __declspec(dllimport)
#endif

EHWrapSocket 클래스를 정의합시다.

class EHWRAP_DLL EHWrapSocket
{
public:

정적 메서드로 자신의 디폴트 IP 주소를 반환하는 메서드를 제공합시다.

    static IN_ADDR GetDefaultIPAddr();

TCP 서버를 만드는 정적 메서드를 제공합시다. 입력 인자로 포트와 백 로그 큐 사이즈를 받고 설정한 소켓을 반환하기로 합시다.

    static SOCKET CreateTCPServer(int port,int blog);

TCP 서버를 만드는 정적 메서드를 하나 더 제공합시다. 이 메서드에서는 입력 인자로 받은 포트가 이미 점유중일 때 비어있는 포트를 사용할 것입니다. 호출한 곳에서 변경한 포트 정보를 알 수 있게 입력 인자를 포인터 형식으로 받습니다.

    static SOCKET CreateTCPServer2(int *port,int blog);

TCP 서버에 연결하는 정적 메서드를 제공합시다. 입력 인자로 연결할 서버의 IP 주소와 포트 번호를 받고 연결한 소켓을 반환하기로 합시다.

    static SOCKET Connect(const char *ip, int port);

생성자의 접근 지정을 private로 설정하여 개체를 생성하지 못하게 하여 정적 메서드만 사용하게 합시다.

private:
    EHWrapSocket();
};

EH 소켓 래핑 라이브러리에 EHWrapSocket.cpp 파일을 추가하세요. 구현할 메서드의 내용은 앞에서 설명했던 것들입니다. 여기에서는 코드 설명은 생략할게요.

#define EHWRAP037DIECUEMFHCUEKUEJDI
#include "..\\common\\EHWrapSocket.h"
#pragma comment(lib,"ws2_32")

자신의 디폴트 IP 주소를 반환하는 메서드입니다.

IN_ADDR EHWrapSocket::GetDefaultIPAddr()
{
    char hostname[256]="";
    gethostname(hostname,255);
    hostent *hentry =  gethostbyname(hostname);
    struct in_addr addr={0};
    while(hentry && hentry->h_name)
    {
        if(hentry->h_addrtype == AF_INET)
        {
            memcpy(&addr,hentry->h_addr_list[0],sizeof(addr));
            return addr;
        }
        hentry++;
    }
    return addr;
}

TCP 서버를 설정하는 메서드입니다.

SOCKET EHWrapSocket::CreateTCPServer(int port,int blog)
{
    SOCKET sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sock == -1)
    {
        return -1;
    }
    SOCKADDR_IN servaddr = {0};
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr = GetDefaultIPAddr();
    servaddr.sin_port = htons(port);
    if(bind(sock,(SOCKADDR *)&servaddr,sizeof(servaddr)) == -1)
    {
        return -1;
    }
    if(listen(sock,blog) == -1)
    {
        return -1;
    }
    return sock;
}

다음은 TCP 서버를 설정하는 메서드로 입력 인자로 기대하는 포트 번호를 설정한 변수의 주소를 전달받아 bind 실패하면 포트를 변경하면서 처음으로 bind 성공하는 포트로 설정합니다.

SOCKET EHWrapSocket::CreateTCPServer2(int *port,int blog)
{
    SOCKET sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sock == -1)
    {
        return -1;
    }
    SOCKADDR_IN servaddr = {0};
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr = GetDefaultIPAddr();
    servaddr.sin_port = htons(*port);
    while(bind(sock,(SOCKADDR *)&servaddr,sizeof(servaddr)) == -1)
    {
        *port +=2;
        servaddr.sin_port = htons(*port);
    }
    if(listen(sock,blog) == -1)
    {
        return -1; 
    }
    return sock;
}

TCP 서버에 연결하는 메서드입니다.

SOCKET EHWrapSocket::Connect(const char *ip, int port)
{
    SOCKET sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sock == -1)
    {
        return -1;
    }
    SOCKADDR_IN servaddr = {0};
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(ip);
    servaddr.sin_port = htons(port);
    if(connect(sock,(SOCKADDR *)&servaddr,sizeof(servaddr))==-1)
    {
        return -1; 
    }
    return sock;
}