diff --git a/CMakeLists.txt b/CMakeLists.txt index f0c9e528..353882f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,4 +115,3 @@ set( IXWEBSOCKET_INCLUDE_DIRS target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} ) add_subdirectory(ws) -add_subdirectory(examples) diff --git a/README.md b/README.md index e5b7b738..bfcb9d74 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ communication channels over a single TCP connection. *IXWebSocket* is a C++ libr ## Examples -The examples folder countains a simple chat program, using a node.js broadcast server. +The ws folder countains many interactive programs for chat and file transfers demonstrating client and server usage. Here is what the client API looks like. @@ -144,16 +144,6 @@ Large frames are broken up into smaller chunks or messages to avoid filling up t * Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that. * The server code is using select to detect incoming data, and creates one OS thread per connection. This isn't as scalable as strategies using epoll or kqueue. -## Examples - -1. Bring up a terminal and jump to the examples folder. -2. Compile the example C++ code. `sh build.sh` -3. Install node.js from [here](https://nodejs.org/en/download/). -4. Type `npm install` to install the node.js dependencies. Then `node broadcast-server.js` to run the server. -5. Bring up a second terminal. `./cmd_websocket_chat bob` -6. Bring up a third terminal. `./cmd_websocket_chat bill` -7. Start typing things in any of those terminals. Hopefully you should see your message being received on the other end. - ## C++ code organization Here's a simplistic diagram which explains how the code is structured in term of class/modules. diff --git a/makefile b/makefile index f5007a18..7b01a7b2 100644 --- a/makefile +++ b/makefile @@ -1,10 +1,10 @@ # # This makefile is just used to easily work with docker (linux build) # -all: run +all: brew brew: - mkdir -p ws/build && (cd ws/build ; cmake .. ; make) + mkdir -p build && (cd build ; cmake .. ; make) .PHONY: docker docker: diff --git a/examples/chat/nlohmann/json.hpp b/third_party/nlohmann/json.hpp similarity index 100% rename from examples/chat/nlohmann/json.hpp rename to third_party/nlohmann/json.hpp diff --git a/ws/CMakeLists.txt b/ws/CMakeLists.txt index 168cd991..fdee2e33 100644 --- a/ws/CMakeLists.txt +++ b/ws/CMakeLists.txt @@ -14,6 +14,7 @@ set (CMAKE_CXX_STANDARD 14) option(USE_TLS "Add TLS support" ON) include_directories(ws .) +include_directories(ws ..) include_directories(ws ../third_party) add_executable(ws @@ -22,6 +23,8 @@ add_executable(ws ixcrypto/IXHash.cpp ixcrypto/IXUuid.cpp + ws_chat.cpp + ws_connect.cpp ws_transfer.cpp ws_send.cpp ws_receive.cpp diff --git a/ws/docker_build.sh b/ws/docker_build.sh index 9dd5a679..d1dadffd 100644 --- a/ws/docker_build.sh +++ b/ws/docker_build.sh @@ -28,6 +28,8 @@ g++ --std=c++14 \ ixcrypto/IXBase64.cpp \ ixcrypto/IXHash.cpp \ ixcrypto/IXUuid.cpp \ + ws_chat.cpp \ + ws_connect.cpp \ ws_transfer.cpp \ ws_send.cpp \ ws_receive.cpp \ diff --git a/ws/ws.cpp b/ws/ws.cpp index 2273b056..b2871aa4 100644 --- a/ws/ws.cpp +++ b/ws/ws.cpp @@ -16,13 +16,18 @@ namespace ix { + int ws_chat_main(const std::string& url, + const std::string& user); + + int ws_connect_main(const std::string& url); + int ws_receive_main(const std::string& url, bool enablePerMessageDeflate); - extern int ws_transfer_main(int port); + int ws_transfer_main(int port); - extern int ws_send_main(const std::string& url, - const std::string& path); + int ws_send_main(const std::string& url, + const std::string& path); } int main(int argc, char** argv) @@ -32,6 +37,7 @@ int main(int argc, char** argv) std::string url; std::string path; + std::string user; int port = 8080; CLI::App* sendApp = app.add_subcommand("send", "Send a file"); @@ -44,6 +50,13 @@ int main(int argc, char** argv) CLI::App* transferApp = app.add_subcommand("transfer", "Broadcasting server"); transferApp->add_option("--port", port, "Connection url"); + CLI::App* connectApp = app.add_subcommand("connect", "Connect to a remote server"); + connectApp->add_option("url", url, "Connection url")->required(); + + CLI::App* chatApp = app.add_subcommand("chat", "Group chat"); + chatApp->add_option("url", url, "Connection url")->required(); + chatApp->add_option("user", user, "User name")->required(); + CLI11_PARSE(app, argc, argv); if (app.got_subcommand("transfer")) @@ -59,6 +72,14 @@ int main(int argc, char** argv) bool enablePerMessageDeflate = false; return ix::ws_receive_main(url, enablePerMessageDeflate); } + else if (app.got_subcommand("connect")) + { + return ix::ws_connect_main(url); + } + else if (app.got_subcommand("chat")) + { + return ix::ws_chat_main(url, user); + } else { assert(false); diff --git a/examples/chat/cmd_websocket_chat.cpp b/ws/ws_chat.cpp similarity index 84% rename from examples/chat/cmd_websocket_chat.cpp rename to ws/ws_chat.cpp index 8af42141..f8613e70 100644 --- a/examples/chat/cmd_websocket_chat.cpp +++ b/ws/ws_chat.cpp @@ -1,7 +1,7 @@ /* - * cmd_websocket_chat.cpp + * ws_chat.cpp * Author: Benjamin Sergeant - * Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved. + * Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved. */ // @@ -20,19 +20,13 @@ // for convenience using json = nlohmann::json; -using namespace ix; - -namespace +namespace ix { - void log(const std::string& msg) - { - std::cout << msg << std::endl; - } - class WebSocketChat { public: - WebSocketChat(const std::string& user); + WebSocketChat(const std::string& url, + const std::string& user); void subscribe(const std::string& channel); void start(); @@ -46,19 +40,27 @@ namespace std::pair decodeMessage(const std::string& str); private: + std::string _url; std::string _user; - ix::WebSocket _webSocket; - std::queue _receivedQueue; + + void log(const std::string& msg); }; - WebSocketChat::WebSocketChat(const std::string& user) : + WebSocketChat::WebSocketChat(const std::string& url, + const std::string& user) : + _url(url), _user(user) { ; } + void WebSocketChat::log(const std::string& msg) + { + std::cout << msg << std::endl; + } + size_t WebSocketChat::getReceivedMessagesCount() const { return _receivedQueue.size(); @@ -76,11 +78,10 @@ namespace void WebSocketChat::start() { - std::string url("ws://localhost:8080/"); - _webSocket.setUrl(url); + _webSocket.setUrl(_url); std::stringstream ss; - log(std::string("Connecting to url: ") + url); + log(std::string("Connecting to url: ") + _url); _webSocket.setOnMessageCallback( [this](ix::WebSocketMessageType messageType, @@ -164,10 +165,11 @@ namespace _webSocket.send(encodeMessage(text)); } - void interactiveMain(const std::string& user) + void interactiveMain(const std::string& url, + const std::string& user) { std::cout << "Type Ctrl-D to exit prompt..." << std::endl; - WebSocketChat webSocketChat(user); + WebSocketChat webSocketChat(url, user); webSocketChat.start(); while (true) @@ -187,17 +189,13 @@ namespace std::cout << std::endl; webSocketChat.stop(); } -} -int main(int argc, char** argv) -{ - std::string user("user"); - if (argc == 2) + int ws_chat_main(const std::string& url, + const std::string& user) { - user = argv[1]; + Socket::init(); + interactiveMain(url, user); + return 0; } - - Socket::init(); - interactiveMain(user); - return 0; } + diff --git a/examples/ws_connect/ws_connect.cpp b/ws/ws_connect.cpp similarity index 93% rename from examples/ws_connect/ws_connect.cpp rename to ws/ws_connect.cpp index 4a80e983..ac0b8577 100644 --- a/examples/ws_connect/ws_connect.cpp +++ b/ws/ws_connect.cpp @@ -9,15 +9,8 @@ #include #include -using namespace ix; - -namespace +namespace ix { - void log(const std::string& msg) - { - std::cout << msg << std::endl; - } - class WebSocketConnect { public: @@ -32,6 +25,8 @@ namespace private: std::string _url; ix::WebSocket _webSocket; + + void log(const std::string& msg); }; WebSocketConnect::WebSocketConnect(const std::string& url) : @@ -40,6 +35,11 @@ namespace ; } + void WebSocketConnect::log(const std::string& msg) + { + std::cout << msg << std::endl; + } + void WebSocketConnect::stop() { _webSocket.stop(); @@ -148,18 +148,12 @@ namespace std::cout << std::endl; webSocketChat.stop(); } -} -int main(int argc, char** argv) -{ - if (argc != 2) + int ws_connect_main(const std::string& url) { - std::cerr << "Usage: ws_connect " << std::endl; - return 1; + Socket::init(); + interactiveMain(url); + return 0; } - std::string url = argv[1]; - - Socket::init(); - interactiveMain(url); - return 0; } +