UDP 客户端和服务器设计使用 boost::asio

UDP client and server design using boost::asio

本文关键字:boost asio 客户端 服务器 UDP      更新时间:2023-10-16

我是C++提升库的新手。我已经设法使用boost asio库实现了UDP服务器和客户端。目前在我的示例程序中,我启动UDP服务器,然后尝试使用UDP客户端进行连接。一旦客户端连接并发送一些数据,服务器就会用一个随机生成的字符串进行响应,我已将其转换为十六进制并将其打印出来。一旦收到字符串,UDP 客户端就会调用析构函数并退出。

我的代码在下面给出 udp_server.hpp 和 udp_client.hpp

#include "udp_server.hpp"
#include <iostream>
#include <exception>
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <algorithm>
#include <sstream>
#include <iomanip>
const int ARG_COUNT = 2;
const int LOWEST_PORT = 1024;
const int HIGHEST_PORT = 65000;
static char message_array[8192];
void gen_random_string(char *s, const int len) 
{
    static const char alphanum[] =
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";
    for (int i = 0; i < len; ++i) {
        s[i] = alphanum[rand() % (sizeof(alphanum) - 1)];
    }
    s[len] = 0;
}

class udp_server
{
public:
    udp_server(boost::asio::io_service& io_service,int port_number)
        : socket_(io_service, boost::asio::ip::udp::udp::endpoint(boost::asio::ip::udp::udp::v4(), port_number))
    {
        std::cout << "UDP server listening on " << port_number << std::endl;
        start_receive();
    }
private:
    void start_receive()
    {
        socket_.async_receive_from(
            boost::asio::buffer(recv_buffer_), remote_endpoint_,
            boost::bind(&udp_server::handle_receive, this,
                        boost::asio::placeholders::error,
                        boost::asio::placeholders::bytes_transferred));
    }
    void handle_receive(const boost::system::error_code& error,
                        std::size_t /*bytes_transferred*/)
    {
        if (!error || error == boost::asio::error::message_size)
        {
            gen_random_string(message_array, 8192);
            boost::shared_ptr<std::string> message(new std::string(message_array));
            socket_.async_send_to(boost::asio::buffer(*message), remote_endpoint_,
                                  boost::bind(&udp_server::handle_send, this, message,
                                              boost::asio::placeholders::error,
                                              boost::asio::placeholders::bytes_transferred));
            start_receive();
        }
    }
    void handle_send(boost::shared_ptr<std::string> /*message*/,
                     const boost::system::error_code& /*error*/,
                     std::size_t /*bytes_transferred*/)
    {
    }
    boost::asio::ip::udp::udp::socket socket_;
    boost::asio::ip::udp::udp::endpoint remote_endpoint_;
    boost::array<char, 1> recv_buffer_;
};

void runUDPServer( CmdLineOpts input )
{
    try
    {
        boost::asio::io_service io_service;
        udp_server server(io_service,input.port);
        io_service.run();
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}

class udp_client
{
public:
    udp_client(
        boost::asio::io_service& io_service,
        const std::string& host,
        const std::string& port
    ) : io_service_(io_service), socket_(io_service, boost::asio::ip::udp::udp::endpoint(boost::asio::ip::udp::udp::v4(), 0)) {
        boost::asio::ip::udp::udp::resolver resolver(io_service_);
        boost::asio::ip::udp::udp::resolver::query query(boost::asio::ip::udp::udp::v4(), host, port);
        boost::asio::ip::udp::udp::resolver::iterator iter = resolver.resolve(query);
        endpoint_ = *iter;
    }
    ~udp_client()
    {
        std::cout << "Calling UDP client destructor" << std::endl;
        socket_.close();
    }
    void send() {
        socket_.send_to(boost::asio::buffer(send_buf), endpoint_);
    }
    void recieve_from() {
        /*Initialize our endpoint*/
        boost::array<unsigned char, 8192> temp; 
       // boost::asio::buffer boost_buf(temp);
        size_t len = socket_.receive_from(
                         boost::asio::buffer(temp), sender_endpoint);
        std::ostringstream ss;
        ss << std::hex << std::uppercase << std::setfill( '0' );
        std::for_each( temp.cbegin(), temp.cend(), [&]( int c ) { ss << std::setw( 2 ) << c; } );
        std::string result = ss.str();
        std::cout << "Length of recieved message " << len << std::endl;
        std::cout << result << std::endl;
    }
private:
    boost::asio::io_service& io_service_;
    boost::asio::ip::udp::udp::socket socket_;
    boost::asio::ip::udp::udp::endpoint endpoint_;
    //boost::array<char, 2048> recv_buf;
    std::vector<unsigned char> recv_buf;
    boost::array<char, 1> send_buf  = {{ 0 }};
    boost::asio::ip::udp::endpoint sender_endpoint;
};
void runUDPClient(std::string portStr)
{
    try
    {
        boost::asio::io_service io_service;
        udp_client client(io_service, "localhost", portStr);
        client.send();
        client.recieve_from();
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}
void runClient( CmdLineOpts input )
{
    runUDPClient(input.portStr);
}
void runServer( CmdLineOpts input )
{
    runUDPServer(input);
}
/**
* Usage: client_server <protocol> <port> <num of packets>
*/
bool clarg_parse ( int argc, char *argv[], CmdLineOpts *input )
{
    bool result = true;
    if (argc - 1 == ARG_COUNT)
    {
        // arg 1: server or client
        int arg1 = std::stoi(argv[1]);
        if (arg1 == 0 || arg1 == 1)
        {
            input->servOrClient = arg1;
        }
        else
        {
            std::cout << "Invalid client server choice.nUsage: client_server <client (0) or server(1)> <port>" << std::endl;
            result = false;
        }
        // arg 2: port
        int arg2 = std::stoi(argv[3]);
        if (arg2 > LOWEST_PORT && arg2 < HIGHEST_PORT )
        {
            input->port = arg2;
            input->portStr = argv[3];
        }
        else
        {
            std::cout << "Invalid port, must be between " << LOWEST_PORT << " and " << HIGHEST_PORT << std::endl;
            std::cout << "Usage: client_server <client (0) or server(1)> <port>" << std::endl;
            result = false;
        }
    }
    else
    {
        std::cout << "Usage: client_server <client (0) or server(1)> <port>" << std::endl;
        result = false;
    }
    return result;
}

int main ( int argc, char *argv[] )
{
    CmdLineOpts input;
    if (clarg_parse(argc, argv, &input))
    {
        if(input.servOrClient == 1)
        {
            runServer(input);
        }
        else if(input.servOrClient == 0)
        {
            runClient(input);
        }
    }
    else
    {
        return 1;
    }
    return 0;
}

头文件 udp_server.hpp

#ifndef UDP_SERVER_H_INCLUDED 
#define UDP_SERVER_H_INCLUDED
#include <string>
struct CmdLineOpts
{
    std::string portStr;
    int port;
    int servOrClient;
};
void runUDPServer ( CmdLineOpts input );
bool clarg_parse ( int argc, char *argv[], CmdLineOpts input );
#endif

制作文件编译上述程序

TARGET = udp_server
LIBS = -lboost_system -lpthread
CXX = g++
CXXFLAGS = -std=c++11 -g -Wall -pedantic
.PHONY: default all clean
default: $(TARGET)
all: default
OBJECTS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
HEADERS = $(wildcard *.hpp)
%.o: %.cpp $(HEADERS)
    $(CXX) $(CXXFLAGS) -c $< -o $@
.PRECIOUS: $(TARGET) $(OBJECTS)
$(TARGET): $(OBJECTS)
    $(CXX) $(OBJECTS) $(LIBS) -o $@
clean:
        -rm -f *.o
        -rm -f $(TARGET)

我的问题如下。

1)设计是否适合发送网络数据包。我担心的是,当客户端向服务器发送一些数据时,数据包似乎被发送(即服务器响应)。换句话说,客户端需要定期轮询服务器以查询数据。是否有其他模型,服务器通知客户端数据可用?这会是一个更好的设计吗?

2)在示例中,我为客户端和服务器分配了一个8192字节的数组。这是必需的吗?据我了解,UDP(以及TCP的MTU是1500字节)。是否有任何理由在客户端和服务器上分配超过 1500 字节的数组。?

如果有人能回答上述问题,那就太好了。

在为应用程序协议设计 I/O 层时,请考虑预期的网络拓扑、应用程序协议要求、任何服务级别协议、硬件要求等。 围绕满足这些约束进行设计,而不是花太多精力优化小效率。 在不知道更多细节的情况下,回答什么设计是好的或其他设计是否更好将是主观的。 不过:

  • 如果让服务器响应客户端的定期请求满足应用程序协议的要求,那么它可能是一个很好的设计。
  • 如果需要减少通知客户端的延迟,那么让服务器在数据可用时启动发送可能是一个很好的设计。 请注意,由于 NAT 遍历,网络拓扑可能会影响这一点。

对于给定层,最大传输单元 (MTU) 定义了可以传递到下一层的协议单元的最大大小。 较高级别的层和协议可能会引入分段处理,从而允许给定层的协议单元的最大大小超过较低层的 MTU。 例如,虽然以太网的 MTU 为 1500 字节,但 UDP 数据报(第 4 层:传输)有效负载的最大大小为 65507 字节。 这是可能的,因为 IP 层数据包(第 3 层:网络)可能由一个或多个以太网(第 2 层:数据链路)构造。

应使用的缓冲区大小通常取决于应用程序协议。 例如,Asio 聊天示例使用 516 字节的缓冲区,因为应用程序协议的最大长度为 516 字节。 下层协议单元的分段和重组对应用程序是透明的。 但是,由于UDP既不提供确认也不提供重传,并且丢失部分数据报将导致丢弃整个数据报,因此较大的数据报由于更大的碎片而丢失的可能性更大。