【问题标题】:boost::asio::ip::tcp::socket - How to bind to a specific local portboost::asio::ip::tcp::socket - 如何绑定到特定的本地端口
【发布时间】:2017-11-30 19:03:42
【问题描述】:

我正在制作一个客户端套接字。

为了方便我的测试人员,我想指定套接字将使用的网卡和端口。

昨天,在我的谷歌搜索中,我发现:Binding boost asio to local tcp endpoint

通过执行 open、bind 和 async_connect,我能够绑定到特定的网卡,并开始在 Wireshark 中看到流量。

但是,Wireshark 报告套接字已被指定一个随机端口,而不是我指定的端口。我想如果端口正在使用中,它会填写传递给绑定方法的 error_code。

我做错了什么?

这是我的最小示例,从我的真实解决方案中提取和编辑。

// Boost Includes
#include <boost/asio.hpp>
#include <boost/atomic.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/thread/condition_variable.hpp>

// Standard Includes
#include <exception>
#include <memory>
#include <string>
#include <sstream>


boost::asio::io_service        g_ioService;                     /** ASIO sockets require an io_service to run on*/
boost::thread                  g_thread;                        /** thread that will run the io_service and hence where callbacks are called*/
boost::asio::ip::tcp::socket   g_socket(g_ioService);           /** Aync socket*/
boost::asio::ip::tcp::resolver g_resolver(g_ioService);         /** Resolves IP Addresses*/

//--------------------------------------------------------------------------------------------------
void OnConnect(const boost::system::error_code & errorCode, boost::asio::ip::tcp::resolver::iterator endpoint)
{
    if (errorCode || endpoint == boost::asio::ip::tcp::resolver::iterator())
    {
        // Error - An error occured while attempting to connect
        throw std::runtime_error("An error occured while attempting to connect");
    }

    // We connected to an endpoint

    /*
    // Start reading from the socket
    auto callback = boost::bind(OnReceive, boost::asio::placeholders::error);
    boost::asio::async_read_until(g_socket, m_receiveBuffer, '\n', callback);
    */
}


//--------------------------------------------------------------------------------------------------
void Connect()
{
    const std::string hostName = "10.84.0.36";
    const unsigned int port = 1007;

    // Resolve to translate the server machine name into a list of endpoints
    std::ostringstream converter;
    converter << port;
    const std::string portAsString = converter.str();

    boost::asio::ip::tcp::resolver::query query(hostName, portAsString);

    boost::system::error_code errorCode;
    boost::asio::ip::tcp::resolver::iterator itEnd;
    boost::asio::ip::tcp::resolver::iterator itEndpoint = g_resolver.resolve(query, errorCode);

    if (errorCode || itEndpoint == itEnd)
    {
        // Error - Could not resolve either machine
        throw std::runtime_error("Could not resolve either machine");
    }

    g_socket.open(boost::asio::ip::tcp::v4(), errorCode);
    if (errorCode)
    {
        // Could open the g_socket
        throw std::runtime_error("Could open the g_socket");
    }

    boost::asio::ip::tcp::endpoint localEndpoint(boost::asio::ip::address::from_string("10.86.0.18"), 6000);
    g_socket.bind(localEndpoint, errorCode);
    if (errorCode)
    {
        // Could bind the g_socket to local endpoint
        throw std::runtime_error("Could bind the socket to local endpoint");
    }

    // Attempt to asynchronously connect using each possible end point until we find one that works
    boost::asio::async_connect(g_socket, itEndpoint, boost::bind(OnConnect, boost::asio::placeholders::error, boost::asio::placeholders::iterator));
}

//--------------------------------------------------------------------------------------------------
void g_ioServiceg_threadProc()
{
    try
    {
        // Connect to the server
        Connect();

        // Run the asynchronous callbacks from the g_socket on this thread
        // Until the io_service is stopped from another thread
        g_ioService.run();
    }
    catch (...)
    {
        throw std::runtime_error("unhandled exception caught from io_service g_thread");
    }
}

