이제 클라이언트 쪽 코드를 만들면된다.
기능은 서버에 연결, 메시지 송수신 정도로 간단하게 구현했다.
#ifndef CLIENT_HPP
#define CLIENT_HPP
#include <iostream>
#include <string>
#include <WinSock2.h>
#include <ws2tcpip.h>
#include <thread>
class ChatClient {
public:
ChatClient(const std::string& ip, int port, const std::string& nickname)
: ip_(ip), port_(port), nickname_(nickname) {
initializeWinsock();
connectToServer();
}
~ChatClient() {
cleanup();
}
void startChat();
void start();
private:
std::string ip_;
bool running = true;
int port_;
std::string nickname_;
SOCKET serverSocket_ = INVALID_SOCKET;
void startReceiveThread();
void initializeWinsock();
void connectToServer();
void sendMessage(const std::string& message);
void receiveMessage();
void cleanup();
};
#endif
initializeWinsock
- Winsock API 사용을 위해 필요한 초기화 작업을 수행한다.
- 이는 네트워크 통신을 시작하기 전에 필수적으로 수행되어야 하는 단계
connectToServer
- 지정된 IP 주소와 포트를 사용하여 서버에 연결을 시도한다.
- 연결 실패 시 적절한 에러 메시지를 출력하고 프로그램을 종료합니다.
startReceiveThread
- 별도의 스레드에서 서버로부터 메시지를 수신하기 위한 메소드이다.
- 이 스레드는 running 변수가 true인 동안 계속해서 실행
start 및 startChat
- 사용자로부터 메시지 입력을 받고 서버에 메시지를 전송한다.
- /x 입력 시 채팅을 종료하고 프로그램을 종료
sendMessage
- 지정된 메시지를 서버에 전송한다
receiveMessage
- 서버로부터 메시지를 수신하고 콘솔에 출력한다.
- 서버 연결이 종료되거나 수신 중 에러가 발생할 경우 적절한 처리를 수행
cleanup
- 열려 있는 소켓을 닫고 Winsock API 사용을 정리
void ChatClient::initializeWinsock() {
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
std::cerr << "Winsock initialization failed: " << result << std::endl;
exit(1);
}
}
initializeWinsock
- Winsock 라이브러리를 초기화
void ChatClient::connectToServer() {
serverSocket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serverSocket_ == INVALID_SOCKET) {
std::cerr << "Failed to create socket: " << WSAGetLastError() << std::endl;
WSACleanup();
exit(1);
}
sockaddr_in serverAddr = {};
serverAddr.sin_family = AF_INET;
InetPtonA(AF_INET, ip_.c_str(), &serverAddr.sin_addr);
serverAddr.sin_port = htons(static_cast<u_short>(port_));
if (connect(serverSocket_, reinterpret_cast<SOCKADDR*>(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) {
std::cerr << "Failed to connect to server: " << WSAGetLastError() << std::endl;
closesocket(serverSocket_);
WSACleanup();
exit(1);
}
}
ChatClient::connectToServer
- 클라이언트가 채팅 서버에 연결을 시도하는 과정을 담당
- 서버와 마찬가지로 소켓을 생성하고, 주소를 설정해서 연결을 시도한다.
- 다만 InetPtonA를 사용하여 ip주소를 네트워크 바이트 순서로 변환한다는 것이 다르다.
void ChatClient::startReceiveThread() {
while (running) {
receiveMessage();
}
}
while 루프
- running 변수가 true로 설정되어 있는 동안, 즉 클라이언트가 실행 중인 동안 무한 루프를 통해 receiveMessage 메서드를 반복 호출하여 새로운 메시지가 서버로부터 도착할 때마다 즉시 처리한다.
void ChatClient::sendMessage(const std::string& message) {
send(serverSocket_, message.c_str(), static_cast<int>(message.length()), 0);
}
void ChatClient::receiveMessage() {
char buffer[1024];
while (running) {
memset(buffer, 0, sizeof(buffer));
int bytesReceived = recv(serverSocket_, buffer, sizeof(buffer), 0);
if (bytesReceived > 0) {
std::cout << "Received: " << std::string(buffer, 0, bytesReceived) << std::endl;
}
else if (bytesReceived == 0) {
std::cout << "Server closed the connection." << std::endl;
break;
}
else {
std::cerr << "recv failed: " << WSAGetLastError() << std::endl;
break;
}
}
}
서버와 클라이언트 간의 메시지 송수신을 담당하는 함수이다.
sendMessage
- 사용자가 입력한 메시지를 서버로 보낸다.
receiveMessage
- 서버로부터 메시지를 수신하는 역할을 한다.
- recv함수를 사용하여 serverSocket을 통해 데이터를 수신하고, 수신한 데이터는 버퍼에 저장한다.
- 수신에 성공하면, 수신된 메시지를 콘솔에 출력한다.
- string생성자를 통해 버퍼의 내용을 문자열로 변환하고 출력 범위를 수신된 바이트 수로 제한한다.
ChatClient 구현은 TCP/IP 기반의 채팅 개발을 하면서, 실시간 통신이 필요한 애플리케이션에서 사용자 입력 처리와 네트워크 I/O 작업을 효율적으로 관리하는 방법을 배울 수 있었다.
이로써 모든 과정이 끝났다... 어려운 점이 많았지만 그래도 완성하고 나니 뿌듯한 것 같다!!
서버의 데이터 베이스 부분을 class화해서 정보를 은닉하기, 로그인 구현, 데이터 베이스 스키마 강화 등 아직 개선점은 많다. 언젠가 시간이 되면 다시 코드를 개선해봐야겠다.
'Project > socket_chat_server' 카테고리의 다른 글
[CPP] 소켓 통신 채팅 서버 만들기 - 서버 구현하기 (0) | 2024.03.21 |
---|---|
CHAT_SERVER 구축 - driver->connect(), abort() error (0) | 2024.03.20 |
[CPP] 소켓 통신 채팅 서버 만들기 - 기본 기능 구현하기 - client (0) | 2024.03.18 |
[CPP] 소켓 통신 채팅 서버 만들기 - 기본 기능 구현하기 - server (0) | 2024.03.18 |
[CPP] 소켓 통신 채팅 서버 만들기 (0) | 2024.03.18 |