728x90
SMALL
- 설명 -
① 1, 2번에서 설명한 Server와 Client를 수정
② 설정에 따라 서버만 모든 클라이언트와 통신 가능하거나, 클라이언트도 같은 클라이언트끼리 통신 가능
1. Client : 다이얼로그 디자인 및 ID
① IDC_EDIT_USERID : 클라이언트가 몇 번째 사용자인지 알려줌(숫자는 0번부터 시작함)
2. Client(SocCom.h) : 사용자 정의 메시지 및 사용자 지정 변수 추가
① SOC_CLIENT_CONNECT : 클라이언트(사용자)가 접속
② SOC_CLIENT_DISCONNECT : 클라이언트 종료 시 서버로 보낼 메세지
③ int m_index : 채팅 클라이언트 번호
- SocCom.h 소스 보기
더보기
#pragma once
// CSocCom 명령 대상
// 통신용 소켓
#include <afxsock.h> // 소켓 클래스 사용 위해 include
#define UM_RECEIVE WM_USER+2
#define SOC_CLIENT_CONNECT "접속성공" // 클라이언트(사용자)가 접속
#define SOC_CLIENT_DISCONNECT "클라이언트 종료" // 클라이언트 종료 시 보낼 메세지
class CSocCom : public CSocket
{
public:
CSocCom();
virtual ~CSocCom();
HWND m_hWnd; // 메인 윈도우 핸들
void CSocCom::Init(HWND hWnd); // 소켓 클래스와 메인 윈도우를 연결시킴
virtual void OnReceive(int nErrorCode); // 데이터가 도착했다는 것을 알려줌
int m_index; // 채팅 클라이언트 번호
};
3. Client(ChatClientDlg.cpp) : OnSysCommand 및 OnReceive 메시지 함수 수정
① OnSysCommand() : 시스템 명령 처리 메시지 함수. 여기서는 프로그램 종료 시 서버에 메시지를 보냄
② OnReceive() : 서버로부터 SOC_CLIENT_CONNECT 메시지를 받으면 사용자 번호를 부여 받음
- OnSysCommand() 소스 보기
더보기
void CChatClientDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
// 위 X키로 종료했을 경우
else if (nID == SC_CLOSE) { // 클라이언트 종료시 서버로 메세지 보냄.
m_socCom.Send(SOC_CLIENT_DISCONNECT, 256);
this->EndDialog(IDCANCEL);
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
- OnReceive() 소스 보기
더보기
// 데이터를 보내는 것은 소켓 클래스의 멤버 함수인 Send를 이용
// 데이터를 받을 때는 통신 소켓 클래스에 오버라이딩한 OnReceive 메시지 함수를 사용
LPARAM CChatClientDlg::OnReceive(UINT wParam, LPARAM lParam) {
// 접속된 곳에서 데이터가 도착했을 때
UpdateData(TRUE);
char pTmp[256];
CString strTmp;
memset(pTmp, '\0', 256);
// 데이터를 pTmp에 받는다.
m_socCom.Receive(pTmp, 256);
strTmp.Format("%s", pTmp);
// 서버로 부터 연결완료 메세지를 받으면
if(strTmp.Find(SOC_CLIENT_CONNECT) == 0){
// Right 함수를 이용해 가장 오른쪽에 있는 번호 추출. 0은 \0이다.
m_strUserID = "사용자 : " + strTmp.Right(1);
}
else {
// 리스트박스에 보여준다.
int i = m_list.GetCount();
m_list.InsertString(i, strTmp);
}
UpdateData(FALSE);
return TRUE;
4. Client : [종료] 버튼 클릭 메시지 함수 생성
① OnSysCommand()에 추가한 내용은 오직 창의 "X" 버튼을 눌러 종료했을 경우 수행
② 그러므로 [종료] 버튼을 눌렀을 경우에도 서버에게 종료 메시지를 보내야 함
- 메시지 함수 소스 보기
더보기
void CChatClientDlg::OnBnClickedCancel()
{
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
m_socCom.Send(SOC_CLIENT_DISCONNECT, 256); // 서버로 종료 메시지 보냄
this->EndDialog(IDCANCEL); // Dialog 닫기
CDialogEx::OnCancel();
}
5. Server(SocCom) : 사용자 정의 메시지 및 사용자 지정 변수 추가 + OnReceive() 수정
① SOC_CLIENT_CONNECT, SOC_CLIENT_DISCONNECT, int m_index 추가
② MAX_CLIENT_COUNT : 최대 수용 가능한 클라이언트(사용자) 수
③ OnReceive() : SendMessage()의 3번째 인자 값에 m_index 추가
- SocCom.h 소스 보기
더보기
#pragma once
// CSocCom 명령 대상
// 통신용 소켓
#define UM_RECEIVE WM_USER+2
#define MAX_CLIENT_COUNT 3 // 서버 수용 가능 최대 클라이언트 수
#define SOC_CLIENT_CONNECT "접속성공" // 클라이언트(사용자)가 접속
#define SOC_CLIENT_DISCONNECT "클라이언트 종료" // 클라이언트 종료 시 보낼 메세지
class CSocCom : public CSocket
{
public:
CSocCom();
virtual ~CSocCom();
HWND m_hWnd; // 메인 윈도우 핸들
void CSocCom::Init(HWND hWnd); // 소켓 클래스와 메인 윈도우를 연결시킴
virtual void OnReceive(int nErrorCode); // 데이터가 도착했다는 것을 알려줌
int m_index;
};
- SocCom.cpp 소스 보기
더보기
// 데이터가 도착했다는 것을 알려주는 가상 함수
void CSocCom::OnReceive(int nErrorCode)
{
// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
SendMessage(m_hWnd, UM_RECEIVE, m_index, 0);
CSocket::OnReceive(nErrorCode);
}
6. Server(SocServer) : STL List 추가 및 수정
① CSocCom m_socCom[MAX_CLIENT_COUNT] : 배열로 변환하여 관리(여기서는 3개)
② std::list<int> m_index : STL List로 소켓 관리
③ 위 2개 변수를 활용하여 OnAccept(), GetAcceptSocCom() 수정
- SocServer.h 소스 보기
더보기
#pragma once
#include "SocCom.h"
#include <list>
#define UM_ACCEPT WM_USER+1
// CSocServer 명령 대상
// 서버용 소켓
class CSocServer : public CSocket
{
public:
CSocServer();
virtual ~CSocServer();
CSocCom m_socCom[MAX_CLIENT_COUNT]; // 연결 요청을 한 클라이언트 서버와 실제 연결이 되는 소켓
CSocCom* GetAcceptSocCom(); // 통신 소켓 리턴
std::list<int> m_index;
HWND m_hWnd; // 메인 윈도우 핸들
void CSocServer::Init(HWND hWnd); // 소켓 클래스와 메인 윈도우를 연결시킴
virtual void OnAccept(int nErrorCode); // 클라이언트 접속 요청 처리
};
- SocServer.cpp 소스 보기
더보기
// 클라이언트에서 접속 요청이 올 경우 OnAccept 함수가 호출됨
// OnAccept 함수가 호출되면 접속 요청할 한 소켓과 다른 소켓을 연결하기 위해 Accept 함수를 호출한 뒤 메인 윈도우에 OnAccept 함수가 호출되었다는 것을 알려줌
void CSocServer::OnAccept(int nErrorCode)
{
// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.
Accept(m_socCom[m_index.front()]); // m_socCom은 연결 요청을 한 클라이언트 서버와 실제 연결이 되는 소켓
SendMessage(m_hWnd, UM_ACCEPT, 0, 0);
CSocket::OnAccept(nErrorCode);
}
// 메인 윈도우에서는 m_socCom을 얻어서 통신을 처리
CSocCom* CSocServer::GetAcceptSocCom() {
// 통신소켓을 return
// 반환되는 통신 소켓은 클라이언트와 연결됨
return &m_socCom[m_index.front()];
}
SMALL
7. Server(ChatSeverDlg)
① 통신용 소켓 CSocCom* m_socCom → CSocCom* m_socCom[MAX_CLIENT_COUNT] 으로 변경
② std::list<int> m_using : STL List로 소켓 관리
③ OnInitDialog() 코드 수정
④ OnAccept() 코드 수정
⑤ OnReceive() 코드 수정
⑥ OnClickedButtonSend() 코드 수정
- OnInitDialog() 소스 보기
더보기
for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
m_socServer.m_index.push_back(i);
}
// 서버 소켓을 생성(포트번호 5000)
m_socServer.Create(5000);
// 클라이언트의 접속을 기다림
m_socServer.Listen();
// 소켓 클래스와 메인 윈도우(여기에서는 CChatServerDlg)를 연결
m_socServer.Init(this->m_hWnd);
- OnAccept() 소스 보기
더보기
// 클라이언트 연결 요청이 왔기 때문에 Accept 함수로 접속
// 실제 접속을 담당하는 것은 CSocServer
// 이렇게 접속한 소켓은 GetAcceptSocCom을 이용해 얻어옴
// OnAccept 실행 이후 서버용 소켓인 m_socServer의 역활은 끝나고, 실제 모든 통신은 통신용 소켓인 m_socCom을 이용
LPARAM CChatServerDlg::OnAccept(UINT wParam, LPARAM lParam) {
// 클라이언트에서 접속 요청이 왔을 때
try {
// 통신용 소켓을 생선한 뒤
int tmp = m_socServer.m_index.front();
CString number; // 클라이언트 번호
number.Format("%d", tmp);
m_socCom[tmp] = new CSocCom();
// 서버소켓과 통신소켓을 연결한다.
m_socCom[tmp] = m_socServer.GetAcceptSocCom();
m_socServer.m_index.pop_front();
m_using.push_back(tmp);
m_socCom[tmp]->m_index = tmp;
m_socCom[tmp]->Init(this->m_hWnd);
// 클라이언트(사용자)에게 연결 성공 메시지를 보낼때 클라이언트 번호도 같이 보냄.
m_socCom[tmp]->Send((SOC_CLIENT_CONNECT + number), 256);
}
catch (CException* ex) {
ex->ReportError();
}
UpdateData(FALSE);
return TRUE;
}
- OnReceive() 소스 보기
더보기
// 데이터를 보내는 것은 소켓 클래스의 멤버 함수인 Send를 이용
// 데이터를 받을 때는 통신 소켓 클래스에 오버라이딩한 OnReceive 메시지 함수를 사용
LPARAM CChatServerDlg::OnReceive(UINT wParam, LPARAM lParam) {
// 접속된 곳에서 데이터가 도착했을 때
char pTmp[256];
CString strTmp;
memset(pTmp, '\0', 256);
// 데이터를 pTmp에 받는다.
m_socCom[wParam]->Receive(pTmp, 256); // wParam = 클라이언트 번호
strTmp.Format("%s", pTmp);
if (strTmp.Compare(SOC_CLIENT_DISCONNECT) == 0) {
m_socServer.m_socCom[wParam].Close();
m_socCom[wParam]->Close();
m_socServer.m_index.push_back(wParam);
m_using.erase(std::remove(m_using.begin(), m_using.end(), wParam), m_using.end());
}
else {
// 리스트박스에 보여준다.
CString id;
id.Format("%d", wParam);
int i = m_list.GetCount();
m_list.InsertString(i, ("사용자" + id + " : " + strTmp));
// 이 부분 제외하면 서버만 다중 클라이언트로 부터 채팅 가능.
for each (int i in m_using) {
if (i != _ttoi(id)) { // 보낸 클라이언트 제외 모든 클라이언트한테 보냄
m_socCom[i]->Send(("사용자" + id + " : " + strTmp), 256);
}
}
}
return TRUE;
}
- OnClickedButtonSend() 소스 보기
더보기
// [전송] 버튼을 클릭했을 때
void CChatServerDlg::OnClickedButtonSend()
{
// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
UpdateData(TRUE);
char pTmp[256];
CString strTmp;
// pTmp에 전송할 데이터 입력
memset(pTmp, '\0', 256);
strcpy_s(pTmp, "관리자 : " + m_strSend);
m_strSend = "";
// 전송
for each (int i in m_using) {
m_socCom[i]->Send(pTmp, 256);
}
// 전송한 데이터도 리스트박스에 보여준다.
strTmp.Format("%s", pTmp);
int i = m_list.GetCount();
m_list.InsertString(i, strTmp);
UpdateData(FALSE);
}
8. 실행 화면
관련 글
728x90
LIST
'C++ > MFC' 카테고리의 다른 글
[MFC] 채팅 프로그램 - 클라이언트 (2/3) (6) | 2021.08.26 |
---|---|
[MFC] 채팅 프로그램 - 서버 (1/3) (0) | 2021.08.25 |
[MFC] 그림 그리기(그림판) (0) | 2021.08.24 |
[MFC] 메모장(에디터 프로그램) (0) | 2021.08.24 |
[MFC] 파일 및 폴더 검색기 (2) | 2021.08.23 |