반응형
TCP/IP 통신 (2) 채팅 프로그램 클라이언트 예제
MFC로 TCP/IP 통신을 사용해 간단한 채팅 프로그램을 구현하였다.
TCP/IP 통신은 서버가 존재하고 해당 서버에 클라이언트가 접속하여 데이터를 보낸다.
그리고 서버에서는 클라이언트로부터 받은 데이터를 다른 클라이언트에게 보낸다.
구체적은 개념은 따로 포스팅 하기로 하고 아래는 소스이다.
TCP 서버
TCP_ServerDlg.h
private:
SOCKET server_socket; // 서버 소켓
CList<SOCKET> userList; // 접속한 클라이언트 리스트
public:
CEdit portEdit; // 포트 입력 EditBox
CListBox m_msg_list; // 메시지 전시 ListBox
CListBox m_client_list; // 접속 클라이언트 전시 ListBox
afx_msg void ConnectBntClicked(); // 서버 연결 버튼 클릭
void InsertMsg(CString str); // 메시지 ListBox에 메시지 추가
void InsertClient(CString str); // 클라이언트 ListBox에 유저 추가
void SendToAll(SOCKET socket, CString str); // 연결된 클라이언트에 메시지 송신
static UINT AcceptFunc(LPVOID pParam); // 클라이언트 연결 대기 스레드
static UINT RecvFunc(LPVOID pParam); // 메시지 수신 대기 스레드
typedef struct _MyData {
CTCPServerDlg* m_pDlg;
SOCKET c_sock;
SOCKADDR_IN c_addr;
} MyData; // 메시지 수신 스레드에 매개로 넘겨줄 구조체
TCP_ServerDlg.cpp
// 연결 버튼 클릭 함수
void CTCPServerDlg::ConnectBntClicked()
{
UINT port;
CString strPort;
portEdit.GetWindowText(strPort); // 사용자가 입력한 포트번호
port = _tstoi(strPort);
SOCKADDR_IN srv_addr;
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
srv_addr.sin_port = htons(port);
// 서버 소켓 생성
server_socket = socket(PF_INET, SOCK_STREAM, 0);
if (server_socket == INVALID_SOCKET) MessageBox(_T("socket error"));
// bind(소켓에 ip, port 할당)
if (bind(server_socket, (SOCKADDR*)&srv_addr, sizeof(srv_addr)) == -1)
InsertMsg(_T("bind error")); // 채팅창에 알림
// bind 성공 시, listen(클라이언트 기다림) 시작
if (listen(server_socket, SOMAXCONN) == -1) InsertMsg(_T("lsten error"));
else InsertMsg(_T("Listening..."));
AfxBeginThread(AcceptFunc, this); // 클라이언트 연결 대기 스레드 시작
}
// 클라이언트 연결 대기 스레드
UINT CTCPServerDlg::AcceptFunc(LPVOID pParam) {
// 자기 자신을 매개로 넘김
CTCPServerDlg* pDlg = (CTCPServerDlg*)pParam;
while (1)
{
SOCKET client_socket;
SOCKADDR_IN cln_addr;
CString strIp;
int sockaddr_in_size = sizeof(sockaddr_in);
// accept() : 클라이언트 접속 요청 확인, 클라이언트 소켓 반환
client_socket = accept(pDlg->server_socket, (SOCKADDR*)&cln_addr, &sockaddr_in_size);
if (client_socket == INVALID_SOCKET) break; // 클라이언트 소켓이 유효하지않으면
else
{
pDlg->userList.AddTail(client_socket); // 클라이언트 리스트에 추가
strIp.Format(_T("- Client (%s : %d)"), inet_ntoa(cln_addr.sin_addr), (int)ntohs(cln_addr.sin_port));
pDlg->InsertClient(strIp); // 클라이언트 ListBox에 추가
strIp += " has entered -";
pDlg->InsertMsg(strIp); // 메시지 ListBox에 클라이언트 접속 메시지 추가
pDlg->SendToAll(client_socket, strIp); // 모든 클라이언트에 접속 메시지 송신
MyData* data = new MyData;
data->m_pDlg = pDlg; // 자기자신
data->c_sock = client_socket;
data->c_addr = cln_addr; // 접속한 클라이언트 정보
AfxBeginThread(RecvFunc, data); // 메시지 수신 스레드 시작
}
}
closesocket(pDlg->server_socket); // 오류 발생 시, 소켓 종료
return 0;
}
반응형
// 메시지 수신 대기 스레드
UINT CTCPServerDlg::RecvFunc(LPVOID pParam) {
MyData* data = (MyData*)pParam;
CTCPServerDlg* pDlg = (CTCPServerDlg*)data->m_pDlg;
SOCKET client_socket;
SOCKADDR_IN cln_addr;
while (1)
{
char temp[1024] = ""; // 메시지를 담을 배열
int strlen;
client_socket = data->c_sock;
cln_addr = data->c_addr;
// 메시지 수신, 수신한 메시지 길이 반환
strlen = recv(client_socket, temp, 1024, NULL);
if (strlen == -1) // 오류
{
// 클라이언트 리스트에 있다면 해당 클라이언트 퇴장 처리
if (pDlg->userList.Find(client_socket) != NULL)
{
POSITION pos = pDlg->userList.Find(client_socket);
pDlg->userList.RemoveAt(pos);
CString user;
user.Format(_T("- Client (%s : %d)"), inet_ntoa(cln_addr.sin_addr), (int)ntohs(cln_addr.sin_port));
int index = pDlg->m_client_list.FindString(pDlg->m_client_list.GetCurSel(), user);
pDlg->m_client_list.DeleteString(index);
user += " has exited -";
pDlg->InsertMsg(user);
pDlg->SendToAll(client_socket, user);
}
break;
}
else // 메시지 정상 수신
{
CString str;
str.Format(_T("Client (%s : %d) : "), inet_ntoa(cln_addr.sin_addr), (int)ntohs(cln_addr.sin_port));
str += (LPSTR)temp;
pDlg->InsertMsg(str); // 메시지 ListBox에 수신 메시지 추가
pDlg->SendToAll(client_socket, str); // 모든 클라이언트에 수신 메시지 송신
str.Empty();
}
}
closesocket(client_socket);
return 0;
}
// 연결된 클라이언트에 메시지 송신 함수
void CTCPServerDlg::SendToAll(SOCKET socket, CString str) {
SOCKET client_socket;
POSITION pos = userList.GetHeadPosition();
while (pos) // 클라이언트 리스트 처음부터
{
client_socket = userList.GetNext(pos);
if (client_socket != INVALID_SOCKET)
if (client_socket != socket) // 메시지를 보낸 클라이언트 제외
{ // 클라이언트로 메시지 송신
if (send(client_socket, (LPCTSTR)str, 1024, 0) == -1) {
InsertMsg(_T("send error"));
}
}
}
}
// 메시지 ListBox 추가 함수
void CTCPServerDlg::InsertMsg(CString str)
{
m_msg_list.InsertString(-1, str);
m_msg_list.SetTopIndex(m_msg_list.GetCount() - 1);
}
// 클라이언트 ListBox 추가 함수
void CTCPServerDlg::InsertClient(CString str)
{
m_client_list.InsertString(-1, str);
m_client_list.SetTopIndex(m_msg_list.GetCount() - 1);
}
위 코드는 클라이언트 접속과 메시지 수신 스레드를 분리하였지만 한 스레드에서 처리하기도 한다.
또한 위 코드는 서버-클라이언트 다중 접속 채팅을 구현하였지만
서버에 메시지 입력을 추가해 서버-클라이언트 간 1대1 채팅으로 구현도 가능하다.
더보기
메시지 전송 버튼을 클릭하면 메시지 입력창의 문자를 send() 함수로 클라이언트에 송신
1대1 채팅의 경우 클라이언트 리스트 대신 클라이언트 소켓 변수 하나만 생성하면 된다.
이 외 메시지 전시 등은 개인 취향대로 수정하여 사용하면 된다.
반응형
'C++ > 예제' 카테고리의 다른 글
[C++][MFC] UDP 통신 (2) 채팅 프로그램 멀티캐스트 예제 (7) | 2022.09.19 |
---|---|
[C++][MFC] UDP 통신 (1) 채팅 프로그램 1대1 예제 (2) | 2022.09.19 |
[C++][MFC] TCP/IP 통신 (2) 채팅 프로그램 클라이언트 예제 (1) | 2022.09.17 |
[C][C++] 배열을 활용한 바이트 스왑 예제 (0) | 2022.04.07 |
댓글