在 Windows 上使用 Boost.Asio 的半并发 ICMP ping

Posted

技术标签:

【中文标题】在 Windows 上使用 Boost.Asio 的半并发 ICMP ping【英文标题】:A semi-concurrent ICMP ping using Boost.Asio on Windows 【发布时间】:2016-05-03 12:18:55 【问题描述】:

我已经修改了示例http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/example/icmp/ping.cpp 关于如何定期 ping 主机以同时 ping 多台主机。 首先,创建所有主机的请求并将其发送到套接字。然后在第二阶段收集所有响应,直到计时器到期。

3 个客户端的修改示例:

// Headers from ping example:
// http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/example/icmp/
#include "icmp_header.hpp"
#include "ipv4_header.hpp"

#include <boost/asio.hpp>
#include <iostream>

using boost::asio::ip::icmp;
using boost::asio::deadline_timer;
using boost::asio::io_service;
using boost::asio::streambuf;
using boost::system::error_code;
using std::cout;
using std::endl;
namespace posix_time = boost::posix_time;

static const std::string BODY = "ping";
static const auto PROCESS = GetCurrentProcessId();

static int gSequence;
static io_service gService;
static icmp::socket gSocket(gService, icmp::v4());
static deadline_timer gTimer(gService);
static streambuf gReply;
static icmp::endpoint gReceiver;

void StartReceive()

    gSocket.async_receive_from(gReply.prepare(65536), gReceiver,
        [&](const error_code& error, size_t length)
    
        gReply.commit(length);

        ipv4_header ipv4Hdr;
        icmp_header icmpHdr;
        std::string body(BODY.size(), 0);

        std::istream is(&gReply);
        is >> ipv4Hdr >> icmpHdr;
        is.read(&body[0], BODY.size());

        auto ip = ipv4Hdr.source_address().to_string();
        auto rc = gReceiver.address().to_string();
        auto id = icmpHdr.identifier();
        auto process = PROCESS;
        auto sn = icmpHdr.sequence_number();
        auto type = icmpHdr.type();

        cout << "  Length              = " << length << endl;
        cout << "  Error               = " << error << endl;
        cout << "  IP checksum         = " << ipv4Hdr.header_checksum() << endl;
        cout << "  IP address          = " << ip << endl;
        cout << "  Receiver address    = " << rc << endl;
        cout << "  ICMP identification = " << id << endl;
        cout << "  ICMP type           = " << (int)type << endl;
        cout << "  Process             = " << process << endl;
        cout << "  Sequence            = " << sn << endl;

        if (is
            && icmpHdr.type() == icmp_header::echo_reply
            && icmpHdr.identifier() == PROCESS
            && icmpHdr.sequence_number() == gSequence
            && body == BODY)
        
            cout << "    > " << ip << endl;
        

        cout << endl;

        gReply.consume(length);

        StartReceive();
    );


int main()

    icmp::resolver resolver(gService);

    icmp_header echoRequest;
    echoRequest.type(icmp_header::echo_request);
    echoRequest.identifier(PROCESS);

    for (gSequence = 0; gSequence < 3; ++gSequence)
    
        cout << "----------------------------------------------------------" << endl;
        cout << "Iteration = " << gSequence << endl;
        cout << "----------------------------------------------------------" << endl;

        echoRequest.sequence_number(gSequence);
        compute_checksum(echoRequest, BODY.begin(), BODY.end());

        streambuf request;
        std::ostream os(&request);
        os << echoRequest << BODY;

        gService.reset();

        StartReceive();

        std::vector<std::string> pool
        
            "10.170.110.29",
            "10.170.97.39",
            "10.170.7.52"
        ;

        for (const auto & ip : pool)
        
            icmp::resolver::query query(icmp::v4(), ip, "");
            auto dest = *resolver.resolve(query);

            gSocket.send_to(request.data(), dest);
        

        gTimer.expires_from_now(posix_time::millisec(2000));
        gTimer.async_wait([&](const error_code& error)  gService.stop(); );

        gService.run();
        gReply.commit(gReply.size());
        gReply.consume(gReply.size());
    

    return 0;

第一次迭代 (0) 每次都按预期工作,尽管接收到的第一个数据包的长度始终为零。但是,在所有后续迭代中,来自一个或多个客户端的响应不会被传递,而是来自另一个客户端的响应被传递多次。使用 Wireshark,我可以看到示例中的所有主机都非常迅速地对请求发送了一个响应。

