개발자 승학

MFC 소켓 프로그래밍 - 채팅 프로그램(서버, 클라이언트) 본문

it/MFC

MFC 소켓 프로그래밍 - 채팅 프로그램(서버, 클라이언트)

유승학 2019. 10. 11. 10:13

일반적인 소켓 프로그래밍은 TCP/IP 소켓 프로그래밍을 말합니다.

 

이 글은 채팅 서버 / 클라이언트 예제입니다.

 

서버 - 클라이언트의 접속을 대기 / 수신한 후 연결된 모든 클라이언트에게 메시지를 전달.

 

클라이언트 - 서버에 접속하여 메시지를 전달하거나 다른 클라이언트의 메시지를 수신.

 

 

 

[서버예제]

 

 

1.

프로젝트 이름 ChatServer.

대화상자기반으로 프로젝트를 생성.

 

프로젝트를 생성하셨다면 다이얼로그에 채팅기록을 볼 수 있는 컨트롤을 배치하겠습니다.

 

리소스뷰 -> DIalog -> IDD_CHATSERVER_DIALOG에 List Box를 배치하고

 

이름을 m_List로 변수추가를 해줍니다.

※ 변수추가방법

위 사진처럼 List Box내에 우클릭하여 변수추가를 클릭 후 변수 이름에 m_List로 설정해주시면 변수추가가 완료됩니다.

 

 

2.

CListenSocket 클래스와 CClientSocket 클래스를 추가합니다.

 

CListenSocket 클래스를 CAsyncSocket을 CClientSocket 클래스는 CSocket 클래스를 상속받아 생성합니다.

 

솔루션 탐색기 우클릭 -> 추가 -> 클래스 -> MFC클래스

위 사진처럼 생성합니다.

 

CAsyncSocket 클래스는 입출력이 비동기적으로 이루어지는 소켓 - 클라이언트 접속을 기다림

CSocket클래스는 동기적으로 이루어지는 소켓

 

 

3.

[CChatServerDlg.h]

// ChatServerDlg.h : 헤더 파일
//

#pragma once
#include "afxwin.h"
#include "ListenSocket.h"

// CChatServerDlg 대화 상자
class CChatServerDlg : public CDialogEx
{
// 생성입니다.
public:
	CChatServerDlg(CWnd* pParent = NULL);	// 표준 생성자입니다.
	CListenSocket m_ListenSocket;

ListenSocket.h를 include하고 CListenSocket 클래스 객체를 멤버로 추가합니다.

 

 

4.

[CListenSocket.h]

#pragma once

// CListenSocket 명령 대상입니다.

class CListenSocket : public CAsyncSocket
{
public:
	CListenSocket();
	virtual ~CListenSocket();

	CPtrList m_ptrClientSocketList;
};

 

CPtrList 클래스 객체를 멤버로 추가합니다.

 

 

5.

[CListenSocket.cpp]

 

클래스뷰 -> CListenSocket 우클릭 -> 속성 -> 재정의(초록색 아이콘) -> OnAccept 재정의

void CListenSocket::OnAccept(int nErrorCode)
{
	// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
	CClientSocket* pClient = new CClientSocket;

	if (Accept(*pClient))
	{
		pClient->SetListenSocket(this);
		m_ptrClientSocketList.AddTail(pClient);
	}
	else
	{
		delete pClient;
		AfxMessageBox(L"ERROR: Failed to accept new client!");
	}

	CAsyncSocket::OnAccept(nErrorCode);
}

CClientSocket 클래스를 사용하기위해 CListenSocket.cpp 상단에 

 

#include "ClientSocket.h" 를 추가합니다.

 

CAsyncSocket 클래스의 OnAccept() - 클라이언트의 TCP접속이 있을 때 자동으로 호출됩니다.

 

 

6.

CloseClientSocket 멤버함수 추가

void CListenSocket::CloseClientSocket(CSocket* pClient)
{
	POSITION pos;
	pos = m_ptrClientSocketList.Find(pClient);
	if (pos != NULL)
	{
		if (pClient != NULL)
		{
			pClient->ShutDown();
			pClient->Close();
		}

		m_ptrClientSocketList.RemoveAt(pos);
		delete pClient;
	}
}

특정 클라이언트와의 접속이 종료되었을 때 통신을 끝내고 클래스 객체를 소멸시키는 역할을 합니다.

 

 

7.

SendChatDataAll 멤버함수 추가

void CListenSocket::SendChatDataAll(TCHAR* pszMessage)
{
	POSITION pos;
	pos = m_ptrClientSocketList.GetHeadPosition();
	CClientSocket* pClient = NULL;

	while (pos != NULL)
	{
		pClient = (CClientSocket*)m_ptrClientSocketList.GetNext(pos);
		if (pClient != NULL)
			pClient->Send(pszMessage, lstrlen(pszMessage) * 2);
	}
}

 

연결된 모든 클라이언트에게 동일한 메시지를 전송합니다.

 

 

8.

[CClientSocket.h]

 

// CClientSocket 명령 대상입니다.

class CClientSocket : public CSocket
{
public:
	CClientSocket();
	virtual ~CClientSocket();

