7.5.6 EH 메신저 – Peer 구현 [TCP/IP 소켓 프로그래밍 with 윈도우즈]

이제 Peer 프로그램을 구현합시다. EH 메신저 솔루션에 Peer 이름의 Win32 프로젝트를 만드세요.

Peer 프로젝트에 리소스를 추가하여 시작 대화상자의 화면을 구성합시다.

[그림 7.20] IDD_START 대화상자
번호컨트롤 ID종류번호컨트롤 ID종류번호컨트롤 ID종류
1IDD_START대화상자3IDE_PWEdit5IDB_REGbutton
2IDE_IDEdit4IDE_NAMEEdit6IDB_LOGINbutton

[표 7.1] 시작 대화상자 컨트롤 ID

그리고 로긴 후에 메시지와 파일을 주고받는 메인 대화상자를 추가 편집하세요.

[그림 7.21] 메인(IDD_MAIN) 대화상자

 그리고 파일을 드래그 드랍(마우스로 끌어서 넣기)할 수 있게 MainDlg의 Accept Files 속성을 True로 지정하세요.

[그림 7.22] Accept Files 속성 설정

 먼저 필요한 헤더 파일을 포함하세요.

#include "..\\Common\\EHMessenger.h"
#pragma comment(lib,"ws2_32")
#pragma comment(lib,"..\\Debug\\EHPacketLib")
#pragma comment(lib,"..\\Debug\\RegLib")
#pragma comment(lib,"..\\Debug\\LogLib")
#pragma comment(lib,"..\\Debug\\StsLib")
#pragma comment(lib,"..\\Debug\\EHWrapSocketLib")
#include "resource.h"
#include <Windows.h>
#pragma warning(disable:4996)

로긴 상태의 다른 사용자 계정의 정보를 기억하기 위한 자료구조는 map을 사용할게요.

#include <map>
using namespace std;

사용하기 편하게 타입 재지정하세요.

typedef map<string,OtherUserInfo *> UserMap;
typedef map<string,OtherUserInfo *>::iterator UIter;

로긴 상태의 다른 사용자 계정 정보를 기억할 변수를 선언하세요. 여기에서는 편의상 전역에 선언할게요.

UserMap umap;

로긴 성공하면 로긴한 아이디 정보와 다른 사용자 계정 정보를 수신할 포트 번호, 숏 메시지 수신 포트 번호, 파일 수신 포트 번호에 대응하는 변수를 선언하세요.

#define RES_LOGINOK 0x1
char login_id[256];
int stsport = 20000;
int smsgport = 20002;
int fileport = 20004;
IN_ADDR myip;

여기에서는 다른 계정의 상태를 수신하는 것과 숏 메시지 수신하는 부분은 WSAAsyncSelect 모델을 사용할 것입니다. 이에 따라 사용자 정의 메시지를 정의하세요.

enum MY_WND_MSG
{
    MWM_ACCEPT1=(WM_APP+1), MWM_ACCEPT2,
    MWM_RECV1, MWM_RECV2 
};

진입점에서는 윈속 초기화 후에 시작 대화상자를 가동하고 닫힐 때 로긴 성공일 때는 메인 대화 상자를 띄워줍니다. 물론 종료하기 전에 윈속 해제화를 해야겠죠.

BOOL CALLBACK StartDlgProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam);
BOOL CALLBACK MainDlgProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam);
INT APIENTRY WinMain(HINSTANCE hIns,HINSTANCE hPrev,LPSTR cmd,INT show)
{
    WSADATA wsadata;
    WSAStartup(MAKEWORD(2,2),&wsadata);    
    if(DialogBox(hIns,MAKEINTRESOURCE(IDD_START),0,StartDlgProc) == RES_LOGINOK)
    {
        DialogBox(hIns,MAKEINTRESOURCE(IDD_MAIN),0,MainDlgProc);
    }
    WSACleanup();
    return 0;
}

시작 대화 상자는 컨트롤에 의한 메시지 처리기만 정의합니다.