这是产生的输出之一:

----------------------------------------------------------
Iteration = 0
----------------------------------------------------------
  Length              = 0
  Error               = system:10022
  IP checksum         = 0
  IP address          = 0.0.0.0
  Receiver address    = 0.0.0.0
  ICMP identification = 0
  ICMP type           = 0
  Process             = 20464
  Sequence            = 0

  Length              = 32
  Error               = system:0
  IP checksum         = 595
  IP address          = 10.170.97.39
  Receiver address    = 10.170.97.39
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 0
    > 10.170.97.39

  Length              = 32
  Error               = system:0
  IP checksum         = 31034
  IP address          = 10.170.110.29
  Receiver address    = 10.170.110.29
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 0
    > 10.170.110.29

  Length              = 32
  Error               = system:0
  IP checksum         = 51432
  IP address          = 10.170.7.52
  Receiver address    = 10.170.7.52
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 0
    > 10.170.7.52

----------------------------------------------------------
Iteration = 1
----------------------------------------------------------
  Length              = 32
  Error               = system:0
  IP checksum         = 594
  IP address          = 10.170.97.39
  Receiver address    = 10.170.97.39
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 1
    > 10.170.97.39

  Length              = 32
  Error               = system:0
  IP checksum         = 51419
  IP address          = 10.170.7.52
  Receiver address    = 10.170.7.52
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 1
    > 10.170.7.52

  Length              = 32
  Error               = system:0
  IP checksum         = 51419
  IP address          = 10.170.7.52
  Receiver address    = 10.170.7.52
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 1
    > 10.170.7.52    

----------------------------------------------------------
Iteration = 2
----------------------------------------------------------
  Length              = 32
  Error               = system:0
  IP checksum         = 593
  IP address          = 10.170.97.39
  Receiver address    = 10.170.97.39
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 2
    > 10.170.97.39

  Length              = 32
  Error               = system:0
  IP checksum         = 51407
  IP address          = 10.170.7.52
  Receiver address    = 10.170.7.52
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 2
    > 10.170.7.52

  Length              = 32
  Error               = system:0
  IP checksum         = 51407
  IP address          = 10.170.7.52
  Receiver address    = 10.170.7.52
  ICMP identification = 20464
  ICMP type           = 0
  Process             = 20464
  Sequence            = 2
    > 10.170.7.52

这是 Boost.Asio 的正确用法和行为吗?

谢谢

【问题讨论】:

【参考方案1】:

看起来不错。它似乎对我有用。

注意事项:

在这里使用streambuf 似乎过于复杂——我想知道streambuf 的重用是否会导致重复发现相同的内容

如果其中一个池地址解析为本地 NIC 地址(因为您将收到自己的 ICMP 数据包),这些内容可能会变得混乱

