1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

initial commit

This commit is contained in:
initial commit 2019-09-07 14:03:22 +04:00 committed by vvaltman
commit c2da007f40
1610 changed files with 398047 additions and 0 deletions

26
tdnet/CMakeLists.txt Normal file
View file

@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
set(TDNET_SOURCE
td/net/FdListener.cpp
td/net/TcpListener.cpp
td/net/UdpServer.cpp
td/net/FdListener.h
td/net/TcpListener.h
td/net/UdpServer.h
)
add_library(tdnet STATIC ${TDNET_SOURCE})
target_include_directories(tdnet PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
target_link_libraries(tdnet PUBLIC tdactor)
add_executable(tcp_ping_pong example/tcp_ping_pong.cpp)
target_link_libraries(tcp_ping_pong PRIVATE tdactor tdnet)
add_executable(udp_ping_pong example/udp_ping_pong.cpp)
target_link_libraries(udp_ping_pong PRIVATE tdactor tdnet)
set(NET_TEST_SOURCE
${CMAKE_CURRENT_SOURCE_DIR}/test/net-test.cpp
PARENT_SCOPE
)

View file

@ -0,0 +1,153 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version. If you delete this exception statement
from all source files in the program, then also delete it here.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "td/actor/actor.h"
#include "td/utils/BufferedFd.h"
#include "td/utils/OptionsParser.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/port/ServerSocketFd.h"
#include "td/utils/Observer.h"
#include "td/net/TcpListener.h"
class PingClient : public td::actor::Actor, td::ObserverBase {
public:
PingClient(td::SocketFd fd) : buffered_fd_(std::move(fd)) {
}
private:
td::BufferedFd<td::SocketFd> buffered_fd_;
td::actor::ActorId<PingClient> self_;
void notify() override {
// NB: Interface will be changed
send_closure_later(self_, &PingClient::on_net);
}
void on_net() {
loop();
}
void start_up() override {
self_ = actor_id(this);
LOG(INFO) << "Start";
// Subscribe for socket updates
// NB: Interface will be changed
td::actor::SchedulerContext::get()->get_poll().subscribe(buffered_fd_.get_poll_info().extract_pollable_fd(this),
td::PollFlags::ReadWrite());
alarm_timestamp() = td::Timestamp::now();
}
void tear_down() override {
LOG(INFO) << "Close";
// unsubscribe from socket updates
// nb: interface will be changed
td::actor::SchedulerContext::get()->get_poll().unsubscribe(buffered_fd_.get_poll_info().get_pollable_fd_ref());
}
void loop() override {
auto status = [&] {
TRY_STATUS(buffered_fd_.flush_read());
auto &input = buffered_fd_.input_buffer();
while (input.size() >= 12) {
auto query = input.cut_head(12).move_as_buffer_slice();
LOG(INFO) << "Got query " << td::format::escaped(query.as_slice());
if (query[5] == 'i') {
LOG(INFO) << "Send ping";
buffered_fd_.output_buffer().append("magkpongpong");
} else {
LOG(INFO) << "Got pong";
}
}
TRY_STATUS(buffered_fd_.flush_write());
if (td::can_close(buffered_fd_)) {
stop();
}
return td::Status::OK();
}();
if (status.is_error()) {
LOG(ERROR) << "Client got error " << status;
stop();
}
}
void alarm() override {
alarm_timestamp() = td::Timestamp::in(5);
LOG(INFO) << "Send ping";
buffered_fd_.output_buffer().append("magkpingping");
loop();
}
};
int main(int argc, char *argv[]) {
td::OptionsParser options_parser;
options_parser.set_description("Tcp ping server/client (based on td::actors2)");
int port = 8081;
bool is_client = false;
options_parser.add_option('p', "port", "listen/connect to tcp port (8081 by default)", [&](td::Slice arg) {
port = td::to_integer<int>(arg);
return td::Status::OK();
});
options_parser.add_option('c', "client", "Work as client (server by default)", [&]() {
is_client = true;
return td::Status::OK();
});
auto status = options_parser.run(argc, argv);
if (status.is_error()) {
LOG(ERROR) << status.error();
LOG(INFO) << options_parser;
return 1;
}
// NB: Interface will be changed
td::actor::Scheduler scheduler({2});
scheduler.run_in_context([&] {
if (is_client) {
td::IPAddress ip_address;
ip_address.init_ipv4_port("127.0.0.1", port).ensure();
td::actor::create_actor<PingClient>(td::actor::ActorOptions().with_name("TcpClient").with_poll(),
td::SocketFd::open(ip_address).move_as_ok())
.release();
} else {
class Callback : public td::TcpListener::Callback {
public:
void accept(td::SocketFd fd) override {
td::actor::create_actor<PingClient>(td::actor::ActorOptions().with_name("TcpClient").with_poll(),
std::move(fd))
.release();
}
};
td::actor::create_actor<td::TcpListener>(td::actor::ActorOptions().with_name("TcpServer").with_poll(), port,
std::make_unique<Callback>())
.release();
}
});
scheduler.run();
return 0;
}

View file

@ -0,0 +1,153 @@
/*
This file is part of TON Blockchain source code.
TON Blockchain is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
TON Blockchain is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
You must obey the GNU General Public License in all respects for all
of the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version. If you delete this exception statement
from all source files in the program, then also delete it here.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "td/actor/actor.h"
#include "td/utils/OptionsParser.h"
#include "td/utils/Observer.h"
#include "td/utils/port/UdpSocketFd.h"
#include "td/net/UdpServer.h"
// PingPong
class PingPong : public td::actor::Actor {
public:
PingPong(int port, td::IPAddress dest, bool use_tcp) : port_(port), dest_(std::move(dest)), use_tcp_(use_tcp) {
}
private:
int port_;
td::actor::ActorOwn<td::UdpServer> udp_server_;
td::IPAddress dest_;
bool is_closing_{false};
bool use_tcp_{false};
void start_up() override {
class Callback : public td::UdpServer::Callback {
public:
Callback(td::actor::ActorShared<PingPong> ping_pong) : ping_pong_(std::move(ping_pong)) {
}
private:
td::actor::ActorShared<PingPong> ping_pong_;
void on_udp_message(td::UdpMessage udp_message) override {
send_closure(ping_pong_, &PingPong::on_udp_message, std::move(udp_message));
}
};
if (use_tcp_) {
udp_server_ = td::UdpServer::create_via_tcp(PSLICE() << "UdpServer(via tcp) " << td::tag("port", port_), port_,
std::make_unique<Callback>(actor_shared(this)))
.move_as_ok();
} else {
udp_server_ = td::UdpServer::create(PSLICE() << "UdpServer " << td::tag("port", port_), port_,
std::make_unique<Callback>(actor_shared(this)))
.move_as_ok();
}
alarm_timestamp() = td::Timestamp::now();
}
void on_udp_message(td::UdpMessage message) {
if (is_closing_) {
return;
}
if (message.error.is_error()) {
LOG(ERROR) << "Got error " << message.error << " from " << message.address;
return;
}
auto data_slice = message.data.as_slice();
LOG(INFO) << "Got query " << td::format::escaped(data_slice) << " from " << message.address;
if (data_slice[5] == 'i') {
send_closure(udp_server_, &td::UdpServer::send,
td::UdpMessage{message.address, td::BufferSlice("magkpongpong"), {}});
}
}
int cnt_{0};
void alarm() override {
if (++cnt_ > 10) {
return close();
}
alarm_timestamp() = td::Timestamp::in(1);
LOG(INFO) << "Send ping";
send_closure(udp_server_, &td::UdpServer::send, td::UdpMessage{dest_, td::BufferSlice("magkpingping"), {}});
}
void close() {
is_closing_ = true;
udp_server_.reset();
}
void hangup_shared() override {
// udp_server_ was_closed
stop();
}
void tear_down() override {
td::actor::SchedulerContext::get()->stop();
}
};
int main(int argc, char *argv[]) {
td::OptionsParser options_parser;
options_parser.set_description("Udp ping server/client (8083 <-> 8084) (based on td::actors2)");
int from_port = 8083;
int to_port = 8084;
bool is_client = false;
bool use_tcp = false;
options_parser.add_option('c', "client", "Work as client (server by default)", [&]() {
is_client = true;
return td::Status::OK();
});
options_parser.add_option('t', "tcp", "Use tcp (udp by default)", [&]() {
use_tcp = true;
return td::Status::OK();
});
auto status = options_parser.run(argc, argv);
if (status.is_error()) {
LOG(ERROR) << status.error();
LOG(INFO) << options_parser;
return 1;
}
if (is_client) {
std::swap(from_port, to_port);
}
td::IPAddress to_ip;
to_ip.init_ipv4_port("127.0.0.1", to_port).ensure();
// NB: Interface will be changed
td::actor::Scheduler scheduler({2});
LOG(INFO) << "Listen to " << from_port;
scheduler.run_in_context([&] {
td::actor::create_actor<PingPong>(td::actor::ActorOptions().with_name("TcpClient").with_poll(), from_port, to_ip,
use_tcp)
.release();
});
scheduler.run();
return 0;
}

View file

@ -0,0 +1,30 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "td/net/FdListener.h"
namespace td {
void FdListener::start_up() {
fd_.add_flags(PollFlags::ReadWrite());
td::actor::SchedulerContext::get()->get_poll().subscribe(std::move(fd_), PollFlags::ReadWrite());
}
void FdListener::tear_down() {
td::actor::SchedulerContext::get()->get_poll().unsubscribe(fd_ref_);
}
} // namespace td

42
tdnet/td/net/FdListener.h Normal file
View file

@ -0,0 +1,42 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/actor/actor.h"
#include "td/utils/Observer.h"
#include "td/utils/port/detail/PollableFd.h"
namespace td {
class FdListener : public td::actor::Actor {
public:
FdListener(td::PollableFd fd, std::unique_ptr<td::Destructor> guard)
: fd_(std::move(fd)), fd_ref_(fd_.ref()), guard_(std::move(guard)) {
}
private:
PollableFd fd_;
PollableFdRef fd_ref_;
std::unique_ptr<Destructor> guard_;
void start_up() override;
void tear_down() override;
};
} // namespace td

View file

@ -0,0 +1,77 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "td/net/TcpListener.h"
namespace td {
TcpListener::TcpListener(int port, std::unique_ptr<Callback> callback) : port_(port), callback_(std::move(callback)) {
}
void TcpListener::notify() {
td::actor::send_closure_later(self_, &TcpListener::on_net);
}
void TcpListener::on_net() {
loop();
}
void TcpListener::start_up() {
self_ = actor_id(this);
auto r_socket = td::ServerSocketFd::open(port_);
if (r_socket.is_error()) {
LOG(ERROR) << r_socket.error();
return stop();
}
server_socket_fd_ = r_socket.move_as_ok();
// Subscribe for socket updates
// NB: Interface will be changed
td::actor::SchedulerContext::get()->get_poll().subscribe(server_socket_fd_.get_poll_info().extract_pollable_fd(this),
PollFlags::Read());
}
void TcpListener::tear_down() {
// unsubscribe from socket updates
// nb: interface will be changed
td::actor::SchedulerContext::get()->get_poll().unsubscribe(server_socket_fd_.get_poll_info().get_pollable_fd_ref());
}
void TcpListener::loop() {
auto status = [&] {
while (td::can_read(server_socket_fd_)) {
auto r_socket = server_socket_fd_.accept();
if (r_socket.is_error() && r_socket.error().code() == -1) {
break;
}
TRY_RESULT(client_socket, std::move(r_socket));
LOG(ERROR) << "Accept";
callback_->accept(std::move(client_socket));
}
if (td::can_close(server_socket_fd_)) {
stop();
}
return td::Status::OK();
}();
if (status.is_error()) {
LOG(ERROR) << "Server error " << status;
return stop();
}
}
} // namespace td

View file

@ -0,0 +1,52 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/actor/actor.h"
#include "td/utils/port/ServerSocketFd.h"
#include "td/utils/Observer.h"
namespace td {
class TcpListener : public td::actor::Actor, private td::ObserverBase {
public:
class Callback {
public:
virtual ~Callback() = default;
virtual void accept(SocketFd fd) = 0;
};
TcpListener(int port, std::unique_ptr<Callback> callback);
private:
int port_;
std::unique_ptr<Callback> callback_;
td::ServerSocketFd server_socket_fd_;
td::actor::ActorId<TcpListener> self_;
void notify() override;
void on_net();
void start_up() override;
void tear_down() override;
void loop() override;
};
} // namespace td

466
tdnet/td/net/UdpServer.cpp Normal file
View file

@ -0,0 +1,466 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "td/net/UdpServer.h"
#include "td/net/FdListener.h"
#include "td/net/TcpListener.h"
#include "td/utils/BufferedFd.h"
#include <map>
namespace td {
namespace {
int VERBOSITY_NAME(udp_server) = VERBOSITY_NAME(DEBUG) + 10;
}
namespace detail {
class UdpServerImpl : public UdpServer {
public:
void send(td::UdpMessage &&message) override;
static td::actor::ActorOwn<UdpServerImpl> create(td::Slice name, td::UdpSocketFd fd,
std::unique_ptr<Callback> callback);
UdpServerImpl(td::UdpSocketFd fd, std::unique_ptr<Callback> callback);
private:
td::actor::ActorOwn<> fd_listener_;
std::unique_ptr<Callback> callback_;
td::BufferedUdp fd_;
bool is_closing_{false};
void start_up() override;
void on_fd_updated();
void loop() override;
void hangup() override;
void hangup_shared() override;
};
void UdpServerImpl::send(td::UdpMessage &&message) {
fd_.send(std::move(message));
loop(); // TODO: some yield logic
}
td::actor::ActorOwn<UdpServerImpl> UdpServerImpl::create(td::Slice name, td::UdpSocketFd fd,
std::unique_ptr<Callback> callback) {
return td::actor::create_actor<UdpServerImpl>(
actor::ActorOptions().with_name(name).with_poll(!td::Poll::is_edge_triggered()), std::move(fd),
std::move(callback));
}
UdpServerImpl::UdpServerImpl(td::UdpSocketFd fd, std::unique_ptr<Callback> callback)
: callback_(std::move(callback)), fd_(std::move(fd)) {
}
void UdpServerImpl::start_up() {
//CHECK(td::actor::SchedulerContext::get()->has_poll() == false);
class Observer : public td::ObserverBase, public Destructor {
public:
Observer(td::actor::ActorShared<UdpServerImpl> udp_server) : udp_server_(std::move(udp_server)) {
}
void notify() override {
VLOG(udp_server) << "on_fd_updated";
td::actor::send_signals_later(udp_server_, td::actor::ActorSignals::wakeup());
}
private:
td::actor::ActorShared<UdpServerImpl> udp_server_;
};
auto observer = std::make_unique<Observer>(actor_shared(this));
auto pollable_fd = fd_.get_poll_info().extract_pollable_fd(observer.get());
fd_listener_ = td::actor::create_actor<FdListener>(actor::ActorOptions().with_name("FdListener").with_poll(),
std::move(pollable_fd), std::move(observer));
}
void UdpServerImpl::on_fd_updated() {
loop();
}
void UdpServerImpl::loop() {
if (is_closing_) {
return;
}
//CHECK(td::actor::SchedulerContext::get()->has_poll() == false);
fd_.get_poll_info().get_flags();
VLOG(udp_server) << "loop " << td::tag("can read", can_read(fd_)) << " " << td::tag("can write", can_write(fd_));
Status status;
status = [&] {
while (true) {
TRY_RESULT(o_message, fd_.receive());
if (!o_message) {
return Status::OK();
}
callback_->on_udp_message(std::move(*o_message));
}
return Status::OK();
}();
if (status.is_ok()) {
status = fd_.flush_send();
}
if (status.is_error()) {
VLOG(udp_server) << "Got " << status << " sleep for 1 second";
alarm_timestamp() = Timestamp::in(1);
}
}
void UdpServerImpl::hangup() {
is_closing_ = true;
// wait till fd_listener_ is closed and fd is unsubscribed
fd_listener_.reset();
}
void UdpServerImpl::hangup_shared() {
stop();
}
class TcpInfiniteListener : public actor::Actor {
public:
TcpInfiniteListener(int32 port, std::unique_ptr<TcpListener::Callback> callback)
: port_(port), callback_(std::move(callback)) {
}
private:
int32 port_;
std::unique_ptr<TcpListener::Callback> callback_;
actor::ActorOwn<TcpListener> tcp_listener_;
int32 refcnt_{0};
bool close_flag_{false};
void start_up() override {
loop();
}
void hangup() override {
close_flag_ = true;
tcp_listener_.reset();
if (refcnt_ == 0) {
stop();
}
}
void loop() override {
if (!tcp_listener_.empty()) {
return;
}
class Callback : public TcpListener::Callback {
public:
Callback(actor::ActorShared<TcpInfiniteListener> parent) : parent_(std::move(parent)) {
}
void accept(SocketFd fd) override {
actor::send_closure(parent_, &TcpInfiniteListener::accept, std::move(fd));
}
private:
actor::ActorShared<TcpInfiniteListener> parent_;
};
VLOG(udp_server) << "Create listener";
refcnt_++;
tcp_listener_ = actor::create_actor<TcpListener>(
actor::ActorOptions().with_name(PSLICE() << "TcpListener" << tag("port", port_)).with_poll(), port_,
std::make_unique<Callback>(actor_shared(this)));
}
void accept(SocketFd fd) {
callback_->accept(std::move(fd));
}
void hangup_shared() override {
refcnt_--;
tcp_listener_.reset();
if (close_flag_) {
if (refcnt_ == 0) {
stop();
}
} else {
alarm_timestamp() = Timestamp::in(5 /*5 seconds*/);
}
}
};
class TcpClient : public td::actor::Actor, td::ObserverBase {
public:
class Callback {
public:
virtual ~Callback() = default;
virtual void on_message(BufferSlice data) = 0;
virtual void on_closed(actor::ActorId<>) = 0;
};
TcpClient(td::SocketFd fd, std::unique_ptr<Callback> callback)
: buffered_fd_(std::move(fd)), callback_(std::move(callback)) {
}
void send(BufferSlice data) {
uint32 data_size = narrow_cast<uint32>(data.size());
buffered_fd_.output_buffer().append(Slice(reinterpret_cast<char *>(&data_size), sizeof(data_size)));
buffered_fd_.output_buffer().append(std::move(data));
loop();
}
private:
td::BufferedFd<td::SocketFd> buffered_fd_;
std::unique_ptr<Callback> callback_;
td::actor::ActorId<TcpClient> self_;
void notify() override {
// NB: Interface will be changed
td::actor::send_closure_later(self_, &TcpClient::on_net);
}
void on_net() {
loop();
}
void start_up() override {
self_ = actor_id(this);
LOG(INFO) << "Start";
// Subscribe for socket updates
// NB: Interface will be changed
td::actor::SchedulerContext::get()->get_poll().subscribe(buffered_fd_.get_poll_info().extract_pollable_fd(this),
PollFlags::ReadWrite());
alarm_timestamp() = Timestamp::in(10);
notify();
}
void tear_down() override {
LOG(INFO) << "Close";
// unsubscribe from socket updates
// nb: interface will be changed
td::actor::SchedulerContext::get()->get_poll().unsubscribe(buffered_fd_.get_poll_info().get_pollable_fd_ref());
callback_->on_closed(actor_id(this));
}
void loop() override {
auto status = [&] {
TRY_STATUS(buffered_fd_.flush_read());
auto &input = buffered_fd_.input_buffer();
while (true) {
constexpr size_t header_size = 4;
if (input.size() < header_size) {
break;
}
auto it = input.clone();
uint32 data_size;
it.advance(header_size, MutableSlice(reinterpret_cast<uint8 *>(&data_size), sizeof(data_size)));
if (data_size > (1 << 26)) {
return Status::Error("Too big packet");
}
if (it.size() < data_size) {
break;
}
auto data = it.cut_head(data_size).move_as_buffer_slice();
alarm_timestamp() = Timestamp::in(10);
callback_->on_message(std::move(data));
input = std::move(it);
}
TRY_STATUS(buffered_fd_.flush_write());
if (td::can_close(buffered_fd_)) {
stop();
}
return td::Status::OK();
}();
if (status.is_error()) {
LOG(INFO) << "Client got error " << status;
stop();
}
}
void alarm() override {
LOG(INFO) << "Close because of timeout";
stop();
}
};
struct Target {
IPAddress ip_address;
actor::ActorOwn<TcpClient> inbound;
actor::ActorOwn<TcpClient> outbound;
};
class TargetSet {
public:
using Id = size_t;
Id register_target(IPAddress address) {
auto it_ok = ip_to_id_.insert(std::make_pair(address, 0));
if (it_ok.second) {
id_to_target_.push_back({});
id_to_target_.back().ip_address = address;
it_ok.first->second = id_to_target_.size();
}
return it_ok.first->second;
}
Target &get_target(Id id) {
return id_to_target_.at(id - 1);
}
private:
std::map<IPAddress, size_t> ip_to_id_;
std::vector<Target> id_to_target_;
};
class UdpServerViaTcp : public UdpServer {
public:
UdpServerViaTcp(int32 port, std::unique_ptr<Callback> callback) : port_(port), callback_(std::move(callback)) {
}
private:
int32 port_;
std::unique_ptr<Callback> callback_;
actor::ActorOwn<TcpInfiniteListener> tcp_listener_;
TargetSet target_set_;
int refcnt_{0};
bool close_flag_{false};
void start_up() override {
//TcpInfiniteListener
class TcpListenerCallback : public TcpListener::Callback {
public:
TcpListenerCallback(actor::ActorShared<UdpServerViaTcp> parent) : parent_(std::move(parent)) {
}
void accept(SocketFd fd) override {
actor::send_closure(parent_, &UdpServerViaTcp::accept, std::move(fd));
}
private:
actor::ActorShared<UdpServerViaTcp> parent_;
};
refcnt_++;
tcp_listener_ = actor::create_actor<TcpInfiniteListener>(PSLICE() << "TcpInfiniteListener" << port_, port_,
std::make_unique<TcpListenerCallback>(actor_shared(this)));
}
void send(UdpMessage &&message) override {
if (close_flag_) {
return;
}
auto target_id = target_set_.register_target(message.address);
auto &target = target_set_.get_target(target_id);
if (target.inbound.empty() && target.outbound.empty()) {
auto r_fd = SocketFd::open(target.ip_address);
if (r_fd.is_error()) {
LOG(INFO) << r_fd.error();
return;
}
auto fd = r_fd.move_as_ok();
do_accept(std::move(fd), message.address, false);
}
if (!target.inbound.empty()) {
send_closure_later(target.inbound, &TcpClient::send, std::move(message.data));
} else if (!target.outbound.empty()) {
send_closure_later(target.outbound, &TcpClient::send, std::move(message.data));
}
}
void on_message(BufferSlice data) {
if (close_flag_) {
return;
}
auto token = get_link_token();
auto &target = target_set_.get_target(narrow_cast<TargetSet::Id>(token));
UdpMessage message;
message.address = target.ip_address;
message.data = std::move(data);
callback_->on_udp_message(std::move(message));
}
void on_closed(actor::ActorId<> id) {
if (close_flag_) {
return;
}
auto token = get_link_token();
auto &target = target_set_.get_target(narrow_cast<TargetSet::Id>(token));
if (target.inbound.get() == id) {
target.inbound.reset();
}
if (target.outbound.get() == id) {
target.outbound.reset();
}
}
void accept(SocketFd fd) {
if (close_flag_) {
return;
}
IPAddress ip_address;
auto status = ip_address.init_peer_address(fd);
if (status.is_error()) {
LOG(INFO) << status;
return;
}
do_accept(std::move(fd), ip_address, true);
}
void do_accept(SocketFd fd, IPAddress ip_address, bool is_inbound) {
class TcpClientCallback : public TcpClient::Callback {
public:
TcpClientCallback(actor::ActorShared<UdpServerViaTcp> parent) : parent_(std::move(parent)) {
}
void on_message(BufferSlice data) override {
send_closure(parent_, &UdpServerViaTcp::on_message, std::move(data));
}
void on_closed(actor::ActorId<> id) override {
send_closure(parent_, &UdpServerViaTcp::on_closed, std::move(id));
}
private:
actor::ActorShared<UdpServerViaTcp> parent_;
};
auto target_id = target_set_.register_target(ip_address);
auto &target = target_set_.get_target(target_id);
refcnt_++;
auto actor = actor::create_actor<TcpClient>(actor::ActorOptions().with_name("TcpClient").with_poll(), std::move(fd),
std::make_unique<TcpClientCallback>(actor_shared(this, target_id)));
if (is_inbound) {
target.inbound = std::move(actor);
} else {
target.outbound = std::move(actor);
}
}
void hangup() override {
close_flag_ = true;
target_set_ = {};
tcp_listener_ = {};
}
void hangup_shared() override {
refcnt_--;
if (refcnt_ == 0) {
stop();
}
}
void loop() override {
}
};
} // namespace detail
Result<actor::ActorOwn<UdpServer>> UdpServer::create(td::Slice name, int32 port, std::unique_ptr<Callback> callback) {
td::IPAddress from_ip;
TRY_STATUS(from_ip.init_ipv4_port("0.0.0.0", port));
TRY_RESULT(fd, UdpSocketFd::open(from_ip));
fd.maximize_rcv_buffer().ensure();
return detail::UdpServerImpl::create(name, std::move(fd), std::move(callback));
}
Result<actor::ActorOwn<UdpServer>> UdpServer::create_via_tcp(td::Slice name, int32 port,
std::unique_ptr<Callback> callback) {
return actor::create_actor<detail::UdpServerViaTcp>(name, port, std::move(callback));
}
} // namespace td

42
tdnet/td/net/UdpServer.h Normal file
View file

@ -0,0 +1,42 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/actor/actor.h"
#include "td/utils/BufferedUdp.h"
#include "td/utils/port/UdpSocketFd.h"
namespace td {
class UdpServer : public td::actor::Actor {
public:
class Callback {
public:
virtual ~Callback() = default;
virtual void on_udp_message(td::UdpMessage udp_message) = 0;
};
virtual void send(td::UdpMessage &&message) = 0;
static Result<actor::ActorOwn<UdpServer>> create(td::Slice name, int32 port, std::unique_ptr<Callback> callback);
static Result<actor::ActorOwn<UdpServer>> create_via_tcp(td::Slice name, int32 port,
std::unique_ptr<Callback> callback);
};
} // namespace td

167
tdnet/test/net-test.cpp Normal file
View file

@ -0,0 +1,167 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "td/actor/actor.h"
#include "td/net/UdpServer.h"
#include "td/utils/tests.h"
class PingPong : public td::actor::Actor {
public:
PingPong(int port, td::IPAddress dest, bool use_tcp, bool is_first)
: port_(port), dest_(std::move(dest)), use_tcp_(use_tcp) {
if (is_first) {
state_ = Send;
to_send_cnt_ = 5;
to_send_cnt_ = 1;
}
}
private:
int port_;
td::actor::ActorOwn<td::UdpServer> udp_server_;
td::IPAddress dest_;
bool is_closing_{false};
bool is_closing_delayed_{false};
bool use_tcp_{false};
enum State { Send, Receive } state_{State::Receive};
int cnt_{0};
int to_send_cnt_{0};
void start_up() override {
class Callback : public td::UdpServer::Callback {
public:
Callback(td::actor::ActorShared<PingPong> ping_pong) : ping_pong_(std::move(ping_pong)) {
}
private:
td::actor::ActorShared<PingPong> ping_pong_;
void on_udp_message(td::UdpMessage udp_message) override {
send_closure(ping_pong_, &PingPong::on_udp_message, std::move(udp_message));
}
};
if (use_tcp_) {
udp_server_ = td::UdpServer::create_via_tcp(PSLICE() << "UdpServer(via tcp) " << td::tag("port", port_), port_,
std::make_unique<Callback>(actor_shared(this)))
.move_as_ok();
} else {
udp_server_ = td::UdpServer::create(PSLICE() << "UdpServer " << td::tag("port", port_), port_,
std::make_unique<Callback>(actor_shared(this)))
.move_as_ok();
}
alarm_timestamp() = td::Timestamp::in(0.1);
}
void on_udp_message(td::UdpMessage message) {
if (is_closing_) {
return;
}
if (message.error.is_error()) {
LOG(ERROR) << "Got error " << message.error << " from " << message.address;
return;
}
auto data_slice = message.data.as_slice();
LOG(INFO) << "Got query " << td::format::escaped(data_slice) << " from " << message.address;
CHECK(state_ == State::Receive);
if (data_slice.size() < 5) {
CHECK(data_slice == "stop");
close();
}
if (data_slice[5] == 'i') {
state_ = State::Send;
to_send_cnt_ = td::Random::fast(1, 4);
to_send_cnt_ = 1;
send_closure_later(actor_id(this), &PingPong::loop);
}
}
void loop() override {
if (state_ != State::Send || is_closing_) {
return;
}
to_send_cnt_--;
td::Slice msg;
if (to_send_cnt_ <= 0) {
state_ = State::Receive;
cnt_++;
if (cnt_ >= 1000) {
msg = "stop";
} else {
msg = "makgpingping";
}
} else {
msg = "magkpongpong";
}
LOG(INFO) << "Send query: " << msg;
send_closure_later(actor_id(this), &PingPong::loop);
send_closure(udp_server_, &td::UdpServer::send, td::UdpMessage{dest_, td::BufferSlice(msg), {}});
if (msg.size() == 4) {
close_delayed();
}
}
void alarm() override {
if (is_closing_delayed_) {
close();
return;
}
send_closure_later(actor_id(this), &PingPong::loop);
}
void close_delayed() {
// Temporary hack to avoid ECONNRESET error
is_closing_ = true;
is_closing_delayed_ = true;
alarm_timestamp() = td::Timestamp::in(0.1);
}
void close() {
is_closing_ = true;
udp_server_.reset();
}
void hangup_shared() override {
// udp_server_ was_closed
stop();
}
void tear_down() override {
td::actor::SchedulerContext::get()->stop();
}
};
void run_server(int from_port, int to_port, bool is_first, bool use_tcp) {
td::IPAddress to_ip;
to_ip.init_host_port("localhost", to_port).ensure();
td::actor::Scheduler scheduler({1});
scheduler.run_in_context([&] {
td::actor::create_actor<PingPong>(td::actor::ActorOptions().with_name("PingPong"), from_port, to_ip, use_tcp,
is_first)
.release();
});
scheduler.run();
}
TEST(Net, PingPong) {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
for (auto use_tcp : {false, true}) {
auto a = td::thread([use_tcp] { run_server(8091, 8092, true, use_tcp); });
auto b = td::thread([use_tcp] { run_server(8092, 8091, false, use_tcp); });
a.join();
b.join();
}
}