//--------------------------------------------------------------------------------------------------
int main()
{
    // Start up the IO service thread
    g_thread.swap(boost::thread(g_ioServiceg_threadProc));

    // Hang out awhile
    boost::this_thread::sleep_for(boost::chrono::seconds(60));

    // Stop the io service and allow the g_thread to exit
    // This will cancel any outstanding work on the io_service
    g_ioService.stop();

    // Join our g_thread
    if (g_thread.joinable())
    {
        g_thread.join();
    }

    return true;
}

正如您在以下屏幕截图中看到的那样,选择了一个随机端口 32781,而不是我请求的端口 6000。

【问题讨论】:

    标签: c++ sockets boost tcp boost-asio


    【解决方案1】:

    6000 是远程端点端口,并且使用正确(否则,您将无法连接到服务器端)。

    发件人:https://idea.popcount.org/2014-04-03-bind-before-connect/

    TCP/IP 连接由四个元素元组标识:{源 IP、源端口、目标 IP、目标端口}。要建立 TCP/IP 连接,只需要目标 IP 和端口号,操作系统会自动选择源 IP 和端口。

    由于您没有绑定到本地端口,因此从“临时端口范围”中随机选择一个。到目前为止,这是通常的连接方式。

    不要害怕:

    在调用connect()之前调用bind()可以让内核选择特定的源IP和端口

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # Let the source address be 192.168.1.21:1234
    s.bind(("192.168.1.21", 1234))
    s.connect(("www.google.com", 80))
    

    示例是python。

    你这样做了,但仍然得到另一个端口。提示端口可能不可用。

    查看链接文章中SO_REUSEADDRSO_REUSEPORT的信息

    【讨论】:

    • 这是我在给出的代码示例中所做的,但它不起作用。在 async_connect 之前查看我对绑定的调用。远程端点端口是 1007,它在 Wireshark 中是这样说的。在调用解析器之前指定远程端点。 6000 是源端口,我像您一样通过绑定来指定它,但它似乎被忽略了,因此是问题所在。在绑定之前添加行 g_socket.set_option(boost::asio::ip::tcp::socket::reuse_address()); 无效,我得到相同的结果。我看不到在 boost::asio.. 中设置 reuse_port 的方法。
    【解决方案2】:

    我怀疑话题发起者仍然对这个问题感兴趣,但对于像我这样的所有未来寻求者来说,这里是解决方案。

    这里的问题是boost::asio::connect 在为提供的范围内的每个端点调用connect 之前关闭套接字:

    来自 boost/asio/impl/connect.hpp:

    template <typename Protocol BOOST_ASIO_SVC_TPARAM,
        typename Iterator, typename ConnectCondition> 
    Iterator connect(basic_socket<Protocol BOOST_ASIO_SVC_TARG>& s,
        Iterator begin, Iterator end, ConnectCondition connect_condition,
        boost::system::error_code& ec)
    { 
      ec = boost::system::error_code();
    
      for (Iterator iter = begin; iter != end; ++iter)
      {
        iter = (detail::call_connect_condition(connect_condition, ec, iter, end));
        if (iter != end)
        {
          s.close(ec); // <------
          s.connect(*iter, ec);
          if (!ec)
            return iter;
        }
    ...
    } 
    

    这就是绑定地址被重置的原因。为了保持绑定,可以直接使用socket.connect/async_connect(...)

    【讨论】:

    • 感谢您跟进此事,这解决了我的问题。从来没有预料到静态版本和成员版本之间会有这样的差异
    猜你喜欢
    • 1970-01-01
    • 2010-10-10
    • 2018-06-08
    • 1970-01-01
    • 2011-10-14
    • 1970-01-01
    • 1970-01-01
    • 2013-01-19
    • 2017-04-27
    相关资源
    最近更新 更多