Carla采用的是CS的架构,即
- Server端是在UE4当中,作为UE4的一个插件Plugin
- Client端是C++客户端或者是Python客户端
- 中间通过rpc框架进行通信,走的是TCP协议
首先来看一张很重要的图
这张图清晰明了的说明了Carla的整体框架,接下来我们一个一个分析
RPC框架
建议读者先要理解RPC框架才往后阅读
Carla所使用的是rpc框架是rpclib ,可以在github上找到:地址
或者在Build/rpclib-src 目录下
LibCarla
LibCarla是Carla的核心代码C++实现,提供给Server 端和Client 端使用,同时对rpclib 进行了封装,具体目录在LibCarla\source\carla 下,其中LibCarla\source\third-party 则是Carla所使用的第三方库
我们可以在LibCarla\cmake 目录中看到以下目录结构,说明Server 端和Client 端是分开构建的
Server 端依赖的代码在Carla构建完之后会被安装到Unreal\CarlaUE4\Plugins\Carla\CarlaDependencies 目录下Client 端依赖的代码在Carla构建完之后会被安装到PythonAPI\carla\dependencies 目录下
Server端
Server端的代码在Unreal/CarlaUE4/Plugins/Carla/Source/Carla 目录下,其中Server/CarlaServer.cpp 里包含了Carla对rpc::Server 的一个封装
class ServerBinder
{
public:
constexpr ServerBinder(const char *name, carla::rpc::Server &srv, bool sync)
: _name(name),
_server(srv),
_sync(sync) {}
template <typename FuncT>
auto operator<<(FuncT func)
{
if (_sync)
{
_server.BindSync(_name, func);
}
else
{
_server.BindAsync(_name, func);
}
return func;
}
private:
const char *_name;
carla::rpc::Server &_server;
bool _sync;
};
#define BIND_SYNC(name) auto name = ServerBinder(# name, Server, true)
#define BIND_ASYNC(name) auto name = ServerBinder(# name, Server, false)
// =============================================================================
// -- Bind Actions -------------------------------------------------------------
// =============================================================================
void FCarlaServer::FPimpl::BindActions()
{
namespace cr = carla::rpc;
namespace cg = carla::geom;
/// Looks for a Traffic Manager running on port
BIND_SYNC(is_traffic_manager_running) << [this] (uint16_t port) ->R<bool>
{
return (TrafficManagerInfo.find(port) != TrafficManagerInfo.end());
};
// ... 其余代码
}
通过源码可以看到,BIND_SYNC 和BIND_ASYNC 两个宏实现了Server端函数调用的绑定,例如:is_traffic_manager_running 函数
Client端(C++)
我们可以在LibCarla\source\carla\client\detail\Client.cpp 中找到Client端的实现代码,不过如果你要编写的是C++的Client的话,你可以从PythonAPI\carla\dependencies 目录下拿取安装好的
class Client::Pimpl {
public:
Pimpl(const std::string &host, uint16_t port, size_t worker_threads)
: endpoint(host + ":" + std::to_string(port)),
rpc_client(host, port),
streaming_client(host) {
rpc_client.set_timeout(5000u);
streaming_client.AsyncRun(
worker_threads > 0u ? worker_threads : std::thread::hardware_concurrency());
}
template <typename ... Args>
auto RawCall(const std::string &function, Args && ... args) {
try {
return rpc_client.call(function, std::forward<Args>(args) ...);
} catch (const ::rpc::timeout &) {
throw_exception(TimeoutException(endpoint, GetTimeout()));
}
}
template <typename T, typename ... Args>
auto CallAndWait(const std::string &function, Args && ... args) {
auto object = RawCall(function, std::forward<Args>(args) ...);
using R = typename carla::rpc::Response<T>;
auto response = object.template as<R>();
if (response.HasError()) {
throw_exception(std::runtime_error(response.GetError().What()));
}
return Get(response);
}
template <typename ... Args>
void AsyncCall(const std::string &function, Args && ... args) {
// Discard returned future.
rpc_client.async_call(function, std::forward<Args>(args) ...);
}
time_duration GetTimeout() const {
auto timeout = rpc_client.get_timeout();
DEBUG_ASSERT(timeout.has_value());
return time_duration::milliseconds(static_cast<size_t>(*timeout));
}
const std::string endpoint;
rpc::Client rpc_client;
streaming::Client streaming_client;
};
Client::Client(
const std::string &host,
const uint16_t port,
const size_t worker_threads)
: _pimpl(std::make_unique<Pimpl>(host, port, worker_threads)) {}
bool Client::IsTrafficManagerRunning(uint16_t port) const {
return _pimpl->CallAndWait<bool>("is_traffic_manager_running", port);
}
通过源码可以看到,Client端调用了Server端的函数is_traffic_manager_running
Client端(Python)
目录:PythonAPI\carla\source\libcarla ,主要是通过boost::python 来实现C++到Python的绑定
我们随便看一个文件,例如PythonAPI\carla\source\libcarla\Actor.cpp 文件,具体的绑定用法需要读者自己去了解boost::python
|