void OnCommandS(HWND hDlg,WORD cid,WORD cmsg,HWND cWnd);
BOOL CALLBACK StartDlgProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    switch(iMessage)
    {
    case WM_COMMAND: 
         OnCommandS(hDlg,LOWORD(wParam),HIWORD(wParam),(HWND)lParam); 
         return TRUE;
    }
    return FALSE;
}

시작 대화상자의 메시지 처리기에서는 가입 버튼을 눌렀을 때와 로긴 버튼을 눌렀을 때 대화 상자를 닫기했을 때 처리를 담당합니다.

void RegReqProc(HWND hDlg);
void LoginReqProc(HWND hDlg);
void ExitProcS(HWND hDlg,int result);
void OnCommandS(HWND hDlg,WORD cid,WORD cmsg,HWND cWnd)
{
    switch(cid)
    {    
    case IDB_REG: RegReqProc(hDlg); return;
    case IDB_LOGIN: LoginReqProc(hDlg); return;
    case IDCANCEL: ExitProcS(hDlg,0); return;
    }
}

먼저 가입 버튼을 눌렀을 때 수행할 함수를 작성합시다.

void RegReqProc(HWND hDlg)
{    

입력한 계정 정보를 얻어옵니다.

    char id[256]="";
    GetDlgItemTextA(hDlg,IDE_ID,id,255);
    char pw[256]="";
    GetDlgItemTextA(hDlg,IDE_PW,pw,255);
    char name[256]="";
    GetDlgItemTextA(hDlg,IDE_NAME,name,255);

가입 요청을 합니다.

    RegReq rr(id,pw,name);
    SOCKET sock = EHWrapSocket::Connect(FEND_IP,FEND_PORT);
    rr.Serialize(sock);

가입 요청 결과를 수신합니다. 만약 수신한 메시지가 가입 요청 응답이 아니면 이를 사용자에게 통보합니다. 그렇지 않다면 가입 성공 여부를 알려줍니다.

    EHPacket ep(sock);
    if(ep.GetMsgId() != MID_REGRES)
    {
        MessageBox(0,TEXT("가입 요청 응답 메시지가 아닙니다."),TEXT("오류 메시지"),0);
    }
    else
    {
        RegRes rres(&ep);
        if(rres.GetResult() ==REG_RES_OK)
        {
            MessageBox(0,TEXT("가입 성공하였습니다."),TEXT("가입 요청 결과"),0);
        }
        else
        {
            MessageBox(0,TEXT("가입 실패하였습니다."),TEXT("가입 요청 결과"),0);
        }
    }
}

로긴 버튼을 눌렀을 때 수행할 함수를 작성합시다.

void LoginReqProc(HWND hDlg)
{   

계정 정보를 얻어옵니다.

    GetDlgItemTextA(hDlg,IDE_ID,login_id,255);
    char pw[256]="";
    GetDlgItemTextA(hDlg,IDE_PW,pw,255);

로긴 요청 메시지를 전송합니다.

    LogInReq lr(login_id,pw);
    SOCKET sock = EHWrapSocket::Connect(FEND_IP,FEND_PORT);
    lr.Serialize(sock);

응답 메시지를 수신합니다. 만약 수신한 메시지가 로긴 요청 응답 메시지가 아니면 사용자에게 통보합니다. 로긴 요청 응답 메시지가 맞다면 결과를 사용자에게 통보합니다.

    EHPacket ep(sock);

    if(ep.GetMsgId() != MID_LOGINRES)
    {
        MessageBox(0,TEXT("로긴 요청 응답 메시지가 아닙니다."),TEXT("오류 메시지"),0);
    }
    else
    {
        LogInRes lres(&ep);
        switch(lres.GetResult())
        {
        case LOGIN_RES_OK: 
             MessageBox(0,TEXT("로긴 성공하였습니다."),TEXT("로긴 요청 결과"),0); 
             ExitProcS(hDlg,RES_LOGINOK); 
             return;
        case LOGIN_RES_NOTEXIST: 
             MessageBox(0,TEXT("로긴 실패(아이디 없음)"),TEXT("로긴 요청 결과"),0); 
             break;
        case LOGIN_RES_LOGGED: 
             MessageBox(0,TEXT("로긴 실패(이미 로긴 중)"),TEXT("로긴 요청 결과"),0); 
             break;
        case LOGIN_RES_NOTCORRECT:
             MessageBox(0,TEXT("로긴 실패(비밀번호 다름)"),TEXT("로긴 요청 결과"),0); 
             break;
        }
        SetDlgItemTextA(hDlg,IDE_ID,"");
        SetDlgItemTextA(hDlg,IDE_PW,"");
        strcpy(login_id,"");
    }
}

