ASIO: Initial version based on IDF 5.0 with history

This commit is contained in:
gabsuren
2022-06-22 14:49:37 +04:00
parent ac7bf465d2
commit 055f051f53
64 changed files with 250 additions and 15 deletions

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "asio_chat.cpp"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,39 @@
menu "Example Configuration"
config EXAMPLE_CHAT_SERVER
bool "Asio example chat server"
default n
help
This example will setup a chat server, binds it to the specified address
and starts listening.
if EXAMPLE_CHAT_SERVER
config EXAMPLE_CHAT_SERVER_BIND_PORT
string "Asio example server bind port"
default "3344"
help
Server listener's socket would be bound to this port.
endif
config EXAMPLE_CHAT_CLIENT
bool "Asio example chat client"
default y
help
This example will setup an asio chat client.
and sends the data.
if EXAMPLE_CHAT_CLIENT
config EXAMPLE_CHAT_CLIENT_CONNECT_ADDRESS
string "Client connection address"
default "192.168.0.1"
help
Client's socket would connect to this address/host.
config EXAMPLE_CHAT_CLIENT_CONNECT_PORT
string "Client connection port"
default "3344"
help
Client's connection port.
endif
endmenu

View File

@ -0,0 +1,119 @@
/* ASIO chat server client example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "protocol_examples_common.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "server.hpp"
#include "client.hpp"
#include <thread>
#include <pthread.h>
using asio::ip::tcp;
static const char *TAG = "asio-chat";
// This variable is necessary for `python test` execution, it provides synchronisation between server/client(as server should be started before client)
std::mutex server_ready;
#ifdef CONFIG_EXAMPLE_CHAT_CLIENT
static void get_string(char *line, size_t size)
{
int count = 0;
while (count < size) {
int c = fgetc(stdin);
if (c == '\n') {
line[count] = '\0';
break;
} else if (c > 0 && c < 127) {
line[count] = c;
++count;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void start_client(void)
{
const std::string port(CONFIG_EXAMPLE_CHAT_CLIENT_CONNECT_PORT);
const std::string name(CONFIG_EXAMPLE_CHAT_CLIENT_CONNECT_ADDRESS);
asio::io_context io_context;
char line[128];
tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve(name, port);
chat_client c(io_context, endpoints);
#ifdef CONFIG_EXAMPLE_CHAT_SERVER
std::lock_guard<std::mutex> guard(server_ready);
#endif
std::thread t([&io_context]() { try {
io_context.run();
} catch (const std::exception &e) {
ESP_LOGE(TAG, "Exception occured during client thread execution %s", e.what());
}
catch (...) {
ESP_LOGE(TAG, "Unknown exception");
}});
do {
ESP_LOGI(TAG, "CLIENT: Waiting for input");
get_string(line, sizeof(line));
chat_message msg;
msg.body_length(std::strlen(line));
std::memcpy(msg.body(), line, msg.body_length());
msg.encode_header();
c.write(msg);
sleep(1);
} while (strcmp(line, "exit") != 0);
c.close();
t.join();
}
#endif // CONFIG_EXAMPLE_CHAT_CLIENT
extern "C" void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
esp_netif_init();
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
try {
#ifdef CONFIG_EXAMPLE_CHAT_SERVER
asio::io_context io_context;
chat_server server(io_context, tcp::endpoint(tcp::v4(), std::atoi(CONFIG_EXAMPLE_CHAT_SERVER_BIND_PORT)));
std::thread t = std::thread([&io_context]() { // Chat server starting here
try {
io_context.run();
} catch (const std::exception &e) {
ESP_LOGE(TAG, "Exception occured during server thread execution %s", e.what());
}
catch (...) {
ESP_LOGE(TAG, "Unknown exception");
}});;
#endif
#ifdef CONFIG_EXAMPLE_CHAT_CLIENT
start_client();
#endif
#ifdef CONFIG_EXAMPLE_CHAT_SERVER
t.join();
#endif
} catch (const std::exception &e) {
ESP_LOGE(TAG, "Exception occured during run %s", e.what());
} catch (...) {
ESP_LOGE(TAG, "Unknown exception");
}
ESP_ERROR_CHECK(example_disconnect());
}

View File

@ -0,0 +1,91 @@
//
// chat_message.hpp
// ~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef CHAT_MESSAGE_HPP
#define CHAT_MESSAGE_HPP
#include <cstdio>
#include <cstdlib>
#include <cstring>
class chat_message
{
public:
static constexpr std::size_t header_length = 4;
static constexpr std::size_t max_body_length = 512;
chat_message()
: body_length_(0)
{
}
const char* data() const
{
return data_;
}
char* data()
{
return data_;
}
std::size_t length() const
{
return header_length + body_length_;
}
const char* body() const
{
return data_ + header_length;
}
char* body()
{
return data_ + header_length;
}
std::size_t body_length() const
{
return body_length_;
}
void body_length(std::size_t new_length)
{
body_length_ = new_length;
if (body_length_ > max_body_length)
body_length_ = max_body_length;
}
bool decode_header()
{
char header[header_length + 1] = "";
std::strncat(header, data_, header_length);
body_length_ = std::atoi(header);
if (body_length_ > max_body_length)
{
body_length_ = 0;
return false;
}
return true;
}
void encode_header()
{
char header[header_length + 1] = "";
std::sprintf(header, "%4d", static_cast<int>(body_length_));
std::memcpy(data_, header, header_length);
}
private:
char data_[header_length + max_body_length];
std::size_t body_length_;
};
#endif // CHAT_MESSAGE_HPP

View File

@ -0,0 +1,126 @@
//
// client.hpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef CHAT_CLIENT_HPP
#define CHAT_CLIENT_HPP
#include <deque>
#include "asio.hpp"
#include "chat_message.hpp"
typedef std::deque<chat_message> chat_message_queue;
class chat_client
{
public:
chat_client(asio::io_context& io_context,
const asio::ip::tcp::resolver::results_type& endpoints)
: io_context_(io_context),
socket_(io_context)
{
do_connect(endpoints);
}
void write(const chat_message& msg)
{
asio::post(io_context_,
[this, msg]()
{
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(msg);
if (!write_in_progress)
{
do_write();
}
});
}
void close()
{
asio::post(io_context_, [this]() { socket_.close(); });
}
private:
void do_connect(const asio::ip::tcp::resolver::results_type& endpoints)
{
asio::async_connect(socket_, endpoints,
[this](std::error_code ec, asio::ip::tcp::endpoint)
{
if (!ec)
{
do_read_header();
}
});
}
void do_read_header()
{
asio::async_read(socket_,
asio::buffer(read_msg_.data(), chat_message::header_length),
[this](std::error_code ec, std::size_t /*length*/)
{
if (!ec && read_msg_.decode_header())
{
do_read_body();
}
else
{
socket_.close();
}
});
}
void do_read_body()
{
asio::async_read(socket_,
asio::buffer(read_msg_.body(), read_msg_.body_length()),
[this](std::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
do_read_header();
}
else
{
socket_.close();
}
});
}
void do_write()
{
asio::async_write(socket_,
asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
[this](std::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
write_msgs_.pop_front();
if (!write_msgs_.empty())
{
do_write();
}
}
else
{
socket_.close();
}
});
}
private:
asio::io_context& io_context_;
asio::ip::tcp::socket socket_;
chat_message read_msg_;
chat_message_queue write_msgs_;
};
#endif // CHAT_CLIENT_HPP