	void SetListenSocket(CAsyncSocket * pSocket);

	CAsyncSocket* m_pListenSocket;
};

[CClientSocket.cpp]

// CClientSocket

CClientSocket::CClientSocket()
{
	m_pListenSocket = NULL;
}

CListenSocket 클래스 객체의 주소를 저장하는 멤버 m_pListenSocket를 추가하고 생성자에서 NULL로 초기화합니다.

 

 

9.

[CClientSocket.cpp]

 

// CClientSocket 멤버 함수
void CClientSocket::SetListenSocket(CAsyncSocket* pSocket)
{
	m_pListenSocket = pSocket;
}

 

 

10.

[CClientSocket.cpp]

클래스뷰 -> CClientSocket우클릭 -> 속성 -> 재정의(초록색 아이콘) -> OnClose 재정의

void CClientSocket::OnClose(int nErrorCode)
{
	// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.

	CSocket::OnClose(nErrorCode);

	CListenSocket* pServerSocket = (CListenSocket*)m_pListenSocket;
	pServerSocket->CloseClientSocket(this);
}

 

CListenSocket 클래스를 사용하기 위해 상단에 #include "ListenSocket.h"를 추가합니다.

 

CAsyncSocket 클래스의 OnClose() 함수는 연결이 종료되는 시점에 호출됩니다.

 

 

11.

[CClientSocket.cpp]

클래스뷰 -> CClientSocket우클릭 -> 속성 -> 재정의(초록색 아이콘) -> OnReceive 재정의

 

void CClientSocket::OnReceive(int nErrorCode)
{
	// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
	CString strTmp = L"";
	CString strIPAdress = L"";
	UINT uPortNumber = 0;
	TCHAR szBuffer[1024];
	::ZeroMemory(szBuffer, sizeof(szBuffer));

	GetPeerName(strIPAdress, uPortNumber);
	if (Receive(szBuffer, sizeof(szBuffer)) > 0)
	{
		CChatServerDlg* pMain = (CChatServerDlg*)AfxGetMainWnd();
		strTmp.Format(_T("[%s:%d] : %s"), strIPAdress, uPortNumber, szBuffer);
		pMain->m_List.AddString(strTmp);
		pMain->m_List.SetCurSel(pMain->m_List.GetCount() - 1);

		CListenSocket* pServerSocket = (CListenSocket*)m_pListenSocket;
		pServerSocket->SendChatDataAll(szBuffer);
	}

	CSocket::OnReceive(nErrorCode);
}

 

CAsyncSocket 클래스의 OnReceive() 메서드는 클라이언트로부터 정보를 수신해야 할 때 호출됩니다.

 

 

12.

[CChatServerDlg.cpp]

 

// CChatServerDlg 메시지 처리기

BOOL CChatServerDlg::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: 여기에 추가 초기화 작업을 추가합니다.
	if (m_ListenSocket.Create(21000, SOCK_STREAM))
	{
		if (!m_ListenSocket.Listen())
		{
			AfxMessageBox(L"ERROR: Listen() return FALSE");
		}
	}
	else
	{
		AfxMessageBox(L"ERROR: Failed to create server socket!");
	}

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

 

CAsyncSocket 클래스의 Create() 메서드는 소켓을 생성하는 역할을 합니다.

 

 

13.

[CChatServerDlg.cpp]

클래스뷰 -> CChatServerDlg우클릭 -> 속성 -> 메시지 -> OnDestroy() 추가

void CChatServerDlg::OnDestroy()
{
	CDialogEx::OnDestroy();

	// TODO: 여기에 메시지 처리기 코드를 추가합니다.
	POSITION pos;
	pos = m_ListenSocket.m_ptrClientSocketList.GetHeadPosition();
	CClientSocket* pClient = NULL;

	while (pos != NULL)
	{
		pClient = (CClientSocket*)m_ListenSocket.m_ptrClientSocketList.GetNext(pos);

		if (pClient != NULL)
		{
			pClient->ShutDown();
			pClient->Close();

			delete pClient;
		}
	}

	m_ListenSocket.ShutDown();
	m_ListenSocket.Close();
}

 

CClientSocket 클래스를 사용하기 위해 상단에 #include "ClientSocket.h"를 추가합니다.

 

모든 클라이언트와의 연결을 종료하고 CClientSocket 클래스 객체를 소멸시킵니다.

 

while문은 연결된 CClientSocket 클래스 객체를 관리하는 목록을 청므부터 끝까지 탐색하고 소켓을 닫은 후

 

CClientSocket 클래스 객체를 소멸시킵니다.

 

다음 포스트에서 클라이언트 예제를 올리겠습니다.

 

 

'it > MFC' 카테고리의 다른 글

MFC 소켓 프로그래밍 - 채팅 프로그램(서버, 클라이언트)  (6) 2019.10.11
Comments