diff --git a/.gitignore b/.gitignore index 0b7b86d..fd13849 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -# Qt Creator files *.o *.so examples/example[1-4] -tests/parsertest \ No newline at end of file +tests/parsertest +/redisclient.pro.user diff --git a/.travis.yml b/.travis.yml index df70de7..c18d888 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,19 @@ language: cpp compiler: - gcc -before_script: - - sudo apt-get install -qq libboost-dev libboost-test-dev libboost-system-dev +before_install: + - if [ "$CXX" == "g++" ]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test; fi + - sudo apt-get update -qq + +install: + - if [ "$CXX" == "g++" ]; then sudo apt-get install -qq g++-4.8; fi + - if [ "$CXX" == "g++" ]; then sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 50; fi + - sudo apt-get install -qq libboost1.48-dev libboost-test1.48-dev libboost-system1.48-dev + - dpkg -l "libboost*" | sort + +script: + - mkdir build && cd build + - cmake .. + - make + - make test -script: make && make test && make -C examples example1 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3e7c6e0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(RedisClient) + +FIND_PACKAGE(Boost COMPONENTS unit_test_framework system REQUIRED) + +SET(CMAKE_CXX_FLAGS "-W -Wall -Wextra -Wshadow -fPIC -pthread") +SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -DDEBUG ") +SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") + +SET(SOURCES + src/redisclient/impl/redisvalue.cpp + src/redisclient/impl/redissyncclient.cpp + src/redisclient/impl/redisparser.cpp + src/redisclient/impl/redisclientimpl.cpp + src/redisclient/impl/redisasyncclient.cpp +) + +SET(HEADERS + src/redisclient/config.h + src/redisclient/version.h + src/redisclient/redisvalue.h + src/redisclient/redissyncclient.h + src/redisclient/redisparser.h + src/redisclient/redisclient.h + src/redisclient/redisbuffer.h + src/redisclient/redisasyncclient.h + src/redisclient/impl/redisclientimpl.h +) + +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${Boost_INCLUDE_DIRS} +) + +ADD_EXECUTABLE(parsertest tests/parsertest.cpp ${SOURCES} ${HEADERS}) +ADD_LIBRARY(redisclient STATIC ${HEADERS} ${SOURCES}) + +TARGET_LINK_LIBRARIES(parsertest + ${Boost_LIBRARIES} +) + +TARGET_LINK_LIBRARIES(redisclient + ${Boost_LIBRARIES} +) + +INSTALL(DIRECTORY ${CMAKE_SOURCE_DIR}/src DESTINATION include) + +ADD_CUSTOM_TARGET(test) +ADD_CUSTOM_COMMAND(TARGET test + POST_BUILD + COMMAND ./parsertest + COMMENT "Run tests" + VERBATIM +) + +ADD_SUBDIRECTORY(examples) diff --git a/Makefile b/Makefile deleted file mode 100644 index 7b91ade..0000000 --- a/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -SRCS = src/redisclient/impl/redisclient.cpp src/redisclient/impl/redisvalue.cpp src/redisclient/impl/redisparser.cpp -HDRS = src/redisclient.h src/redisvalie.h src/redisparser.h -OBJS = $(SRCS:.cpp=.o) -LIBNAME = redisclient -LIB = lib$(LIBNAME).so -LIBS = -pthread -lboost_system -LDFLAGS = -g -shared -CXXFLAGS = -g -O2 -Wall -Wextra -fPIC -std=c++0x -DREDIS_CLIENT_DYNLIB -DREDIS_CLIENT_BUILD - -all: test -dynlib: $(LIB) -$(LIB): $(OBJS) - $(CXX) $^ -o $@ $(LDFLAGS) -examples: $(LIB) - +make -C examples examples -tests: test -test: - +make -C tests test - -.cpp.o: - $(CXX) $(CXXFLAGS) -c $< -o $@ - -clean: - rm -f $(OBJS) - make -C tests clean - make -C examples clean - -.PHONY: all test clean examples diff --git a/README.md b/README.md index 3bb6a75..e0aeaaa 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,157 @@ redisclient =========== -Build status: [![Build Status](https://travis-ci.org/nekipelov/redisclient.svg?branch=master)](https://travis-ci.org/nekipelov/redisclient) +Build status: [![Build Status](https://travis-ci.org/nekipelov/redisclient.svg?branch=unstable)](https://travis-ci.org/nekipelov/redisclient) -Current version: 0.3.2 +Current version: 0.4.1 Boost.asio based Redis-client header-only library. Simple but powerfull. Get/set example: - #include "redisclient.h" - - static const std::string redisKey = "unique-redis-key-example"; - static const std::string redisValue = "unique-redis-value"; + #include int main(int, char **) { - const char *address = "127.0.0.1"; - const int port = 6379; + boost::asio::ip::address address = boost::asio::ip::address::from_string("127.0.0.1"); + const unsigned short port = 6379; boost::asio::io_service ioService; - RedisClient redis(ioService); + RedisSyncClient redis(ioService); + std::string errmsg; + + if( !redis.connect(address, port, errmsg) ) + { + std::cerr << "Can't connect to redis: " << errmsg << std::endl; + return EXIT_FAILURE; + } + + RedisValue result; + + result = redis.command("SET", "key", "value"); + + if( result.isError() ) + { + std::cerr << "SET error: " << result.toString() << "\n"; + return EXIT_FAILURE; + } + + result = redis.command("GET", "key"); - if( !redis.connect(address, port) ) + if( result.isOk() ) { - std::cerr << "Can't connecto to redis" << std::endl; + std::cout << "GET " << key << ": " << result.toString() << "\n"; + return EXIT_SUCCESS; + } + else + { + std::cerr << "GET error: " << result.toString() << "\n"; return EXIT_FAILURE; } + } + +Async get/set example: + + #include - redis.command("SET", redisKey, redisValue, [&](const Value &v) { - std::cerr << "SET: " << v.toString() << std::endl; + static const std::string redisKey = "unique-redis-key-example"; + static const std::string redisValue = "unique-redis-value"; - redis.command("GET", redisKey, [&](const Value &v) { - std::cerr << "GET: " << v.toString() << std::endl; + void handleConnected(boost::asio::io_service &ioService, RedisAsyncClient &redis, + bool ok, const std::string &errmsg) + { + if( ok ) + { + redis.command("SET", redisKey, redisValue, [&](const RedisValue &v) { + std::cerr << "SET: " << v.toString() << std::endl; + + redis.command("GET", redisKey, [&](const RedisValue &v) { + std::cerr << "GET: " << v.toString() << std::endl; - redis.command("DEL", redisKey, [&](const Value &v) { - ioService.stop(); + redis.command("DEL", redisKey, [&](const RedisValue &) { + ioService.stop(); + }); }); }); - }); + } + else + { + std::cerr << "Can't connect to redis: " << errmsg << std::endl; + } + } + + int main(int, char **) + { + boost::asio::ip::address address = boost::asio::ip::address::from_string("127.0.0.1"); + const unsigned short port = 6379; + + boost::asio::io_service ioService; + RedisAsyncClient redis(ioService); + + redis.asyncConnect(address, port, + boost::bind(&handleConnected, boost::ref(ioService), boost::ref(redis), _1, _2)); ioService.run(); return 0; } + Publish/subscribe example: - #include "redisclient.h" + #include static const std::string channelName = "unique-redis-channel-name-example"; - int main(int, char **) + void subscribeHandler(boost::asio::io_service &ioService, const std::vector &buf) { - const char *address = "127.0.0.1"; - const int port = 6379; + std::string msg(buf.begin(), buf.end()); - boost::asio::io_service ioService; - RedisClient publisher(ioService); - RedisClient subscriber(ioService); + if( msg == "stop" ) + ioService.stop(); + } - if( !publisher.connect(address, port) || !subscriber.connect(address, port) ) - { - std::cerr << "Can't connecto to redis" << std::endl; - return EXIT_FAILURE; - } + void publishHandler(RedisAsyncClient &publisher, const RedisValue &) + { + publisher.publish(channelName, "First hello", [&](const RedisValue &) { + publisher.publish(channelName, "Last hello", [&](const RedisValue &) { + publisher.publish(channelName, "stop"); + }); + }); + } - subscriber.subscribe(channelName, [&](const std::string &msg) { - std::cerr << "Message: " << msg << std::endl; + int main(int, char **) + { + boost::asio::ip::address address = boost::asio::ip::address::from_string("127.0.0.1"); + const unsigned short port = 6379; - if( msg == "stop" ) - ioService.stop(); - }); + boost::asio::io_service ioService; + RedisAsyncClient publisher(ioService); + RedisAsyncClient subscriber(ioService); - publisher.publish(channelName, "First hello", [&](const Value &) { - publisher.publish(channelName, "Last hello", [&](const Value &) { - publisher.publish(channelName, "stop"); - }); + publisher.asyncConnect(address, port, [&](bool status, const std::string &err) + { + if( !status ) + { + std::cerr << "Can't connect to to redis" << err << std::endl; + } + else + { + subscriber.asyncConnect(address, port, [&](bool status, const std::string &err) + { + if( !status ) + { + std::cerr << "Can't connect to to redis" << err << std::endl; + } + else + { + subscriber.subscribe(channelName, + boost::bind(&subscribeHandler, boost::ref(ioService), _1), + boost::bind(&publishHandler, boost::ref(publisher), _1)); + } + }); + } }); ioService.run(); @@ -85,6 +159,7 @@ Publish/subscribe example: return 0; } + Also you can build the library like a shared library. Just use -DREDIS_CLIENT_DYNLIB and -DREDIS_CLIENT_BUILD to build redisclient and -DREDIS_CLIENT_DYNLIB to build your project. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..7dc8d65 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 2.6) + +FIND_PACKAGE(Boost COMPONENTS system REQUIRED) + +SET(CMAKE_CXX_FLAGS "-W -Wall -Wextra -fPIC -pthread -std=c++11") +SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -DDEBUG ") +SET(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") + +SET(REDISCLIENT_SOURCES + ${CMAKE_SOURCE_DIR}/src/redisclient/impl/redisvalue.cpp + ${CMAKE_SOURCE_DIR}/src/redisclient/impl/redissyncclient.cpp + ${CMAKE_SOURCE_DIR}/src/redisclient/impl/redisparser.cpp + ${CMAKE_SOURCE_DIR}/src/redisclient/impl/redisclientimpl.cpp + ${CMAKE_SOURCE_DIR}/src/redisclient/impl/redisasyncclient.cpp + ${CMAKE_SOURCE_DIR}/src/redisclient/config.h + ${CMAKE_SOURCE_DIR}/src/redisclient/version.h + ${CMAKE_SOURCE_DIR}/src/redisclient/redisvalue.h + ${CMAKE_SOURCE_DIR}/src/redisclient/redissyncclient.h + ${CMAKE_SOURCE_DIR}/src/redisclient/redisparser.h + ${CMAKE_SOURCE_DIR}/src/redisclient/redisclient.h + ${CMAKE_SOURCE_DIR}/src/redisclient/redisbuffer.h + ${CMAKE_SOURCE_DIR}/src/redisclient/redisasyncclient.h + ${CMAKE_SOURCE_DIR}/src/redisclient/impl/redisclientimpl.h +) + +INCLUDE_DIRECTORIES( + ${Boost_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/src/ +) + +SET(EXAMPLES + async_pubsub2.cpp + async_pubsub.cpp + async_set_get2.cpp + async_set_get.cpp + sync_set_get.cpp +) + +FOREACH(EXAMPLE ${EXAMPLES}) + GET_FILENAME_COMPONENT(EXECUTABLE ${EXAMPLE} NAME_WE) + ADD_EXECUTABLE(${EXECUTABLE} ${EXAMPLE} ${REDISCLIENT_SOURCES}) + TARGET_LINK_LIBRARIES(${EXECUTABLE} ${Boost_LIBRARIES}) +ENDFOREACH(EXAMPLE) diff --git a/examples/Makefile b/examples/Makefile deleted file mode 100644 index 9ad143f..0000000 --- a/examples/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -LIBS = -lboost_system -LDFLAGS = -g -L.. -pthread -CXXFLAGS = -g -O2 -Wall -Wextra -I../src -CXX11FLAGS = -g -O2 -std=c++11 -Wall -Wextra -I../src - -all: examples -examples: example1 example2 example3 example4 example5 - -example1: $(OBJS) example1.o - $(CXX) $^ -o $@ $(LDFLAGS) $(LIBS) -example1.o: example1.cpp - $(CXX) $(CXXFLAGS) -c $< -o $@ - -example2: $(OBJS) example2.o - $(CXX) $^ -o $@ $(LDFLAGS) $(LIBS) - -example3: $(OBJS) example3.o - $(CXX) $^ -o $@ $(LDFLAGS) $(LIBS) - -example4: $(OBJS) example4.o - $(CXX) $^ -o $@ $(LDFLAGS) $(LIBS) - -example5: $(OBJS) example5.o - $(CXX) $^ -o $@ $(LDFLAGS) $(LIBS) - -%.o: %.cpp - $(CXX) $(CXX11FLAGS) -c $< -o $@ - -clean: - rm -f $(OBJS) example1.o example1 example2.o example2 example3.o example3 example4.o example4 example5.o example5 - -.PHONY: all examples clean diff --git a/examples/async_pubsub.cpp b/examples/async_pubsub.cpp new file mode 100644 index 0000000..957aa37 --- /dev/null +++ b/examples/async_pubsub.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include + +#include + +static const std::string channelName = "unique-redis-channel-name-example"; + +void subscribeHandler(boost::asio::io_service &ioService, const std::vector &buf) +{ + std::string msg(buf.begin(), buf.end()); + + std::cerr << "Message: " << msg << std::endl; + + if( msg == "stop" ) + ioService.stop(); +} + +void publishHandler(RedisAsyncClient &publisher, const RedisValue &) +{ + publisher.publish(channelName, "First hello", [&](const RedisValue &) { + publisher.publish(channelName, "Last hello", [&](const RedisValue &) { + publisher.publish(channelName, "stop"); + }); + }); +} + +int main(int, char **) +{ + boost::asio::ip::address address = boost::asio::ip::address::from_string("127.0.0.1"); + const unsigned short port = 6379; + + boost::asio::io_service ioService; + RedisAsyncClient publisher(ioService); + RedisAsyncClient subscriber(ioService); + + + publisher.asyncConnect(address, port, [&](bool status, const std::string &err) + { + if( !status ) + { + std::cerr << "Can't connect to to redis" << err << std::endl; + } + else + { + subscriber.asyncConnect(address, port, [&](bool status, const std::string &err) + { + if( !status ) + { + std::cerr << "Can't connect to to redis" << err << std::endl; + } + else + { + subscriber.subscribe(channelName, + boost::bind(&subscribeHandler, boost::ref(ioService), _1), + boost::bind(&publishHandler, boost::ref(publisher), _1)); + } + }); + } + }); + + ioService.run(); + + return 0; +} diff --git a/examples/async_pubsub2.cpp b/examples/async_pubsub2.cpp new file mode 100644 index 0000000..19eb022 --- /dev/null +++ b/examples/async_pubsub2.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include + +#include + +static const std::string channelName = "unique-redis-channel-name-example"; + + +class Client +{ +public: + Client(boost::asio::io_service &ioService) + : ioService(ioService) + {} + + void onMessage(const std::vector &buf) + { + std::string msg(buf.begin(), buf.end()); + + std::cerr << "Message: " << msg << std::endl; + + if( msg == "stop" ) + ioService.stop(); + } + +private: + boost::asio::io_service &ioService; +}; + +void publishHandler(RedisAsyncClient &publisher, const RedisValue &) +{ + publisher.publish(channelName, "First hello", [&](const RedisValue &) { + publisher.publish(channelName, "Last hello", [&](const RedisValue &) { + publisher.publish(channelName, "stop"); + }); + }); +} + +int main(int, char **) +{ + boost::asio::ip::address address = boost::asio::ip::address::from_string("127.0.0.1"); + const unsigned short port = 6379; + + boost::asio::io_service ioService; + RedisAsyncClient publisher(ioService); + RedisAsyncClient subscriber(ioService); + Client client(ioService); + + publisher.asyncConnect(address, port, [&](bool status, const std::string &err) + { + if( !status ) + { + std::cerr << "Can't connect to to redis" << err << std::endl; + } + else + { + subscriber.asyncConnect(address, port, [&](bool status, const std::string &err) + { + if( !status ) + { + std::cerr << "Can't connect to to redis" << err << std::endl; + } + else + { + subscriber.subscribe(channelName, + boost::bind(&Client::onMessage, &client, _1), + boost::bind(&publishHandler, boost::ref(publisher), _1)); + } + }); + } + }); + + ioService.run(); + + return 0; +} diff --git a/examples/example1.cpp b/examples/async_set_get.cpp similarity index 85% rename from examples/example1.cpp rename to examples/async_set_get.cpp index 8b0c820..56032d0 100644 --- a/examples/example1.cpp +++ b/examples/async_set_get.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include static const std::string redisKey = "unique-redis-key-example"; static const std::string redisValue = "unique-redis-value"; @@ -11,7 +11,7 @@ static const std::string redisValue = "unique-redis-value"; class Worker { public: - Worker(boost::asio::io_service &ioService, RedisClient &redisClient) + Worker(boost::asio::io_service &ioService, RedisAsyncClient &redisClient) : ioService(ioService), redisClient(redisClient) {} @@ -22,7 +22,7 @@ class Worker private: boost::asio::io_service &ioService; - RedisClient &redisClient; + RedisAsyncClient &redisClient; }; void Worker::onConnect(bool connected, const std::string &errorMessage) @@ -40,6 +40,7 @@ void Worker::onConnect(bool connected, const std::string &errorMessage) void Worker::onSet(const RedisValue &value) { + std::cerr << "SET: " << value.toString() << std::endl; if( value.toString() == "OK" ) { redisClient.command("GET", redisKey, @@ -53,6 +54,7 @@ void Worker::onSet(const RedisValue &value) void Worker::onGet(const RedisValue &value) { + std::cerr << "GET " << value.toString() << std::endl; if( value.toString() != redisValue ) { std::cerr << "Invalid value from redis: " << value.toString() << std::endl; @@ -69,7 +71,7 @@ int main(int, char **) const int port = 6379; boost::asio::io_service ioService; - RedisClient client(ioService); + RedisAsyncClient client(ioService); Worker worker(ioService, client); boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(address), port); diff --git a/examples/example5.cpp b/examples/async_set_get2.cpp similarity index 88% rename from examples/example5.cpp rename to examples/async_set_get2.cpp index eed5348..791001d 100644 --- a/examples/example5.cpp +++ b/examples/async_set_get2.cpp @@ -2,12 +2,13 @@ #include #include #include -#include + +#include static const std::string redisKey = "unique-redis-key-example"; static const std::string redisValue = "unique-redis-value"; -void handleConnected(boost::asio::io_service &ioService, RedisClient &redis, +void handleConnected(boost::asio::io_service &ioService, RedisAsyncClient &redis, bool ok, const std::string &errmsg) { if( ok ) @@ -36,7 +37,7 @@ int main(int, char **) const unsigned short port = 6379; boost::asio::io_service ioService; - RedisClient redis(ioService); + RedisAsyncClient redis(ioService); redis.asyncConnect(address, port, boost::bind(&handleConnected, boost::ref(ioService), boost::ref(redis), _1, _2)); diff --git a/examples/example1.pro b/examples/example1.pro new file mode 100644 index 0000000..fdbb889 --- /dev/null +++ b/examples/example1.pro @@ -0,0 +1,13 @@ +TEMPLATE = app +CONFIG += console +CONFIG -= app_bundle +CONFIG -= qt + +include(../globals.pri) +include(examples.pri) + +SOURCES += \ + example1.cpp + + + diff --git a/examples/example2.cpp b/examples/example2.cpp deleted file mode 100644 index 0c70427..0000000 --- a/examples/example2.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include -#include -#include - -static const std::string redisKey = "unique-redis-key-example"; -static const std::string redisValue = "unique-redis-value"; - -int main(int, char **) -{ - boost::asio::ip::address address = boost::asio::ip::address::from_string("127.0.0.1"); - const unsigned short port = 6379; - - boost::asio::io_service ioService; - RedisClient redis(ioService); - - if( !redis.connect(address, port) ) - { - std::cerr << "Can't connecto to redis" << std::endl; - return EXIT_FAILURE; - } - - redis.command("SET", redisKey, redisValue, [&](const RedisValue &v) { - std::cerr << "SET: " << v.toString() << std::endl; - - redis.command("GET", redisKey, [&](const RedisValue &v) { - std::cerr << "GET: " << v.toString() << std::endl; - - redis.command("DEL", redisKey, [&](const RedisValue &) { - ioService.stop(); - }); - }); - }); - - ioService.run(); - - return 0; -} diff --git a/examples/example2.pro b/examples/example2.pro new file mode 100644 index 0000000..f97ed12 --- /dev/null +++ b/examples/example2.pro @@ -0,0 +1,13 @@ +TEMPLATE = app +CONFIG += console +CONFIG -= app_bundle +CONFIG -= qt + +include(../globals.pri) +include(examples.pri) + +SOURCES += \ + example2.cpp + + + diff --git a/examples/example3.cpp b/examples/example3.cpp deleted file mode 100644 index 5be2a6a..0000000 --- a/examples/example3.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include -#include - -#include - -static const std::string channelName = "unique-redis-channel-name-example"; - -int main(int, char **) -{ - boost::asio::ip::address address = boost::asio::ip::address::from_string("127.0.0.1"); - const unsigned short port = 6379; - - boost::asio::io_service ioService; - RedisClient publisher(ioService); - RedisClient subscriber(ioService); - - if( !publisher.connect(address, port) || !subscriber.connect(address, port) ) - { - std::cerr << "Can't connecto to redis" << std::endl; - return EXIT_FAILURE; - } - - subscriber.subscribe(channelName, [&](const std::string &msg) { - std::cerr << "Message: " << msg << std::endl; - - if( msg == "stop" ) - ioService.stop(); - }); - - publisher.publish(channelName, "First hello", [&](const RedisValue &) { - publisher.publish(channelName, "Last hello", [&](const RedisValue &) { - publisher.publish(channelName, "stop"); - }); - }); - - ioService.run(); - - return 0; -} diff --git a/examples/example3.pro b/examples/example3.pro new file mode 100644 index 0000000..64624ed --- /dev/null +++ b/examples/example3.pro @@ -0,0 +1,13 @@ +TEMPLATE = app +CONFIG += console +CONFIG -= app_bundle +CONFIG -= qt + +include(../globals.pri) +include(examples.pri) + +SOURCES += \ + example3.cpp + + + diff --git a/examples/example4.cpp b/examples/example4.cpp deleted file mode 100644 index 8c1711a..0000000 --- a/examples/example4.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include -#include -#include -#include -#include // for boost::crc_32_type - -#include - - -#include - -static const std::string channelName = "unique-redis-channel-name-example"; - -int main(int, char **) -{ - boost::asio::ip::address address = boost::asio::ip::address::from_string("127.0.0.1"); - const unsigned short port = 6379; - - boost::asio::io_service ioService; - RedisClient publisher(ioService); - RedisClient subscriber(ioService); - - if( !publisher.connect(address, port) || !subscriber.connect(address, port) ) - { - std::cerr << "Can't connecto to redis" << std::endl; - return EXIT_FAILURE; - } - - size_t expectedCrc = 0; - - subscriber.subscribe(channelName, [&](const std::string &msg) { - boost::crc_32_type crc32; - crc32.process_bytes(msg.c_str(), msg.size()); - size_t checksum = crc32.checksum(); - - if( checksum != expectedCrc ) - std::cerr << "fail" << std::endl; - else - std::cerr << "ok" << std::endl; - ioService.stop(); - }); - - ioService.poll(); - - size_t size = 200000; - std::string msg; - - - for(size_t i = 0; i < size; ++i) - msg += (boost::format("Hello! Message number %1%") % i).str(); - - boost::crc_32_type crc32; - crc32.process_bytes(msg.c_str(), msg.size()); - expectedCrc = crc32.checksum(); - - publisher.publish(channelName, msg, [&](const RedisValue &) {}); - - ioService.run(); - - return 0; -} diff --git a/examples/example4.pro b/examples/example4.pro new file mode 100644 index 0000000..3a2de56 --- /dev/null +++ b/examples/example4.pro @@ -0,0 +1,13 @@ +TEMPLATE = app +CONFIG += console +CONFIG -= app_bundle +CONFIG -= qt + +include(../globals.pri) +include(examples.pri) + +SOURCES += \ + example4.cpp + + + diff --git a/examples/example5.pro b/examples/example5.pro new file mode 100644 index 0000000..09f857d --- /dev/null +++ b/examples/example5.pro @@ -0,0 +1,13 @@ +TEMPLATE = app +CONFIG += console +CONFIG -= app_bundle +CONFIG -= qt + +include(../globals.pri) +include(examples.pri) + +SOURCES += \ + example5.cpp + + + diff --git a/examples/examples.pri b/examples/examples.pri new file mode 100644 index 0000000..cc29ea4 --- /dev/null +++ b/examples/examples.pri @@ -0,0 +1,2 @@ +DEPENDPATH += ../src +INCLUDEPATH += ../src diff --git a/examples/sync_set_get.cpp b/examples/sync_set_get.cpp new file mode 100644 index 0000000..660e776 --- /dev/null +++ b/examples/sync_set_get.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include + +int main(int, char **) +{ + boost::asio::ip::address address = boost::asio::ip::address::from_string("127.0.0.1"); + const unsigned short port = 6379; + + boost::asio::io_service ioService; + RedisSyncClient redis(ioService); + std::string errmsg; + + if( !redis.connect(address, port, errmsg) ) + { + std::cerr << "Can't connect to redis: " << errmsg << std::endl; + return EXIT_FAILURE; + } + + RedisValue result; + + result = redis.command("SET", "key", "value"); + + if( result.isError() ) + { + std::cerr << "SET error: " << result.toString() << "\n"; + return EXIT_FAILURE; + } + + result = redis.command("GET", "key"); + + if( result.isOk() ) + { + std::cout << "GET: " << result.toString() << "\n"; + return EXIT_SUCCESS; + } + else + { + std::cerr << "GET error: " << result.toString() << "\n"; + return EXIT_FAILURE; + } +} diff --git a/globals.pri b/globals.pri new file mode 100644 index 0000000..a28a398 --- /dev/null +++ b/globals.pri @@ -0,0 +1,8 @@ +CONFIG += c++11 + +win32:LIBS += -Lc:/local/boost_1_56_0/lib32-msvc-12.0/ + +win32:INCLUDEPATH += c:/local/boost_1_56_0 +win32:DEPENDPATH += c:/local/boost_1_56_0 + +unix:LIBS += -lboost_system diff --git a/redisclient.pro b/redisclient.pro new file mode 100644 index 0000000..90c5e6e --- /dev/null +++ b/redisclient.pro @@ -0,0 +1,9 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + examples/example1.pro \ + examples/example2.pro \ + examples/example3.pro \ + examples/example4.pro \ + examples/example5.pro \ + src/redisclient.pro diff --git a/src/redisclient.pro b/src/redisclient.pro new file mode 100644 index 0000000..13311f6 --- /dev/null +++ b/src/redisclient.pro @@ -0,0 +1,26 @@ +QT -= core gui + +TARGET = redisclient +TEMPLATE = lib +CONFIG += staticlib + +include(../globals.pri) + + +HEADERS += \ + redisclient/config.h \ + redisclient/redisasyncclient.h \ + redisclient/redisbuffer.h \ + redisclient/redisclient.h \ + redisclient/redisparser.h \ + redisclient/redissyncclient.h \ + redisclient/redisvalue.h \ + redisclient/version.h + +SOURCES += \ + redisclient/impl/redisasyncclient.cpp \ + redisclient/impl/redisclientimpl.cpp \ + redisclient/impl/redisparser.cpp \ + redisclient/impl/redissyncclient.cpp \ + redisclient/impl/redisvalue.cpp + diff --git a/src/redisclient/impl/redisclient.cpp b/src/redisclient/impl/redisasyncclient.cpp similarity index 51% rename from src/redisclient/impl/redisclient.cpp rename to src/redisclient/impl/redisasyncclient.cpp index 8255c6f..3633d24 100644 --- a/src/redisclient/impl/redisclient.cpp +++ b/src/redisclient/impl/redisasyncclient.cpp @@ -3,53 +3,33 @@ * License: MIT */ -#ifndef REDISCLIENT_REDISCLIENT_CPP -#define REDISCLIENT_REDISCLIENT_CPP +#ifndef REDISASYNCCLIENT_REDISASYNCCLIENT_CPP +#define REDISASYNCCLIENT_REDISASYNCCLIENT_CPP #include #include "../redisclient.h" -RedisClient::RedisClient(boost::asio::io_service &ioService) +RedisAsyncClient::RedisAsyncClient(boost::asio::io_service &ioService) : pimpl(boost::make_shared(boost::ref(ioService))) { pimpl->errorHandler = boost::bind(&RedisClientImpl::defaulErrorHandler, pimpl, _1); } -RedisClient::~RedisClient() +RedisAsyncClient::~RedisAsyncClient() { pimpl->close(); } -bool RedisClient::connect(const boost::asio::ip::address &address, - unsigned short port) -{ - boost::asio::ip::tcp::endpoint endpoint(address, port); - boost::system::error_code ec; - - pimpl->socket.connect(endpoint, ec); - - if( !ec ) - { - pimpl->state = RedisClientImpl::Connected; - pimpl->processMessage(); - return true; - } - else - { - return false; - } -} - -void RedisClient::asyncConnect(const boost::asio::ip::address &address, +void RedisAsyncClient::connect(const boost::asio::ip::address &address, unsigned short port, const boost::function &handler) { boost::asio::ip::tcp::endpoint endpoint(address, port); - asyncConnect(endpoint, handler); + connect(endpoint, handler); } -void RedisClient::asyncConnect(const boost::asio::ip::tcp::endpoint &endpoint, +void RedisAsyncClient::connect(const boost::asio::ip::tcp::endpoint &endpoint, const boost::function &handler) { pimpl->socket.async_connect(endpoint, boost::bind(&RedisClientImpl::handleAsyncConnect, @@ -57,93 +37,98 @@ void RedisClient::asyncConnect(const boost::asio::ip::tcp::endpoint &endpoint, } -void RedisClient::installErrorHandler( +void RedisAsyncClient::installErrorHandler( const boost::function &handler) { pimpl->errorHandler = handler; } -void RedisClient::command(const std::string &s, const boost::function &handler) +void RedisAsyncClient::command(const std::string &s, const boost::function &handler) { if(stateValid()) { - std::vector items(1); + std::vector items(1); items[0] = s; - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); } } -void RedisClient::command(const std::string &cmd, const std::string &arg1, +void RedisAsyncClient::command(const std::string &cmd, const RedisBuffer &arg1, const boost::function &handler) { if(stateValid()) { - std::vector items(2); + std::vector items(2); items[0] = cmd; items[1] = arg1; - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); } } -void RedisClient::command(const std::string &cmd, const std::string &arg1, - const std::string &arg2, +void RedisAsyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const boost::function &handler) { if(stateValid()) { - std::vector items(3); + std::vector items(3); items[0] = cmd; items[1] = arg1; items[2] = arg2; - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); } } -void RedisClient::command(const std::string &cmd, const std::string &arg1, - const std::string &arg2, const std::string &arg3, +void RedisAsyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, const boost::function &handler) { if(stateValid()) { - std::vector items(4); + std::vector items(4); items[0] = cmd; items[1] = arg1; items[2] = arg2; items[3] = arg3; - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); } } -void RedisClient::command(const std::string &cmd, const std::string &arg1, - const std::string &arg2, const std::string &arg3, - const std::string &arg4, +void RedisAsyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const boost::function &handler) { if(stateValid()) { - std::vector items(5); + std::vector items(5); items[0] = cmd; items[1] = arg1; items[2] = arg2; items[3] = arg3; items[4] = arg4; - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); } } -void RedisClient::command(const std::string &cmd, const std::string &arg1, - const std::string &arg2, const std::string &arg3, - const std::string &arg4, const std::string &arg5, +void RedisAsyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5, const boost::function &handler) { if(stateValid()) { - std::vector items(6); + std::vector items(6); items[0] = cmd; items[1] = arg1; items[2] = arg2; @@ -151,19 +136,20 @@ void RedisClient::command(const std::string &cmd, const std::string &arg1, items[4] = arg4; items[5] = arg5; - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); } } -void RedisClient::command(const std::string &cmd, const std::string &arg1, - const std::string &arg2, const std::string &arg3, - const std::string &arg4, const std::string &arg5, - const std::string &arg6, +void RedisAsyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5, + const RedisBuffer &arg6, const boost::function &handler) { if(stateValid()) { - std::vector items(7); + std::vector items(7); items[0] = cmd; items[1] = arg1; items[2] = arg2; @@ -172,19 +158,20 @@ void RedisClient::command(const std::string &cmd, const std::string &arg1, items[5] = arg5; items[6] = arg6; - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); } } -void RedisClient::command(const std::string &cmd, const std::string &arg1, - const std::string &arg2, const std::string &arg3, - const std::string &arg4, const std::string &arg5, - const std::string &arg6, const std::string &arg7, +void RedisAsyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5, + const RedisBuffer &arg6, const RedisBuffer &arg7, const boost::function &handler) { if(stateValid()) { - std::vector items(8); + std::vector items(8); items[0] = cmd; items[1] = arg1; items[2] = arg2; @@ -194,44 +181,47 @@ void RedisClient::command(const std::string &cmd, const std::string &arg1, items[6] = arg6; items[7] = arg7; - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); } } -void RedisClient::command(const std::string &cmd, const std::list &args, +void RedisAsyncClient::command(const std::string &cmd, const std::list &args, const boost::function &handler) { if(stateValid()) { - std::vector items(1); + std::vector items(1); items[0] = cmd; items.reserve(1 + args.size()); std::copy(args.begin(), args.end(), std::back_inserter(items)); - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); } } -RedisClient::Handle RedisClient::subscribe( +RedisAsyncClient::Handle RedisAsyncClient::subscribe( const std::string &channel, - const boost::function &msgHandler, + const boost::function &msg)> &msgHandler, const boost::function &handler) { assert( pimpl->state == RedisClientImpl::Connected || pimpl->state == RedisClientImpl::Subscribed); - static std::string subscribe = "SUBSCRIBE"; + static const std::string subscribeStr = "SUBSCRIBE"; if( pimpl->state == RedisClientImpl::Connected || pimpl->state == RedisClientImpl::Subscribed ) { Handle handle = {pimpl->subscribeSeq++, channel}; - std::vector items(2); - items[0] = subscribe; + std::vector items(2); + items[0] = subscribeStr; items[1] = channel; - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); pimpl->msgHandlers.insert(std::make_pair(channel, std::make_pair(handle.id, msgHandler))); pimpl->state = RedisClientImpl::Subscribed; @@ -241,15 +231,15 @@ RedisClient::Handle RedisClient::subscribe( { std::stringstream ss; - ss << "RedisClient::command called with invalid state " + ss << "RedisAsyncClient::command called with invalid state " << pimpl->state; - pimpl->onError(ss.str()); + pimpl->errorHandler(ss.str()); return Handle(); } } -void RedisClient::unsubscribe(const Handle &handle) +void RedisAsyncClient::unsubscribe(const Handle &handle) { #ifdef DEBUG static int recursion = 0; @@ -259,7 +249,7 @@ void RedisClient::unsubscribe(const Handle &handle) assert( pimpl->state == RedisClientImpl::Connected || pimpl->state == RedisClientImpl::Subscribed); - static std::string unsubscribe = "UNSUBSCRIBE"; + static const std::string unsubscribeStr = "UNSUBSCRIBE"; if( pimpl->state == RedisClientImpl::Connected || pimpl->state == RedisClientImpl::Subscribed ) @@ -280,24 +270,25 @@ void RedisClient::unsubscribe(const Handle &handle) } } - std::vector items(2); - items[0] = unsubscribe; + std::vector items(2); + items[0] = unsubscribeStr; items[1] = handle.channel; // Unsubscribe command for Redis - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, dummyHandler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), dummyHandler)); } else { std::stringstream ss; - ss << "RedisClient::command called with invalid state " + ss << "RedisAsyncClient::command called with invalid state " << pimpl->state; #ifdef DEBUG --recursion; #endif - pimpl->onError(ss.str()); + pimpl->errorHandler(ss.str()); return; } @@ -306,23 +297,24 @@ void RedisClient::unsubscribe(const Handle &handle) #endif } -void RedisClient::singleShotSubscribe(const std::string &channel, - const boost::function &msgHandler, +void RedisAsyncClient::singleShotSubscribe(const std::string &channel, + const boost::function &msg)> &msgHandler, const boost::function &handler) { assert( pimpl->state == RedisClientImpl::Connected || pimpl->state == RedisClientImpl::Subscribed); - static std::string subscribe = "SUBSCRIBE"; + static const std::string subscribeStr = "SUBSCRIBE"; if( pimpl->state == RedisClientImpl::Connected || pimpl->state == RedisClientImpl::Subscribed ) { - std::vector items(2); - items[0] = subscribe; + std::vector items(2); + items[0] = subscribeStr; items[1] = channel; - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); pimpl->singleShotMsgHandlers.insert(std::make_pair(channel, msgHandler)); pimpl->state = RedisClientImpl::Subscribed; } @@ -330,43 +322,44 @@ void RedisClient::singleShotSubscribe(const std::string &channel, { std::stringstream ss; - ss << "RedisClient::command called with invalid state " + ss << "RedisAsyncClient::command called with invalid state " << pimpl->state; - pimpl->onError(ss.str()); + pimpl->errorHandler(ss.str()); } } -void RedisClient::publish(const std::string &channel, const std::string &msg, +void RedisAsyncClient::publish(const std::string &channel, const RedisBuffer &msg, const boost::function &handler) { assert( pimpl->state == RedisClientImpl::Connected ); - static std::string publish = "PUBLISH"; + static const std::string publishStr = "PUBLISH"; if( pimpl->state == RedisClientImpl::Connected ) { - std::vector items(3); + std::vector items(3); - items[0] = publish; + items[0] = publishStr; items[1] = channel; items[2] = msg; - pimpl->post(boost::bind(&RedisClientImpl::doCommand, pimpl, items, handler)); + pimpl->post(boost::bind(&RedisClientImpl::doAsyncCommand, pimpl, + pimpl->makeCommand(items), handler)); } else { std::stringstream ss; - ss << "RedisClient::command called with invalid state " + ss << "RedisAsyncClient::command called with invalid state " << pimpl->state; - pimpl->onError(ss.str()); + pimpl->errorHandler(ss.str()); } } -bool RedisClient::stateValid() const +bool RedisAsyncClient::stateValid() const { assert( pimpl->state == RedisClientImpl::Connected ); @@ -374,14 +367,14 @@ bool RedisClient::stateValid() const { std::stringstream ss; - ss << "RedisClient::command called with invalid state " + ss << "RedisAsyncClient::command called with invalid state " << pimpl->state; - pimpl->onError(ss.str()); + pimpl->errorHandler(ss.str()); return false; } return true; } -#endif // REDISCLIENT_REDISCLIENT_CPP +#endif // REDISASYNCCLIENT_REDISASYNCCLIENT_CPP diff --git a/src/redisclient/impl/redisclientimpl.cpp b/src/redisclient/impl/redisclientimpl.cpp index bc2a3f7..b23ed8c 100644 --- a/src/redisclient/impl/redisclientimpl.cpp +++ b/src/redisclient/impl/redisclientimpl.cpp @@ -30,9 +30,8 @@ void RedisClientImpl::close() { boost::system::error_code ignored_ec; - errorHandler = boost::function(); + errorHandler = boost::bind(&RedisClientImpl::ignoreErrorHandler, _1); socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); - socket.close(ignored_ec); state = RedisClientImpl::Closed; } } @@ -56,24 +55,27 @@ void RedisClientImpl::doProcessMessage(const RedisValue &v) if( result.size() == 3 ) { const RedisValue &command = result[0]; - const RedisValue &queue = result[1]; + const RedisValue &queueName = result[1]; const RedisValue &value = result[2]; const std::string &cmd = command.toString(); if( cmd == "message" ) { - SingleShotHandlersMap::iterator it = singleShotMsgHandlers.find(queue.toString()); + SingleShotHandlersMap::iterator it = singleShotMsgHandlers.find(queueName.toString()); if( it != singleShotMsgHandlers.end() ) { - strand.post(boost::bind(it->second, value.toString())); + strand.post(boost::bind(it->second, value.toByteArray())); singleShotMsgHandlers.erase(it); } std::pair pair = - msgHandlers.equal_range(queue.toString()); - for(MsgHandlersMap::iterator it = pair.first; it != pair.second; ++it) - strand.post(boost::bind(it->second.second, value.toString())); + msgHandlers.equal_range(queueName.toString()); + for(MsgHandlersMap::iterator handlerIt = pair.first; + handlerIt != pair.second; ++handlerIt) + { + strand.post(boost::bind(handlerIt->second.second, value.toByteArray())); + } } else if( cmd == "subscribe" && handlers.empty() == false ) { @@ -92,13 +94,13 @@ void RedisClientImpl::doProcessMessage(const RedisValue &v) ss << "[RedisClient] invalid command: " << command.toString(); - onError(ss.str()); + errorHandler(ss.str()); return; } } else { - onError("[RedisClient] Protocol error"); + errorHandler("[RedisClient] Protocol error"); return; } } @@ -116,7 +118,7 @@ void RedisClientImpl::doProcessMessage(const RedisValue &v) ss << "[RedisClient] unexpected message: " << v.inspect(); - onError(ss.str()); + errorHandler(ss.str()); return; } } @@ -126,7 +128,7 @@ void RedisClientImpl::asyncWrite(const boost::system::error_code &ec, const size { if( ec ) { - onError(ec.message()); + errorHandler(ec.message()); return; } @@ -159,32 +161,87 @@ void RedisClientImpl::handleAsyncConnect(const boost::system::error_code &ec, } } -void RedisClientImpl::doCommand(const std::vector &command, - const boost::function &handler) +std::vector RedisClientImpl::makeCommand(const std::vector &items) { - - using boost::system::error_code; static const char crlf[] = {'\r', '\n'}; - QueueItem item; - - item.buff.reset( new std::vector() ); - item.handler = handler; - queue.push(item); + std::vector result; - append(*item.buff.get(), '*'); - append(*item.buff.get(), boost::lexical_cast(command.size())); - append(*item.buff.get(), crlf); + append(result, '*'); + append(result, boost::lexical_cast(items.size())); + append<>(result, crlf); - std::vector::const_iterator it = command.begin(), end = command.end(); + std::vector::const_iterator it = items.begin(), end = items.end(); for(; it != end; ++it) { - append(*item.buff.get(), '$'); - append(*item.buff.get(), boost::lexical_cast(it->size())); - append(*item.buff.get(), crlf); - append(*item.buff.get(), boost::lexical_cast(*it)); - append(*item.buff.get(), crlf); + append(result, '$'); + append(result, boost::lexical_cast(it->size())); + append<>(result, crlf); + append(result, *it); + append<>(result, crlf); + } + + return result; +} + +RedisValue RedisClientImpl::doSyncCommand(const std::vector &buff) +{ + assert( queue.empty() ); + + boost::system::error_code ec; + + + { + std::vector data = makeCommand(buff); + boost::asio::write(socket, boost::asio::buffer(data), boost::asio::transfer_all(), ec); + } + + if( ec ) + { + errorHandler(ec.message()); + return RedisValue(); + } + else + { + boost::array inbuff; + + for(;;) + { + size_t size = socket.read_some(boost::asio::buffer(inbuff)); + + for(size_t pos = 0; pos < size;) + { + std::pair result = + redisParser.parse(inbuff.data() + pos, size - pos); + + if( result.second == RedisParser::Completed ) + { + return redisParser.result(); + } + else if( result.second == RedisParser::Incompleted ) + { + continue; + } + else + { + errorHandler("[RedisClient] Parser error"); + return RedisValue(); + } + + pos += result.first; + } + } } +} + +void RedisClientImpl::doAsyncCommand(const std::vector &buff, + const boost::function &handler) +{ + QueueItem item; + + item.buff.reset( new std::vector(buff) ); + item.handler = handler; + queue.push(item); handlers.push( item.handler ); @@ -200,7 +257,7 @@ void RedisClientImpl::asyncRead(const boost::system::error_code &ec, const size_ { if( ec || size == 0 ) { - onError(ec.message()); + errorHandler(ec.message()); return; } @@ -219,7 +276,7 @@ void RedisClientImpl::asyncRead(const boost::system::error_code &ec, const size_ } else { - onError("[RedisClient] Parser error"); + errorHandler("[RedisClient] Parser error"); return; } @@ -232,26 +289,26 @@ void RedisClientImpl::asyncRead(const boost::system::error_code &ec, const size_ void RedisClientImpl::onRedisError(const RedisValue &v) { - if( errorHandler ) - { - std::string message = v.toString(); - errorHandler(message); - } + std::string message = v.toString(); + errorHandler(message); } -void RedisClientImpl::onError(const std::string &s) -{ - if( errorHandler ) - { - errorHandler(s); - } -} void RedisClientImpl::defaulErrorHandler(const std::string &s) { throw std::runtime_error(s); } +void RedisClientImpl::ignoreErrorHandler(const std::string &) +{ + // empty +} + +void RedisClientImpl::append(std::vector &vec, const RedisBuffer &buf) +{ + vec.insert(vec.end(), buf.data(), buf.data() + buf.size()); +} + void RedisClientImpl::append(std::vector &vec, const std::string &s) { vec.insert(vec.end(), s.begin(), s.end()); @@ -263,7 +320,7 @@ void RedisClientImpl::append(std::vector &vec, const char *s) } template -void RedisClientImpl::append(std::vector &vec, const char s[size]) +void RedisClientImpl::append(std::vector &vec, const char (&s)[size]) { vec.insert(vec.end(), s, s + size); } diff --git a/src/redisclient/impl/redisclientimpl.h b/src/redisclient/impl/redisclientimpl.h index 1ea8db9..e6feeff 100644 --- a/src/redisclient/impl/redisclientimpl.h +++ b/src/redisclient/impl/redisclientimpl.h @@ -18,8 +18,8 @@ #include #include -#include "../redisclient.h" #include "../redisparser.h" +#include "../redisbuffer.h" #include "../config.h" class RedisClientImpl : public boost::enable_shared_from_this { @@ -33,8 +33,12 @@ class RedisClientImpl : public boost::enable_shared_from_this { REDIS_CLIENT_DECL void close(); - REDIS_CLIENT_DECL void doCommand( - const std::vector &command, + REDIS_CLIENT_DECL static std::vector makeCommand(const std::vector &items); + + REDIS_CLIENT_DECL RedisValue doSyncCommand(const std::vector &buff); + + REDIS_CLIENT_DECL void doAsyncCommand( + const std::vector &buff, const boost::function &handler); REDIS_CLIENT_DECL void sendNextCommand(); @@ -44,14 +48,15 @@ class RedisClientImpl : public boost::enable_shared_from_this { REDIS_CLIENT_DECL void asyncRead(const boost::system::error_code &ec, const size_t); REDIS_CLIENT_DECL void onRedisError(const RedisValue &); - REDIS_CLIENT_DECL void onError(const std::string &s); REDIS_CLIENT_DECL void defaulErrorHandler(const std::string &s); + REDIS_CLIENT_DECL static void ignoreErrorHandler(const std::string &s); + REDIS_CLIENT_DECL static void append(std::vector &vec, const RedisBuffer &buf); REDIS_CLIENT_DECL static void append(std::vector &vec, const std::string &s); REDIS_CLIENT_DECL static void append(std::vector &vec, const char *s); REDIS_CLIENT_DECL static void append(std::vector &vec, char c); template - REDIS_CLIENT_DECL static void append(std::vector &vec, const char s[size]); + REDIS_CLIENT_DECL static void append(std::vector &vec, const char (&s)[size]); template REDIS_CLIENT_DECL void post(const Handler &handler); @@ -69,8 +74,8 @@ class RedisClientImpl : public boost::enable_shared_from_this { boost::array buf; size_t subscribeSeq; - typedef std::pair > MsgHandlerType; - typedef boost::function SingleShotHandlerType; + typedef std::pair &buf)> > MsgHandlerType; + typedef boost::function &buf)> SingleShotHandlerType; typedef std::multimap MsgHandlersMap; typedef std::multimap SingleShotHandlersMap; diff --git a/src/redisclient/impl/redisparser.cpp b/src/redisclient/impl/redisparser.cpp index 18fce17..e57a51b 100644 --- a/src/redisclient/impl/redisparser.cpp +++ b/src/redisclient/impl/redisparser.cpp @@ -117,7 +117,7 @@ std::pair RedisParser::parseChunk(const char * switch(state) { case Start: - string.clear(); + buf.clear(); switch(c) { case stringReply: @@ -148,7 +148,7 @@ std::pair RedisParser::parseChunk(const char * } else if( isChar(c) && !isControl(c) ) { - string += c; + buf.push_back(c); } else { @@ -163,7 +163,7 @@ std::pair RedisParser::parseChunk(const char * } else if( isChar(c) && !isControl(c) ) { - string += c; + buf.push_back(c); } else { @@ -174,7 +174,7 @@ std::pair RedisParser::parseChunk(const char * case BulkSize: if( c == '\r' ) { - if( string.empty() ) + if( buf.empty() ) { state = Start; return std::make_pair(i + 1, Error); @@ -186,7 +186,7 @@ std::pair RedisParser::parseChunk(const char * } else if( isdigit(c) || c == '-' ) { - string += c; + buf.push_back(c); } else { @@ -195,11 +195,24 @@ std::pair RedisParser::parseChunk(const char * } break; case StringLF: + if( c == '\n') + { + state = Start; + valueStack.push(buf); + return std::make_pair(i + 1, Completed); + } + else + { + state = Start; + return std::make_pair(i + 1, Error); + } + break; case ErrorLF: if( c == '\n') { state = Start; - valueStack.push(string); + RedisValue::ErrorTag tag; + valueStack.push(RedisValue(buf, tag)); return std::make_pair(i + 1, Completed); } else @@ -211,8 +224,10 @@ std::pair RedisParser::parseChunk(const char * case BulkSizeLF: if( c == '\n' ) { - bulkSize = strtol(string.c_str(), 0, 10); - string.clear(); + // TODO optimize me + std::string tmp(buf.begin(), buf.end()); + bulkSize = strtol(tmp.c_str(), 0, 10); + buf.clear(); if( bulkSize == -1 ) { @@ -231,14 +246,14 @@ std::pair RedisParser::parseChunk(const char * } else { - string.reserve(bulkSize); + buf.reserve(bulkSize); long int available = size - i - 1; long int canRead = std::min(bulkSize, available); if( canRead > 0 ) { - string.assign(ptr + i + 1, ptr + i + canRead + 1); + buf.assign(ptr + i + 1, ptr + i + canRead + 1); } i += canRead; @@ -267,7 +282,7 @@ std::pair RedisParser::parseChunk(const char * long int available = size - i; long int canRead = std::min(available, bulkSize); - string.insert(string.end(), ptr + i, ptr + canRead); + buf.insert(buf.end(), ptr + i, ptr + canRead); bulkSize -= canRead; i += canRead - 1; @@ -301,7 +316,7 @@ std::pair RedisParser::parseChunk(const char * if( c == '\n') { state = Start; - valueStack.push(string); + valueStack.push(buf); return std::make_pair(i + 1, Completed); } else @@ -313,7 +328,7 @@ std::pair RedisParser::parseChunk(const char * case ArraySize: if( c == '\r' ) { - if( string.empty() ) + if( buf.empty() ) { state = Start; return std::make_pair(i + 1, Error); @@ -325,7 +340,7 @@ std::pair RedisParser::parseChunk(const char * } else if( isdigit(c) || c == '-' ) { - string += c; + buf.push_back(c); } else { @@ -336,8 +351,10 @@ std::pair RedisParser::parseChunk(const char * case ArraySizeLF: if( c == '\n' ) { - long int arraySize = strtol(string.c_str(), 0, 10); - string.clear(); + // TODO optimize me + std::string tmp(buf.begin(), buf.end()); + long int arraySize = strtol(tmp.c_str(), 0, 10); + buf.clear(); std::vector array; if( arraySize == -1 || arraySize == 0) @@ -361,9 +378,9 @@ std::pair RedisParser::parseChunk(const char * if( i + 1 != size ) { - std::pair result = parseArray(ptr + i + 1, size - i - 1); - result.first += i + 1; - return result; + std::pair parseResult = parseArray(ptr + i + 1, size - i - 1); + parseResult.first += i + 1; + return parseResult; } else { @@ -380,7 +397,7 @@ std::pair RedisParser::parseChunk(const char * case Integer: if( c == '\r' ) { - if( string.empty() ) + if( buf.empty() ) { state = Start; return std::make_pair(i + 1, Error); @@ -392,7 +409,7 @@ std::pair RedisParser::parseChunk(const char * } else if( isdigit(c) || c == '-' ) { - string += c; + buf.push_back(c); } else { @@ -403,9 +420,11 @@ std::pair RedisParser::parseChunk(const char * case IntegerLF: if( c == '\n' ) { - long int value = strtol(string.c_str(), 0, 10); + // TODO optimize me + std::string tmp(buf.begin(), buf.end()); + long int value = strtol(tmp.c_str(), 0, 10); - string.clear(); + buf.clear(); valueStack.push(value); state = Start; diff --git a/src/redisclient/impl/redissyncclient.cpp b/src/redisclient/impl/redissyncclient.cpp new file mode 100644 index 0000000..9ded16a --- /dev/null +++ b/src/redisclient/impl/redissyncclient.cpp @@ -0,0 +1,264 @@ +/* + * Copyright (C) Alex Nekipelov (alex@nekipelov.net) + * License: MIT + */ + +#ifndef REDISCLIENT_REDISSYNCCLIENT_CPP +#define REDISCLIENT_REDISSYNCCLIENT_CPP + +#include +#include "../redissyncclient.h" + +RedisSyncClient::RedisSyncClient(boost::asio::io_service &ioService) + : pimpl(boost::make_shared(boost::ref(ioService))) +{ + pimpl->errorHandler = boost::bind(&RedisClientImpl::defaulErrorHandler, + pimpl, _1); +} + +RedisSyncClient::~RedisSyncClient() +{ + pimpl->close(); +} + +bool RedisSyncClient::connect(const boost::asio::ip::tcp::endpoint &endpoint, + std::string &errmsg) +{ + boost::system::error_code ec; + + pimpl->socket.open(endpoint.protocol(), ec); + + if( !ec ) + { + pimpl->socket.set_option(boost::asio::ip::tcp::no_delay(true), ec); + + if( !ec ) + { + pimpl->socket.connect(endpoint, ec); + } + } + + if( !ec ) + { + pimpl->state = RedisClientImpl::Connected; + return true; + } + else + { + errmsg = ec.message(); + return false; + } +} + +bool RedisSyncClient::connect(const boost::asio::ip::address &address, + unsigned short port, + std::string &errmsg) +{ + boost::asio::ip::tcp::endpoint endpoint(address, port); + + return connect(endpoint, errmsg); +} + +void RedisSyncClient::installErrorHandler( + const boost::function &handler) +{ + pimpl->errorHandler = handler; +} + +RedisValue RedisSyncClient::command(const std::string &s) +{ + if(stateValid()) + { + std::vector items(1); + items[0] = s; + + return pimpl->doSyncCommand(items); + } + else + { + return RedisValue(); + } +} + +RedisValue RedisSyncClient::command(const std::string &cmd, const RedisBuffer &arg1) +{ + if(stateValid()) + { + std::vector items(2); + items[0] = cmd; + items[1] = arg1; + + return pimpl->doSyncCommand(items); + } + else + { + return RedisValue(); + } +} + +RedisValue RedisSyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2) +{ + if(stateValid()) + { + std::vector items(3); + items[0] = cmd; + items[1] = arg1; + items[2] = arg2; + + return pimpl->doSyncCommand(items); + } + else + { + return RedisValue(); + } +} + +RedisValue RedisSyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3) +{ + if(stateValid()) + { + std::vector items(4); + items[0] = cmd; + items[1] = arg1; + items[2] = arg2; + items[3] = arg3; + + return pimpl->doSyncCommand(items); + } + else + { + return RedisValue(); + } +} + +RedisValue RedisSyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4) +{ + if(stateValid()) + { + std::vector items(5); + items[0] = cmd; + items[1] = arg1; + items[2] = arg2; + items[3] = arg3; + items[4] = arg4; + + return pimpl->doSyncCommand(items); + } + else + { + return RedisValue(); + } +} + +RedisValue RedisSyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5) +{ + if(stateValid()) + { + std::vector items(6); + items[0] = cmd; + items[1] = arg1; + items[2] = arg2; + items[3] = arg3; + items[4] = arg4; + items[5] = arg5; + + return pimpl->doSyncCommand(items); + } + else + { + return RedisValue(); + } +} + +RedisValue RedisSyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5, + const RedisBuffer &arg6) +{ + if(stateValid()) + { + std::vector items(7); + items[0] = cmd; + items[1] = arg1; + items[2] = arg2; + items[3] = arg3; + items[4] = arg4; + items[5] = arg5; + items[6] = arg6; + + return pimpl->doSyncCommand(items); + } + else + { + return RedisValue(); + } +} + +RedisValue RedisSyncClient::command(const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5, + const RedisBuffer &arg6, const RedisBuffer &arg7) +{ + if(stateValid()) + { + std::vector items(8); + items[0] = cmd; + items[1] = arg1; + items[2] = arg2; + items[3] = arg3; + items[4] = arg4; + items[5] = arg5; + items[6] = arg6; + items[7] = arg7; + + return pimpl->doSyncCommand(items); + } + else + { + return RedisValue(); + } +} + +RedisValue RedisSyncClient::command(const std::string &cmd, const std::list &args) +{ + if(stateValid()) + { + std::vector items(1); + items[0] = cmd; + + items.reserve(1 + args.size()); + + std::copy(args.begin(), args.end(), std::back_inserter(items)); + return pimpl->doSyncCommand(items); + } + else + { + return RedisValue(); + } +} + +bool RedisSyncClient::stateValid() const +{ + assert( pimpl->state == RedisClientImpl::Connected ); + + if( pimpl->state != RedisClientImpl::Connected ) + { + std::stringstream ss; + + ss << "RedisClient::command called with invalid state " + << pimpl->state; + + pimpl->errorHandler(ss.str()); + return false; + } + + return true; +} + +#endif // REDISCLIENT_REDISSYNCCLIENT_CPP diff --git a/src/redisclient/impl/redisvalue.cpp b/src/redisclient/impl/redisvalue.cpp index cc239e9..c8aaf1b 100644 --- a/src/redisclient/impl/redisvalue.cpp +++ b/src/redisclient/impl/redisvalue.cpp @@ -6,31 +6,42 @@ #ifndef REDISCLIENT_REDISVALUE_CPP #define REDISCLIENT_REDISVALUE_CPP +#include #include #include "../redisvalue.h" RedisValue::RedisValue() - : value(NullTag()) + : value(NullTag()), error(false) { } RedisValue::RedisValue(int i) - : value(i) + : value(i), error(false) { } RedisValue::RedisValue(const char *s) - : value( std::string(s) ) + : value( std::vector(s, s + strlen(s)) ), error(false) { } RedisValue::RedisValue(const std::string &s) - : value(s) + : value( std::vector(s.begin(), s.end()) ), error(false) +{ +} + +RedisValue::RedisValue(const std::vector &buf) + : value(buf), error(false) +{ +} + +RedisValue::RedisValue(const std::vector &buf, struct ErrorTag &) + : value(buf), error(true) { } RedisValue::RedisValue(const std::vector &array) - : value(array) + : value(array), error(false) { } @@ -41,7 +52,13 @@ std::vector RedisValue::toArray() const std::string RedisValue::toString() const { - return castTo(); + const std::vector &buf = toByteArray(); + return std::string(buf.begin(), buf.end()); +} + +std::vector RedisValue::toByteArray() const +{ + return castTo >(); } int RedisValue::toInt() const @@ -51,7 +68,17 @@ int RedisValue::toInt() const std::string RedisValue::inspect() const { - if( isNull() ) + if( isError() ) + { + static std::string err = "error: "; + std::string result; + + result = err; + result += toString(); + + return result; + } + else if( isNull() ) { static std::string null = "(null)"; return null; @@ -89,6 +116,16 @@ std::string RedisValue::inspect() const } } +bool RedisValue::isOk() const +{ + return !isError(); +} + +bool RedisValue::isError() const +{ + return error; +} + bool RedisValue::isNull() const { return typeEq(); @@ -101,7 +138,12 @@ bool RedisValue::isInt() const bool RedisValue::isString() const { - return typeEq(); + return typeEq >(); +} + +bool RedisValue::isByteArray() const +{ + return typeEq >(); } bool RedisValue::isArray() const diff --git a/src/redisclient/redisasyncclient.h b/src/redisclient/redisasyncclient.h new file mode 100644 index 0000000..2533c50 --- /dev/null +++ b/src/redisclient/redisasyncclient.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) Alex Nekipelov (alex@nekipelov.net) + * License: MIT + */ + +#ifndef REDISASYNCCLIENT_REDISCLIENT_H +#define REDISASYNCCLIENT_REDISCLIENT_H + +#include +#include +#include + +#include +#include + +#include "impl/redisclientimpl.h" +#include "redisvalue.h" +#include "redisbuffer.h" +#include "config.h" + +class RedisClientImpl; + +class RedisAsyncClient : boost::noncopyable { +public: + // Subscribe handle. + struct Handle { + size_t id; + std::string channel; + }; + + REDIS_CLIENT_DECL RedisAsyncClient(boost::asio::io_service &ioService); + REDIS_CLIENT_DECL ~RedisAsyncClient(); + + // Connect to redis server + REDIS_CLIENT_DECL void connect( + const boost::asio::ip::address &address, + unsigned short port, + const boost::function &handler); + + // Connect to redis server + REDIS_CLIENT_DECL void connect( + const boost::asio::ip::tcp::endpoint &endpoint, + const boost::function &handler); + + // backward compatibility + inline void asyncConnect( + const boost::asio::ip::address &address, + unsigned short port, + const boost::function &handler) + { + connect(address, port, handler); + } + + // backward compatibility + inline void asyncConnect( + const boost::asio::ip::tcp::endpoint &endpoint, + const boost::function &handler) + { + connect(endpoint, handler); + } + + + // Set custom error handler. + REDIS_CLIENT_DECL void installErrorHandler( + const boost::function &handler); + + // Execute command on Redis server. + REDIS_CLIENT_DECL void command( + const std::string &cmd, + const boost::function &handler = &dummyHandler); + + // Execute command on Redis server with one argument. + REDIS_CLIENT_DECL void command( + const std::string &cmd, const RedisBuffer &arg1, + const boost::function &handler = &dummyHandler); + + // Execute command on Redis server with two arguments. + REDIS_CLIENT_DECL void command( + const std::string &cmd, const RedisBuffer &arg1, const RedisBuffer &arg2, + const boost::function &handler = &dummyHandler); + + // Execute command on Redis server with three arguments. + REDIS_CLIENT_DECL void command( + const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const boost::function &handler = &dummyHandler); + + // Execute command on Redis server with four arguments. + REDIS_CLIENT_DECL void command( + const std::string &cmd, const RedisBuffer &arg1, const RedisBuffer &arg2, + const RedisBuffer &arg3, const RedisBuffer &arg4, + const boost::function &handler = &dummyHandler); + + // Execute command on Redis server with five arguments. + REDIS_CLIENT_DECL void command( + const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5, + const boost::function &handler = &dummyHandler); + + // Execute command on Redis server with six arguments. + REDIS_CLIENT_DECL void command( + const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5, + const RedisBuffer &arg6, + const boost::function &handler = &dummyHandler); + + + // Execute command on Redis server with seven arguments. + REDIS_CLIENT_DECL void command( + const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5, + const RedisBuffer &arg6, const RedisBuffer &arg7, + const boost::function &handler = &dummyHandler); + + // Execute command on Redis server with the list of arguments. + REDIS_CLIENT_DECL void command( + const std::string &cmd, const std::list &args, + const boost::function &handler = &dummyHandler); + + // Subscribe to channel. Handler msgHandler will be called + // when someone publish message on channel. Call unsubscribe + // to stop the subscription. + REDIS_CLIENT_DECL Handle subscribe( + const std::string &channelName, + const boost::function &msg)> &msgHandler, + const boost::function &handler = &dummyHandler); + + // Unsubscribe + REDIS_CLIENT_DECL void unsubscribe(const Handle &handle); + + // Subscribe to channel. Handler msgHandler will be called + // when someone publish message on channel; it will be + // unsubscribed after call. + REDIS_CLIENT_DECL void singleShotSubscribe( + const std::string &channel, + const boost::function &msg)> &msgHandler, + const boost::function &handler = &dummyHandler); + + // Publish message on channel. + REDIS_CLIENT_DECL void publish( + const std::string &channel, const RedisBuffer &msg, + const boost::function &handler = &dummyHandler); + + REDIS_CLIENT_DECL static void dummyHandler(const RedisValue &) {} + +protected: + REDIS_CLIENT_DECL bool stateValid() const; + +private: + boost::shared_ptr pimpl; +}; + +#ifdef REDIS_CLIENT_HEADER_ONLY +#include "impl/redisasyncclient.cpp" +#endif + +#endif // REDISASYNCCLIENT_REDISCLIENT_H diff --git a/src/redisclient/redisbuffer.h b/src/redisclient/redisbuffer.h new file mode 100644 index 0000000..9cda55f --- /dev/null +++ b/src/redisclient/redisbuffer.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) Alex Nekipelov (alex@nekipelov.net) + * License: MIT + */ + + +#ifndef REDISSYNCCLIENT_REDISBUFFER_H +#define REDISSYNCCLIENT_REDISBUFFER_H + +#include + +#include +#include + +#include "config.h" + +class RedisBuffer +{ +public: + inline RedisBuffer(); + inline RedisBuffer(const char *ptr, size_t dataSize); + inline RedisBuffer(const char *s); + inline RedisBuffer(const std::string &s); + inline RedisBuffer(const std::vector &buf); + + inline size_t size() const; + inline const char *data() const; + +private: + const char *ptr_; + size_t size_; +}; + + +RedisBuffer::RedisBuffer() + : ptr_(NULL), size_(0) +{ +} + +RedisBuffer::RedisBuffer(const char *ptr, size_t dataSize) + : ptr_(ptr), size_(dataSize) +{ +} + +RedisBuffer::RedisBuffer(const char *s) + : ptr_(s), size_(s == NULL ? 0 : strlen(s)) +{ +} + +RedisBuffer::RedisBuffer(const std::string &s) + : ptr_(s.c_str()), size_(s.length()) +{ +} + +RedisBuffer::RedisBuffer(const std::vector &buf) + : ptr_(buf.empty() ? NULL : &buf[0]), size_(buf.size()) +{ +} + +size_t RedisBuffer::size() const +{ + return size_; +} + +const char *RedisBuffer::data() const +{ + return ptr_; +} + +#endif //REDISSYNCCLIENT_REDISBUFFER_H + diff --git a/src/redisclient/redisclient.h b/src/redisclient/redisclient.h index ef8d265..a6c26d1 100644 --- a/src/redisclient/redisclient.h +++ b/src/redisclient/redisclient.h @@ -6,140 +6,9 @@ #ifndef REDISCLIENT_REDISCLIENT_H #define REDISCLIENT_REDISCLIENT_H -#include -#include -#include +#include "redisasyncclient.h" -#include -#include - -#include "impl/redisclientimpl.h" -#include "redisvalue.h" -#include "config.h" - -class RedisClientImpl; - -class RedisClient : boost::noncopyable { -public: - // Subscribe handle. - struct Handle { - size_t id; - std::string channel; - }; - - REDIS_CLIENT_DECL RedisClient(boost::asio::io_service &ioService); - REDIS_CLIENT_DECL ~RedisClient(); - - // Connect to redis server, blocking call. - REDIS_CLIENT_DECL bool connect(const boost::asio::ip::address &address, - unsigned short port); - - // Connect to redis server, asynchronous call. - REDIS_CLIENT_DECL void asyncConnect( - const boost::asio::ip::address &address, - unsigned short port, - const boost::function &handler); - - // Connect to redis server, asynchronous call. - REDIS_CLIENT_DECL void asyncConnect( - const boost::asio::ip::tcp::endpoint &endpoint, - const boost::function &handler); - - // Set custom error handler. - REDIS_CLIENT_DECL void installErrorHandler( - const boost::function &handler); - - // Execute command on Redis server. - REDIS_CLIENT_DECL void command( - const std::string &cmd, - const boost::function &handler = &dummyHandler); - - // Execute command on Redis server with one argument. - REDIS_CLIENT_DECL void command( - const std::string &cmd, const std::string &arg1, - const boost::function &handler = &dummyHandler); - - // Execute command on Redis server with two arguments. - REDIS_CLIENT_DECL void command( - const std::string &cmd, const std::string &arg1, const std::string &arg2, - const boost::function &handler = &dummyHandler); - - // Execute command on Redis server with three arguments. - REDIS_CLIENT_DECL void command( - const std::string &cmd, const std::string &arg1, - const std::string &arg2, const std::string &arg3, - const boost::function &handler = &dummyHandler); - - // Execute command on Redis server with four arguments. - REDIS_CLIENT_DECL void command( - const std::string &cmd, const std::string &arg1, const std::string &arg2, - const std::string &arg3, const std::string &arg4, - const boost::function &handler = &dummyHandler); - - // Execute command on Redis server with five arguments. - REDIS_CLIENT_DECL void command( - const std::string &cmd, const std::string &arg1, - const std::string &arg2, const std::string &arg3, - const std::string &arg4, const std::string &arg5, - const boost::function &handler = &dummyHandler); - - // Execute command on Redis server with six arguments. - REDIS_CLIENT_DECL void command( - const std::string &cmd, const std::string &arg1, - const std::string &arg2, const std::string &arg3, - const std::string &arg4, const std::string &arg5, - const std::string &arg6, - const boost::function &handler = &dummyHandler); - - - // Execute command on Redis server with seven arguments. - REDIS_CLIENT_DECL void command( - const std::string &cmd, const std::string &arg1, - const std::string &arg2, const std::string &arg3, - const std::string &arg4, const std::string &arg5, - const std::string &arg6, const std::string &arg7, - const boost::function &handler = &dummyHandler); - - // Execute command on Redis server with the list of arguments. - REDIS_CLIENT_DECL void command( - const std::string &cmd, const std::list &args, - const boost::function &handler = &dummyHandler); - - // Subscribe to channel. Handler msgHandler will be called - // when someone publish message on channel. Call unsubscribe - // to stop the subscription. - REDIS_CLIENT_DECL Handle subscribe( - const std::string &channelName, - const boost::function &msgHandler, - const boost::function &handler = &dummyHandler); - - // Unsubscribe - REDIS_CLIENT_DECL void unsubscribe(const Handle &handle); - - // Subscribe to channel. Handler msgHandler will be called - // when someone publish message on channel; it will be - // unsubscribed after call. - REDIS_CLIENT_DECL void singleShotSubscribe( - const std::string &channel, - const boost::function &msgHandler, - const boost::function &handler = &dummyHandler); - - // Publish message on channel. - REDIS_CLIENT_DECL void publish( - const std::string &channel, const std::string &msg, - const boost::function &handler = &dummyHandler); - - REDIS_CLIENT_DECL static void dummyHandler(const RedisValue &) {} - -protected: - REDIS_CLIENT_DECL bool stateValid() const; - -private: - boost::shared_ptr pimpl; -}; - -#ifdef REDIS_CLIENT_HEADER_ONLY -#include "impl/redisclient.cpp" -#endif +// backward compatibility +typedef RedisAsyncClient RedisClient; #endif // REDISCLIENT_REDISCLIENT_H diff --git a/src/redisclient/redisparser.h b/src/redisclient/redisparser.h index 0028fd9..d1d57bd 100644 --- a/src/redisclient/redisparser.h +++ b/src/redisclient/redisparser.h @@ -7,6 +7,7 @@ #define REDISCLIENT_REDISPARSER_H #include +#include #include "redisvalue.h" #include "config.h" @@ -65,7 +66,7 @@ class RedisParser } state; long int bulkSize; - std::string string; + std::vector buf; std::stack arrayStack; std::stack valueStack; diff --git a/src/redisclient/redissyncclient.h b/src/redisclient/redissyncclient.h new file mode 100644 index 0000000..7d446a3 --- /dev/null +++ b/src/redisclient/redissyncclient.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) Alex Nekipelov (alex@nekipelov.net) + * License: MIT + */ + +#ifndef REDISSYNCCLIENT_REDISCLIENT_H +#define REDISSYNCCLIENT_REDISCLIENT_H + +#include +#include +#include + +#include +#include + +#include "impl/redisclientimpl.h" +#include "redisbuffer.h" +#include "redisvalue.h" +#include "config.h" + +class RedisClientImpl; + +class RedisSyncClient : boost::noncopyable { +public: + REDIS_CLIENT_DECL RedisSyncClient(boost::asio::io_service &ioService); + REDIS_CLIENT_DECL ~RedisSyncClient(); + + // Connect to redis server + REDIS_CLIENT_DECL bool connect( + const boost::asio::ip::tcp::endpoint &endpoint, + std::string &errmsg); + + // Connect to redis server + REDIS_CLIENT_DECL bool connect( + const boost::asio::ip::address &address, + unsigned short port, + std::string &errmsg); + + // Set custom error handler. + REDIS_CLIENT_DECL void installErrorHandler( + const boost::function &handler); + + // Execute command on Redis server. + REDIS_CLIENT_DECL RedisValue command(const std::string &cmd); + + // Execute command on Redis server with one argument. + REDIS_CLIENT_DECL RedisValue command(const std::string &cmd, const RedisBuffer &arg1); + + // Execute command on Redis server with two arguments. + REDIS_CLIENT_DECL RedisValue command( + const std::string &cmd, const RedisBuffer &arg1, const RedisBuffer &arg2); + + // Execute command on Redis server with three arguments. + REDIS_CLIENT_DECL RedisValue command( + const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3); + + // Execute command on Redis server with four arguments. + REDIS_CLIENT_DECL RedisValue command( + const std::string &cmd, const RedisBuffer &arg1, const RedisBuffer &arg2, + const RedisBuffer &arg3, const RedisBuffer &arg4); + + // Execute command on Redis server with five arguments. + REDIS_CLIENT_DECL RedisValue command( + const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5); + + // Execute command on Redis server with six arguments. + REDIS_CLIENT_DECL RedisValue command( + const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5, + const RedisBuffer &arg6); + + // Execute command on Redis server with seven arguments. + REDIS_CLIENT_DECL RedisValue command( + const std::string &cmd, const RedisBuffer &arg1, + const RedisBuffer &arg2, const RedisBuffer &arg3, + const RedisBuffer &arg4, const RedisBuffer &arg5, + const RedisBuffer &arg6, const RedisBuffer &arg7); + + // Execute command on Redis server with the list of arguments. + REDIS_CLIENT_DECL RedisValue command( + const std::string &cmd, const std::list &args); + +protected: + REDIS_CLIENT_DECL bool stateValid() const; + +private: + boost::shared_ptr pimpl; +}; + +#ifdef REDIS_CLIENT_HEADER_ONLY +#include "impl/redissyncclient.cpp" +#endif + +#endif // REDISSYNCCLIENT_REDISCLIENT_H diff --git a/src/redisclient/redisvalue.h b/src/redisclient/redisvalue.h index 975529a..efcf7e6 100644 --- a/src/redisclient/redisvalue.h +++ b/src/redisclient/redisvalue.h @@ -14,15 +14,23 @@ class RedisValue { public: + struct ErrorTag {}; + REDIS_CLIENT_DECL RedisValue(); REDIS_CLIENT_DECL RedisValue(int i); REDIS_CLIENT_DECL RedisValue(const char *s); REDIS_CLIENT_DECL RedisValue(const std::string &s); + REDIS_CLIENT_DECL RedisValue(const std::vector &buf); + REDIS_CLIENT_DECL RedisValue(const std::vector &buf, struct ErrorTag &); REDIS_CLIENT_DECL RedisValue(const std::vector &array); // Return the value as a std::string if - // type is a std::string; otherwise returns an empty std::string. + // type is a byte string; otherwise returns an empty std::string. REDIS_CLIENT_DECL std::string toString() const; + + // Return the value as a std::vector if + // type is a byte string; otherwise returns an empty std::vector. + REDIS_CLIENT_DECL std::vector toByteArray() const; // Return the value as a std::vector if // type is an int; otherwise returns 0. @@ -36,14 +44,22 @@ class RedisValue { // for dump content of the value. REDIS_CLIENT_DECL std::string inspect() const; + // Return true if value not a error + REDIS_CLIENT_DECL bool isOk() const; + // Return true if value is a error + REDIS_CLIENT_DECL bool isError() const; + + // Return true if this is a null. REDIS_CLIENT_DECL bool isNull() const; // Return true if type is an int REDIS_CLIENT_DECL bool isInt() const; - // Return true if type is a string - REDIS_CLIENT_DECL bool isString() const; // Return true if type is an array REDIS_CLIENT_DECL bool isArray() const; + // Return true if type is a string/byte array. Alias for isString(); + REDIS_CLIENT_DECL bool isByteArray() const; + // Return true if type is a string/byte array. Alias for isByteArray(). + REDIS_CLIENT_DECL bool isString() const; REDIS_CLIENT_DECL bool operator == (const RedisValue &rhs) const; REDIS_CLIENT_DECL bool operator != (const RedisValue &rhs) const; @@ -63,7 +79,8 @@ class RedisValue { }; - boost::variant > value; + boost::variant, std::vector > value; + bool error; }; diff --git a/src/redisclient/version.h b/src/redisclient/version.h index 63488b9..bf623ce 100644 --- a/src/redisclient/version.h +++ b/src/redisclient/version.h @@ -6,6 +6,6 @@ #ifndef REDISCLIENT_VERSION_H #define REDISCLIENT_VERSION_H -#define REDIS_CLIENT_VERSION 302 // 0.3.2 +#define REDIS_CLIENT_VERSION 401 // 0.4.1 #endif // REDISCLIENT_VERSION_H