닫기를 눌렀을 때는 대화상자를 종료합니다.

void ExitProcS(HWND hDlg,int result)
{
    EndDialog(hDlg,result);
}

이번에는 Main 대화상자의 콜백 프로시저를 작성합시다. 초기화 및 사용자의 컨트롤 사용에 관한 처리기와 파일을 드래그 드롭했을 때 처리기, WSAAsyncSelect 모델을 사용하면서 정의한 사용자 정의 메시지에 관한 처리기가 필요합니다.

void OnInitDialogM(HWND hDlg);
void OnCommandM(HWND hDlg,WORD cid,WORD cmsg,HWND cWnd);
void OnDropFiles(HWND hDlg,HDROP hdrop);
void OnAccept(HWND hDlg,SOCKET sock,int snum);
void OtherUserInfoRecvProc(HWND hDlg,SOCKET sock,WORD netevent);
void SmsgRecvProc(HWND hDlg,SOCKET sock,WORD netevent);
BOOL CALLBACK MainDlgProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    switch(iMessage)
    {
    case WM_INITDIALOG: 
         OnInitDialogM(hDlg); 
         return TRUE;
    case WM_COMMAND: 
         OnCommandM(hDlg,LOWORD(wParam),HIWORD(wParam),(HWND)lParam); 
         return TRUE;
    case WM_DROPFILES: 
         OnDropFiles(hDlg,(HDROP)wParam); 
         return TRUE;
    case MWM_ACCEPT1:
    case MWM_ACCEPT2:
         OnAccept(hDlg,(SOCKET)wParam,iMessage); 
         return TRUE;
    case MWM_RECV1: 
         OtherUserInfoRecvProc(hDlg,(SOCKET)wParam,LOWORD(lParam)); 
         return TRUE;
    case MWM_RECV2: 
         SmsgRecvProc(hDlg,(SOCKET)wParam,LOWORD(lParam)); 
         return TRUE;    
    }
    return FALSE;
}

먼저 Main 대화상자의 초기에 처리해야 할 작업을 정의합시다.

HWND MainDlg;
DWORD WINAPI AcceptFileRecvProc(LPVOID pin);
void CALLBACK KeepAliveTimerProc(HWND, UINT, UINT_PTR, DWORD);
void OnInitDialogM(HWND hDlg)
{
    MainDlg = hDlg;

파일 수신할 디렉토리를 생성하세요.

    ::CreateDirectoryA("./recv",0);

1초 주기로 Keep Alive를 전송할 수 있게 타이머를 생성하세요.

    SetTimer(hDlg,0,1000,KeepAliveTimerProc);

다른 계정의 상태 정보를 수신하기 위한 소켓을 생성한 후에 WSAAyncSelect 함수로 FD_ACCEPT에 관해 설정합니다. 숏 메시지에 관한 소켓도 마찬가지입니다.

    SOCKET sock = EHWrapSocket::CreateTCPServer2(&stsport,5);
    WSAAsyncSelect(sock,hDlg,MWM_ACCEPT1,FD_ACCEPT);
    sock = EHWrapSocket::CreateTCPServer2(&smsgport,5);
    WSAAsyncSelect(sock,hDlg,MWM_ACCEPT2,FD_ACCEPT);

파일을 수신할 소켓을 생성합니다. 파일 수신은 쓰레드로 처리합시다. 다른 계정의 상태 정보나 숏 메시지는 하나의 작업을 위해 쓰레드를 사용하는 것은 처리할 작업에 비해 낭비적인 요소가 있지만 파일을 수신하는 작업 정도라면 쓰레드를 사용하는 것인 낭비라고 볼 수는 없을 거예요.

    sock = EHWrapSocket::CreateTCPServer2(&fileport,5);
    DWORD ThreadId;
    CloseHandle(CreateThread(0,0,AcceptFileRecvProc,(LPVOID)sock,0,&ThreadId));    

최초 Keep Alive에서 자신의 정보를 전송합니다.

    myip = EHWrapSocket::GetDefaultIPAddr();    
    KeepAlive ka(login_id,myip,stsport,smsgport,fileport);
    SOCKET clisock = EHWrapSocket::Connect(FEND_IP, FEND_PORT);
    ka.Serialize(clisock);
    closesocket(clisock);
}

