
개요
동기 UDP 클라이언트는 다음과 같은 사항을 만족시키는 분산 어플리케이션이다.
- 클라이언트 서버 통신 모델에서 클라이언트로 동작한다.
- UDP 프로토콜을 사용해 서버 프로그램과 통신한다.
I/O 및 제어 연산을 하는 동안에는 연산이 끝나거나 오류가 발생할 때까지 실행 스레드를 멈춘다.
일반적인 동기 UDP 클라이언트는 다음과 같은 알고리즘에 따라 동작한다.
- 클라이언트 프로토콜이 통신할 각 서버 프로그램의 IP 주소와 프로토콜 포트 번호를 알아낸다.
- UDP 소켓을 할당한다.
- 서버들과 메시지를 주고 받는다.
- 소켓을 할당 해지한다.
동기 UDP 클라이언트 구현
#include <boost/asio.hpp>
#include <iostream>
using namespace boost;
class SyncUDPClient {
public:
SyncUDPClient() :
m_sock(m_ios) {
m_sock.open(asio::ip::udp::v4());
}
std::string emulateLongComputationOp(
unsigned int duration_sec,
const std::string& raw_ip_address,
unsigned short port_num) {
std::string request = "EMULATE_LONG_COMP_OP "
+ std::to_string(duration_sec)
+ "\n";
asio::ip::udp::endpoint ep(
asio::ip::address::from_string(raw_ip_address),
port_num);
sendRequest(ep, request);
return receiveResponse(ep);
};
private:
void sendRequest(const asio::ip::udp::endpoint& ep,
const std::string& request) {
m_sock.send_to(asio::buffer(request), ep);
}
std::string receiveResponse(asio::ip::udp::endpoint& ep) {
char response[6];
std::size_t bytes_received =
m_sock.receive_from(asio::buffer(response), ep);
return std::string(response, bytes_received);
}
private:
asio::io_context m_ios; // 최신 버전에서는 io_context 사용
asio::ip::udp::socket m_sock;
};
int main()
{
const std::string server1_raw_ip_address = "127.0.0.1";
const unsigned short server1_port_num = 3333;
const std::string server2_raw_ip_address = "192.168.1.10";
const unsigned short server2_port_num = 3334;
try {
SyncUDPClient client;
std::cout << "Sending request to the server #1 ... "
<< std::endl;
std::string response =
client.emulateLongComputationOp(10,
server1_raw_ip_address, server1_port_num);
std::cout << "Response from the server #1 received: "
<< response << std::endl;
std::cout << "Sending request to the server #2... "
<< std::endl;
response =
client.emulateLongComputationOp(10,
server2_raw_ip_address, server2_port_num);
std::cout << "Response from the server #2 received: "
<< response << std::endl;
}
catch (system::system_error& e) {
std::cout << "Error occurred! Error code = " << e.code()
<< ". Message: " << e.what();
return e.code().value();
}
return 0;
}
- 우선 SyncUDPClient의 인스턴스로 생성할때 생성자의 인자로 아무것도 전달하지 않았다.
- 해당 클래스의 인스턴스를 생성할 때 클래스의 생성자는 io_context와 소켓을 초기화 한다.
- 또한 emulateLongComputationOp 메서드에 ip주소와 포트 번호를 인자로 받는다.
- 따라서 소켓 통신을 할 때 해당 메서드에 ip주소와 포트 번호를 전달하면 통신이 가능하다.
- UDP 통신에선 상대방과 연결이 잘 됐는지, 메시지를 잘 받았는지는 상관없이 자기가 원하는 통신만 진행한다.
- emulateLongComputationOp 메서드에서 요청을 보낼 내용을 문자열 변수 request에 저장한다.
- 매개변수로 입력 받았던 ip주소와 포트 번호를 from_string메서드를 통해 종료점을 생성해 준다.
- sendRequest 함수에 종료점과 버퍼를 매개변수로 전달한다.
- sendRequest 함수에선 send_to 메서드르르 통하여 request를 버퍼로 사용하여 종료점에 데이터를 쓴다.
- 해당 작업 이후 emulateLongComputationOp 메서드에서 receiveResponse 함수를 호출하고 해당 리턴값을 리턴한다.
- receiveResponse 함수는 종료점을 인자로 받으며, 해당 종료점으로 부터 receive_from 메서드를 통해 버퍼에 데이터를 읽는다, 또한 bytes_received 변수를 통해 읽은 바이트 수를 받고, 해당 크기만큼 문자열을 반환한다.
이 처럼 UDP 통신은 연결less 프로토콜로, 상태를 유지하지 않으며, 각 패킷은 독립적으로 처리한다.
TCP 통신과 달리 UDP 통신에서 응답을 받을 때 정적 배열을 사용하는 것에 대해 의문을 가질 수 있다.
이는 UDP 통신의 특성으로 서버에서 보내는 데이터의 크기나 구조가 미리 정의된 경우가 많기 때문이다.
서버에서 보내는 응답의 크기가 일정하거나 예상되는 최대 크기가 있을 때, 정적 크기의 버퍼를 사용하여 간단히 처리할 수 있다.
만약 서버의 응답 크기가 예측할 수 없거나 동적으로 변화하는 경우라면, 동적 크기 버퍼를 사용하는 것이 좋다.
예제에서 크기를 6바이트로 설정한 이유는 응답 데이터가 "OK\n", "ERROR\n" 둘 뿐이라 가정했기 때문이다.
728x90
'소켓 통신 > Boost' 카테고리의 다른 글
Boost.asio 비동기 멀티 스레드 TCP 클라이언트 (0) | 2024.11.18 |
---|---|
Boost.asio 비동기 단일 스레드 TCP 클라이언트 (1) | 2024.11.18 |
Boost.asio 동기 TCP 클라이언트 (0) | 2024.11.18 |
Boost.asio 클라이언트 개요 (1) | 2024.11.14 |
Boost.asio I/O 소켓 종료하기와 닫기 (1) | 2024.11.14 |