[MFC] 채팅 프로그램 : Server

[MFC] 채팅 프로그램 : 서버

 

이번 포스팅에서는 MFC 프로젝트를 생성하여 간단한 채팅 프로그램을 만들어 보도록 하겠습니다.

채팅 프로그램을 만들려면 기본적으로 서버와 클라이언트가 있어야 하는데 이번 포스팅에서는 서버를 구현하는 방법에 대해서 알아보도록 하겠습니다.

 

우선 채팅서버를 구현하려면 2개의 소켓이 필요하게 됩니다.

 

하나는 클라이언트 측의 연결 요청을 받아줄 서버 소켓이 필요하며,

다른 하나는 실제로 클라이언트와 통신을 담당할 데이터 소켓 총 이렇게 2개의 소켓이 필요하게 됩니다.

 

소켓 프로그래밍을 하기 전, 꼭 알아야 할 함수가 있는데, 함수 앞에 On~으로 시작하는 가상함수 들입니다. 3개의 가상함수가 있고 해당 함수들의 역할은 아래와 같습니다


 

가상함수

설명

OnAccept()

서버 소켓에서 사용되고 클라이언트가 Connect() 함수를 호출하면 자동 호출됩니다

OnReceive()

상대 소켓이 Send() 함수를 호출하여 정보를 전송하면 자동 호출됩니다.

OnClose()

상대 소켓이 닫히면 자동 호출 됩니다.

 


그럼 바로 실제 프로젝트를 생성하여 서버를 어떻게 구현하는지에 대해 코드로 알아보도록 하겠습니다.

 

먼저, 대화상자 기반으로 MFC 프로젝트를 생성해 주시기 바랍니다. 여기서 그동안 프로젝트 생성할 때와의 차이점은 대화상자 기반으로 생성하되, 고급 기능에서 [Windows 소켓]을 체크하여 마침 버튼을 누릅니다.




 

그리고 아래와 같이 List Box 컨트롤을 배치해 주시기 바랍니다.


해당 List Box를 위와 같이 배치를 하셨다면 이제는 List Box 컨트롤의 속성을 아래와 같이 설정을 해 주시기 바랍니다.



 

여기까지 따라 하셨다면 이제 CListenSocket, CChildSocket 클래스를 클래스 마법사를 이용해서 아래와 같이 선언하여 주시기 바랍니다.




그리고 이제 각각의 헤더파일과 cpp파일에 알맞게 코드를 작성하여 주시기 바랍니다.


[CListenSocket.h]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

#pragma once

#include "afxsock.h"

class CListenSocket :

    public CSocket

{

public:

    CListenSocket(void);

    ~CListenSocket(void);

 

public:

    //연결된 클라이언트 소켓 객체를 관리하는 리스트 객체 선언

    CPtrList m_ptrChildSocketList;

    virtual void OnAccept(int nErrorCode);

    void CloseClientSocket(CSocket* pChild);

    void BroadCast(char* pszBuffer, int len);

};

 

 

Colored by Color Scripter

cs


 

[CListenSocket.cpp]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

#include "stdafx.h"

#include "ListenSocket.h"

#include "ChildSocket.h"

#include "ServerDlg.h"

 

 

CListenSocket::CListenSocket(void)

{

}

 

 

CListenSocket::~CListenSocket(void)

{

}

 

//클라이언트가 접속 요청을  오면 호출 되는 함수

void CListenSocket::OnAccept(int nErrorCode)

{

    // TODO: 여기에 특수화된 코드를 추가 /또는 기본 클래스를 호출합니다.

 

    CChildSocket* pChild = new CChildSocket(); //클라이언트와 연결할 데이터 소켓 객체를 생성하여

 

    BOOL check = Accept(*pChild); //클라이언트의 접속을 허용하고 데이터 소켓과 연결한다.

 

    if(check == FALSE)

    {

        delete pChild;

        AfxMessageBox(_T("접속 허용 실패"));

        return;

    }

 

    //CListenSocket 객체의 주소를 CChildSocket 객체에 알려주기 위한 함수를 호출

    pChild->SetListenSocket(this); //CChildSocket 클래스에 사용자가 정의한 함수

    m_ptrChildSocketList.AddTail(pChild);

 

    //클라이언트가 접속해 옴을 리스트에 출력

    CServerDlg* pMain = (CServerDlg*)AfxGetMainWnd();

 

    pMain->m_List.AddString(_T("서버 접속 허용"));

    pMain->m_List.SetCurSel(pMain->m_List.GetCount() -1);

 

    CSocket::OnAccept(nErrorCode);

}

 