파일 수신 소켓에 연결 요청을 수락하는 쓰레드 진입점에서는 accept loop으로 구성합니다. 그리고 accept 후에는 파일 수신하는 부분을 별도의 쓰레드로 처리합시다.

DWORD WINAPI RecvFileDoIt(LPVOID pin);
DWORD WINAPI AcceptFileRecvProc(LPVOID pin)
{
    SOCKET sock = (SOCKET)pin;
    SOCKADDR_IN clientaddr;
    int len = sizeof(clientaddr);
    SOCKET dosock;
    DWORD ThreadID;
    while(1)
    {        
        dosock = accept(sock,(SOCKADDR *)&clientaddr, &len);
        CloseHandle(CreateThread(0,0,RecvFileDoIt,(LPVOID)dosock,0,&ThreadID));
    }    
    return 0;
}

파일을 수신하는 부분은 다른 프로그램에서도 사용할 만한 부분이라 별도의 함수를 만들어 호출할게요. 이 부분(recv_file 함수)은 뒤에서 설명할게요.

bool recv_file(SOCKET sock,char *dname);
DWORD WINAPI RecvFileDoIt(LPVOID pin)
{
    SOCKET sock = (SOCKET)pin;
    recv_file(sock,"./recv");
    return 0;
}

Keep Alive 타이머가 발생하면 Keep Alive 메시지를 전송하세요. 이를 통해 논리적인 세션을 유지할 것입니다.

void CALLBACK KeepAliveTimerProc(HWND, UINT, UINT_PTR, DWORD)
{    
    KeepAlive ka(login_id,myip);
    SOCKET clisock = EHWrapSocket::Connect(FEND_IP, FEND_PORT);
    ka.Serialize(clisock);
    closesocket(clisock);
}

Main 대화상자에서 사용자에 의한 제어는 로그 아웃 요청과 탈퇴 요청, 메시지 전송 및 대화상자 닫기가 있겠죠.

void LogOutProc(HWND hDlg);
void UnRegProc(HWND hDlg);
void MsgSendProc(HWND hDlg);
void OnCommandM(HWND hDlg,WORD cid,WORD cmsg,HWND cWnd)
{
    switch(cid)
    {
    case IDB_LOGOUT: LogOutProc(hDlg); return;
    case IDB_UNREG: UnRegProc(hDlg); return;
    case IDB_SEND: MsgSendProc(hDlg); return;
    case IDCANCEL: EndDialog(hDlg,0); return;
    }
}

로그 아웃 요청에서는 LogOutReq 메시지를 전송 후에 대화 상자를 종료하세요.

void LogOutProc(HWND hDlg)
{
    LogOutReq lor(login_id);
    
    SOCKET sock = EHWrapSocket::Connect(FEND_IP,FEND_PORT);
    lor.Serialize(sock);
    MessageBox(0,TEXT("로그 아웃"),TEXT("즐거우셨나요?"),0);
    EndDialog(hDlg,0);
}

탈퇴 요청에서는 UnRegReq 메시지를 전송 후에 대화 상자를 종료하세요.

void UnRegProc(HWND hDlg)
{
    UnRegReq urr(login_id);
    
    SOCKET sock = EHWrapSocket::Connect(FEND_IP,FEND_PORT);
    urr.Serialize(sock);
    MessageBox(0,TEXT("탈퇴"),TEXT("탈퇴하셨습니다."),0);
    EndDialog(hDlg,0);
}

