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

[C++ 네트워크 프로그래밍] Day 4: 비동기 소켓 프로그래밍

by cogito21_cpp 2024. 8. 1.
반응형

비동기 소켓 프로그래밍

비동기 소켓 프로그래밍은 네트워크 통신에서 비동기 I/O를 사용하여 동시성을 높이고, CPU의 유휴 시간을 줄이는 방법입니다. 비동기 프로그래밍을 통해 소켓의 I/O 작업을 비동기적으로 처리할 수 있습니다. 이를 통해 하나의 스레드가 여러 I/O 작업을 처리할 수 있습니다.

비동기 소켓 프로그래밍의 주요 개념

블로킹과 논블로킹 I/O

  • 블로킹 I/O: 함수 호출이 완료될 때까지 호출한 스레드가 대기 상태에 있습니다. 예를 들어, recv() 함수는 데이터가 수신될 때까지 블로킹됩니다.
  • 논블로킹 I/O: 함수 호출이 즉시 반환되며, 호출한 스레드는 다른 작업을 계속 수행할 수 있습니다. 데이터가 준비되지 않은 경우, 에러를 반환합니다.

비동기 I/O

  • 비동기 I/O: I/O 작업을 비동기적으로 처리하며, 작업이 완료되면 콜백 함수가 호출됩니다. 이 방식은 이벤트 기반 프로그래밍 모델을 사용합니다.
  • Boost.Asio: C++에서 비동기 I/O를 구현하기 위한 라이브러리로, 비동기 소켓 프로그래밍을 쉽게 구현할 수 있습니다.

Boost.Asio를 이용한 비동기 소켓 프로그래밍

Boost.Asio는 비동기 I/O를 처리하기 위한 다양한 기능을 제공합니다. 이를 사용하여 비동기 소켓 프로그래밍을 구현할 수 있습니다.

io_context

  • io_context: 비동기 I/O 작업을 관리하는 핵심 클래스입니다. I/O 작업을 수행하고, 작업 완료 시 콜백 함수를 호출합니다.

비동기 소켓 함수

  • async_accept(): 비동기적으로 클라이언트 연결을 수락합니다.
  • async_read_some(): 비동기적으로 데이터를 읽습니다.
  • async_write_some(): 비동기적으로 데이터를 씁니다.

비동기 TCP 서버 구현

비동기 TCP 서버 코드 예제

#include <iostream>
#include <boost/asio.hpp>
#include <memory>

using boost::asio::ip::tcp;

class TcpServer : public std::enable_shared_from_this<TcpServer> {
public:
    TcpServer(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
        start_accept();
    }

private:
    void start_accept() {
        auto new_session = std::make_shared<TcpSession>(acceptor_.get_executor().context());
        acceptor_.async_accept(new_session->socket(),
            [this, new_session](const boost::system::error_code& error) {
                if (!error) {
                    new_session->start();
                }
                start_accept();
            });
    }

    tcp::acceptor acceptor_;
};

class TcpSession : public std::enable_shared_from_this<TcpSession> {
public:
    TcpSession(boost::asio::io_context& io_context)
        : socket_(io_context) {}

    tcp::socket& socket() {
        return socket_;
    }

    void start() {
        do_read();
    }

private:
    void do_read() {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_),
            [this, self](const boost::system::error_code& error, std::size_t length) {
                if (!error) {
                    do_write(length);
                }
            });
    }

    void do_write(std::size_t length) {
        auto self(shared_from_this());
        boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
            [this, self](const boost::system::error_code& error, std::size_t) {
                if (!error) {
                    do_read();
                }
            });
    }

    tcp::socket socket_;
    char data_[1024];
};

int main() {
    try {
        boost::asio::io_context io_context;
        TcpServer server(io_context, 12345);
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "예외 발생: " << e.what() << std::endl;
    }

    return 0;
}

 

비동기 TCP 클라이언트 구현

비동기 TCP 클라이언트 코드 예제

#include <iostream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class TcpClient {
public:
    TcpClient(boost::asio::io_context& io_context, const std::string& host, const std::string& service)
        : resolver_(io_context), socket_(io_context) {
        connect(host, service);
    }

private:
    void connect(const std::string& host, const std::string& service) {
        auto endpoints = resolver_.resolve(host, service);
        boost::asio::async_connect(socket_, endpoints,
            [this](const boost::system::error_code& error, const tcp::endpoint&) {
                if (!error) {
                    do_write();
                }
            });
    }

    void do_write() {
        std::string message = "Hello from client!";
        boost::asio::async_write(socket_, boost::asio::buffer(message),
            [this](const boost::system::error_code& error, std::size_t) {
                if (!error) {
                    do_read();
                }
            });
    }

    void do_read() {
        socket_.async_read_some(boost::asio::buffer(data_),
            [this](const boost::system::error_code& error, std::size_t length) {
                if (!error) {
                    std::cout << "서버로부터 수신한 메시지: " << std::string(data_, length) << std::endl;
                }
            });
    }

    tcp::resolver resolver_;
    tcp::socket socket_;
    char data_[1024];
};

int main() {
    try {
        boost::asio::io_context io_context;
        TcpClient client(io_context, "127.0.0.1", "12345");
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "예외 발생: " << e.what() << std::endl;
    }

    return 0;
}

 

설명