void CListenSocket::CloseClientSocket(CSocket* pChild)

{

    POSITION pos;

    pos = m_ptrChildSocketList.Find(pChild);

 

    if(pos != NULL)

    {

        pChild->ShutDown(); //클라이언트와 연결된 데이터 소켓을 닫습니다.

        pChild->Close();

    }

 

    m_ptrChildSocketList.RemoveAt(pos); //리스트에서 제거한 

    delete pChild; //메모리에서 해제

}

 

void CListenSocket::BroadCast(char* pszBuffer, int len)

{

    POSITION pos;

    pos = m_ptrChildSocketList.GetHeadPosition();

    CChildSocket* pChild = NULL;

 

    while(pos!= NULL)

    {

        pChild = (CChildSocket*)m_ptrChildSocketList.GetNext(pos);

 

        if(pChild != NULL)

        {

            pChild->Send(pszBuffer,len*2);

        }

    }

}

Colored by Color Scripter

cs


 

 

[CChildSocket.h]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#pragma once

#include "afxsock.h"

class CChildSocket :

    public CSocket

{

public:

    CChildSocket(void);

    ~CChildSocket(void);

 

public:

    CAsyncSocket *m_pListenSocket;

    void SetListenSocket(CAsyncSocket* pSocket);

    virtual void OnClose(int nErrorCode);

    virtual void OnReceive(int nErrorCode);

};

 

 

Colored by Color Scripter

cs


 

[CChildSocket.cpp]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

#include "stdafx.h"

#include "ChildSocket.h"

#include "ListenSocket.h"

#include "ServerDlg.h"

 

 

CChildSocket::CChildSocket(void)

{

}

 

 

CChildSocket::~CChildSocket(void)

{

}

 

//연결된 클라이언트의 소켓 주소를 m_pListenSocket 저장

void CChildSocket::SetListenSocket(CAsyncSocket* pSocket)

{

    m_pListenSocket = pSocket;

}

 

//클라이언트 소켓이 닫히게 되면 호출되는 함수

void CChildSocket::OnClose(int nErrorCode)

{

    // TODO: 여기에 특수화된 코드를 추가 /또는 기본 클래스를 호출합니다.

    CListenSocket* pServerSocket = (CListenSocket*)m_pListenSocket;

    pServerSocket->CloseClientSocket(this);

 

    CSocket::OnClose(nErrorCode);

}

 

 

//클라이언트가 서버로부터 데이터를 보내게 되면 자동으로 호출되는 함수

void CChildSocket::OnReceive(int nErrorCode)

{

    // TODO: 여기에 특수화된 코드를 추가 /또는 기본 클래스를 호출합니다.

    CString temp = _T("");

    CString strIPAddress = _T("");

 

    UINT uPortNumber = 0;

 

    char szBuffer[1024];

    ::ZeroMemory(szBuffer, 1024);

 

    //연결된 클라이언트의 IP주소와 포트 번호를 알아낸다.

    GetPeerName(strIPAddress, uPortNumber);

 

    //실제로 데이터를 수신한다.

    int len = 0;

    if((len = Receive(szBuffer, 1024)) > 0)

    {

        //데이터를 수신하였다면 받은 메시지를 리스트에 출력

        CServerDlg* pMain = (CServerDlg*)AfxGetMainWnd();

 

        temp.Format(_T("[%s] : %s"), strIPAddress, szBuffer);

 

        pMain->m_List.AddString(temp);

        pMain->m_List.SetCurSel(pMain->m_List.GetCount() -1);

 

        //연결된 모든 클라이언트에 해당 메시지 에코

        CListenSocket* pServerSocket = (CListenSocket*)m_pListenSocket;

         pServerSocket->BroadCast(szBuffer, len);

    }

    CSocket::OnReceive(nErrorCode);

}

 

