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} )
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
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.

View File

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

View File

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

View File

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

View File

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

View File

@ -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<std::string, std::string> decodeMessage(const std::string& str);
private:
std::string _url;
std::string _user;
ix::WebSocket _webSocket;
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)
{
;
}
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;
}

View File

@ -9,15 +9,8 @@
#include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXSocket.h>
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 <url>" << std::endl;
return 1;
Socket::init();
interactiveMain(url);
return 0;
}
std::string url = argv[1];
Socket::init();
interactiveMain(url);
return 0;
}