숏 메시지 전송에서는 사용자가 입력한 메시지와 선택한 계정 정보를 얻어와서 상대 숏메시지 수신 소켓에 연결한 후에 숏 메시지를 전송합니다.

void MsgSendProc(HWND hDlg)
{
    HWND hList = GetDlgItem(hDlg,IDL_USER);
    int index = SendMessage(hList,LB_GETCURSEL,0,0);
    if(index == -1)
    {
        return;
    }
    char id[256]="";
    SendMessageA(hList,LB_GETTEXT,index,(LPARAM)id);
    OtherUserInfo *oui = umap[id];
    if(oui ==0)
    {
        MessageBox(0,TEXT("사용자 정보 map에 없습니다."),TEXT("Peer 오류"),0);
    }
    else
    {
        IN_ADDR oip =oui->GetIP();
        char msg[256]="";
        GetDlgItemTextA(hDlg,IDE_MSG,msg,255);
        SOCKET clisock = EHWrapSocket::Connect(inet_ntoa(oip),oui->GetSmsgPort());
        send(clisock,msg,sizeof(msg),0);
        closesocket(clisock);
        HWND hList2 = GetDlgItem(hDlg,IDL_MSG);
        char buf[256]="";
        sprintf(buf,"보낸 메시지:%s",msg);
        SendMessageA(hList2,LB_ADDSTRING,0,(LPARAM)buf);
    }
}

파일을 전송하는 부분에서도 쓰레드를 사용할 거예요. 이 때 쓰레드 전달 인자로 전송할 파일 경로명과 소켓 정보가 필요합니다. 이에 간단한 구조체를 정의하세요.

struct FileParam
{
    char path[MAX_PATH];
    SOCKET sock;
    FileParam(const wchar_t *wpath,SOCKET sock)
    {
        WideCharToMultiByte(CP_ACP,0,wpath,-1,path,MAX_PATH,0,0);        
        this->sock = sock;
    }
};

파일을 드래그 드롭했을 때 처리하는 함수를 작성합시다.

DWORD WINAPI EntryPointSendFile(LPVOID pin);
void OnDropFiles(HWND hDlg,HDROP hdrop)
{

파일을 보낼 상대를 선택하였는지 확인하세요.

    HWND hList = GetDlgItem(hDlg,IDL_USER);
    int index = SendMessage(hList,LB_GETCURSEL,0,0);
    if(index == -1)
    {
        return;
    }
    char id[256]="";
    SendMessageA(hList,LB_GETTEXT,index,(LPARAM)id);
    OtherUserInfo *oui = umap[id];
    if(oui ==0)
    {
        MessageBox(0,TEXT("사용자 정보 map에 없습니다."),TEXT("Peer 오류"),0);
        return;
    }

상대 파일 수신 소켓에 연결한 후에 전송할 파일 정보를 얻습니다.

    IN_ADDR oip =oui->GetIP();    
    SOCKET clisock = EHWrapSocket::Connect(inet_ntoa(oip),oui->GetFilePort());       

    DragQueryFile(hdrop,0xffffffff,0,0);
    wchar_t fullname[MAX_PATH];
    DWORD ThreadId;
    DragQueryFile(hdrop,0,fullname,MAX_PATH-1);    
    FileParam *fp = new FileParam(fullname,clisock);
    
    HWND hList2 = GetDlgItem(hDlg,IDL_MSG);
    char buf2[256]="";
    sprintf(buf2,"파일 보내기:%s",fp->path);
    SendMessageA(hList2,LB_ADDSTRING,0,(LPARAM)buf2);

쓰레드로 파일 명과 연결한 소켓 정보를 갖고 있는 파라미터를 전달하세요.

    CloseHandle(CreateThread(0,0,EntryPointSendFile,(LPVOID)fp,0,&ThreadId));
}

파일 전송 부분도 다른 프로그램에서 재사용할 수 있는 부분입니다. 이 부분(send_file 함수)도 뒤에서 설명할게요.

