158 lines
4.8 KiB
C++
158 lines
4.8 KiB
C++
|
// Copyright (c) 2017 Computer Vision Center (CVC) at the Universitat Autonoma
|
||
|
// de Barcelona (UAB).
|
||
|
//
|
||
|
// This work is licensed under the terms of the MIT license.
|
||
|
// For a copy, see <https://opensource.org/licenses/MIT>.
|
||
|
|
||
|
#include "carla/streaming/detail/tcp/ServerSession.h"
|
||
|
#include "carla/streaming/detail/tcp/Server.h"
|
||
|
|
||
|
#include "carla/Debug.h"
|
||
|
#include "carla/Logging.h"
|
||
|
|
||
|
#include <boost/asio/read.hpp>
|
||
|
#include <boost/asio/write.hpp>
|
||
|
#include <boost/asio/bind_executor.hpp>
|
||
|
#include <boost/asio/post.hpp>
|
||
|
|
||
|
#include <atomic>
|
||
|
#include <thread>
|
||
|
|
||
|
namespace carla {
|
||
|
namespace streaming {
|
||
|
namespace detail {
|
||
|
namespace tcp {
|
||
|
|
||
|
static std::atomic_size_t SESSION_COUNTER{0u};
|
||
|
|
||
|
ServerSession::ServerSession(
|
||
|
boost::asio::io_context &io_context,
|
||
|
const time_duration timeout,
|
||
|
Server &server)
|
||
|
: LIBCARLA_INITIALIZE_LIFETIME_PROFILER(
|
||
|
std::string("tcp server session ") + std::to_string(SESSION_COUNTER)),
|
||
|
_server(server),
|
||
|
_session_id(SESSION_COUNTER++),
|
||
|
_socket(io_context),
|
||
|
_timeout(timeout),
|
||
|
_deadline(io_context),
|
||
|
_strand(io_context) {}
|
||
|
|
||
|
void ServerSession::Open(
|
||
|
callback_function_type on_opened,
|
||
|
callback_function_type on_closed) {
|
||
|
DEBUG_ASSERT(on_opened && on_closed);
|
||
|
_on_closed = std::move(on_closed);
|
||
|
|
||
|
// This forces not using Nagle's algorithm.
|
||
|
// Improves the sync mode velocity on Linux by a factor of ~3.
|
||
|
const boost::asio::ip::tcp::no_delay option(true);
|
||
|
_socket.set_option(option);
|
||
|
|
||
|
StartTimer();
|
||
|
auto self = shared_from_this(); // To keep myself alive.
|
||
|
boost::asio::post(_strand, [=]() {
|
||
|
|
||
|
auto handle_query = [this, self, callback=std::move(on_opened)](
|
||
|
const boost::system::error_code &ec,
|
||
|
size_t DEBUG_ONLY(bytes_received)) {
|
||
|
if (!ec) {
|
||
|
DEBUG_ASSERT_EQ(bytes_received, sizeof(_stream_id));
|
||
|
log_debug("session", _session_id, "for stream", _stream_id, " started");
|
||
|
boost::asio::post(_strand.context(), [=]() { callback(self); });
|
||
|
} else {
|
||
|
log_error("session", _session_id, ": error retrieving stream id :", ec.message());
|
||
|
CloseNow(ec);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Read the stream id.
|
||
|
_deadline.expires_from_now(_timeout);
|
||
|
boost::asio::async_read(
|
||
|
_socket,
|
||
|
boost::asio::buffer(&_stream_id, sizeof(_stream_id)),
|
||
|
boost::asio::bind_executor(_strand, handle_query));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void ServerSession::Write(std::shared_ptr<const Message> message) {
|
||
|
DEBUG_ASSERT(message != nullptr);
|
||
|
DEBUG_ASSERT(!message->empty());
|
||
|
auto self = shared_from_this();
|
||
|
boost::asio::post(_strand, [=]() {
|
||
|
if (!_socket.is_open()) {
|
||
|
return;
|
||
|
}
|
||
|
if (_is_writing) {
|
||
|
if (_server.IsSynchronousMode()) {
|
||
|
// wait until previous message has been sent
|
||
|
while (_is_writing) {
|
||
|
std::this_thread::yield();
|
||
|
}
|
||
|
} else {
|
||
|
// ignore this message
|
||
|
log_debug("session", _session_id, ": connection too slow: message discarded");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
_is_writing = true;
|
||
|
|
||
|
auto handle_sent = [this, self, message](const boost::system::error_code &ec, size_t DEBUG_ONLY(bytes)) {
|
||
|
_is_writing = false;
|
||
|
if (ec) {
|
||
|
log_info("session", _session_id, ": error sending data :", ec.message());
|
||
|
CloseNow(ec);
|
||
|
} else {
|
||
|
DEBUG_ONLY(log_debug("session", _session_id, ": successfully sent", bytes, "bytes"));
|
||
|
DEBUG_ASSERT_EQ(bytes, sizeof(message_size_type) + message->size());
|
||
|
}
|
||
|
};
|
||
|
|
||
|
log_debug("session", _session_id, ": sending message of", message->size(), "bytes");
|
||
|
|
||
|
_deadline.expires_from_now(_timeout);
|
||
|
boost::asio::async_write(
|
||
|
_socket,
|
||
|
message->GetBufferSequence(),
|
||
|
handle_sent);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void ServerSession::Close() {
|
||
|
boost::asio::post(_strand, [self=shared_from_this()]() { self->CloseNow(); });
|
||
|
}
|
||
|
|
||
|
void ServerSession::StartTimer() {
|
||
|
if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now()) {
|
||
|
log_debug("session", _session_id, "timed out");
|
||
|
Close();
|
||
|
} else {
|
||
|
_deadline.async_wait([this, self=shared_from_this()](boost::system::error_code ec) {
|
||
|
if (!ec) {
|
||
|
StartTimer();
|
||
|
} else {
|
||
|
log_debug("session", _session_id, "timed out error:", ec.message());
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ServerSession::CloseNow(boost::system::error_code ec) {
|
||
|
_deadline.cancel();
|
||
|
if (!ec)
|
||
|
{
|
||
|
if (_socket.is_open()) {
|
||
|
boost::system::error_code ec2;
|
||
|
_socket.shutdown(boost::asio::socket_base::shutdown_both, ec2);
|
||
|
_socket.close();
|
||
|
}
|
||
|
}
|
||
|
_on_closed(shared_from_this());
|
||
|
log_debug("session", _session_id, "closed");
|
||
|
}
|
||
|
|
||
|
} // namespace tcp
|
||
|
} // namespace detail
|
||
|
} // namespace streaming
|
||
|
} // namespace carla
|