如果send()返回x个字节,recv()在一次调用中获得相同的字节数吗?

时间:2017-12-12 11:25:37

标签: c++ sockets tcp posix

我知道需要在循环中调用send(),直到发送了所需的字节数。也在接收方。

以下是我写的recv包装的一部分:

do{
            result = socket->receiveData(m_recvBuf + recv_len , BUF_LENGTH - recv_len);
            recv_len += result;
.....

我对一些事情感到有些困惑,所以这里有:

  1. 如果send()返回10个字节,那10个字节仍然只在发送方,但准备好发送。或者将字节物理地传送到接收器计算机?

  2. 如果上面的答案是肯定的,那么调用recv()是否总是返回那些已经到达的10个字节?

  3. 我也可以这样说;如果send每次返回10都被调用三次,那么发送的总字节数应该是30.然后调用recv(),一次,返回30个字节?

  4. 问题1.编辑为“仍然只在接收方”应该“仍然只在发送方”。

    方案: 我在pc1中的程序调用send(); send()返回1; 我的代码是一个字节已被发送到pc2中的接收器程序。 在send()函数返回1之后,网络电缆被狗吃掉了。

    如果在现实生活中如此,我肯定误解了TCP与UDP的好处。

    感谢所有人给予时间回答。

4 个答案:

答案 0 :(得分:1)

我会尝试:

  1. 你不知道。所有已知的是网络堆栈已接受数据,并将尽力转移它。
  2. N / A
  3. 不,没有这样的保证。 TCP / IP是面向流的,它是两个字节流,没有其他结构(消息,数据包,写入,帧,等等)。您必须确保数据序列化格式支持查找消息边界,以便接收方可以解释数据。

答案 1 :(得分:1)

  

如果send()返回x个字节,recv()在一次调用中获得相同的字节数吗?

一般来说,当然没有 !!

例如,对于通过wifi路由器和/或洲际路由器的TCP / IP套接字(请参阅tcp(7)& socket(7)),数据包可能会被分段和/或重新组合。因此,给定的send可以对应于多个recv,反之亦然,并且不尊重消息的“边界”。因此,对于应用程序,TCP是字节流,没有任何消息边界。另请阅读TCP内部使用的sliding window protocolTCP congestiion control

在实践中,您可能会观察到,例如在同一根以太网电缆上的两台计算机之间,这些数据包不会被分段或重新组装。但你应该代码与该假设。

具体而言,应将HTTPSMTPJSONRPCX11 protocols等应用程序级协议设计为定义消息边界,并且服务器端和客户端都应该这样做缓冲

您需要使用poll(2),请参阅this answer

  

如果send()返回10个字节,那10个字节仍然只在接收端,但准备好发送。

定义“仍处于接收方”的真正含义并不容易(因为您并不真正关心内核,网络控制器内部或洲际电缆上发生的情况)。因此,上述句子毫无意义。

您的应用代码应该只关注system calls(在syscalls(2) ...中列出),例如poll(2)send(2)和相关的,write(2),{{ 3}}和相关,recv(2)read(2)socket(2)accept(2)connect(2)等......

您可能希望使用bind(2)等邮件库。

  

我的send()函数返回1后,网络线被狗吃掉了。

为什么你对这种情况非常关心。你的狗也可以把你的笔记本电脑扯下来,或者对它进行撒尿。一旦send告诉你的应用程序已经发出十个字节,你应该相信你的内核。但是接收程序可能还没有获得这些字节(在另一个大陆上,你需要等待几十毫秒,这对计算机来说是一个相当大的延迟)。很可能,当您的狗咬过以太网电缆时,十个字节位于海洋中间(您可以合理代码,就像它们已被发射一样)。 TCP协议将检测到链接已被中断,但该错误将在稍后的时间内提供给您的程序(可能是因为下一次调用send之后10秒钟发生的错误)

(TCP定义中存在一些大的宏观延迟,可能长达128秒 - 我忘记了细节 - 这些延迟对于行星际通信而言太小;因此TCP无法用于火星)功能

你应该(大部分时间)只是在系统调用级别上推理。

(当然,在某些情况下 - 远程神经外科机器人的想法 - 可能还不够)

  

我肯定误解了TCP与UDP的好处。

如果您刚刚使用UDP,则某个数据包可能会被分段,丢失或接收几次次。使用TCP,这是不可能合理发生的(至少在下一个数据包已成功发送和接收时)。

答案 2 :(得分:0)

我的座右铭:"如果有疑问,请尝试一下"。

这是一个完整的程序,它演示了在我的机器上,一百万字节的整个数据包甚至没有通过环回适配器而不被缓冲到单独的读取(并且可能写入,因为我使用了复合函数{ {1}}:

asio::write()

样本结果:

#include <thread>
#include <mutex>
#include <condition_variable>
#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <vector>
#include <iostream>

namespace asio
{
    using namespace boost::asio;
    using boost::system::error_code;
}

using protocol = asio::ip::tcp;

struct event
{
    std::mutex mutex;
    std::condition_variable cv;
    bool notified = false;

    void notify()
    {
        auto lock = std::unique_lock<std::mutex>(mutex);
        notified = true;
        lock.unlock();
        cv.notify_all();
    }

    void wait()
    {
        auto lock = std::unique_lock<std::mutex>(mutex);
        cv.wait(lock, [this] { return this->notified; });
    }
};

struct emitter
{
    emitter(std::ostream& os) : os(os) {}

    template<class...Ts>
    void operator()(Ts&&...ts)
    {
        auto lock = std::unique_lock<std::mutex>(m);
        auto emit_item = [&os = this->os](auto&& x)
        {
            os << x;
        };
        using expand = int[];
        void(expand { 0,
            (emit_item(ts),0)...
        });
        os << std::endl;
    }

    std::ostream& os;
    std::mutex m;
};

event rx_ready;
emitter stdout_emit { std::cout };

void sender()
{
    asio::io_service executor;

    auto big_buffer = std::vector<char>(1000000, 'a');
    protocol::socket sock(executor);
    rx_ready.wait();
    asio::error_code ec;
    if(sock.connect(protocol::endpoint(asio::ip::address_v4(0x7f000001), 12345)), ec) {
        stdout_emit("connect failure: ", ec.message());
        return;
    }
    auto written = asio::write(sock, asio::buffer(big_buffer), ec);
    stdout_emit("wrote: ", written);
    if (ec) {
        stdout_emit("write failure: ", ec.message());
    }
    sock.shutdown(protocol::socket::shutdown_send, ec);
    if (ec) {
        stdout_emit("shutdown failure: ", ec.message());
    }
    sock.close(ec);
    if (ec) {
        stdout_emit("close failure: ", ec.message());
    }
}

void start_receiving(protocol::socket& s)
{
    auto huge_buffer_ptr = std::make_shared<std::vector<char>>(1000000);

    s.async_read_some(asio::buffer(*huge_buffer_ptr), [huge_buffer_ptr, &s](asio::error_code ec, std::size_t size)
    {
        stdout_emit("read ", size, " bytes");
        if (ec)
        {
            stdout_emit("read error: ", ec.message());
        }
        else
        {
            start_receiving(s);
        }
    });
}

void receiver()
{
    asio::io_service executor;
    protocol::acceptor acceptor(executor);
    auto ep = protocol::endpoint(protocol::v4(), 12345);
    acceptor.open(ep.protocol());
    acceptor.bind(ep);
    acceptor.listen();

    protocol::socket s(executor);
    acceptor.async_accept(s, [&](asio::error_code ec){
        if (ec) {
            stdout_emit("accept: ", ec.message());
        }
        else
        {
            start_receiving(s);
        }
    });
    rx_ready.notify();
    executor.run();
}

int main()
{
    auto t = std::thread(receiver);

    sender();

    t.join();
}

将读写缓冲区更改为10,000,000字节给了我这个:

read 393216 bytes
wrote: 1000000
read 606784 bytes
read 0 bytes
read error: End of file

Process finished with exit code 0

答案 3 :(得分:0)

  

如果send()返回10个字节,则这10个字节仍然只在   发送方,但准备发送。或者物理上有字节   到达接收器计算机?

你无法确定10个字节的确切位置,其中一些可以在发送机器的某个地方等待,有些通过某些线路,有些在接收机器的某处等待。

  

如果上面的答案是肯定的,那么总是调用recv()   返回那些已经到达的10个字节?

N / A

  

我也可以这样说;如果发送已被调用三次   时间返回10,所以发送的总字节数据为30.然后   调用recv(),一次,返回30个字节?

你说不出来!在TCP模式下,唯一可以说的是,字节的接收顺序与它们发送的顺序相同。

  

场景:我在pc1中的程序调用send(); send()返回1;我的代码   将一个字节发送到pc2中的接收器程序的事情。该   在send()函数之后,网络线被狗吃掉了   返回1.

然后你不能说什么......

如果在现实生活中如此,我肯定误解了TCP与UDP的好处。

UDP是面向数据报的语义,如邮政系统(没有保留的顺序,没有任何保证,可能丢失,重复等)

TCP是面向流的语义,如电话系统(保留顺序且无损失)。

当然,在硬网络出现故障的情况下,TCP无法确保任何事情!

由于TCP构建在IP(数据报)之上,因此通过TCP发送的内容将被分段以通过IP发送,并且您无法控制此类碎片(我忘记了解缓存等)。