add ws_chat and ws_connect sub commands to ws

This commit is contained in:
Benjamin Sergeant 2019-02-22 20:49:26 -08:00
parent 41a40b8b9f
commit 76e2f9f3ac
9 changed files with 72 additions and 65 deletions

View File

@ -115,4 +115,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)

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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 \

View File

@ -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);

View File

@ -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;
} }

View File

@ -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;
} }