bool send_file(SOCKET sock,char *path);
DWORD WINAPI EntryPointSendFile(LPVOID pin)
{
    FileParam *fp = (FileParam *)pin;    
    send_file(fp->sock,fp->path);
    closesocket(fp->sock);
    delete fp;
    return 0;
}

상대 계정 정보 수신 소켓이나 숏 메시지 수신 소켓에 연결 요청이 오면 accept 호출과 WSAAsyncSelect 설정합니다.

void OnAccept(HWND hDlg,SOCKET sock,int snum)
{
    SOCKADDR_IN clientaddr;
    int len = sizeof(clientaddr);
    SOCKET dosock;
    dosock = accept(sock,(SOCKADDR *)&clientaddr, &len);    
    switch(snum)
    {
        case MWM_ACCEPT1: 
             WSAAsyncSelect(dosock,hDlg,MWM_RECV1,FD_READ|FD_CLOSE); 
             break;
        case MWM_ACCEPT2: 
             WSAAsyncSelect(dosock,hDlg,MWM_RECV2,FD_READ|FD_CLOSE); 
             break;        
    }
}

상대 계정 정보 소켓에 메시지 수신 혹은 연결 종료가 왔을 때 처리할 처리기입니다.

void RecvOtherUserInfoProc(HWND hDlg,SOCKET sock);
void OtherUserInfoRecvProc(HWND hDlg,SOCKET sock,WORD netevent)
{   
    switch(netevent)
    {
    case FD_READ: RecvOtherUserInfoProc(hDlg,sock); break;
    case FD_CLOSE: closesocket(sock); break;
    }    
}

상대 계정 정보를 수신한 후에 새로운 로긴한 계정 정보인지 로그 아웃한 계정 정보인지에 따라 처리합니다.

void UpdateUserList(HWND hDlg,HWND hList);
void RecvOtherUserInfoProc(HWND hDlg,SOCKET sock)
{
    EHPacket ep(sock);    
    if(ep.GetMsgId() != MID_USERINFO)
    {
        MessageBox(0,TEXT("사용자 정보 메시지가 아닙니다."),TEXT("오류"),0);
        return;
    }
    OtherUserInfo *oui = new OtherUserInfo(&ep);
    HWND hList = GetDlgItem(hDlg,IDL_USER);
    if(oui->GetSmsgPort() == 0)
    { 
        OtherUserInfo *stored_oui = umap[oui->GetId()];
        umap[oui->GetId()] = 0;
        delete stored_oui;
        delete oui;        
        UpdateUserList(hDlg,hList);
    }
    else
    {
        SendMessageA(hList,LB_ADDSTRING,0,(LPARAM)oui->GetId().c_str());
        umap[oui->GetId()] = oui;
    }
}

상대 계정이 로그 아웃하였을 때는 리스트 목록을 리셋한 후에 로긴한 계정 정보만 다시 목록에 추가하세요.

void UpdateUserList(HWND hDlg,HWND hList)
{
    SendMessage(hList,LB_RESETCONTENT,0,0);
    UIter seek = umap.begin();
    UIter last = umap.end();
    OtherUserInfo *oui=0;
    for(seek = seek; seek != last; ++seek)
    {
        oui = (*seek).second;
        if(oui)
        {
            SendMessageA(hList,LB_ADDSTRING,0,(LPARAM)oui->GetId().c_str());
        }
    }
}

숏 메시지 수신 소켓에 메시지 수신 혹은 연결 종료가 왔을 때 처리할 처리기입니다.

void RecvMsgProc(HWND hDlg,SOCKET sock);
void SmsgRecvProc(HWND hDlg,SOCKET sock,WORD netevent)
{
    switch(netevent)
    {
    case FD_READ: RecvMsgProc(hDlg,sock); break;
    case FD_CLOSE: closesocket(sock); break;
    }
}

메시지를 수신하였을 때 수신한 메시지를 리스트에 추가합니다.

void RecvMsgProc(HWND hDlg,SOCKET sock)
{
    char msg[256]="";
    recv(sock,msg,sizeof(msg),0);
    HWND hList = GetDlgItem(hDlg,IDL_MSG);
    SendMessageA(hList,LB_ADDSTRING,0,(LPARAM)msg);    
}

