본문 바로가기
-----ETC-----/C++ 네트워크 프로그래밍 시리즈

[C++ 네트워크 프로그래밍] Day 29: 프로젝트: 최적화 및 테스트

by cogito21_cpp 2024. 8. 1.
반응형

최적화 및 테스트

실시간 채팅 애플리케이션의 성능을 최적화하고, 철저한 테스트를 통해 안정성과 효율성을 검증하는 것이 중요합니다. 이번 단계에서는 네트워크 애플리케이션의 최적화 기법과 테스트 방법을 다루겠습니다.

성능 최적화 기법

  1. 비동기 I/O 사용
    • 비동기 I/O를 사용하여 블로킹 없이 I/O 작업을 처리할 수 있습니다. 이를 통해 여러 I/O 작업을 동시에 처리할 수 있으며, 시스템 자원을 효율적으로 사용할 수 있습니다.
  2. 멀티스레딩
    • 여러 스레드를 사용하여 병렬로 작업을 수행하면 CPU 자원을 최대한 활용하고, 동시에 여러 클라이언트 요청을 처리할 수 있습니다.
  3. 효율적인 메모리 관리
    • 동적 메모리 할당을 최소화하고, 메모리 풀이나 스마트 포인터를 사용하여 메모리 관리를 효율적으로 할 수 있습니다.
  4. 캐싱
    • 자주 사용하는 데이터를 캐시에 저장하여, 데이터베이스 조회나 네트워크 요청을 줄이고 응답 시간을 단축할 수 있습니다.

테스트 전략

  1. 단위 테스트 (Unit Testing)
    • 개별 함수와 클래스의 기능을 검증합니다. GTest와 같은 C++ 단위 테스트 프레임워크를 사용할 수 있습니다.
  2. 통합 테스트 (Integration Testing)
    • 여러 구성 요소가 함께 작동하는지 검증합니다. 서버와 클라이언트 간의 상호 작용을 테스트합니다.
  3. 부하 테스트 (Load Testing)
    • 많은 수의 클라이언트 요청을 처리할 때의 성능을 테스트합니다. Apache JMeter와 같은 도구를 사용할 수 있습니다.
  4. 성능 테스트 (Performance Testing)
    • 애플리케이션의 응답 시간, 처리량, 자원 사용량을 측정합니다.

코드 예제

최적화된 서버 코드

ChatServer.h

#ifndef CHATSERVER_H
#define CHATSERVER_H

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <memory>
#include <unordered_set>
#include <unordered_map>
#include <string>
#include <nlohmann/json.hpp>

namespace net = boost::asio;
namespace ssl = net::ssl;
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, ssl::context& ssl_ctx, tcp::endpoint endpoint)
        : acceptor_(ioc), ssl_ctx_(ssl_ctx), 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_;
    ssl::context& ssl_ctx_;
    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/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <memory>
#include "ChatServer.h"

namespace net = boost::asio;
namespace ssl = net::ssl;
namespace websocket = boost::beast::websocket;
using tcp = net::ip::tcp;

class ClientSession : public std::enable_shared_from_this<ClientSession> {
public:
    ClientSession(tcp::socket socket, ssl::context& ssl_ctx, ChatServer& server)
        : ws_(std::move(socket), ssl_ctx), server_(server) {}

    void start();
    void send(const nlohmann::json& message);

private:
    void do_handshake();
    void do_read();
    void do_write();
    void handle_message(const std::string& message);

    websocket::stream<ssl::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_), ssl_ctx_, *this);
            session->start();
        }
        do_accept();
    });
}

 

ClientSession.cpp

#include "ClientSession.h"
#include <nlohmann/json.hpp>

void ClientSession::start() {
    do_handshake();
}

void ClientSession::do_handshake() {
    auto self(shared_from_this());
    ws_.next_layer().async_handshake(ssl::stream_base::server, [this, self](boost::system::error_code ec) {
        if (!ec) {
            ws_.async_accept([this, self](boost::system::error_code ec) {
                if (!ec) {
                    do_read();
                } else {
                    server_.leave(shared_from_this());
                }
            });
        } else {
            server_.leave(shared_from_this());
        }
    });
}

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);
    }
}

 

클라이언트 코드

ChatClient.h

#ifndef CHATCLIENT_H
#define CHATCLIENT_H

#include <boost/asio.hpp>
#include

 <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <memory>
#include <string>
#include <queue>
#include <iostream>

namespace net = boost::asio;
namespace ssl = net::ssl;
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, ssl::context& ssl_ctx, const tcp::resolver::results_type& endpoints)
        : resolver_(ioc), ws_(ioc, ssl_ctx), endpoints_(endpoints) {}

    void connect();
    void disconnect();
    void sendMessage(const std::string& message);
    void setUsername(const std::string& username);

private:
    void doRead();
    void doWrite();
    void doHandshake();
    std::string formatMessage(const std::string& message);

    tcp::resolver resolver_;
    websocket::stream<ssl::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().next_layer(), endpoints_,
        [this, self](boost::system::error_code ec, tcp::endpoint) {
            if (!ec) {
                doHandshake();
            } else {
                std::cerr << "Connect failed: " << ec.message() << std::endl;
            }
        });
}

void ChatClient::doHandshake() {
    auto self(shared_from_this());
    ws_.next_layer().async_handshake(ssl::stream_base::client, [this, self](boost::system::error_code ec) {
        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 << "SSL Handshake 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 <boost/asio/ssl.hpp>
#include "ChatClient.h"

namespace net = boost::asio;
namespace ssl = net::ssl;
using tcp = net::ip::tcp;

int main() {
    try {
        net::io_context ioc;
        ssl::context ctx{ssl::context::tlsv12};
        ctx.set_verify_mode(ssl::verify_none); // 또는 서버의 인증서를 검증하도록 설정할 수 있습니다.

        tcp::resolver resolver(ioc);
        auto endpoints = resolver.resolve("localhost", "12345");

        auto client = std::make_shared<ChatClient>(ioc, ctx, 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;
}

 

성능 테스트와 최적화

  1. 성능 프로파일링: gprof, Valgrind, perf 등을 사용하여 코드의 성능 병목 지점을 찾아 최적화합니다.
  2. 부하 테스트: Apache JMeter와 같은 도구를 사용하여 많은 클라이언트 요청을 시뮬레이션하여 서버의 성능을 측정합니다.
  3. 리소스 사용량 모니터링: htop, vmstat 등을 사용하여 CPU, 메모리, 네트워크 사용량을 모니터링합니다.
  4. 최적화 적용: 성능 병목 지점을 발견한 후, 비동기 I/O, 멀티스레딩, 캐싱 등의 기법을 사용하여 최적화합니다.

이제 스물아홉 번째 날의 학습을 마쳤습니다. 실시간 채팅 애플리케이션의 성능을 최적화하고, 철저한 테스트를 통해 안정성과 효율성을 검증하는 방법을 학습했습니다.

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "프로젝트: 배포 및 유지보수"에 대해 학습하겠습니다.

반응형