简体   繁体   English

boost::asio::ip::tcp::socket - 如何绑定到特定的本地端口

[英]boost::asio::ip::tcp::socket - How to bind to a specific local port

I am making a client socket.我正在制作一个客户端套接字。

To make things easier for my testers, I'd like to specify the network card and port that the socket will use.为了让我的测试人员更轻松,我想指定套接字将使用的网卡和端口。

Yesterday, in my Google search, I found: Binding boost asio to local tcp endpoint昨天,在我的谷歌搜索中,我发现: Binding boost asio to local tcp endpoint

By performing the open, bind, and async_connect, I was able to bind to a specific network card and I started seeing traffic in Wireshark.通过执行 open、bind 和 async_connect,我能够绑定到特定的网卡,并且我开始在 Wireshark 中看到流量。

However, Wireshark reports that the socket has been given a random port rather than the one I specified.但是,Wireshark 报告套接字已被赋予一个随机端口,而不是我指定的端口。 I would think if the port was in use it would have filled out the error_code passed to the bind method.我想如果端口正在使用它会填写传递给绑定方法的 error_code 。

What am I doing wrong?我做错了什么?

Here is my minimal example, extracted and edited from my real solution.这是我的最小示例,从我的真实解决方案中提取和编辑。

// 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;
}

As you can see in the following screenshot, a random port 32781 was selected rather than my requested port 6000.正如您在以下屏幕截图中看到的,选择了一个随机端口 32781,而不是我请求的端口 6000。

Wireshark 屏幕截图

6000 is the remote endpoint port, and it is correctly used (otherwise, you wouldn't be connecting to the server side). 6000 是远程端点端口,它被正确使用(否则,您将无法连接到服务器端)。

From: https://idea.popcount.org/2014-04-03-bind-before-connect/来自: https : //idea.popcount.org/2014-04-03-bind-before-connect/

A TCP/IP connection is identified by a four element tuple: {source IP, source port, destination IP, destination port}. TCP/IP 连接由四元素元组标识:{源 IP、源端口、目标 IP、目标端口}。 To establish a TCP/IP connection only a destination IP and port number are needed, the operating system automatically selects source IP and port.要建立 TCP/IP 连接,只需要目标 IP 和端口号,操作系统会自动选择源 IP 和端口。

Since you do not bind to a local port, one is selected randomly from the "ephemeral port range".由于您没有绑定到本地端口,因此会从“临时端口范围”中随机选择一个。 This is, by far, the usual way to connect.到目前为止,这是通常的连接方式。

Fear not:不要害怕:

It is possible to ask the kernel to select a specific source IP and port by calling bind() before calling connect()可以通过在调用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))

The sample is python.样本是python。

You do that, but still get another port.你这样做了,但仍然得到另一个端口。 It's likely that the hint port is not available.提示端口可能不可用。

Check the information on SO_REUSEADDR and SO_REUSEPORT in the linked article查看链接文章中关于SO_REUSEADDRSO_REUSEPORT的信息

I doubt topic starter is still interested in this question, but for all of future seekers like myself, here is the solution.我怀疑话题启动者是否仍然对这个问题感兴趣,但对于像我这样的所有未来寻求者,这里是解决方案。

The issue here is that boost::asio::connect closes the socket before calling connect for every endpoint in the provided range:这里的问题是boost::asio::connect在为提供的范围内的每个端点调用connect之前关闭套接字:

From boost/asio/impl/connect.hpp:来自 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;
    }
...
} 

That is why bound address is reset.这就是绑定地址被重置的原因。 To keep it bound one can use socket.connect/async_connect(...) directly为了保持绑定,可以直接使用socket.connect/async_connect(...)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM