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

[C++ 네트워크 프로그래밍] Day 23: 프로젝트: 클라이언트 개발 (1)

by cogito21_cpp 2024. 8. 1.
반응형

실시간 채팅 애플리케이션 클라이언트 개발

이번 단계에서는 실시간 채팅 애플리케이션의 클라이언트 부분을 개발하겠습니다. 클라이언트는 사용자의 입력을 받아 서버에 메시지를 전송하고, 서버로부터 메시지를 수신하여 화면에 출력합니다.

클라이언트 기능 요구사항

  1. 서버 연결: 사용자가 서버에 연결할 수 있어야 합니다.
  2. 메시지 전송: 사용자가 입력한 메시지를 서버로 전송합니다.
  3. 메시지 수신: 서버로부터 다른 사용자들이 보낸 메시지를 수신합니다.
  4. 연결 유지: 서버와의 연결을 유지하고, 끊어졌을 경우 재연결을 시도합니다.
  5. 오류 처리: 연결 오류, 전송 오류 등을 처리합니다.

클라이언트 클래스 다이어그램

+-------------------+
|    ChatClient     |
+-------------------+
| +connect()        |
| +disconnect()     |
| +sendMessage()    |
| -doRead()         |
| -doWrite()        |
+-------------------+

 

클라이언트 코드 구현

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

private:
    void doRead();
    void doWrite();

    tcp::resolver resolver_;
    websocket::stream<tcp::socket> ws_;
    tcp::resolver::results_type endpoints_;
    boost::beast::flat_buffer buffer_;
    std::queue<std::string> writeMessages_;
};

#endif // CHATCLIENT_H

 

ChatClient.cpp

#include "ChatClient.h"

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(message);
        if (!writeInProgress) {
            doWrite();
        }
    });
}

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

 

설명

위의 코드는 Boost.Asio와 Boost.Beast를 사용하여 간단한 WebSocket 클라이언트를 구현한 예제입니다. 이 클라이언트는 서버에 연결하고, 메시지를 주고받을 수 있습니다.

  • ChatClient 클래스:
    • connect() 메서드는 서버에 연결을 설정합니다.
    • disconnect() 메서드는 서버와의 연결을 종료합니다.
    • sendMessage() 메서드는 사용자의 메시지를 서버로 전송합니다.
    • doRead() 메서드는 서버로부터 메시지를 읽습니다.
    • doWrite() 메서드는 서버로 메시지를 씁니다.
  • main 함수:
    • main 함수는 io_context와 클라이언트를 생성하고, 서버에 연결을 시도합니다.
    • 사용자의 입력을 받아 클라이언트에 메시지를 전송하고, 연결을 종료합니다.

이제 스물세 번째 날의 학습을 마쳤습니다. 실시간 채팅 애플리케이션의 클라이언트를 Boost.Asio와 Boost.Beast를 사용하여 구현하는 방법을 학습했습니다.

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "프로젝트: 클라이언트 개발 (2)"에 대해 학습하겠습니다.

반응형