TCP/IP 프로그래밍 - 채팅 프로그램

2021. 1. 6. 20:39·C, C++, MFC

이전글에 작성해놓은 범용 소켓 클래스를 이용하여 MFC 채팅 프로그램을 만들어보는 예제이다. 이번 예제에서 Visual Studio 2019를 처음으로 이용해 보는 것이라 시행착오 과정도 같이 메모해 보려고 한다.

 

* 개발 환경

Visual Studio 2019

다이얼로그 기반 MFC 프로젝트 생성

- ListBoxCtrl (m_ctrlListBox) : Sort=False

- EditCtrl (m_strEdit-CString Value) : Multiline=True(엔터키 입력을 받기 위함)

- ButtonCtrl

멀티바이트 문자 집합 사용

 

 

1. 소켓 관련 헤더 include

소켓 관련 기능을 사용하기 위해 Stdafx.h(pch.h)에 다음을 추가한다.

#include <afxsock.h>

 

* Visual Studio 2019에서는 Stdafx.h 파일이 pch.h로 변경되었다.

더보기

Visual Studio에서 새 프로젝트를 만들 때 pch. h 라는 미리 컴파일된 헤더 파일 이 프로젝트에 추가됩니다. (Visual Studio 2017 이하 버전에서는 이 파일을 stdafx.h 라고 합니다.) 

https://docs.microsoft.com/ko-kr/cpp/build/creating-precompiled-header-files?view=msvc-160

 

소켓 초기화를 하기위해 InitInstance 함수에서 AfxSocketInit 함수를 호출하면 소켓을 사용할 준비는 끝난다.

BOOL CChatApp::InitInstance()
{
	...

	if (!AfxSocketInit())
	{
		AfxMessageBox("소켓 초기화 실패");
		return FALSE;
	}
    
	...
}

 

2. 소켓 클래스 포함 시키기

범용으로 만들었던 소켓 클래스를 현재 프로젝트에 추가해준다.

Data.h

Data.cpp

DataSocket.h

DataSocket.cpp

ServerSocket.h

ServerSocket.cpp

 

pch.h에 헤더 파일을 include 한다.

#include "ServerSocket.h"
#include "DataSocket.h"

 

소켓 클래스를 멤버 변수로 선언한다.

class CChatDlg : public CDialogEx
{
	...
    
	CServerSocket m_ServerSocket;
	CDataSocket m_DataSocket;
};

 

3. 서버 기능 구현

OnInitDialog 함수에서 Init 함수를 호출하여 서버 소켓을 초기화한다.

BOOL CChatDlg::OnInitDialog()
{
	...

	m_ServerSocket.Init(this, 2000);	// 서버 소켓 초기화

	return TRUE;
}

 

클라이언트의 접속 요청을 받아들이는 작업을 하는 OnAccept 함수를 추가한다.

클라이언트가 접속 요청을 하면 CServerSocket의 OnAccept 함수가 호출되고, 여기서 메인 프레임에 UM_ACCEPT 메시지를 보낸다.

먼저 헤더에 메시지 처리기를 선언한다.

class CChatDlg : public CDialogEx
{
	...
	afx_msg LRESULT OnAccept(WPARAM wParam, LPARAM lParam);
};

그리고 메시지 맵에 등록하고 함수를 정의한다.

BEGIN_MESSAGE_MAP(CChatDlg, CDialogEx)
	...
	ON_MESSAGE(UM_ACCEPT, OnAccept)
END_MESSAGE_MAP()
LRESULT CChatDlg::OnAccept(WPARAM wParam, LPARAM lParam)
{
	// 연결을 받아들이고, 클라이언트와 m_DataSocket을 연결시켜 줌
	if (!m_ServerSocket.Accept(m_DataSocket))
	{
		AfxMessageBox("Accept 실패");
		return 0;
	}

	// 데이터 소켓 초기화
	m_DataSocket.Init(this);

	m_ctrlListBox.AddString("*** 연결되었습니다. ***");
	return 0L;
}

 

4. 클라이언트 기능 구현

클라이언트가 서버에 접속 요청을 하는 기능을 구현한다.

IP 주소와 포트 번호를 인자로 하여 Connect 함수를 호출한다.

void CChatDlg::OnBnClickedConnectButton()
{
	// IP 주소를 입력 받는 대화상자 출력
	CConnectDlg dlg;
	if (dlg.DoModal() != IDOK) return;

	// 클라이언트 소켓 생성
	if (!m_DataSocket.Create())
	{
		AfxMessageBox("클라이언트 소켓 생성 실패");
		return;
	}

	// 접속 요청
	if (!m_DataSocket.Connect(dlg.m_strAddress, 2000))
	{
		AfxMessageBox("서버 접속 실패");
		return;
	}

	// 클라이언트 소켓 초기화
	m_DataSocket.Init(this);

	m_ctrlListBox.AddString("*** 연결되었습니다. ***");
}

 

5. 데이터 포맷 설정

주고 받을 데이터를 CData에 정의한다. 채팅 프로그램에서 문자열만 주고 받을 것이므로, CString을 CData 클래스의 멤버 변수로 설정한다.

class CData : public CObject
{
	...
	CString m_strData;	// 텍스트 데이터만 전송하므로
};

 

