Commit d5d8534062b76364f621ef49f790450997d7e6f4
1 parent
15005b6b
fix(packet): use a single buffer for a packet
Before this commit, we had one small buffer telling the packet type and length, and another buffer with the payload. They were send to the kernel one by one. For small packets, this is a problem, as NODELAY causes the first buffer to be send on the IP stack, and the payload after. This increases the IP overhead for no good reason. Now instead, already reserve room in the packet to write the header, and send it as one single unit to the kernel.
Showing
1 changed file
with
33 additions
and
26 deletions
src/Packet.cpp
| ... | ... | @@ -38,6 +38,12 @@ public: |
| 38 | 38 | : m_packet_type(packet_type), |
| 39 | 39 | m_flags(flags) |
| 40 | 40 | { |
| 41 | + // Reserve space for the header. | |
| 42 | + m_buffer.push_back(0); // Packet type and flags. | |
| 43 | + m_buffer.push_back(0); // Remaining length (at most 4 bytes). | |
| 44 | + m_buffer.push_back(0); | |
| 45 | + m_buffer.push_back(0); | |
| 46 | + m_buffer.push_back(0); | |
| 41 | 47 | } |
| 42 | 48 | |
| 43 | 49 | Packet(PacketType packet_type, uint8_t flags, std::vector<uint8_t> data) |
| ... | ... | @@ -344,13 +350,17 @@ bool TrueMQTT::Client::Impl::Connection::send(Packet &packet) const |
| 344 | 350 | |
| 345 | 351 | LOG_TRACE(&m_impl, "Sending packet of type " + std::string(magic_enum::enum_name(packet.m_packet_type)) + " with flags " + std::to_string(packet.m_flags) + " and length " + std::to_string(packet.m_buffer.size())); |
| 346 | 352 | |
| 347 | - std::vector<uint8_t> buffer; | |
| 353 | + // Calculate where in the header we need to start writing, to create | |
| 354 | + // a contiguous buffer. The buffer size is including the header, but | |
| 355 | + // the length should be without. Hence the minus five. | |
| 356 | + size_t length = packet.m_buffer.size() - 5; | |
| 357 | + size_t offset = length <= 127 ? 3 : (length <= 16383 ? 2 : (length <= 2097151 ? 1 : 0)); | |
| 358 | + size_t bufferOffset = offset; | |
| 348 | 359 | |
| 349 | - // Create the header. | |
| 350 | - buffer.push_back((static_cast<uint8_t>(packet.m_packet_type) << 4) | packet.m_flags); | |
| 360 | + // Set the header. | |
| 361 | + packet.m_buffer[offset++] = (static_cast<uint8_t>(packet.m_packet_type) << 4) | packet.m_flags; | |
| 351 | 362 | |
| 352 | - // Calculate the length buffer. | |
| 353 | - size_t length = packet.m_buffer.size(); | |
| 363 | + // Set the remaining length. | |
| 354 | 364 | do |
| 355 | 365 | { |
| 356 | 366 | uint8_t byte = length & 0x7F; |
| ... | ... | @@ -359,43 +369,40 @@ bool TrueMQTT::Client::Impl::Connection::send(Packet &packet) const |
| 359 | 369 | { |
| 360 | 370 | byte |= 0x80; |
| 361 | 371 | } |
| 362 | - buffer.push_back(byte); | |
| 372 | + packet.m_buffer[offset++] = byte; | |
| 363 | 373 | } while (length > 0); |
| 364 | 374 | |
| 365 | - // Write header and packet. | |
| 366 | - if (::send(m_socket, (char *)buffer.data(), buffer.size(), MSG_NOSIGNAL) < 0) | |
| 375 | + ssize_t res = ::send(m_socket, (char *)packet.m_buffer.data() + bufferOffset, packet.m_buffer.size() - bufferOffset, MSG_NOSIGNAL); | |
| 376 | + // If the first packet is rejected in full, return this to the caller. | |
| 377 | + if (res < 0) | |
| 367 | 378 | { |
| 368 | 379 | if (errno == EAGAIN) |
| 369 | 380 | { |
| 370 | 381 | // sndbuf is full, so we hand it back to the sender to deal with this. |
| 371 | 382 | return false; |
| 372 | 383 | } |
| 373 | - if (errno == ECONNRESET || errno == EPIPE) | |
| 374 | - { | |
| 375 | - LOG_ERROR(&m_impl, "Connection closed by broker"); | |
| 376 | - m_impl.m_connection->socketError(); | |
| 377 | - return false; | |
| 378 | - } | |
| 379 | 384 | |
| 380 | - LOG_WARNING(&m_impl, "Connection write error: " + std::string(strerror(errno))); | |
| 385 | + LOG_ERROR(&m_impl, "Connection write error: " + std::string(strerror(errno))); | |
| 386 | + m_impl.m_connection->socketError(); | |
| 381 | 387 | return false; |
| 382 | 388 | } |
| 383 | - if (::send(m_socket, (char *)packet.m_buffer.data(), packet.m_buffer.size(), MSG_NOSIGNAL) < 0) | |
| 389 | + // If we still have data to send for this packet, keep trying to send the data till we succeed. | |
| 390 | + bufferOffset += res; | |
| 391 | + while (bufferOffset < packet.m_buffer.size()) | |
| 384 | 392 | { |
| 385 | - if (errno == EAGAIN) | |
| 393 | + res = ::send(m_socket, (char *)packet.m_buffer.data() + bufferOffset, packet.m_buffer.size() - bufferOffset, MSG_NOSIGNAL); | |
| 394 | + if (res < 0) | |
| 386 | 395 | { |
| 387 | - // sndbuf is full, so we hand it back to the sender to deal with this. | |
| 388 | - return false; | |
| 389 | - } | |
| 390 | - if (errno == ECONNRESET || errno == EPIPE) | |
| 391 | - { | |
| 392 | - LOG_ERROR(&m_impl, "Connection closed by broker"); | |
| 396 | + if (errno == EAGAIN) | |
| 397 | + { | |
| 398 | + continue; | |
| 399 | + } | |
| 400 | + | |
| 401 | + LOG_ERROR(&m_impl, "Connection write error: " + std::string(strerror(errno))); | |
| 393 | 402 | m_impl.m_connection->socketError(); |
| 394 | 403 | return false; |
| 395 | 404 | } |
| 396 | - | |
| 397 | - LOG_WARNING(&m_impl, "Connection write error: " + std::string(strerror(errno))); | |
| 398 | - return false; | |
| 405 | + bufferOffset += res; | |
| 399 | 406 | } |
| 400 | 407 | |
| 401 | 408 | return true; | ... | ... |