Colored by Color Scripter

cs


 

 

[CServerDlg.h]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

 

// ServerDlg.h : 헤더 파일

//

 

#pragma once

#include "afxwin.h"

#include "ListenSocket.h"

#include "resource.h"

 

// CServerDlg 대화 상자

class CServerDlg : public CDialogEx

{

// 생성입니다.

public:

    CServerDlg(CWnd* pParent = NULL);    // 표준 생성자입니다.

 

    //클라이언트의 접속을 위해 리슨(대기하는 서버 소켓

    CListenSocket* m_pListenSocket;

 

// 대화 상자 데이터입니다.

    enum { IDD = IDD_SERVER_DIALOG };

 

    protected:

    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 지원입니다.

 

 

// 구현입니다.

protected:

    HICON m_hIcon;

 

    // 생성된 메시지  함수

    virtual BOOL OnInitDialog();

    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);

    afx_msg void OnPaint();

    afx_msg HCURSOR OnQueryDragIcon();

    DECLARE_MESSAGE_MAP()

public:

    CListBox m_List;

    afx_msg void OnDestroy();

};

 

Colored by Color Scripter

cs


 

 

[CServerDlg.cpp]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

 

// ServerDlg.cpp : 구현 파일

//

 

#include "stdafx.h"

#include "Server.h"

#include "ServerDlg.h"

#include "afxdialogex.h"

#include "ListenSocket.h"

#include "ChildSocket.h"

 

#ifdef _DEBUG

#define new DEBUG_NEW

#endif

 

 

// 응용 프로그램 정보에 사용되는 CAboutDlg 대화 상자입니다.

 

class CAboutDlg : public CDialogEx

{

public:

    CAboutDlg();

 

// 대화 상자 데이터입니다.

    enum { IDD = IDD_ABOUTBOX };

 

    protected:

    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 지원입니다.

 

// 구현입니다.

protected:

    DECLARE_MESSAGE_MAP()

};

 

CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)

{

}

 

void CAboutDlg::DoDataExchange(CDataExchange* pDX)

{

    CDialogEx::DoDataExchange(pDX);

}

 

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)

END_MESSAGE_MAP()

 

 

// CServerDlg 대화 상자

 

 

 

CServerDlg::CServerDlg(CWnd* pParent /*=NULL*/)

    : CDialogEx(CServerDlg::IDD, pParent)

{

    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

}

 

void CServerDlg::DoDataExchange(CDataExchange* pDX)

{

    CDialogEx::DoDataExchange(pDX);

    DDX_Control(pDX, IDC_LIST1, m_List);

}

 

BEGIN_MESSAGE_MAP(CServerDlg, CDialogEx)

    ON_WM_SYSCOMMAND()

    ON_WM_PAINT()

    ON_WM_QUERYDRAGICON()

    ON_WM_DESTROY()

END_MESSAGE_MAP()

 

 

// CServerDlg 메시지 처리기

 

BOOL CServerDlg::OnInitDialog()