6. 데이터 송신

에디트 컨트롤을 추가하여 데이터를 입력한 뒤 엔터를 누르면 데이터가 전송되도록 만든다.

class CChatDlg : public CDialogEx
{
	...
	afx_msg void OnEnterKey();
};

그리고 메시지 맵에 등록하고 함수를 정의한다.

BEGIN_MESSAGE_MAP(CChatDlg, CDialogEx)
	...
	ON_COMMAND(ID_ENTERKEY, OnEnterKey)
END_MESSAGE_MAP()

CData 클래스 형 변수 data에 전송하고자 하는 데이터를 설정한 후 m_DataSocket에 << 연산자를 이용하여 밀어 넣으면 데이터가 전송된다. << 연산자는 Send 함수를 호출한다.

void CChatDlg::OnEnterKey()
{
	UpdateData();

	// 데이터 전송
	CData data;
	data.m_strData = m_strEdit;
	m_DataSocket << data;
	
	// 전송된 문자열을 상단 윈도우에도 표시
	m_ctrlListBox.AddString(m_strEdit);
	m_ctrlListBox.SetCurSel(m_ctrlListBox.GetCount() - 1);
	m_ctrlListBox.SetCurSel(-1);

	// Edit 컨트롤 초기화
	m_strEdit.Empty();
	UpdateData(FALSE);
}

 

추가로, 엔터키를 누르면 IDOK 이벤트가 발생해서 대화상자가 종료되는 문제가 있으므로 이를 막기 위해 OnOK 함수를 재정의한다.

void CChatDlg::OnOK()
{
	//CDialogEx::OnOK();
}

 

7. 데이터 수신

새로운 데이터가 수신되면 CDataSocket의 OnReceive 함수가 호출되고, 여기서 메인 프레임에 UM_DATARECEIVE 메시지를 보내준다.

먼저 헤더에 메시지 처리기를 선언한다.

class CChatDlg : public CDialogEx
{
	...
	afx_msg LRESULT OnReceive(WPARAM wParam, LPARAM lParam);
};

그리고 메시지 맵에 등록하고 함수를 정의한다.

BEGIN_MESSAGE_MAP(CChatDlg, CDialogEx)
	...
	ON_MESSAGE(UM_DATARECEIVE, OnReceive)
END_MESSAGE_MAP()

m_DataSocket으로부터 >> 연산자를 이용하여 CData 형 변수에 데이터를 끌어 옴으로써 데이터를 수신할 수 있다. >> 연산자는 Receive 함수를 호출한다.

LRESULT CChatDlg::OnReceive(WPARAM wParam, LPARAM lParam)
{
	// 통신 버퍼에 있는 데이터를 전부 수신할 때까지...
	do {
		// 데이터 수신
		CData data;
		m_DataSocket >> data;

		// 수신된 데이터 표시
		m_ctrlListBox.AddString(data.m_strData);
		m_ctrlListBox.SetCurSel(m_ctrlListBox.GetCount() - 1);
		m_ctrlListBox.SetCurSel(-1);
	} while (!m_DataSocket.m_pArchiveIn->IsBufferEmpty());
	return 0L;
}

데이터가 짧은 시간 안에 연속적으로 수신되면 소켓을 이를 차례로 받아놓고, OnReceive 함수는 한번만 호출한다. 따라서 OnReceive 함수가 한번만 호출되었어도 새로 수신된 데이터는 여러개 일 수 있다. 따라서 do-while을 이용하여 수신 버퍼가 빌 때까지 데이터를 가져와야 한다.

 

 

 

 

kimgopa/mfc_project

visual c++ mfc project. Contribute to kimgopa/mfc_project development by creating an account on GitHub.

github.com

 

 

 

 

저작자표시 비영리 변경금지 (새창열림)

'C, C++, MFC' 카테고리의 다른 글

동적 링크 라이브러리(DLL) - DLL 생성과 암시적/명시적 연결  (0) 2021.02.16
모달(Modal)과 모달리스(Modeless)  (0) 2021.02.05
TCP/IP 프로그래밍 - 범용 소켓 클래스  (0) 2020.12.31
TCP/IP 프로그래밍 - 소켓 통신  (0) 2020.12.19
ODBC를 이용한 MFC 데이터베이스 프로그래밍  (1) 2020.12.02
'C, C++, MFC' 카테고리의 다른 글
  • 동적 링크 라이브러리(DLL) - DLL 생성과 암시적/명시적 연결
  • 모달(Modal)과 모달리스(Modeless)
  • TCP/IP 프로그래밍 - 범용 소켓 클래스
  • TCP/IP 프로그래밍 - 소켓 통신
김고파
김고파
채워나가는 중
  • 김고파
    개발자 김고파
    김고파
  • 전체
    오늘
    어제
    • all
      • C, C++, MFC
        • 따배C++
      • Qt
      • OpenCV
      • Data Structure
      • Dev Tools
      • JAVA
  • 링크

    • github
    • 네이버블로그
    • BOJ
  • 인기 글

  • hELLO· Designed By정상우.v4.10.3
김고파
TCP/IP 프로그래밍 - 채팅 프로그램
상단으로

티스토리툴바