View File

@ -0,0 +1,202 @@
//
// server.hpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef CHAT_SERVER_HPP
#define CHAT_SERVER_HPP
#include <list>
#include <set>
#include <deque>
#include <utility>
#include "asio.hpp"
#include "chat_message.hpp"
//----------------------------------------------------------------------
typedef std::deque<chat_message> chat_message_queue;
extern std::mutex server_ready;
//----------------------------------------------------------------------
class chat_participant
{
public:
virtual ~chat_participant() {}
virtual void deliver(const chat_message& msg) = 0;
};
typedef std::shared_ptr<chat_participant> chat_participant_ptr;
//----------------------------------------------------------------------
class chat_room
{
public:
void join(chat_participant_ptr participant)
{
participants_.insert(participant);
for (auto msg: recent_msgs_)
participant->deliver(msg);
}
void leave(chat_participant_ptr participant)
{
participants_.erase(participant);
}
void deliver(const chat_message& msg)
{
recent_msgs_.push_back(msg);
while (recent_msgs_.size() > max_recent_msgs)
recent_msgs_.pop_front();
for (auto participant: participants_)
participant->deliver(msg);
}
private:
std::set<chat_participant_ptr> participants_;
enum { max_recent_msgs = 100 };
chat_message_queue recent_msgs_;
};
//----------------------------------------------------------------------
class chat_session
: public chat_participant,
public std::enable_shared_from_this<chat_session>
{
public:
chat_session(asio::ip::tcp::socket socket, chat_room& room)
: socket_(std::move(socket)),
room_(room)
{
}
void start()
{
room_.join(shared_from_this());
do_read_header();
}
void deliver(const chat_message& msg)
{
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(msg);
if (!write_in_progress)
{
do_write();
}
}
private:
void do_read_header()
{
auto self(shared_from_this());
asio::async_read(socket_,
asio::buffer(read_msg_.data(), chat_message::header_length),
[this, self](std::error_code ec, std::size_t /*length*/)
{
if (!ec && read_msg_.decode_header())
{
do_read_body();
}
else
{
room_.leave(shared_from_this());
}
});
}
void do_read_body()
{
auto self(shared_from_this());
asio::async_read(socket_,
asio::buffer(read_msg_.body(), read_msg_.body_length()),
[this, self](std::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
ESP_LOGD("asio-chat:", "%s", read_msg_.body());
room_.deliver(read_msg_);
do_read_header();
}
else
{
room_.leave(shared_from_this());
}
});
}
void do_write()
{
auto self(shared_from_this());
asio::async_write(socket_,
asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
[this, self](std::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
write_msgs_.pop_front();
if (!write_msgs_.empty())
{
do_write();
}
}
else
{
room_.leave(shared_from_this());
}
});
}
asio::ip::tcp::socket socket_;
chat_room& room_;
chat_message read_msg_;
chat_message_queue write_msgs_;
};
//----------------------------------------------------------------------
class chat_server
{
public:
chat_server(asio::io_context& io_context,
const asio::ip::tcp::endpoint& endpoint)
: acceptor_(io_context, endpoint)
{
do_accept();
}
private:
void do_accept()
{
std::lock_guard<std::mutex> guard(server_ready);
acceptor_.async_accept(
[this](std::error_code ec, asio::ip::tcp::socket socket)
{
if (!ec)
{
std::make_shared<chat_session>(std::move(socket), room_)->start();
}
do_accept();
});
}
asio::ip::tcp::acceptor acceptor_;
chat_room room_;
};
#endif // CHAT_SERVER_HPP