add ws_chat and ws_connect sub commands to ws
This commit is contained in:
		| @@ -120,4 +120,3 @@ set( IXWEBSOCKET_INCLUDE_DIRS | |||||||
| target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} ) | target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} ) | ||||||
|  |  | ||||||
| add_subdirectory(ws) | add_subdirectory(ws) | ||||||
| add_subdirectory(examples) |  | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							| @@ -15,7 +15,7 @@ communication channels over a single TCP connection. *IXWebSocket* is a C++ libr | |||||||
|  |  | ||||||
| ## Examples | ## 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. | 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. | * 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. | * 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 | ## C++ code organization | ||||||
|  |  | ||||||
| Here's a simplistic diagram which explains how the code is structured in term of class/modules. | Here's a simplistic diagram which explains how the code is structured in term of class/modules. | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								makefile
									
									
									
									
									
								
							| @@ -1,10 +1,10 @@ | |||||||
| # | # | ||||||
| # This makefile is just used to easily work with docker (linux build) | # This makefile is just used to easily work with docker (linux build) | ||||||
| # | # | ||||||
| all: run | all: brew | ||||||
|  |  | ||||||
| brew: | brew: | ||||||
| 	mkdir -p ws/build && (cd ws/build ; cmake .. ; make) | 	mkdir -p build && (cd build ; cmake .. ; make) | ||||||
|  |  | ||||||
| .PHONY: docker | .PHONY: docker | ||||||
| docker: | docker: | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ set (CMAKE_CXX_STANDARD 14) | |||||||
| option(USE_TLS "Add TLS support" ON) | option(USE_TLS "Add TLS support" ON) | ||||||
|  |  | ||||||
| include_directories(ws .) | include_directories(ws .) | ||||||
|  | include_directories(ws ..) | ||||||
| include_directories(ws ../third_party) | include_directories(ws ../third_party) | ||||||
|  |  | ||||||
| add_executable(ws  | add_executable(ws  | ||||||
| @@ -22,6 +23,8 @@ add_executable(ws | |||||||
|   ixcrypto/IXHash.cpp |   ixcrypto/IXHash.cpp | ||||||
|   ixcrypto/IXUuid.cpp |   ixcrypto/IXUuid.cpp | ||||||
|  |  | ||||||
|  |   ws_chat.cpp | ||||||
|  |   ws_connect.cpp | ||||||
|   ws_transfer.cpp |   ws_transfer.cpp | ||||||
|   ws_send.cpp |   ws_send.cpp | ||||||
|   ws_receive.cpp |   ws_receive.cpp | ||||||
|   | |||||||
| @@ -28,6 +28,8 @@ g++ --std=c++14 \ | |||||||
|     ixcrypto/IXBase64.cpp \ |     ixcrypto/IXBase64.cpp \ | ||||||
|     ixcrypto/IXHash.cpp \ |     ixcrypto/IXHash.cpp \ | ||||||
|     ixcrypto/IXUuid.cpp \ |     ixcrypto/IXUuid.cpp \ | ||||||
|  |     ws_chat.cpp \ | ||||||
|  |     ws_connect.cpp \ | ||||||
|     ws_transfer.cpp \ |     ws_transfer.cpp \ | ||||||
|     ws_send.cpp \ |     ws_send.cpp \ | ||||||
|     ws_receive.cpp \ |     ws_receive.cpp \ | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								ws/ws.cpp
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								ws/ws.cpp
									
									
									
									
									
								
							| @@ -16,13 +16,18 @@ | |||||||
|  |  | ||||||
| namespace ix | 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, |     int ws_receive_main(const std::string& url, | ||||||
|                         bool enablePerMessageDeflate); |                         bool enablePerMessageDeflate); | ||||||
|  |  | ||||||
|     extern int ws_transfer_main(int port); |     int ws_transfer_main(int port); | ||||||
|  |  | ||||||
|     extern int ws_send_main(const std::string& url, |     int ws_send_main(const std::string& url, | ||||||
|                             const std::string& path); |                      const std::string& path); | ||||||
| } | } | ||||||
|  |  | ||||||
| int main(int argc, char** argv) | int main(int argc, char** argv) | ||||||
| @@ -32,6 +37,7 @@ int main(int argc, char** argv) | |||||||
|  |  | ||||||
|     std::string url; |     std::string url; | ||||||
|     std::string path; |     std::string path; | ||||||
|  |     std::string user; | ||||||
|     int port = 8080; |     int port = 8080; | ||||||
|  |  | ||||||
|     CLI::App* sendApp = app.add_subcommand("send", "Send a file"); |     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"); |     CLI::App* transferApp = app.add_subcommand("transfer", "Broadcasting server"); | ||||||
|     transferApp->add_option("--port", port, "Connection url"); |     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); |     CLI11_PARSE(app, argc, argv); | ||||||
|  |  | ||||||
|     if (app.got_subcommand("transfer")) |     if (app.got_subcommand("transfer")) | ||||||
| @@ -59,6 +72,14 @@ int main(int argc, char** argv) | |||||||
|         bool enablePerMessageDeflate = false; |         bool enablePerMessageDeflate = false; | ||||||
|         return ix::ws_receive_main(url, enablePerMessageDeflate); |         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 |     else | ||||||
|     { |     { | ||||||
|         assert(false); |         assert(false); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| /*
 | /*
 | ||||||
|  *  cmd_websocket_chat.cpp |  *  ws_chat.cpp | ||||||
|  *  Author: Benjamin Sergeant |  *  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
 | // for convenience
 | ||||||
| using json = nlohmann::json; | using json = nlohmann::json; | ||||||
| 
 | 
 | ||||||
| using namespace ix; | namespace ix | ||||||
| 
 |  | ||||||
| namespace |  | ||||||
| { | { | ||||||
|     void log(const std::string& msg) |  | ||||||
|     { |  | ||||||
|         std::cout << msg << std::endl; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     class WebSocketChat |     class WebSocketChat | ||||||
|     { |     { | ||||||
|         public: |         public: | ||||||
|             WebSocketChat(const std::string& user); |             WebSocketChat(const std::string& url, | ||||||
|  |                           const std::string& user); | ||||||
| 
 | 
 | ||||||
|             void subscribe(const std::string& channel); |             void subscribe(const std::string& channel); | ||||||
|             void start(); |             void start(); | ||||||
| @@ -46,19 +40,27 @@ namespace | |||||||
|             std::pair<std::string, std::string> decodeMessage(const std::string& str); |             std::pair<std::string, std::string> decodeMessage(const std::string& str); | ||||||
| 
 | 
 | ||||||
|         private: |         private: | ||||||
|  |             std::string _url; | ||||||
|             std::string _user; |             std::string _user; | ||||||
| 
 |  | ||||||
|             ix::WebSocket _webSocket; |             ix::WebSocket _webSocket; | ||||||
| 
 |  | ||||||
|             std::queue<std::string> _receivedQueue; |             std::queue<std::string> _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) |         _user(user) | ||||||
|     { |     { | ||||||
|         ; |         ; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     void WebSocketChat::log(const std::string& msg) | ||||||
|  |     { | ||||||
|  |         std::cout << msg << std::endl; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     size_t WebSocketChat::getReceivedMessagesCount() const |     size_t WebSocketChat::getReceivedMessagesCount() const | ||||||
|     { |     { | ||||||
|         return _receivedQueue.size(); |         return _receivedQueue.size(); | ||||||
| @@ -76,11 +78,10 @@ namespace | |||||||
| 
 | 
 | ||||||
|     void WebSocketChat::start() |     void WebSocketChat::start() | ||||||
|     { |     { | ||||||
|         std::string url("ws://localhost:8080/"); |         _webSocket.setUrl(_url); | ||||||
|         _webSocket.setUrl(url); |  | ||||||
| 
 | 
 | ||||||
|         std::stringstream ss; |         std::stringstream ss; | ||||||
|         log(std::string("Connecting to url: ") + url); |         log(std::string("Connecting to url: ") + _url); | ||||||
| 
 | 
 | ||||||
|         _webSocket.setOnMessageCallback( |         _webSocket.setOnMessageCallback( | ||||||
|             [this](ix::WebSocketMessageType messageType, |             [this](ix::WebSocketMessageType messageType, | ||||||
| @@ -164,10 +165,11 @@ namespace | |||||||
|         _webSocket.send(encodeMessage(text)); |         _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; |         std::cout << "Type Ctrl-D to exit prompt..." << std::endl; | ||||||
|         WebSocketChat webSocketChat(user); |         WebSocketChat webSocketChat(url, user); | ||||||
|         webSocketChat.start(); |         webSocketChat.start(); | ||||||
| 
 | 
 | ||||||
|         while (true) |         while (true) | ||||||
| @@ -187,17 +189,13 @@ namespace | |||||||
|         std::cout << std::endl; |         std::cout << std::endl; | ||||||
|         webSocketChat.stop(); |         webSocketChat.stop(); | ||||||
|     } |     } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| int main(int argc, char** argv) |     int ws_chat_main(const std::string& url, | ||||||
| { |                      const std::string& user) | ||||||
|     std::string user("user"); |  | ||||||
|     if (argc == 2) |  | ||||||
|     { |     { | ||||||
|         user = argv[1]; |         Socket::init(); | ||||||
|  |         interactiveMain(url, user); | ||||||
|  |         return 0; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     Socket::init(); |  | ||||||
|     interactiveMain(user); |  | ||||||
|     return 0; |  | ||||||
| } | } | ||||||
|  | 
 | ||||||
| @@ -9,15 +9,8 @@ | |||||||
| #include <ixwebsocket/IXWebSocket.h> | #include <ixwebsocket/IXWebSocket.h> | ||||||
| #include <ixwebsocket/IXSocket.h> | #include <ixwebsocket/IXSocket.h> | ||||||
| 
 | 
 | ||||||
| using namespace ix; | namespace ix | ||||||
| 
 |  | ||||||
| namespace |  | ||||||
| { | { | ||||||
|     void log(const std::string& msg) |  | ||||||
|     { |  | ||||||
|         std::cout << msg << std::endl; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     class WebSocketConnect |     class WebSocketConnect | ||||||
|     { |     { | ||||||
|         public: |         public: | ||||||
| @@ -32,6 +25,8 @@ namespace | |||||||
|         private: |         private: | ||||||
|             std::string _url; |             std::string _url; | ||||||
|             ix::WebSocket _webSocket; |             ix::WebSocket _webSocket; | ||||||
|  | 
 | ||||||
|  |             void log(const std::string& msg); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     WebSocketConnect::WebSocketConnect(const std::string& url) : |     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() |     void WebSocketConnect::stop() | ||||||
|     { |     { | ||||||
|         _webSocket.stop(); |         _webSocket.stop(); | ||||||
| @@ -148,18 +148,12 @@ namespace | |||||||
|         std::cout << std::endl; |         std::cout << std::endl; | ||||||
|         webSocketChat.stop(); |         webSocketChat.stop(); | ||||||
|     } |     } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| int main(int argc, char** argv) |     int ws_connect_main(const std::string& url) | ||||||
| { |  | ||||||
|     if (argc != 2) |  | ||||||
|     { |     { | ||||||
|         std::cerr << "Usage: ws_connect <url>" << std::endl; |         Socket::init(); | ||||||
|         return 1; |         interactiveMain(url); | ||||||
|  |         return 0; | ||||||
|     } |     } | ||||||
|     std::string url = argv[1]; |  | ||||||
| 
 |  | ||||||
|     Socket::init(); |  | ||||||
|     interactiveMain(url); |  | ||||||
|     return 0; |  | ||||||
| } | } | ||||||
|  | 
 | ||||||
		Reference in New Issue
	
	Block a user