diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b379960..9d8e3851 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ target_include_directories(aedis INTERFACE ) if(AEDIS_BUILD_EXAMPLES) - add_executable(example examples/example.cpp) + add_executable(example examples/async.cpp) target_link_libraries(example PRIVATE aedis::aedis) endif() diff --git a/Makefile b/Makefile index 2dbc20c1..6ac86e44 100644 --- a/Makefile +++ b/Makefile @@ -18,14 +18,17 @@ CPPFLAGS += -D BOOST_ASIO_NO_TS_EXECUTORS LDFLAGS += -pthread -all: example general +all: sync async general Makefile.dep: -$(CXX) -MM -I./include ./examples/*.cpp ./tests/*.cpp > $@ -include Makefile.dep -example: examples/example.cpp +async: examples/async.cpp + $(CXX) -o $@ $^ $(CPPFLAGS) $(LDFLAGS) + +sync: examples/sync.cpp $(CXX) -o $@ $^ $(CPPFLAGS) $(LDFLAGS) general: % : tests/general.cpp @@ -37,5 +40,5 @@ check: general .PHONY: clean clean: - rm -f example example.o general general.o Makefile.dep + rm -f sync sync.o async async.o general general.o Makefile.dep diff --git a/README.md b/README.md index c3da94e7..8b8bfb9f 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,86 @@ Aedis is a redis client designed for seamless integration with async code while providing a easy and intuitive interface. To use this library include `aedis.hpp` in your project. -## Examples +## Tutoria and examples -The examples below will use coroutines, callbacks and futures are -supported as well. +Below we show how to use the library focused in sync and async code. -### Basics - -Below a basic example +### Sync ```cpp -net::awaitable example1() +void sync_example1() +{ + io_context ioc {1}; + + tcp::resolver resv(ioc); + tcp::socket socket {ioc}; + net::connect(socket, resv.resolve("127.0.0.1", "6379")); + + resp::pipeline p; + p.ping(); + + net::write(socket, buffer(p.payload)); + + resp::buffer buffer; + resp::response res; + resp::read(socket, buffer, res); + + // res.result contains the response as std::vector. +} +``` + +The example above is overly simple. In real world cases it is +necessary, for many reasons to keep reading from the socket, for +example to detect the connection has been lost or to be able to deal +with redis unsolicited events. A more realistic example therefore is + +```cpp +void sync_example2() +{ + io_context ioc {1}; + + tcp::resolver resv(ioc); + tcp::socket socket {ioc}; + net::connect(socket, resv.resolve("127.0.0.1", "6379")); + + resp::pipeline p; + p.multi(); + p.ping(); + p.set("Name", {"Marcelo"}); + p.incr("Age"); + p.exec(); + p.quit(); + + net::write(socket, buffer(p.payload)); + + resp::buffer buffer; + for (;;) { + boost::system::error_code ec; + resp::response res; + resp::read(socket, buffer, res, ec); + if (ec) { + std::cerr << ec.message() << std::endl; + break; + } + resp::print(res.result); + } +} + +In this example we add more commands to the pipeline, they will be all +sent together to redis improving performance. Second we keep reading +until the socket is closed by redis after it receives the quit +command. + +``` +### Async + +The sync examples above are good as introduction and can be also used +in production. However to don't scale, this is specially problematic +on backends. Fourtunately in C++20 it became trivial to convert the +sync into asyn code. The example below shows an example. + +```cpp +net::awaitable async_example1() { auto ex = co_await this_coro::executor; diff --git a/examples/example.cpp b/examples/async.cpp similarity index 77% rename from examples/example.cpp rename to examples/async.cpp index 6cda3463..6aef321c 100644 --- a/examples/example.cpp +++ b/examples/async.cpp @@ -45,7 +45,7 @@ net::awaitable example1() for (;;) { resp::response res; co_await resp::async_read(socket, buffer, res); - resp::print(res.res); + resp::print(res.result); } } @@ -67,36 +67,12 @@ net::awaitable example2() for (;;) { resp::response res; co_await resp::async_read(socket, buffer, res); - resp::print(res.res); + resp::print(res.result); } } -void example4() -{ - io_context ioc {1}; - auto ex = ioc.get_executor(); - tcp::resolver resv(ex); - auto const r = resv.resolve("127.0.0.1", "6379"); - - tcp::socket socket {ex}; - net::connect(socket, r); - - resp::pipeline p; - p.ping(); - - net::write(socket, buffer(p.payload)); - - resp::buffer buffer; - resp::response res; - boost::system::error_code ec; - resp::read(socket, buffer, res, ec); - resp::print(res.res); - ioc.run(); -} - int main() { - example4(); io_context ioc {1}; co_spawn(ioc, example1(), detached); co_spawn(ioc, example2(), detached); diff --git a/examples/sync.cpp b/examples/sync.cpp new file mode 100644 index 00000000..0f9fb6a5 --- /dev/null +++ b/examples/sync.cpp @@ -0,0 +1,71 @@ +/* Copyright (c) 2019 - 2020 Marcelo Zimbres Silva (mzimbres at gmail dot com) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include + +namespace net = aedis::net; + +using namespace net; +using namespace aedis; + +void sync_example1() +{ + io_context ioc {1}; + + tcp::resolver resv(ioc); + tcp::socket socket {ioc}; + net::connect(socket, resv.resolve("127.0.0.1", "6379")); + + resp::pipeline p; + p.ping(); + p.quit(); + + net::write(socket, buffer(p.payload)); + + resp::buffer buffer; + resp::response res; + resp::read(socket, buffer, res); + resp::print(res.result); +} + +void sync_example2() +{ + io_context ioc {1}; + + tcp::resolver resv(ioc); + tcp::socket socket {ioc}; + net::connect(socket, resv.resolve("127.0.0.1", "6379")); + + resp::pipeline p; + p.multi(); + p.ping(); + p.set("Name", {"Marcelo"}); + p.incr("Age"); + p.exec(); + p.quit(); + + net::write(socket, buffer(p.payload)); + + resp::buffer buffer; + for (;;) { + boost::system::error_code ec; + resp::response res; + resp::read(socket, buffer, res, ec); + if (ec) { + std::cerr << ec.message() << std::endl; + break; + } + resp::print(res.result); + } +} + +int main() +{ + sync_example1(); + sync_example2(); +} + diff --git a/include/aedis/aedis.hpp b/include/aedis/aedis.hpp index d7de7324..d8241aa4 100644 --- a/include/aedis/aedis.hpp +++ b/include/aedis/aedis.hpp @@ -41,16 +41,18 @@ namespace resp using buffer = std::string; -struct response { +// General purpose response. Copies the string reponses in the result +// vector. +struct response_vector { private: void add(std::string_view s = {}) - { res.emplace_back(s.data(), std::size(s)); } + { result.emplace_back(s.data(), std::size(s)); } public: - std::vector res; + std::vector result; - void clear() { res.clear(); } - auto size() const noexcept { return std::size(res); } + void clear() { result.clear(); } + auto size() const noexcept { return std::size(result); } void select_array(int n) { } void select_push(int n) { } @@ -71,9 +73,11 @@ public: void on_streamed_string_part(std::string_view s = {}) { add(s); } }; +using response = response_vector; + // Converts a decimal number in ascii format to an integer. inline -std::size_t make_length(char const* p) +std::size_t length(char const* p) { std::size_t len = 0; while (*p != '\r') { @@ -124,7 +128,7 @@ private: auto on_array_impl(char const* data, int m = 1) { - auto const l = make_length(data + 1); + auto const l = length(data + 1); if (l == 0) { --sizes_[depth_]; return l; @@ -199,7 +203,7 @@ private: auto on_blob_error_impl(char const* data, bulk b) { - auto const l = make_length(data + 1); + auto const l = length(data + 1); if (l == -1 || l == 0) { on_bulk(b); return bulk::none; @@ -355,9 +359,9 @@ public: } }; -template +template auto read( - AsyncReadStream& stream, + SyncReadStream& stream, resp::buffer& buf, resp::response& res, boost::system::error_code& ec) @@ -376,7 +380,7 @@ start: auto const l = p.bulk_length(); if (s < (l + 2)) { buf.resize(l + 2); - n = net::read(stream, net::buffer(buf.data() + s, l + 2 - s), net::transfer_all()); + n = net::read(stream, net::buffer(buf.data() + s, l + 2 - s)); if (ec || n < 2) return n; } @@ -390,6 +394,22 @@ start: return n; } +template +std::size_t +read( + SyncReadStream& stream, + resp::buffer& buf, + resp::response& res) +{ + boost::system::error_code ec; + auto const n = read(stream, buf, res, ec); + + if (ec) + BOOST_THROW_EXCEPTION(boost::system::system_error{ec}); + + return n; +} + template < class AsyncReadStream, class CompletionToken = diff --git a/tests/general.cpp b/tests/general.cpp index 99244d46..e7d79903 100644 --- a/tests/general.cpp +++ b/tests/general.cpp @@ -94,7 +94,7 @@ net::awaitable test_list(int version) for (auto const& o : r) { resp::response res; co_await resp::async_read(socket, buffer, res); - check_equal(res.res, o.first, o.second); + check_equal(res.result, o.first, o.second); } } @@ -159,7 +159,7 @@ net::awaitable test_set(int version) for (auto const& o : r) { resp::response res; co_await resp::async_read(socket, buffer, res); - check_equal(res.res, o.first, o.second); + check_equal(res.result, o.first, o.second); } } @@ -218,10 +218,10 @@ net::awaitable offline() test_tcp_socket ts {e.first}; resp::response res; co_await resp::async_read(ts, buffer, res); - if (e.second != res.res) { + if (e.second != res.result) { std::cout << "Error: " << std::size(e.second) - << " " << std::size(res.res) << std::endl; + << " " << std::size(res.result) << std::endl; } else { std::cout << "Success: Offline tests." << std::endl; }