반응형
실시간 채팅 애플리케이션 서버 개발 (계속)
이전 단계에서는 기본적인 메시지 중계와 사용자 이름 관리를 구현했습니다. 이번 단계에서는 서버에 더 많은 기능을 추가하고, 개선하겠습니다.
추가 기능 요구사항
- 사용자 목록 관리: 현재 접속해 있는 사용자 목록을 관리하고 클라이언트에게 제공해야 합니다.
- 연결 및 연결 해제 알림: 사용자가 연결되거나 연결이 해제될 때 모든 사용자에게 알림을 보냅니다.
- 메시지 포맷 관리: JSON 형식의 메시지를 사용하여 메시지를 구조화합니다.
서버 클래스 다이어그램 (업데이트)
+-------------------+
| ChatServer |
+-------------------+
| +start() |
| +stop() |
| -accept() |
| -read() |
| -write() |
| -broadcast() |
| -updateUserList() |
+-------------------+
|
v
+-------------------+
| ClientSession |
+-------------------+
| +start() |
| +send() |
| -read() |
| -write() |
| -handleMessage() |
+-------------------+
서버 코드 구현
ChatServer.h
#ifndef CHATSERVER_H
#define CHATSERVER_H
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <memory>
#include <unordered_set>
#include <unordered_map>
#include <string>
#include <nlohmann/json.hpp>
namespace net = boost::asio;
namespace websocket = boost::beast::websocket;
using tcp = net::ip::tcp;
class ClientSession;
class ChatServer : public std::enable_shared_from_this<ChatServer> {
public:
ChatServer(net::io_context& ioc, tcp::endpoint endpoint)
: acceptor_(ioc), socket_(ioc) {
acceptor_.open(endpoint.protocol());
acceptor_.set_option(net::socket_base::reuse_address(true));
acceptor_.bind(endpoint);
acceptor_.listen();
do_accept();
}
void broadcast(const nlohmann::json& message);
void join(std::shared_ptr<ClientSession> session, const std::string& username);
void leave(std::shared_ptr<ClientSession> session);
void updateUserList();
private:
void do_accept();
tcp::acceptor acceptor_;
tcp::socket socket_;
std::unordered_set<std::shared_ptr<ClientSession>> sessions_;
std::unordered_map<std::string, std::shared_ptr<ClientSession>> user_sessions_;
};
#endif // CHATSERVER_H
ClientSession.h
#ifndef CLIENTSESSION_H
#define CLIENTSESSION_H
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <memory>
#include "ChatServer.h"
namespace net = boost::asio;
namespace websocket = boost::beast::websocket;
using tcp = net::ip::tcp;
class ClientSession : public std::enable_shared_from_this<ClientSession> {
public:
ClientSession(tcp::socket socket, ChatServer& server)
: ws_(std::move(socket)), server_(server) {}
void start();
void send(const nlohmann::json& message);
private:
void do_read();
void do_write();
void handle_message(const std::string& message);
websocket::stream<tcp::socket> ws_;
ChatServer& server_;
boost::beast::flat_buffer buffer_;
std::vector<std::string> write_msgs_;
std::string username_;
};
#endif // CLIENTSESSION_H
ChatServer.cpp
#include "ChatServer.h"
#include "ClientSession.h"
void ChatServer::broadcast(const nlohmann::json& message) {
for (auto& session : sessions_) {
session->send(message);
}
}
void ChatServer::join(std::shared_ptr<ClientSession> session, const std::string& username) {
sessions_.insert(session);
user_sessions_[username] = session;
// 알림 메시지를 생성하여 모든 사용자에게 브로드캐스트
nlohmann::json notification;
notification["type"] = "join";
notification["username"] = username;
broadcast(notification);
updateUserList();
}
void ChatServer::leave(std::shared_ptr<ClientSession> session) {
sessions_.erase(session);
for (auto it = user_sessions_.begin(); it != user_sessions_.end(); ++it) {
if (it->second == session) {
// 알림 메시지를 생성하여 모든 사용자에게 브로드캐스트
nlohmann::json notification;
notification["type"] = "leave";
notification["username"] = it->first;
broadcast(notification);
user_sessions_.erase(it);
break;
}
}
updateUserList();
}
void ChatServer::updateUserList() {
nlohmann::json user_list_message;
user_list_message["type"] = "user_list";
for (const auto& user_session : user_sessions_) {
user_list_message["users"].push_back(user_session.first);
}
broadcast(user_list_message);
}
void ChatServer::do_accept() {
acceptor_.async_accept(socket_, [this](boost::system::error_code ec) {
if (!ec) {
auto session = std::make_shared<ClientSession>(std::move(socket_), *this);
session->start();
}
do_accept();
});
}
ClientSession.cpp
#include "ClientSession.h"
#include <nlohmann/json.hpp>
void ClientSession::start() {
do_read();
}
void ClientSession::send(const nlohmann::json& message) {
auto self(shared_from_this());
net::post(ws_.get_executor(), [this, self, message]() {
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(message.dump());
if (!write_in_progress) {
do_write();
}
});
}
void ClientSession::do_read() {
auto self(shared_from_this());
ws_.async_read(buffer_, [this, self](boost::system::error_code ec, std::size_t length) {
if (!ec) {
auto message = boost::beast::buffers_to_string(buffer_.data());
handle_message(message);
buffer_.consume(length);
do_read();
} else {
server_.leave(shared_from_this());
}
});
}
void ClientSession::do_write() {
auto self(shared_from_this());
ws_.async_write(net::buffer(write_msgs_.front()), [this, self](boost::system::error_code ec, std::size_t length) {
if (!ec) {
write_msgs_.erase(write_msgs_.begin());
if (!write_msgs_.empty()) {
do_write();
}
} else {
server_.leave(shared_from_this());
}
});
}
void ClientSession::handle_message(const std::string& message) {
auto json_message = nlohmann::json::parse(message);
if (json_message.contains("username")) {
username_ = json_message["username"].get<std::string>();
server_.join(shared_from_this(), username_);
}
if (json_message.contains("message")) {
nlohmann::json broadcast_message;
broadcast_message["type"] = "message";
broadcast_message["username"] = username_;
broadcast_message["message"] = json_message["message"];
server_.broadcast(broadcast_message);
}
}
main.cpp
#include <boost/asio.hpp>
#include "ChatServer.h"
int main() {
try {
net::io_context ioc{1};
tcp::endpoint endpoint{net::ip::make_address("0.0.0.0"), 12345};
auto server = std::make_shared<ChatServer>(ioc, endpoint);
ioc.run();
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
설명
위의 코드는 실시간 채팅 애플리케이션 서버의 기능을 확장한 예제입니다. 추가된 기능은 다음과 같습니다.
- ChatServer 클래스:
broadcast()
메서드는 모든 클라이언트에게 메시지를 전송합니다.join()
메서드는 클라이언트가 서버에 연결될 때 호출되며, 사용자 이름을 관리하고, 연결 알림을 브로드캐스트합니다.leave()
메서드는 클라이언트가 서버에서 연결을 종료할 때 호출되며, 연결 해제 알림을 브로드캐스트합니다.updateUserList()
메서드는 현재 연결된 사용자 목록을 업데이트하고, 모든 사용자에게 전송합니다.
- ClientSession 클래스:
start()
메서드는 세션을 시작합니다.send()
메서드는 서버에서 클라이언트로 메시지를 전송합니다.do_read()
메서드는 클라이언트로부터 메시지를 읽습니다.do_write()
메서드는 클라이언트에게 메시지를 씁니다.handle_message()
메서드는 수신된 메시지를 처리합니다. 사용자 이름과 메시지를 구분하여 처리합니다.
이제 스물여섯 번째 날의 학습을 마쳤습니다. 실시간 채팅 애플리케이션의 서버에 사용자 목록 관리, 연결 및 연결 해제 알림 기능을 추가하는 방법을 학습했습니다.
질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "프로젝트: 실시간 채팅 기능 구현"에 대해 학습하겠습니다.
반응형
'-----ETC----- > C++ 네트워크 프로그래밍 시리즈' 카테고리의 다른 글
[C++ 네트워크 프로그래밍] Day 28: 프로젝트: 보안 기능 추가 (0) | 2024.08.01 |
---|---|
[C++ 네트워크 프로그래밍] Day 25: 프로젝트: 서버 개발 (1) (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 24: 프로젝트: 클라이언트 개발 (2) (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 22: 프로젝트 소개 및 설계 (실시간 채팅 애플리케이션) (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 23: 프로젝트: 클라이언트 개발 (1) (0) | 2024.08.01 |