티스토리 뷰

Visual Studio 2012


소켓으로 통신하는 채팅 서버 프로그램을 만들어보자.


서버 프로그램에서 사용하는 소켓은 두 종류가 있다.

- Listen 소켓

- Client 매칭 소켓


1. MFC 프로젝트 생성


  • 새 프로젝트 - MFC 응용 프로그램 선택


  • 대화상자 기반으로 선택한다


  • 아래 창에선 다음과 같이 Windows 소켓에 체크표시하고 '마침' 누름


2. 소켓에 해당되는 클래스 만들기


  • 클래스 마법사에서 'MFC 클래스' 클릭


  • 클래스 추가 - 기본클래스는 CSocket으로 지정

    1. Listen 소켓 클래스 이름(CServerSocket)

    2. Client 매칭 소켓 클래스 이름(CClientSocket)


3. Listen 소켓 클래스 구현

- Listen할 때 필요한 함수

  1. SetWnd : Sendmessage 활용을 위한 메인의 핸들을 받는 함수

  2. OnAccept : 클라이언트가 서버와 연결 시 처리 시의 함수이다.

- OnAccept 함수는 Client 매칭되는 소켓 클래스를 구현할 때 같이 구현한다.


  • 함수 및 핸들 변수 선언

// ServerSocket.h : 헤더 파일입니다.
//

class CServerSocket : public CSocket
{
	...
	void SetWnd(HWND hWnd);
	HWND m_hWnd;
	virtual void OnAccept(int nErrorCode);
};


  • 함수 구현
// ServerSocket.cpp : 구현 파일입니다.
//

// CServerSocket 멤버 함수
void CServerSocket::SetWnd(HWND hWnd)
{
	m_hWnd = hWnd;
}

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


4. Client 매칭 소켓 클래스 구현

- 필요 함수

  1. SetWnd : Sendmessage 활용을 위한 메인의 핸들을 받는 함수
  2. OnReceive : 서버 통신 받을 시의 처리 함수
  3. OnClose : 소켓 해제 시의 처리 함수


  • 함수 및 핸들 변수 선언
// ClientSocket.h : 헤더 파일입니다.
//

class CClientSocket : public CSocket
{
	...
public:
	void SetWnd(HWND hWnd);
	HWND m_hWnd;
	virtual void OnReceive(int nErrorCode);
	virtual void OnClose(int nErrorCode);
};


  • 함수 구현
// ClientSocket.cpp : 구현 파일입니다.
//

void CClientSocket::SetWnd(HWND hWnd)
{
	m_hWnd = hWnd;
}

void CClientSocket::OnReceive(int nErrorCode)
{
	// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
	CString strTmp = _T(""), strIPAddress = _T("");
	UINT uPortNumber = 0;
	TCHAR strBuffer[1024];
	ZeroMemory(strBuffer, sizeof(strBuffer));
	
	GetPeerName(strIPAddress, uPortNumber);
	if(Receive(strBuffer, sizeof(strBuffer)) > 0) { // 전달된 데이터(문자열)가 있을 경우
		strTmp.Format(_T("[%s : %d]: %s"), strIPAddress, uPortNumber, strBuffer);
	}

	CSocket::OnReceive(nErrorCode);
}

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

5. Listen 소켓에서 OnAccept을 통해 Client 매칭 소켓 연결하기


  • 헤더 추가
// ServerSocket.h : 헤더 파일입니다.
//

#include "ClientSocket.h"


  • OnAccept 구현
// ServerSocket.cpp : 구현 파일입니다.
//

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

	if(Accept(*pClient))	//Listen에서 클라이언트 접속을 확인하면
	{
		pClient->SetWnd(m_hWnd);	//Client소켓에 메인핸들 연결
	}
	else		//클라이언트 접속 문제시
	{
		delete pClient;	
		AfxMessageBox(_T("ERROR : Failed can't accept new Client!"));
	}

	CSocket::OnAccept(nErrorCode);
}


6. 메인 클래스 구현


  • 대화상자 설정

아래과 같이 구상한다. 리스트박스와 관련된 소스들은 추후에 Sendmessage 적용하면서 구현함.



- IDC_LIST_CLIENT : 접속한 클라이언트 표시용 리스트박스

- IDC_LIST_MSG : 메시지 표시용 리스트박스


  • 헤더 설정
// socServerDlg.h : 헤더 파일
//

#include "ServerSocket.h"

class CsocServerDlg : public CDialogEx
{
	...
// 구현입니다.
private:
	CClientSocket *m_pClientSocket;
	CServerSocket *m_pServerSocket;
public:
	CListBox m_list_client;			//IDC_LIST_CLIENT
	CListBox m_list_msg;			//IDC_LIST_MSG
	CPtrList m_ptrClientSocketList;	//For manage Client Sockets
};


  • 소스 구현
// socServerDlg.cpp : 구현 파일
//

#define PORT 9999		//임의의 포트 설정

void CsocServerDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LIST_CLIENT, m_list_client);
	DDX_Control(pDX, IDC_LIST_MSG, m_list_msg);
}