위의 코드는 간단한 비동기 TCP 서버와 클라이언트를 구현한 예제입니다. 서버는 클라이언트의 연결을 비동기적으로 수락하고, 데이터를 비동기적으로 수신하고 다시 전송합니다. 클라이언트는 서버에 연결하여 메시지를 전송하고, 서버로부터 메시지를 비동기적으로 수신합니다.

  • 비동기 TCP 서버:
    • 서버는 TcpServer 클래스를 통해 비동기적으로 클라이언트의 연결을 수락합니다.
    • TcpSession 클래스는 클라이언트와의 세션을 관리하며, 데이터를 비동기적으로 수신하고 전송합니다.
  • 비동기 TCP 클라이언트:
    • 클라이언트는 TcpClient 클래스를 통해 서버에 비동기적으로 연결합니다.
    • 서버에 메시지를 전송하고, 서버로부터 메시지를 비동기적으로 수신합니다.

실습 문제

문제 1: 비동기 에코 서버와 클라이언트 작성하기

비동기 에코 서버와 클라이언트를 작성하여, 클라이언트가 보낸 메시지를 서버가 다시 클라이언트에게 비동기적으로 보내도록 구현하세요.

 

해설:

비동기 에코 서버

#include <iostream>
#include <boost/asio.hpp>
#include <memory>

using boost::asio::ip::tcp;

class EchoServer : public std::enable_shared_from_this<EchoServer> {
public:
    EchoServer(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
        start_accept();
    }

private:
    void start_accept() {
        auto new_session = std::make_shared<EchoSession>(acceptor_.get_executor().context());
        acceptor_.async_accept(new_session->socket(),
            [this, new_session](const boost::system::error_code& error) {
                if (!error) {
                    new_session->start();
                }
                start_accept();
            });
    }

    tcp::acceptor acceptor_;
};

class EchoSession : public std::enable_shared_from_this<EchoSession> {
public:
    EchoSession(boost::asio::io_context& io_context)
        : socket_(io_context) {}

    tcp::socket& socket() {
        return socket_;
    }

    void start() {
        do_read();
    }

private:
    void do_read() {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_),
            [this, self](const boost::system::error_code& error, std::size_t length) {
                if (!error) {
                    do_write(length);
                }
            });
    }

    void do_write(std::size_t length) {
        auto self

(shared_from_this());
        boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
            [this, self](const boost::system::error_code& error, std::size_t) {
                if (!error) {
                    do_read();
                }
            });
    }

    tcp::socket socket_;
    char data_[1024];
};

int main() {
    try {
        boost::asio::io_context io_context;
        EchoServer server(io_context, 12345);
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "예외 발생: " << e.what() << std::endl;
    }

    return 0;
}

 

비동기 에코 클라이언트

#include <iostream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class EchoClient {
public:
    EchoClient(boost::asio::io_context& io_context, const std::string& host, const std::string& service)
        : resolver_(io_context), socket_(io_context) {
        connect(host, service);
    }

private:
    void connect(const std::string& host, const std::string& service) {
        auto endpoints = resolver_.resolve(host, service);
        boost::asio::async_connect(socket_, endpoints,
            [this](const boost::system::error_code& error, const tcp::endpoint&) {
                if (!error) {
                    do_write();
                }
            });
    }

    void do_write() {
        std::cout << "메시지를 입력하세요: ";
        std::getline(std::cin, message_);
        message_ += "\n";

        boost::asio::async_write(socket_, boost::asio::buffer(message_),
            [this](const boost::system::error_code& error, std::size_t) {
                if (!error) {
                    do_read();
                }
            });
    }

    void do_read() {
        socket_.async_read_some(boost::asio::buffer(data_),
            [this](const boost::system::error_code& error, std::size_t length) {
                if (!error) {
                    std::cout << "서버로부터 에코된 메시지: " << std::string(data_, length) << std::endl;
                    do_write();
                }
            });
    }

    tcp::resolver resolver_;
    tcp::socket socket_;
    std::string message_;
    char data_[1024];
};

int main() {
    try {
        boost::asio::io_context io_context;
        EchoClient client(io_context, "127.0.0.1", "12345");
        io_context.run();
    } catch (std::exception& e) {
        std::cerr << "예외 발생: " << e.what() << std::endl;
    }

    return 0;
}

 

설명

위의 코드는 비동기 에코 서버와 클라이언트를 구현한 예제입니다. 클라이언트는 사용자가 입력한 메시지를 서버로 전송하고, 서버는 수신한 메시지를 비동기적으로 클라이언트로 다시 전송합니다.

  • 비동기 에코 서버:
    • 서버는 EchoServer 클래스를 통해 비동기적으로 클라이언트의 연결을 수락합니다.
    • EchoSession 클래스는 클라이언트와의 세션을 관리하며, 데이터를 비동기적으로 수신하고 에코합니다.
  • 비동기 에코 클라이언트:
    • 클라이언트는 EchoClient 클래스를 통해 서버에 비동기적으로 연결합니다.
    • 사용자가 입력한 메시지를 서버로 전송하고, 서버로부터 에코된 메시지를 비동기적으로 수신합니다.

이제 네 번째 날의 학습을 마쳤습니다. 비동기 소켓 프로그래밍의 기본 개념과 구현 방법을 학습했습니다.

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "네트워크 데이터 직렬화"에 대해 학습하겠습니다.

반응형