除了第一个匹配项之外,您永远不会解析任何地址,并且根本不检查解析是否有效;此外,您每次都会解决(这可能是设计使然,但也可能是一个错误。DNS 请求也可能会干扰您的观察(尤其是如果您有本地 DNS 缓存/网关?)。

考虑使用boost::asio::async_resolve 并可能将其从循环中取出,以免干扰计时。

这是一个简化版:

// Headers from ping example:
// http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/example/icmp/
#include "icmp_header.hpp"
#include "ipv4_header.hpp"

#include <sys/types.h>
#include <unistd.h>
#include <boost/asio.hpp>
#include <iostream>
#include <sstream>

using boost::asio::ip::icmp;
using boost::asio::deadline_timer;
using boost::asio::io_service;
using boost::asio::streambuf;
using boost::system::error_code;
using std::cout;
using std::endl;
namespace posix_time = boost::posix_time;

static const std::string BODY = "ping";
static const auto PROCESS = getpid();

static int gSequence;
static io_service gService;
static icmp::socket gSocket(gService, icmp::v4());
static char gReply[65536];
static icmp::endpoint gReceiver;

void StartReceive() 
    gSocket.async_receive_from(boost::asio::buffer(gReply), gReceiver, [&](const error_code &error, size_t length) 

        ipv4_header ipv4Hdr;
        icmp_header icmpHdr;
        std::string body(BODY.size(), 0);

        std::istringstream is(std::string(gReply, length));
        is >> ipv4Hdr >> icmpHdr;
        is.read(&body[0], BODY.size());

        auto ip      = ipv4Hdr.source_address().to_string();
        auto rc      = gReceiver.address().to_string();
        auto id      = icmpHdr.identifier();
        auto process = PROCESS;
        auto sn      = icmpHdr.sequence_number();
        auto type    = icmpHdr.type();

        cout << " Length="              << length <<
                " Error="               << error <<
                " IP checksum="         << ipv4Hdr.header_checksum() <<
                " IP address="          << ip <<
                " Receiver address="    << rc <<
                " ICMP identification=" << id <<
                " ICMP type="           << (int)type <<
                " Process="             << process <<
                " Sequence="            << sn << "\n";

        if (is && icmpHdr.type() == icmp_header::echo_reply && icmpHdr.identifier() == PROCESS &&
            icmpHdr.sequence_number() == gSequence && body == BODY) 
            cout << "    > " << ip << endl;
        

        cout << endl;

        StartReceive();
    );


int main() 
    icmp::resolver resolver(gService);

    icmp_header echoRequest;
    echoRequest.type(icmp_header::echo_request);
    echoRequest.identifier(PROCESS);

    for (gSequence = 0; gSequence < 3; ++gSequence) 
        cout << "----------------------------------------------------------" << endl;
        cout << "Iteration=" << gSequence << endl;
        cout << "----------------------------------------------------------" << endl;

        echoRequest.sequence_number(gSequence);
        compute_checksum(echoRequest, BODY.begin(), BODY.end());

        streambuf request;
        std::ostream os(&request);
        os << echoRequest << BODY;

        gService.reset();

        StartReceive();

        for (std::string ip :  "www.msn.com", "www.google.com" ) 
            icmp::resolver::query query(icmp::v4(), ip, "");
            auto dest = *resolver.resolve(query);

            gSocket.send_to(request.data(), dest);
            std::cout << "Sent to " << dest.endpoint() << "\n";
        

        deadline_timer gTimer(gService);
        gTimer.expires_from_now(posix_time::millisec(2000));
        gTimer.async_wait([&](error_code)  gService.stop(); );

        gService.run();
    

打印,例如

----------------------------------------------------------
Iteration=0
----------------------------------------------------------
Sent to 204.79.197.203:0
Sent to 216.58.212.164:0
 Length=32 Error=system:0 IP checksum=49241 IP address=204.79.197.203 Receiver address=204.79.197.203 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=0
    > 204.79.197.203

 Length=32 Error=system:0 IP checksum=5449 IP address=216.58.212.164 Receiver address=216.58.212.164 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=0
    > 216.58.212.164

----------------------------------------------------------
Iteration=1
----------------------------------------------------------
Sent to 204.79.197.203:0
Sent to 216.58.212.164:0
 Length=32 Error=system:0 IP checksum=49240 IP address=204.79.197.203 Receiver address=204.79.197.203 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=1
    > 204.79.197.203

 Length=32 Error=system:0 IP checksum=5449 IP address=216.58.212.164 Receiver address=216.58.212.164 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=1
    > 216.58.212.164

----------------------------------------------------------
Iteration=2
----------------------------------------------------------
Sent to 204.79.197.203:0
Sent to 216.58.212.164:0
 Length=32 Error=system:0 IP checksum=49239 IP address=204.79.197.203 Receiver address=204.79.197.203 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=2
    > 204.79.197.203

 Length=32 Error=system:0 IP checksum=5449 IP address=216.58.212.164 Receiver address=216.58.212.164 ICMP identification=8041 ICMP type=0 Process=8041 Sequence=2
    > 216.58.212.164

【讨论】:

以上是关于在 Windows 上使用 Boost.Asio 的半并发 ICMP ping的主要内容,如果未能解决你的问题,请参考以下文章

Boost.Asio Win32 Windows 应用程序

Asio 或 Boost.Asio 能否在 iPhone 或 Android 上运行?

优雅地终止基于 Boost Asio 的 Windows 控制台应用程序

使用 boost::asio 在同一主机上多播消息

BOOST ASIO:为啥我在 Windows 中没有得到“绑定:地址已在使用”(但在 Linux 中得到它)?

Windows 7使用boost asio MinGW编译错误问题,怎么解决