BOOL CsocServerDlg::OnInitDialog()
{
	...
	// TODO: 여기에 추가 초기화 작업을 추가합니다.
	//Listen 소켓 초기화
	m_pServerSocket = new CServerSocket;
	m_pServerSocket->SetWnd(this->m_hWnd);

	//소켓 Listen하기
	m_pServerSocket->Create(PORT);
	m_pServerSocket->Listen();
	return TRUE;  // 포커스를 컨트롤에 설정하지 않으면 TRUE를 반환합니다.
}


7. 대화상자 컨트롤 작동 구현


- Sendmessage를 활용해야 하는 경우(사용자 메시지 함수 사용)

  1. WM_ACCEPT_SOCKET : 클라이언트가 서버에 접속할 때 IDC_LIST_CLIENT에 접속 표시
  2. WM_CLEINT_MSG_RECV : 클라이언트가 서버로 메시지 보낼 때 IDC_LIST_MSG에 메시지 표시
  3. WM_CLIENT_CLOSE : 클라이언트가 서버와 연결 해제 시의 대처

  • 사용자 메시지 정의

WM_ACCEPT_SOCKET은 Listen 소켓 클래스에서 정의

// ServerSocket.h : 헤더 파일입니다.
//

#define WM_ACCEPT_SOCKET WM_USER+1


나머지 메시지들은 Client 매칭 소켓 클래스에서 정의

// ClientSocket.h : 헤더 파일입니다.
//

#define WM_CLIENT_MSG_RECV WM_USER+2
#define WM_CLIENT_CLOSE WM_USER+3


  • 각 소켓 클래스에서 메인으로 메시지 보내기

WM_ACCEPT_SOCKET : OnAccept()함수

// ServerSocket.cpp : 구현 파일입니다.
//

void CServerSocket::OnAccept(int nErrorCode)
{
	// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
	...
	SendMessage(m_hWnd, WM_ACCEPT_SOCKET, 0, (LPARAM)pClient);
	CSocket::OnAccept(nErrorCode);
}


WM_CLEINT_MSG_RECV : OnReceive()함수

WM_CLIENT_CLOSE : OnClose()함수

// ClientSocket.cpp : 구현 파일입니다.
//

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

	//Main Window에 Send
	SendMessage(m_hWnd,WM_CLIENT_MSG_RECV,0,(LPARAM)((LPCTSTR)strTmp));

	CSocket::OnReceive(nErrorCode);
}


void CClientSocket::OnClose(int nErrorCode)
{
	// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
	SendMessage(m_hWnd, WM_CLIENT_CLOSE, 0, (LPARAM)this);
	CSocket::OnClose(nErrorCode);
}

  • 메인에서 메시지 핸들 함수 구현

사용자 지정 메시지 추가

- 클래스 마법사 - 클래스 이름(DLG클래스로) - 메시지탭 - 사용자 지정 메시지 추가

위에 적용한 3개의 사용자 지정 메시지를 각각 추가하기



  • 사용자 지정 메시지 함수 구현

OnAcceptSocket 함수

// socServerDlg.cpp : 구현 파일
//
afx_msg LRESULT CsocServerDlg::OnAcceptSocket(WPARAM wParam, LPARAM lParam)
{
	CString str;
	m_pClientSocket = (CClientSocket*) lParam;
	m_ptrClientSocketList.AddTail(m_pClientSocket);

	str.Format(_T("Client (%d)"), (int)(m_pClientSocket));
	m_list_client.InsertString(-1,str);

	m_pClientSocket = NULL;
	delete m_pClientSocket;

	return 0;
}


OnClientMsgRecv 함수

// socServerDlg.cpp : 구현 파일
//
afx_msg LRESULT CsocServerDlg::OnClientMsgRecv(WPARAM wParam, LPARAM lParam)
{
	LPCTSTR lpszStr = (LPCTSTR)lParam;

	//BroadCasting
	POSITION pos = m_ptrClientSocketList.GetHeadPosition();
	
	while(pos != NULL)
	{
		CClientSocket* pClient = (CClientSocket*)m_ptrClientSocketList.GetNext(pos);
		if(pClient!=NULL)
		{
			//UNICODE 사용하면 기존 메모리크기 *2 해야함
			//Client에 Broadcasting하기
			pClient->Send(lpszStr, lstrlen(lpszStr) * 2);
		}
	}

	//m_list_msg에 메시지 추가
	m_list_msg.InsertString(-1,lpszStr);
	m_list_msg.SetCurSel(m_list_msg.GetCount()-1);
	return 0;
}


OnClientClose 함수

// socServerDlg.cpp : 구현 파일
//
afx_msg LRESULT CsocServerDlg::OnClientClose(WPARAM wParam, LPARAM lParam)
{
	CClientSocket *pClient = (CClientSocket *)lParam;
	CString str;
	UINT idx=0;
	POSITION pos = m_ptrClientSocketList.Find(pClient);

	if(pos!=NULL)
	{
		//m_list_client에서 해당 Client 삭제
		str.Format(_T("Client (%d)"), (int)pClient);
		idx = m_list_client.SelectString(-1, (LPCTSTR)str);
		m_list_client.DeleteString(idx);
		
		//CPtrList에서도 해당 Client 삭제
		m_ptrClientSocketList.RemoveAt(pos);
	}

	return 0;
}


소스코드 참조 : https://github.com/jjj0214/MFC_Samples/tree/master/socServer


댓글