비동기 소켓 프로그래밍
비동기 소켓 프로그래밍은 네트워크 통신에서 비동기 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
클래스를 통해 서버에 비동기적으로 연결합니다. - 사용자가 입력한 메시지를 서버로 전송하고, 서버로부터 에코된 메시지를 비동기적으로 수신합니다.
- 클라이언트는
이제 네 번째 날의 학습을 마쳤습니다. 비동기 소켓 프로그래밍의 기본 개념과 구현 방법을 학습했습니다.
질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "네트워크 데이터 직렬화"에 대해 학습하겠습니다.
'-----ETC----- > C++ 네트워크 프로그래밍 시리즈' 카테고리의 다른 글
[C++ 네트워크 프로그래밍] Day 6: 네트워크 프로토콜 기초 (0) | 2024.08.01 |
---|---|
[C++ 네트워크 프로그래밍] Day 3: 소켓 프로그래밍 기초 (UDP) (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 2: 소켓 프로그래밍 기초 (TCP) (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍 ] Day 1: 네트워크 프로그래밍 소개와 개발 환경 설정 (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] 목차 (0) | 2024.06.20 |