【问题标题】:simple networking tcp communication with a server using Boost.ASIO使用 Boost.ASIO 与服务器进行简单的网络 tcp 通信
【发布时间】:2016-05-22 08:31:58
【问题描述】:

我大约在 10 年前使用过 ASIO(我记得当时还有一个 boost.netwrok 库在很短的时间内),从那时起我一直在使用我自己的网络代码或其他一些实现或语言(例如ACE、nodejs 等)。现在我想使用 ASIO 编写简单的代码,但我无法用足够的语言来形容我有多讨厌它。

首先,我想要的是简单的同步代码,它只有几行 BSD/winsock 代码,而且使用 asio 完全是一团糟。不仅所有的代码看起来都像是满嘴乱七八糟的东西,掩盖了真实的逻辑,它还迫使我使用一些我真正想避免的东西,比如瘟疫。

所以,首先,我描述了我需要做的事情: 1) 连接到 mymagichost.com 上的服务器:1234 在 ASIO 中,这部分虽然看起来像是满口的命名空间谈话,但或多或​​少都可以:

#include <boost/asio.hpp>
// I'm scared what the code would look like without this one.
using boost::asio::ip::tcp; 
int main()
{
    boost::asio::io_service io_service;
    tcp::socket sock(io_service);
    tcp::resolver resolver(io_service);
    boost::asio::connect(sock, resolver.resolve(
        tcp::resolver::query(tcp::v4(), "mymagichost.com", "1234")));
}

我根本不明白我怎么可能无法将 1234 作为 int 传递,这是否意味着在这里传递一个字符串会将整个巨大的服务名称列表拉入我的二进制文件中,以便能够弄清楚我需要端口 1234?!什么鬼?

连接后,我需要与服务器进行简单的对话:

// when I start talking I say PING\n to the server
"PING\n"

// it replies back with 64-bit unsigned int id
"PONG <connection id>\n"

// I ask server to execute command, where <cmd id>
// is an int and connection id is that number that
// server sent to me in PONG.
"COMMAND <cmd id>:<connection id>\n"

// server executes my command
"OK <message id>\n<binary data until remote closes socket>"

使用阻塞 bsd 袜子就像 10-20 行代码:

void talk(int sock)  // consider it pseudo-code
{
    char buf[1024];
    int cmd_id = 1234;
    send(sock, "PING\n", 5, 0);
    unsigned long long connection_id, msg_id;
    recv(sock, buf, sizeof(buffer), 0);
    sscanf(buf, "PONG %llu\n", &connection_id);
    int n = sprintf(buf, "COMMAND %u:llu\n", cmd_id, connection_id);
    send(sock, buf, n, 0);
    vector<char> data;
    for (; (n = recv(sock, buf, sizeof(buf), 0)) > 0;)
       data.insert(data.end(), buf, buf+n);
    sscanf(buf, "OK %llu\n%n", &msg_id, &n);
    char *binary_data = buf.data()+n;
    int binary_data_size = data.size()-n;
}

那么,如何在同步 asio 中做到这一点? 在我的 BSD 代码中,我有很多缺点:在收到回复时,我可能收到了部分回复,例如我需要循环接收,直到收到尾随的 '\n' (这个简单的例子可能不会发生)。 在许多接收数据的函数中,有一个正是这样做的:

boost::asio::read_until(sock, buf, '\n');

但是,与 boost::asio::read 不同,read_until 只接受 streambuf(我真的想避免像瘟疫一样)。我只是不明白,为什么它不允许我使用自己的固定缓冲区,是因为 asio dev 决定阻止我的脚射击吗?我仍然可以尊重空指针是我想要的。无论如何,没什么大不了的,我可以使用 streambuf,但这里的事情变得非常丑陋。 根据文档,在服务器 OK 回复缓冲区中的 read_until last '\n' 实际上可能包含更多数据之后,here's what the docs say:“在成功的 read_until 操作之后,streambuf 可能包含超出分隔符的其他数据。应用程序通常会离开streambuf 中的数据供后续的 read_until 操作检查。"。 现在在处理了来自服务器的最后一个 OK 回复之后,我真的别无选择,只能继续使用可怕的 streambuf 来读入所有剩余的数据(它的兆),即使我更喜欢循环并附加到我的最终缓冲区(字符串或向量)。当我读完数据后,所有从流 buf 中获取数据的方法都非常糟糕:多次复制数据或使用 istream_iterator/istreambuf_iterator 将其附加到我的容器中。在我看来,这两个都非常慢,我的视觉工作室实际上挂了几秒钟(顺便说一下,我没有收到数据)。

那么,用 asio 处理它的正确方法是什么?

【问题讨论】:

  • 请教一个问题。咆哮只会让你看起来不耐烦,不值得费心去解释。 “其他”库可能是 cppnetlib 并且仍然存在。现在还有很多其他的。
  • @sehe 我知道它们存在,但我想完全使用 boost 编写一些测试项目,这就是重点。否则我永远不会碰 asio。

标签: c++ boost network-programming boost-asio


【解决方案1】:

首先,地址不能假定为数字,因为您要求解析它。如果解析器看到端口是数字 AFAICT,则说明解析器无法避免加载服务定义。

其次,ASIO 中有一些高级原语。你可以使用它:

void talk(std::iostream& sock)  // consider it pseudo-code
{
    int cmd_id = 1234;
    sock << "PING" << std::endl;

    uint64_t connection_id;

    if (sock >> connection_id) {
        sock << "COMMAND " << cmd_id << ":" << connection_id << std::endl;

        char buf[1024];
        std::vector<char> data;
        // read till eof
        while (sock.read(buf, sizeof(buf))
            data.insert(data.end(), buf, buf+sizeof(buf));
        data.insert(data.end(), buf, buf+sock.gcount());
    }

    uint64_t msg_id;
    int n;
    if (sock >> qi::phrase_match("OK" >> qi::auto_ >> '\n' >> qi::auto_, qi::blank, msg_id, n)) {
        // do something with n and msg_id
    }
}

int main() {
    boost::asio::ip::tcp::iostream s { "127.0.0.1", "1234" };
    talk(s);
}

Live On Coliru

如果您想在没有精神的情况下做事,您可能会使用 getline 和类似的 sscanf 装置来解决问题。

【讨论】:

  • 端口作为字符串传递,这意味着所有服务文本字符串都被编译进来(除非当然 asio 使用操作系统来解析服务名称,这不太可能是 IMO)。我还要求解析主机名,这与解析端口非常不同
  • 相反。 Asio 不太可能不使用操作系统来解析服务名称,因为这就是可移植 TCP 堆栈的操作方式。
  • 使用这个 iostream 接口似乎可以让它轻松工作,除了读取比空格分隔的命令复杂得多
  • 好吧。当你有时间的时候可能会检查现实:) 其他任何事情都不好:它可能被解释为硬编码服务名称的安全漏洞。
  • 鉴于您的问题,这真的值得所有这些讨论。请重新阅读我对您的问题的评论-仍然有效。如果你认为这个问题值得回答,没有什么能阻止你提出这个问题。可能Super UserServer Fault 已经有答案了。
猜你喜欢
  • 1970-01-01
  • 2013-01-28
  • 2018-10-09
  • 1970-01-01
  • 1970-01-01
  • 2020-01-02
  • 1970-01-01
  • 1970-01-01
  • 2021-12-29
相关资源
最近更新 更多