본문 바로가기
C++/예제

[C++][MFC] TCP/IP 통신 (1) 채팅 프로그램 서버 예제

by 부먹짱 2022. 9. 17.
반응형

TCP/IP 통신 (2) 채팅 프로그램 클라이언트 예제

 

[C++][MFC] TCP/IP 통신 (2) 채팅 프로그램 클라이언트 예제

TCP/IP 통신 (1) 채팅 프로그램 서버 예제 [C++][MFC] TCP/IP 통신 (1) 채팅 프로그램 서버 예제 MFC로 TCP/IP 통신을 사용해 간단한 채팅 프로그램을 구현하였다. TCP/IP 통신은 서버가 존재하고 해당 서버에

bumukisbest.tistory.com

 

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 채팅의 경우 클라이언트 리스트 대신 클라이언트 소켓 변수 하나만 생성하면 된다.

 

이 외 메시지 전시 등은 개인 취향대로 수정하여 사용하면 된다.

 

TCP_ServerDlg.h
0.00MB
TCP_ServerDlg.cpp
0.01MB

 

반응형

댓글