Compare commits

..

10 Commits

Author SHA1 Message Date
Benjamin Sergeant
44a813caae quit checkConnection after sleeping if requested to stop (iterate on #151) 2020-01-28 10:46:04 -08:00
Benjamin Sergeant
78af792f23 wait with a condition variable instead of a this_thread::sleep_for so that waiting can be cancelled when we stop/shutdown the data thread (see #151) 2020-01-28 10:04:32 -08:00
Benjamin Sergeant
1ee8e31483 bump version 2020-01-27 17:41:27 -08:00
Benjamin Sergeant
e927dcca3a Fix #148 2020-01-27 17:29:44 -08:00
Seth Alves
86c2ab450e bind returns zero on success (#149) 2020-01-27 17:15:44 -08:00
Benjamin Sergeant
f3d562ff02 add -6 option to ws echo_server / cf #148 2020-01-26 16:44:44 -08:00
Benjamin Sergeant
3c27228585 (SocketServer) add ability to bind on an ipv6 address 2020-01-26 16:21:56 -08:00
Benjamin Sergeant
9d7807b25c (ws) add a dnslookup sub-command, to get the ip address of a remote host 2020-01-26 16:21:56 -08:00
ronen barzel
6256264d78 Remove 'handleConnect() done' info message (#147) 2020-01-26 10:03:17 -08:00
Olivia Zoe
04c9985030 Doc update for Conan (#146)
* Doc update for Conan

* Migration notice
2020-01-17 07:15:41 -08:00
22 changed files with 138 additions and 103 deletions

View File

@@ -1,6 +1,10 @@
# Changelog
All changes to this project will be documented in this file.
## [8.0.0] - 2020-01-26
(SocketServer) add ability to bind on an ipv6 address
## [7.9.6] - 2020-01-22
(ws) add a dnslookup sub-command, to get the ip address of a remote host

View File

@@ -33,11 +33,16 @@ vcpkg install ixwebsocket
### Conan
Support for building with conan was contributed by Olivia Zoe (thanks!). The package name to reference is `IXWebSocket/5.0.0@LunarWatcher/stable`, and a list of the uploaded versions is available on [Bintray](https://bintray.com/oliviazoe0/conan-packages/IXWebSocket%3ALunarWatcher). The package is in the process to be published to the official conan package repo, but in the meantime, it can be accessed by adding a new remote
[ ![Download](https://api.bintray.com/packages/conan/conan-center/ixwebsocket%3A_/images/download.svg) ](https://bintray.com/conan/conan-center/ixwebsocket%3A_/_latestVersion)
```
conan remote add remote_name_here https://api.bintray.com/conan/oliviazoe0/conan-packages
```
Conan is currently supported through a recipe in [Conan Center](https://github.com/conan-io/conan-center-index/tree/master/recipes/ixwebsocket) ([Bintray entry](https://bintray.com/conan/conan-center/ixwebsocket%3A_)).
Package reference
* Conan 1.21.0 and up: `ixwebsocket/7.9.2`
* Earlier versions: `ixwebsocket/7.9.2@_/_`
Note that the version listed here might not be the latest one. See Bintray or the recipe itself for the latest version. If you're migrating from the previous, custom Bintray remote, note that the package reference _has_ to be lower-case.
### Docker

View File

@@ -17,8 +17,8 @@
namespace ix
{
RedisServer::RedisServer(int port, const std::string& host, int backlog, size_t maxConnections)
: SocketServer(port, host, backlog, maxConnections)
RedisServer::RedisServer(int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
: SocketServer(port, host, backlog, maxConnections, addressFamily)
, _connectedClientsCount(0)
, _stopHandlingConnections(false)
{

View File

@@ -25,7 +25,8 @@ namespace ix
RedisServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections);
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int addressFamily = SocketServer::kDefaultAddressFamily);
virtual ~RedisServer();
virtual void stop() final;

View File

@@ -42,8 +42,8 @@ namespace
namespace ix
{
HttpServer::HttpServer(int port, const std::string& host, int backlog, size_t maxConnections)
: SocketServer(port, host, backlog, maxConnections)
HttpServer::HttpServer(int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
: SocketServer(port, host, backlog, maxConnections, addressFamily)
, _connectedClientsCount(0)
{
setDefaultConnectionCallback();

View File

@@ -28,7 +28,8 @@ namespace ix
HttpServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections);
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int addressFamily = SocketServer::kDefaultAddressFamily);
virtual ~HttpServer();
virtual void stop() final;

View File

@@ -20,7 +20,6 @@
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sstream>
#define socketerrno errno
#include <Security/SecureTransport.h>
@@ -32,17 +31,12 @@ namespace ix
, _sslContext(nullptr)
, _tlsOptions(tlsOptions)
{
_sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
SSLSetIOFuncs(
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
;
}
SocketAppleSSL::~SocketAppleSSL()
{
CFRelease(_sslContext);
_sslContext = nullptr;
Socket::close();
SocketAppleSSL::close();
}
std::string SocketAppleSSL::getSSLErrorDescription(OSStatus status)
@@ -183,16 +177,14 @@ namespace ix
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
if (_sockfd == -1) return false;
_sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
SSLSetIOFuncs(
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
// Record a peer id, which speed up SSL connection when reconnecting to the same host
std::stringstream ss;
ss << host << ":" << port;
_peerId = ss.str();
SSLSetPeerID(_sslContext, (void*) _peerId.c_str(), _peerId.size());
if (_tlsOptions.isPeerVerifyDisabled())
{
Boolean option(1);
@@ -235,7 +227,12 @@ namespace ix
{
std::lock_guard<std::mutex> lock(_mutex);
if (_sslContext == nullptr) return;
SSLClose(_sslContext);
CFRelease(_sslContext);
_sslContext = nullptr;
Socket::close();
}

View File

@@ -41,8 +41,6 @@ namespace ix
mutable std::mutex _mutex; // AppleSSL routines are not thread-safe
SocketTLSOptions _tlsOptions;
std::string _peerId;
};
} // namespace ix

View File

@@ -19,12 +19,10 @@
#include <linux/tcp.h>
#endif
#include <iostream>
namespace ix
{
//
// This function can be cancelled every 10 ms
// This function can be cancelled every 50 ms
// This is important so that we don't block the main UI thread when shutting down a
// connection which is already trying to reconnect, and can be blocked waiting for
// ::connect to respond.
@@ -46,15 +44,8 @@ namespace ix
// block us for too long
SocketConnect::configure(fd);
auto start = std::chrono::system_clock::now();
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
auto now = std::chrono::system_clock::now();
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
auto ms = milliseconds.count();
std::cout << "tcp connection completed in " << ms << "ms" << std::endl;
if (res == -1 && !Socket::isWaitNeeded())
{
errMsg = strerror(Socket::getErrno());
@@ -107,19 +98,11 @@ namespace ix
std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
auto start = std::chrono::system_clock::now();
//
// First do DNS resolution
//
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
struct addrinfo* res = dnsLookup->resolve(errMsg, isCancellationRequested);
auto now = std::chrono::system_clock::now();
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
auto ms = milliseconds.count();
std::cout << "dns resolution completed in " << ms << "ms" << std::endl;
if (res == nullptr)
{
return -1;

View File

@@ -21,15 +21,18 @@ namespace ix
const std::string SocketServer::kDefaultHost("127.0.0.1");
const int SocketServer::kDefaultTcpBacklog(5);
const size_t SocketServer::kDefaultMaxConnections(32);
const int SocketServer::kDefaultAddressFamily(AF_INET);
SocketServer::SocketServer(int port,
const std::string& host,
int backlog,
size_t maxConnections)
size_t maxConnections,
int addressFamily)
: _port(port)
, _host(host)
, _backlog(backlog)
, _maxConnections(maxConnections)
, _addressFamily(addressFamily)
, _serverFd(-1)
, _stop(false)
, _stopGc(false)
@@ -56,10 +59,15 @@ namespace ix
std::pair<bool, std::string> SocketServer::listen()
{
struct sockaddr_in server; // server address information
if (_addressFamily != AF_INET && _addressFamily != AF_INET6)
{
std::string errMsg("SocketServer::listen() AF_INET and AF_INET6 are currently "
"the only supported address families");
return std::make_pair(false, errMsg);
}
// Get a socket for accepting connections.
if ((_serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
if ((_serverFd = socket(_addressFamily, SOCK_STREAM, 0)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error creating socket): " << strerror(Socket::getErrno());
@@ -79,19 +87,23 @@ namespace ix
return std::make_pair(false, ss.str());
}
// Bind the socket to the server address.
server.sin_family = AF_INET;
if (_addressFamily == AF_INET)
{
struct sockaddr_in server;
server.sin_family = _addressFamily;
server.sin_port = htons(_port);
// Using INADDR_ANY trigger a pop-up box as binding to any address is detected
// by the osx firewall. We need to codesign the binary with a self-signed cert
// to allow that, but this is a bit of a pain. (this is what node or python would do).
//
// Using INADDR_LOOPBACK also does not work ... while it should.
// We default to 127.0.0.1 (localhost)
//
server.sin_addr.s_addr = inet_addr(_host.c_str());
if (inet_pton(_addressFamily, _host.c_str(), &server.sin_addr.s_addr) <= 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling inet_pton "
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
// Bind the socket to the server address.
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
{
std::stringstream ss;
@@ -101,6 +113,34 @@ namespace ix
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
}
else // AF_INET6
{
struct sockaddr_in6 server;
server.sin6_family = _addressFamily;
server.sin6_port = htons(_port);
if (inet_pton(_addressFamily, _host.c_str(), &server.sin6_addr) <= 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling inet_pton "
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
// Bind the socket to the server address.
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
{
std::stringstream ss;
ss << "SocketServer::listen() error calling bind "
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
Socket::closeSocket(_serverFd);
return std::make_pair(false, ss.str());
}
}
//
// Listen for connections. Specify the tcp backlog.

View File

@@ -36,7 +36,8 @@ namespace ix
SocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections);
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int addressFamily = SocketServer::kDefaultAddressFamily);
virtual ~SocketServer();
virtual void stop();
@@ -49,6 +50,7 @@ namespace ix
const static std::string kDefaultHost;
const static int kDefaultTcpBacklog;
const static size_t kDefaultMaxConnections;
const static int kDefaultAddressFamily;
void start();
std::pair<bool, std::string> listen();
@@ -69,6 +71,7 @@ namespace ix
std::string _host;
int _backlog;
size_t _maxConnections;
int _addressFamily;
// socket for accepting connections
int _serverFd;

View File

@@ -169,6 +169,7 @@ namespace ix
// wait until working thread will exit
// it will exit after close operation is finished
_stop = true;
_sleepCondition.notify_one();
_thread.join();
_stop = false;
}
@@ -282,8 +283,13 @@ namespace ix
// Only sleep if we are retrying
if (duration.count() > 0)
{
// to do: make sleeping conditional
std::this_thread::sleep_for(duration);
std::unique_lock<std::mutex> lock(_sleepMutex);
_sleepCondition.wait_for(lock, duration);
}
if (_stop)
{
break;
}
// Try to connect synchronously

View File

@@ -22,6 +22,7 @@
#include <mutex>
#include <string>
#include <thread>
#include <condition_variable>
namespace ix
{
@@ -140,6 +141,10 @@ namespace ix
static const uint32_t kDefaultMaxWaitBetweenReconnectionRetries;
uint32_t _maxWaitBetweenReconnectionRetries;
// Make the sleeping in the automatic reconnection cancellable
std::mutex _sleepMutex;
std::condition_variable _sleepCondition;
std::atomic<int> _handshakeTimeoutSecs;
static const int kDefaultHandShakeTimeoutSecs;

View File

@@ -15,9 +15,6 @@
#include <random>
#include <sstream>
#include <iostream>
#include <chrono>
namespace ix
{
@@ -100,16 +97,8 @@ namespace ix
auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
auto start = std::chrono::system_clock::now();
std::string errMsg;
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
auto now = std::chrono::system_clock::now();
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
auto ms = milliseconds.count();
std::cout << "connection completed in " << ms << "ms" << std::endl;
if (!success)
{
std::stringstream ss;

View File

@@ -23,8 +23,9 @@ namespace ix
const std::string& host,
int backlog,
size_t maxConnections,
int handshakeTimeoutSecs)
: SocketServer(port, host, backlog, maxConnections)
int handshakeTimeoutSecs,
int addressFamily)
: SocketServer(port, host, backlog, maxConnections, addressFamily)
, _handshakeTimeoutSecs(handshakeTimeoutSecs)
, _enablePong(kDefaultEnablePong)
{
@@ -106,7 +107,6 @@ namespace ix
}
}
logInfo("WebSocketServer::handleConnection() done");
connectionState->setTerminated();
}

View File

@@ -29,7 +29,8 @@ namespace ix
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs,
int addressFamily = SocketServer::kDefaultAddressFamily);
virtual ~WebSocketServer();
virtual void stop() final;
@@ -41,6 +42,8 @@ namespace ix
// Get all the connected clients
std::set<std::shared_ptr<WebSocket>> getClients();
const static int kDefaultHandShakeTimeoutSecs;
private:
// Member variables
int _handshakeTimeoutSecs;
@@ -51,7 +54,6 @@ namespace ix
std::mutex _clientsMutex;
std::set<std::shared_ptr<WebSocket>> _clients;
const static int kDefaultHandShakeTimeoutSecs;
const static bool kDefaultEnablePong;
// Methods

View File

@@ -151,18 +151,7 @@ namespace ix
std::string errorMsg;
bool tls = protocol == "wss";
if (_host == host)
{
_socket->close();
}
else
{
_socket = createSocket(tls, -1, errorMsg, _socketTLSOptions);
}
// Record the host for later
_host = host;
if (!_socket)
{

View File

@@ -107,7 +107,7 @@ namespace ix
size_t bufferedAmount() const;
private:
std::string _host;
std::string _url;
struct wsheader_type
{

View File

@@ -6,4 +6,4 @@
#pragma once
#define IX_WEBSOCKET_VERSION "7.9.6"
#define IX_WEBSOCKET_VERSION "8.0.0"

View File

@@ -94,6 +94,7 @@ int main(int argc, char** argv)
bool disableAutomaticReconnection = false;
bool disablePerMessageDeflate = false;
bool greetings = false;
bool ipv6 = false;
bool binaryMode = false;
bool redirect = false;
bool version = false;
@@ -171,6 +172,7 @@ int main(int argc, char** argv)
echoServerApp->add_option("--port", port, "Port");
echoServerApp->add_option("--host", hostname, "Hostname");
echoServerApp->add_flag("-g", greetings, "Verbose");
echoServerApp->add_flag("-6", ipv6, "IpV6");
addTLSOptions(echoServerApp);
CLI::App* broadcastServerApp = app.add_subcommand("broadcast_server", "Broadcasting server");
@@ -390,7 +392,7 @@ int main(int argc, char** argv)
}
else if (app.got_subcommand("echo_server"))
{
ret = ix::ws_echo_server_main(port, greetings, hostname, tlsOptions);
ret = ix::ws_echo_server_main(port, greetings, hostname, tlsOptions, ipv6);
}
else if (app.got_subcommand("broadcast_server"))
{

View File

@@ -29,7 +29,9 @@ namespace ix
int ws_echo_server_main(int port,
bool greetings,
const std::string& hostname,
const ix::SocketTLSOptions& tlsOptions);
const ix::SocketTLSOptions& tlsOptions,
bool ipv6);
int ws_broadcast_server_main(int port,
const std::string& hostname,
const ix::SocketTLSOptions& tlsOptions);

View File

@@ -5,6 +5,7 @@
*/
#include <ixwebsocket/IXWebSocketServer.h>
#include <ixwebsocket/IXNetSystem.h>
#include <spdlog/spdlog.h>
#include <sstream>
@@ -13,11 +14,18 @@ namespace ix
int ws_echo_server_main(int port,
bool greetings,
const std::string& hostname,
const ix::SocketTLSOptions& tlsOptions)
const ix::SocketTLSOptions& tlsOptions,
bool ipv6)
{
spdlog::info("Listening on {}:{}", hostname, port);
ix::WebSocketServer server(port, hostname);
ix::WebSocketServer server(port,
hostname,
SocketServer::kDefaultTcpBacklog,
SocketServer::kDefaultMaxConnections,
WebSocketServer::kDefaultHandShakeTimeoutSecs,
(ipv6) ? AF_INET6 : AF_INET);
server.setTLSOptions(tlsOptions);
server.setOnConnectionCallback(