반응형
개요
서버 프로그램은 아스키 문자열로 표현된 요청을 받는다, 요청 문자열의 형식은 다음과 같다.
EMULATE_LONG_COMP_OP [s]<LF>
여기서 [s]는 양수를, <LF>는 아스키로 개행 문자를 나타낸다.
서버는 이 문자열을 [s]초 동안 무의미한 연산을 실행해달라는 요청으로 해석한다.
"EMULATE_LONG_COMP_OP 10\n"
위와 같은 문자열이 서버에 요청으로 들어왔다고 가정한다면, 서버는 클라이언트가 10초간 무의미한 연산을 한 후 응답을 보내달라고 요청한 것으로 생각한다.
클라이언트의 요청과 마찬가지로, 서버가 보내는 응답도 아스키 문자열로 표현된다.
연산이 성공한다면 OK<LF>를, 실패한다면 ERROR<LF>를 보낸다.
동기 TCP 클라이언트 구현
동기 TCP 클라이언트는 다음과 같은 사항을 만족시키는 분산 어플리케이션이다.
- 클라이언트 서버 통신 모델에서 클라이언트로 동작한다.
- TCP 프로토콜을 사용해 서버 프로그램과 통신한다.
- I/O 및 제어 연산을 하는 동안에는연산이 끝나거나 오류가 발생할 때까지 실행 스레드를 멈춘다.
일반적인 동기 TCP 클라이언트는 다음과 같은 알고리즘에 따라 동작한다.
- 서버 프로그램의 IP주소와 프로토콜 포트 번호를 알아낸다.
- 능동 소켓을 할당한다.
- 서버 프로그램과 연결을 수립한다.
- 서버와 메시지를 주고 받는다.
- 연결을 종료한다.
- 소켓을 할당 해지한다.
#include <boost/asio.hpp>
#include <iostream>
using namespace boost;
// 동기 TCP 클라이언트 클래스
class SyncTCPClient {
public:
SyncTCPClient(const std::string& raw_ip_address,
unsigned short port_num) :
m_ep(asio::ip::make_address(raw_ip_address), port_num), // 최신 버전에서는 from_string 대신 make_address 사용
m_sock(m_ios) {
m_sock.open(m_ep.protocol());
}
// 서버에 연결하는 함수
void connect() {
m_sock.connect(m_ep);
}
// 연결을 종료하는 함수
void close() {
m_sock.shutdown(asio::ip::tcp::socket::shutdown_both);
m_sock.close();
}
// 서버에 오래 걸리는 작업을 요청하고 응답을 받는 함수
std::string emulateLongComputationOp(unsigned int duration_sec) {
std::string request = "EMULATE_LONG_COMP_OP " + std::to_string(duration_sec) + "\n";
sendRequest(request);
return receiveResponse();
}
private:
// 서버에 요청을 보내는 함수
void sendRequest(const std::string& request) {
asio::write(m_sock, asio::buffer(request));
}
// 서버의 응답을 받는 함수
std::string receiveResponse() {
asio::streambuf buf;
asio::read_until(m_sock, buf, '\n');
std::istream input(&buf);
std::string response;
std::getline(input, response);
return response;
}
private:
asio::io_context m_ios; // io_service 대신 io_context 사용
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
};
int main() {
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 3333;
try {
SyncTCPClient client(raw_ip_address, port_num);
// 동기 연결
client.connect();
std::cout << "서버에 요청을 보냅니다..." << std::endl;
std::string response = client.emulateLongComputationOp(10);
std::cout << "응답을 받았습니다: " << response << std::endl;
// 연결을 종료하고 리소스를 해제합니다.
client.close();
}
catch (system::system_error& e) {
std::cout << "오류 발생! 오류 코드 = " << e.code()
<< ". 메시지: " << e.what();
return e.code().value();
}
return 0;
}
모두 기존에 I/O 연산에서 사용했던 메서드가 모여있는 것을 알 수 있다.
다만 SyncTCPClient라는 클래스르 별도로 생성하여 연결부터 연산 작업, 소켓 종료와 닫기 모두 진행된다.
- main함수에서는 ip주소와 포트번호를 알고 있다고 가정하고 있다.
- SyncTCPClient 클래스의 생성자에 ip주소와 포트 번호를 전달하여 인스턴스 client를 생성한다.
- client를 통해 SyncTCPClient 클래스의 connect 메서드를 호출하여 연결을 시도한다.
- 연결이 완료되었을 경우 client를 통해 SyncTCPClient 클래스의 emulateLongComputationOp 메서드를 호출한다.
- emulateLongComputationOp 메서드는 양수 정수를 매개변수로 받아 해당 초간 무의미한 연산 작업을 수행하는 버퍼를 초기화 한 뒤 sendRequest 함수에 매개변수로 전달하여 실행한다.
- sendRequest 함수는 write를 통해 버퍼로 전달받은 문자열을 소켓에 쓴다.
- 작업이 완료된 후엔 receiveResponse 함수의 결괏값을 리턴해 준다.
- receiveResponse 함수에서는 가변 버퍼 buf에 read_until 메서드를 통해 개행 문자를 만날 때 까지 읽기 연산을 한다.
- 이후 버퍼를 istream 타입의 객체로 변환하고, 빈 문자열 response를 초기화 해준다.
- getline 메서드를 통해 input에서 개행 문자를 구분 문자로 response에 데이터를 파싱해 준다.
- 이후 response를 리턴하면 쵲오적으로 emulateLongComputationOp 메서드의 반환값이 된다.
- 이를 main함수의 response에 값을 받아들인 뒤 클라이언트가 원하는 작업을 진행하면 된다.
- I/O 연산이 완료되면 client를 통해 SyncTCPClient 클래스의 close 메서드를 실행해 소켓을 종료하고 닫는다.
해당 동기 TCP 클라이언트 예제에서는 각 작업이 진행되는 모든 순간동안 스레드가 멈추게 된다.
728x90
반응형
'네트워크 통신 > Boost' 카테고리의 다른 글
Boost.asio 비동기 단일 스레드 TCP 클라이언트 (1) | 2024.11.18 |
---|---|
Boost.asio 동기 UDP 클라이언트 (0) | 2024.11.18 |
Boost.asio 클라이언트 개요 (1) | 2024.11.14 |
Boost.asio I/O 소켓 종료하기와 닫기 (1) | 2024.11.14 |
Boost.asio I/O 비동기 연산 취소하기 (0) | 2024.11.14 |