{

    CDialogEx::OnInitDialog();

 

    // 시스템 메뉴에 "정보..." 메뉴 항목을 추가합니다.

 

    // IDM_ABOUTBOX 시스템 명령 범위에 있어야 합니다.

    ASSERT((IDM_ABOUTBOX & 0xFFF0== IDM_ABOUTBOX);

    ASSERT(IDM_ABOUTBOX < 0xF000);

 

    CMenu* pSysMenu = GetSystemMenu(FALSE);

    if (pSysMenu != NULL)

    {

        BOOL bNameValid;

        CString strAboutMenu;

        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);

        ASSERT(bNameValid);

        if (!strAboutMenu.IsEmpty())

        {

            pSysMenu->AppendMenu(MF_SEPARATOR);

            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);

        }

    }

 

    //  대화 상자의 아이콘을 설정합니다응용 프로그램의  창이 대화 상자가 아닐 경우에는

    //  프레임워크가  작업을 자동으로 수행합니다.

    SetIcon(m_hIcon, TRUE);            //  아이콘을 설정합니다.

    SetIcon(m_hIcon, FALSE);        // 작은 아이콘을 설정합니다.

 

    // TODO: 여기에 추가 초기화 작업을 추가합니다.

 

    //Listen 소켓 생성

    m_pListenSocket = new CListenSocket;

 

    //TCP 소켓을 생성하고 7000 포트에서 연결을 대기한다.

    if(m_pListenSocket->Create(7000, SOCK_STREAM))

    {

        if(m_pListenSocket->Listen())

        {

 

        }

        else

        {

            AfxMessageBox(_T("연결 실패"));

        }

    }

    else

    {

        AfxMessageBox(_T("실패"));

    }

 

    return TRUE;  // 포커스를 컨트롤에 설정하지 않으면 TRUE 반환합니다.

}

 

void CServerDlg::OnSysCommand(UINT nID, LPARAM lParam)

{

    if ((nID & 0xFFF0== IDM_ABOUTBOX)

    {

        CAboutDlg dlgAbout;

        dlgAbout.DoModal();

    }

    else

    {

        CDialogEx::OnSysCommand(nID, lParam);

    }

}

 

// 대화 상자에 최소화 단추를 추가할 경우 아이콘을 그리려면

//  아래 코드가 필요합니다문서/ 모델을 사용하는 MFC 응용 프로그램의 경우에는

//  프레임워크에서  작업을 자동으로 수행합니다.

 

void CServerDlg::OnPaint()

{

    if (IsIconic())

    {

        CPaintDC dc(this); // 그리기를 위한 디바이스 컨텍스트입니다.

 

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

 

        // 클라이언트 사각형에서 아이콘을 가운데에 맞춥니다.

        int cxIcon = GetSystemMetrics(SM_CXICON);

        int cyIcon = GetSystemMetrics(SM_CYICON);

        CRect rect;

        GetClientRect(&rect);

        int x = (rect.Width() - cxIcon + 1/ 2;

        int y = (rect.Height() - cyIcon + 1/ 2;

 

        // 아이콘을 그립니다.

        dc.DrawIcon(x, y, m_hIcon);

    }

    else

    {

        CDialogEx::OnPaint();

    }

}

 

// 사용자가 최소화된 창을 끄는 동안에 커서가 표시되도록 시스템에서

//   함수를 호출합니다.

HCURSOR CServerDlg::OnQueryDragIcon()

{

    return static_cast<HCURSOR>(m_hIcon);

}

 

 

 

void CServerDlg::OnDestroy()

{

    CDialogEx::OnDestroy();

 

    // TODO: 여기에 메시지 처리기 코드를 추가합니다.

    POSITION pos;

    pos = m_pListenSocket->m_ptrChildSocketList.GetHeadPosition();

    CChildSocket* pChild = NULL;

 

    while (pos != NULL)

    {

        pChild = (CChildSocket*)m_pListenSocket->m_ptrChildSocketList.GetNext(pos);

 

        if(pChild != NULL)

        {

            pChild->ShutDown();

            pChild->Close();

            delete pChild;

        }

    }

 

    m_pListenSocket->ShutDown();

    m_pListenSocket->Close();

}

 

Colored by Color Scripter

cs

 


 

[실행 결과 화면]


 

여기까지 잘 하셨다면 Server 프로그램은 마무리 된 것입니다. 이제 다음 포스팅에서 해당 서버와 통신할 클라이언트 프로그램을 구현하여 실제로 해당 통신이 제대로 이루어 지는지에 대해서 확인해 보도록 하겠습니다.

 

감사합니다.^^


728x90

'MFC 소켓 프로그래밍' 카테고리의 다른 글

[MFC] 채팅 프로그램 : 클라이언트  (5) 2018.06.28
[MFC] 네트워크 개요  (0) 2018.06.28

이 글을 공유하기

댓글

Designed by JB FACTORY