Пытался придумать тестовую программу:
Coliru
#include <boost/asio.hpp>
#include <iostream>
using namespace boost::asio;
using ip::tcp;
using boost::system::error_code;
using namespace std::chrono_literals;
auto now = &std::chrono::high_resolution_clock::now;
auto sleep_for = [](auto dur) { std::this_thread::sleep_for(dur); };
auto timestamp = [start = now()] { return (now() - start)/1.0ms; };
int main() {
static constexpr size_t msglen = 16 << 20; // 16 mib
thread_pool io(1);
tcp::acceptor a(io, {{}, 7878});
a.set_option(tcp::acceptor::reuse_address(true));
a.listen();
#define CHECKED_OPTION(s, name, requested) do { \
tcp::socket::name option(requested); \
/*s.set_option(option);*/ \
s.get_option(option); \
std::cout << " " << #name << ":" << option.value(); \
} while (0)
a.async_accept([=](error_code ec, tcp::socket&& con) {
std::cout << timestamp() << "ms accept " << ec.message();
std::cout << " " << con.remote_endpoint(ec);
con.set_option(tcp::no_delay(true));
CHECKED_OPTION(con, receive_buffer_size, 100);
CHECKED_OPTION(con, send_buffer_size, 100);
std::cout << std::endl;
if (!ec) {
sleep_for(1s);
std::cout << timestamp() << "ms start write" << std::endl;
auto xfr = con.write_some(buffer(std::string(msglen, '*')), ec);
std::cout << timestamp() << "ms write completed: " << xfr << "/" << msglen << " (" << ec.message() << ")" << std::endl;
}
});
{
tcp::socket s(io);
sleep_for(1s);
std::cout << timestamp() << "ms connecting" << std::endl;
s.connect({{}, 7878});
std::cout << timestamp() << "ms connected";
CHECKED_OPTION(s, receive_buffer_size, 100);
CHECKED_OPTION(s, send_buffer_size, 100);
std::cout << std::endl;
sleep_for(3s);
std::cout << timestamp() << "ms disconnecting" << std::endl;
}
std::cout << timestamp() << "ms disconnected" << std::endl;
a.cancel();
io.join();
}
Обратите внимание, как мы стараемся отправлять больше данных, чем читается с большим запасом, чтобы заполнить любую задействованную буферизацию. (На самом деле мы вообще не читаем данные из клиентского сокета)
Он печатает (на Coliru):
1000.48ms connecting
1001.07ms connected receive_buffer_size:530904 send_buffer_size:1313280
1001.23ms accept Success 127.0.0.1:41614 receive_buffer_size:531000 send_buffer_size:1313280
2001.64ms start write
4001.37ms disconnecting
4001.62ms disconnected
4013.33ms write completed: 4481610/16777216 (Success)
Понятно, что
- write_some завершается только тогда, когда пакеты подтверждены ACK и возвращено фактическое количество переданных байтов.
- пакеты ACK ядра независимо от уровня приложения
Фактически пакеты могут поступать не по порядку, и в этом случае ядро подтверждает их индивидуально перед секвенированием, чтобы приложение считало данные через API сокета.
Буферизация
Буферизация неизбежна , но можно настраивать в определенных пределах. Например, раскомментирование этой строки из макроса CHECKED_OPTION:
s.set_option(option); \
дает другой вывод ( Live On Coliru ):
1000.44ms connecting
1001.08ms connected receive_buffer_size:1152 send_buffer_size:2304
1001.31ms accept Success 127.0.0.1:41618 receive_buffer_size:1152 send_buffer_size:2304
2001.88ms start write
4001.42ms disconnecting
4001.61ms disconnected
4008.21ms write completed: 43776/16777216 (Success)