다음은 send를 wrapping 한 함수입니다.

int mysend(SOCKET sock,void *buf,int len)
{
    char *cbuf = (char*)buf;
    int trans = 0;
    int re = 0;

    while(trans<len)
    {
        re =send(sock,cbuf+trans,len-trans,0);
        if(re <0)
        {
            return -1;    
        }

        trans += re;
    }    
    return trans;
}

다음은 recv를 wrapping한 함수입니다.

int myrecv(SOCKET sock,void *buf,int blen)
{    
    char *cbuf = (char*)buf;
    int trans=0;
    int re = 0;

    while(blen>0)
    {
        re =recv(sock,cbuf+trans,blen,0);
        if(re <= 0)
        {
            return -1;    
        }
        trans += re;
        blen -= re;
    }
    return true;
}

파일을 수신하는 함수입니다. 입력 인자로 소켓과 수신한 파일을 기록할 디렉토리 정보를 받습니다.

bool recv_file(SOCKET sock,char *dname)
{

먼저 파일 이름을 수신합니다.

    char fname[256];
    if(myrecv(sock,fname,256)<=0)
    {
        return false;    
    }

    char full_name[256];

디렉토리 계정과 함께 경로를 설정하여 파일을 쓰기 모드로 엽니다. 앞에서 Win32 API 파일 입출력으로 파일 매핑을 하였으니 여기에서는 C언어 표준 입출력 함수를 이용할게요. 어떤 것을 선택하더라도 관계 없어요. 여기에서는 단지 서로 다른 함수를 사용하는 것을 익히는 것을 추구할 뿐입니다.

    sprintf(full_name,"%s\\%s",dname,fname);    
    FILE *fp =fopen(full_name,"wb");

파일 길이를 수신합니다.

    int len=0;
    if(myrecv(sock,&len,sizeof(int)) <=0)
    {
        return false;    
    }

파일 정보 조각을 수신할 때는 1024 바이트 단위로 수신할게요.

    char buf[1024];
    int re = 0;

만약 수신해야 할 길이가 1024 바이트보다 크면 1024바이트 수신하고 파일에 기록합니다.

    while(len>1024)
    {
        if((re = myrecv(sock,buf,1024))<=0)
        {
           fclose(fp);
           return false;
        }
        fwrite(buf,1,1024,fp);
        len -= 1024;
    }

마지막 파일 정보 조각을 수신하여 파일에 기록합니다.

    if(len >0 )
    {
        if(myrecv(sock,buf,1024)<=0)
        {
           fclose(fp);
           return false;
        }
        else
        {
            fwrite(buf,1,len,fp);
            len = 0;
        }
    }
    fclose(fp);

수신한 정보를 리스트에 추가합니다.

    HWND hList = GetDlgItem(MainDlg,IDL_MSG);
    char msg[256];
    sprintf(msg,"수신:%s",fname);
    SendMessageA(hList,LB_ADDSTRING,0,(LPARAM)msg);    
    return true;
}

파일 전송 부분은 수신 부분의 역으로 생각하세요.

bool send_file(SOCKET sock,char *path)
{
    char ext[100];
    char pre_fname[256];
   _splitpath(path,0,0,pre_fname,ext);
    char fname[256];

    sprintf(fname,"%s%s",pre_fname,ext);
    FILE *fp =fopen(path,"rb");

    if(fp == 0)
    {
        return false;    
    }

    int len = 0;
    
    fseek(fp,0,SEEK_END);
    len = ftell(fp);
    fseek(fp,0,SEEK_SET);

    char buf[1024];
    mysend(sock,fname,256);
    mysend(sock,&len,sizeof(len));

    while(len>1024)
    {
        fread(buf,1,1024,fp);
        if(mysend(sock,buf,1024) == -1)
        {
            return false;
        }
        len -=1024;
    }

    if(len>0)
    {
        fread(buf,1,len,fp);
        mysend(sock,buf,len);
    }
    return true;
}

이상으로 EH 메신저 프로젝트를 마칠게요. 보다 나은 형태로 품질을 개선하는 것은 여러분의 몫으로 남길게요.