반응형
실시간 채팅 애플리케이션 클라이언트 개발 (계속)
이전 단계에서는 기본적인 WebSocket 클라이언트를 구현하였습니다. 이번 단계에서는 클라이언트에 더 많은 기능을 추가하고, 사용자 인터페이스를 개선하겠습니다.
추가 기능 요구사항
- 사용자 인터페이스 개선: 더 나은 사용자 경험을 위해 사용자 인터페이스를 개선합니다.
- 사용자 이름 설정: 각 사용자가 고유한 사용자 이름으로 메시지를 전송할 수 있도록 합니다.
- 메시지 형식 지정: 메시지를 JSON 형식으로 전송하여 사용자 이름과 메시지를 구분합니다.
클라이언트 클래스 다이어그램
+-------------------+
| ChatClient |
+-------------------+
| +connect() |
| +disconnect() |
| +sendMessage() |
| -doRead() |
| -doWrite() |
| -setUsername() |
| -formatMessage() |
+-------------------+
클라이언트 코드 구현
ChatClient.h
#ifndef CHATCLIENT_H
#define CHATCLIENT_H
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <memory>
#include <string>
#include <queue>
#include <iostream>
namespace net = boost::asio;
namespace websocket = boost::beast::websocket;
using tcp = net::ip::tcp;
class ChatClient : public std::enable_shared_from_this<ChatClient> {
public:
ChatClient(net::io_context& ioc, const tcp::resolver::results_type& endpoints)
: resolver_(ioc), ws_(ioc), endpoints_(endpoints) {}
void connect();
void disconnect();
void sendMessage(const std::string& message);
void setUsername(const std::string& username);
private:
void doRead();
void doWrite();
std::string formatMessage(const std::string& message);
tcp::resolver resolver_;
websocket::stream<tcp::socket> ws_;
tcp::resolver::results_type endpoints_;
boost::beast::flat_buffer buffer_;
std::queue<std::string> writeMessages_;
std::string username_;
};
#endif // CHATCLIENT_H
ChatClient.cpp
#include "ChatClient.h"
#include <nlohmann/json.hpp>
void ChatClient::connect() {
auto self(shared_from_this());
net::async_connect(ws_.next_layer(), endpoints_,
[this, self](boost::system::error_code ec, tcp::endpoint) {
if (!ec) {
ws_.async_handshake("localhost", "/",
[this, self](boost::system::error_code ec) {
if (!ec) {
doRead();
} else {
std::cerr << "Handshake failed: " << ec.message() << std::endl;
}
});
} else {
std::cerr << "Connect failed: " << ec.message() << std::endl;
}
});
}
void ChatClient::disconnect() {
auto self(shared_from_this());
ws_.async_close(websocket::close_code::normal,
[this, self](boost::system::error_code ec) {
if (ec) {
std::cerr << "Close failed: " << ec.message() << std::endl;
}
});
}
void ChatClient::sendMessage(const std::string& message) {
auto self(shared_from_this());
net::post(ws_.get_executor(), [this, self, message]() {
bool writeInProgress = !writeMessages_.empty();
writeMessages_.push(formatMessage(message));
if (!writeInProgress) {
doWrite();
}
});
}
void ChatClient::setUsername(const std::string& username) {
username_ = username;
}
std::string ChatClient::formatMessage(const std::string& message) {
nlohmann::json jsonMessage;
jsonMessage["username"] = username_;
jsonMessage["message"] = message;
return jsonMessage.dump();
}
void ChatClient::doRead() {
auto self(shared_from_this());
ws_.async_read(buffer_,
[this, self](boost::system::error_code ec, std::size_t length) {
if (!ec) {
std::cout << "Received: " << boost::beast::buffers_to_string(buffer_.data()) << std::endl;
buffer_.consume(length);
doRead();
} else {
std::cerr << "Read failed: " << ec.message() << std::endl;
disconnect();
}
});
}
void ChatClient::doWrite() {
auto self(shared_from_this());
ws_.async_write(net::buffer(writeMessages_.front()),
[this, self](boost::system::error_code ec, std::size_t length) {
if (!ec) {
writeMessages_.pop();
if (!writeMessages_.empty()) {
doWrite();
}
} else {
std::cerr << "Write failed: " << ec.message() << std::endl;
disconnect();
}
});
}
main.cpp
#include <boost/asio.hpp>
#include "ChatClient.h"
int main() {
try {
net::io_context ioc;
tcp::resolver resolver(ioc);
auto endpoints = resolver.resolve("localhost", "12345");
auto client = std::make_shared<ChatClient>(ioc, endpoints);
std::cout << "Enter your username: ";
std::string username;
std::getline(std::cin, username);
client->setUsername(username);
client->connect();
std::thread t([&ioc]() { ioc.run(); });
std::string message;
while (std::getline(std::cin, message)) {
client->sendMessage(message);
}
client->disconnect();
t.join();
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
설명
위의 코드는 이전 단계에서 구현한 WebSocket 클라이언트에 사용자 이름 설정과 메시지 형식을 추가한 예제입니다. 메시지는 JSON 형식으로 서버에 전송되며, 사용자 이름과 메시지를 포함합니다.
- ChatClient 클래스:
connect()
메서드는 서버에 연결을 설정합니다.disconnect()
메서드는 서버와의 연결을 종료합니다.sendMessage()
메서드는 사용자의 메시지를 서버로 전송합니다.setUsername()
메서드는 사용자의 이름을 설정합니다.formatMessage()
메서드는 메시지를 JSON 형식으로 변환합니다.doRead()
메서드는 서버로부터 메시지를 읽습니다.doWrite()
메서드는 서버로 메시지를 씁니다.
- main 함수:
main
함수는 io_context와 클라이언트를 생성하고, 서버에 연결을 시도합니다.- 사용자로부터 이름과 메시지를 입력받아 클라이언트에 전송하고, 연결을 종료합니다.
이제 스물네 번째 날의 학습을 마쳤습니다. 실시간 채팅 애플리케이션의 클라이언트에 사용자 이름 설정과 메시지 형식을 추가하는 방법을 학습했습니다.
질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "프로젝트: 서버 개발 (1)"에 대해 학습하겠습니다.
반응형
'-----ETC----- > C++ 네트워크 프로그래밍 시리즈' 카테고리의 다른 글
[C++ 네트워크 프로그래밍] Day 25: 프로젝트: 서버 개발 (1) (0) | 2024.08.01 |
---|---|
[C++ 네트워크 프로그래밍] Day 26: 프로젝트: 서버 개발 (2) (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 22: 프로젝트 소개 및 설계 (실시간 채팅 애플리케이션) (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 23: 프로젝트: 클라이언트 개발 (1) (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 20: 로드 밸런싱과 스케일링 (0) | 2024.08.01 |