diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..28e1136 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.18) + +project(cppwebsockets) +find_package(PkgConfig) +pkg_check_modules(LIB_WEBSOCKETS REQUIRED libwebsockets) + +set(CMAKE_CXX_FLAGS -DLWS_NO_EXTENSIONS ) +include_directories(${CMAKE_INSTALL_PREFIX}/include) + +add_library(cppwebsockets + Util.cpp + Util.h + WebSocketServer.cpp + WebSocketServer.h) + +target_link_libraries(cppwebsockets PUBLIC + ${LIB_WEBSOCKETS_LIBRARIES}) + +install(FILES Util.h WebSocketServer.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/cppwebsockets) +install(TARGETS cppwebsockets DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) diff --git a/Util.cpp b/Util.cpp index a39e603..62c5a05 100644 --- a/Util.cpp +++ b/Util.cpp @@ -6,28 +6,27 @@ * Author : Jason Kruse or @mnisjk * Copyright : 2014 * License : BSD (see LICENSE) - * -------------------------------------------------------------------------- + * -------------------------------------------------------------------------- **/ -#include #include "Util.h" +#include -using namespace std; +constexpr auto LOG_PREFIX = "[cppWebSockets] "; -#define LOG_PREFIX "[cppWebSockets] " - -void Util::log( const string& message ) +void Util::log( const std::string& message ) { - const string& logMessage = LOG_PREFIX + message; + const std::string& logMessage = LOG_PREFIX + message; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg, hicpp-vararg) syslog( LOG_WARNING, "%s", logMessage.c_str( ) ); #ifdef LOG_TO_STDOUT - printf( "%s\n", logMessage.c_str( ) ); -#endif + std::cout << logMessage << std::endl; +#endif } void Util::log( const char* message ) { - log( string( message ) ); + log( std::string( message ) ); } diff --git a/Util.h b/Util.h index bd0c632..51592d8 100644 --- a/Util.h +++ b/Util.h @@ -6,18 +6,16 @@ * Author : Jason Kruse or @mnisjk * Copyright : 2014 * License : BSD (see LICENSE) - * -------------------------------------------------------------------------- + * -------------------------------------------------------------------------- **/ -#ifndef _UTIL_H -#define _UTIL_H +#ifndef UTIL_H +#define UTIL_H -#include -#include #include #include - -using namespace std; +#include +#include ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// @@ -27,10 +25,10 @@ using namespace std; class Util { public: - static void log( const string& message ); + static void log( const std::string& message ); static void log( const char* message ); template - static inline string toString(T t) { stringstream s; s << t; return s.str(); } + static inline std::string toString(T t) { std::stringstream s; s << t; return s.str(); } }; // Util.h diff --git a/WebSocketServer.cpp b/WebSocketServer.cpp index 33da537..aabf329 100644 --- a/WebSocketServer.cpp +++ b/WebSocketServer.cpp @@ -11,32 +11,30 @@ * -------------------------------------------------------------------------- **/ -#include -#include -#include -#include -#include #include "libwebsockets.h" #include "Util.h" #include "WebSocketServer.h" - -using namespace std; +#include +#include +#include +#include +#include +#include +#include // 0 for unlimited -#define MAX_BUFFER_SIZE 0 +constexpr int MAX_BUFFER_SIZE=0; // Nasty hack because certain callbacks are statically defined -WebSocketServer *self; +static WebSocketServer *self; static int callback_main( struct lws *wsi, enum lws_callback_reasons reason, - void *user, + void * /* user */, void *in, size_t len ) { - int fd; - unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 + LWS_SEND_BUFFER_POST_PADDING]; - unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING]; + int fd = -1; switch( reason ) { case LWS_CALLBACK_ESTABLISHED: @@ -46,22 +44,31 @@ static int callback_main( struct lws *wsi, case LWS_CALLBACK_SERVER_WRITEABLE: fd = lws_get_socket_fd( wsi ); - while( !self->connections[fd]->buffer.empty( ) ) + while( !self->connections()[fd]->buffer.empty( ) ) { - const char * message = self->connections[fd]->buffer.front( ); - int msgLen = strlen(message); - int charsSent = lws_write( wsi, (unsigned char *)message, msgLen, LWS_WRITE_TEXT ); + std::string message = self->connections()[fd]->buffer.front( ); + size_t msgLen = message.size(); + + std::vector buf; + buf.resize(LWS_SEND_BUFFER_PRE_PADDING + msgLen + LWS_SEND_BUFFER_POST_PADDING); + unsigned char *ptr = &buf[LWS_SEND_BUFFER_PRE_PADDING]; + std::memcpy(ptr, message.c_str(), message.size()); + auto charsSent = size_t(lws_write( wsi, ptr, msgLen, LWS_WRITE_TEXT )); if( charsSent < msgLen ) - self->onErrorWrapper( fd, string( "Error writing to socket" ) ); + { + self->onErrorWrapper( fd, std::string( "Error writing to socket" ) ); + } else + { // Only pop the message if it was sent successfully. - self->connections[fd]->buffer.pop_front( ); + self->connections()[fd]->buffer.pop_front(); + } } lws_callback_on_writable( wsi ); break; case LWS_CALLBACK_RECEIVE: - self->onMessage( lws_get_socket_fd( wsi ), string( (const char *)in, len ) ); + self->onMessage( lws_get_socket_fd( wsi ), std::string( reinterpret_cast(in), len ) ); break; case LWS_CALLBACK_CLOSED: @@ -74,27 +81,31 @@ static int callback_main( struct lws *wsi, return 0; } -static struct lws_protocols protocols[] = { - { - "/", - callback_main, - 0, // user data struct not used - MAX_BUFFER_SIZE, - },{ NULL, NULL, 0, 0 } // terminator -}; - -WebSocketServer::WebSocketServer( int port, const string certPath, const string& keyPath ) +WebSocketServer::WebSocketServer( int port, const std::string &certPath, const std::string& keyPath ): + _port(port), + _keyPath(keyPath), + _certPath(certPath) { - this->_port = port; - this->_certPath = certPath; - this->_keyPath = keyPath; + struct lws_protocols protocols[] = + { + { + "/", + callback_main, + 0, // user data struct not used + MAX_BUFFER_SIZE, + 0, + nullptr, + 0 + }, + { nullptr, nullptr, 0, 0, 0, nullptr, 0 + } // terminator + }; lws_set_log_level( 0, lwsl_emit_syslog ); // We'll do our own logging, thank you. - struct lws_context_creation_info info; - memset( &info, 0, sizeof info ); + struct lws_context_creation_info info = {}; info.port = this->_port; - info.iface = NULL; - info.protocols = protocols; + info.iface = nullptr; + info.protocols = &protocols[0]; #ifndef LWS_NO_EXTENSIONS info.extensions = lws_get_internal_extensions( ); #endif @@ -108,21 +119,23 @@ WebSocketServer::WebSocketServer( int port, const string certPath, const string& else { Util::log( "Not using SSL" ); - info.ssl_cert_filepath = NULL; - info.ssl_private_key_filepath = NULL; + info.ssl_cert_filepath = nullptr; + info.ssl_private_key_filepath = nullptr; } - info.gid = -1; - info.uid = -1; + info.gid = gid_t(-1); + info.uid = gid_t(-1); info.options = 0; // keep alive info.ka_time = 60; // 60 seconds until connection is suspicious info.ka_probes = 10; // 10 probes after ^ time info.ka_interval = 10; // 10s interval for sending probes - this->_context = lws_create_context( &info ); - if( !this->_context ) - throw "libwebsocket init failed"; - Util::log( "Server started on port " + Util::toString( this->_port ) ); + _context = lws_create_context( &info ); + if(_context == nullptr) + { + throw std::invalid_argument("libwebsocket init failed"); + } + Util::log( "Server started on port " + Util::toString( _port ) ); // Some of the libwebsocket stuff is define statically outside the class. This // allows us to call instance variables from the outside. Unfortunately this @@ -133,19 +146,19 @@ WebSocketServer::WebSocketServer( int port, const string certPath, const string& WebSocketServer::~WebSocketServer( ) { // Free up some memory - for( map::const_iterator it = this->connections.begin( ); it != this->connections.end( ); ++it ) + for( auto it = this->connections().begin( ); it != this->connections().end( ); ++it ) { Connection* c = it->second; - this->connections.erase( it->first ); + this->connections().erase( it->first ); delete c; } } void WebSocketServer::onConnectWrapper( int socketID ) { - Connection* c = new Connection; - c->createTime = time( 0 ); - this->connections[ socketID ] = c; + auto *c = new Connection; + c->createTime = time( nullptr ); + this->connections()[ socketID ] = c; this->onConnect( socketID ); } @@ -155,56 +168,65 @@ void WebSocketServer::onDisconnectWrapper( int socketID ) this->_removeConnection( socketID ); } -void WebSocketServer::onErrorWrapper( int socketID, const string& message ) +void WebSocketServer::onErrorWrapper( int socketID, const std::string& message ) { Util::log( "Error: " + message + " on socketID '" + Util::toString( socketID ) + "'" ); this->onError( socketID, message ); this->_removeConnection( socketID ); } -void WebSocketServer::send( int socketID, string data ) +void WebSocketServer::send( int socketID, const std::string &data ) { // Push this onto the buffer. It will be written out when the socket is writable. - this->connections[socketID]->buffer.push_back( data.c_str() ); + connections()[socketID]->buffer.emplace_back( data.c_str() ); } -void WebSocketServer::broadcast(string data ) +void WebSocketServer::broadcast(const std::string &data ) { - for( map::const_iterator it = this->connections.begin( ); it != this->connections.end( ); ++it ) - this->send( it->first, data ); + for( auto it = this->connections().begin( ); it != this->connections().end( ); ++it ) + { + send( it->first, data ); + } } -void WebSocketServer::setValue( int socketID, const string& name, const string& value ) +void WebSocketServer::setValue( int socketID, const std::string& name, const std::string& value ) { - this->connections[socketID]->keyValueMap[name] = value; + this->connections()[socketID]->keyValueMap[name] = value; } -string WebSocketServer::getValue( int socketID, const string& name ) +std::string WebSocketServer::getValue( int socketID, const std::string& name ) const { - return this->connections[socketID]->keyValueMap[name]; + auto it = _connections.find(socketID); + if (it != _connections.end()) + { + return it->second->keyValueMap[name]; + } + return std::string(); } -int WebSocketServer::getNumberOfConnections( ) +int WebSocketServer::getNumberOfConnections( ) const { - return this->connections.size( ); + return int(_connections.size( )); } -void WebSocketServer::run( uint64_t timeout ) +void WebSocketServer::run( int timeout ) { - while( 1 ) + while( true ) { - this->wait( timeout ); + wait( timeout ); } } -void WebSocketServer::wait( uint64_t timeout ) +void WebSocketServer::wait( int timeout ) { - if( lws_service( this->_context, timeout ) < 0 ) - throw "Error polling for socket activity."; + if( lws_service( this->_context, int(timeout) ) < 0 ) + { + throw std::out_of_range("Error polling for socket activity."); + } } void WebSocketServer::_removeConnection( int socketID ) { - Connection* c = this->connections[ socketID ]; - this->connections.erase( socketID ); + Connection* c = _connections[ socketID ]; + _connections.erase( socketID ); delete c; } diff --git a/WebSocketServer.h b/WebSocketServer.h index a13a403..2e9df52 100644 --- a/WebSocketServer.h +++ b/WebSocketServer.h @@ -11,20 +11,19 @@ * -------------------------------------------------------------------------- **/ -#ifndef _WEBSOCKETSERVER_H -#define _WEBSOCKETSERVER_H -#include -#include -#include -#include -#include +#ifndef WEBSOCKETSERVER_H +#define WEBSOCKETSERVER_H + +#include "libwebsockets.h" +#include +#include #include -#include #include +#include +#include #include -#include "libwebsockets.h" - -using namespace std; +#include +#include ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// @@ -36,51 +35,57 @@ class WebSocketServer // Represents a client connection struct Connection { - list buffer; // Ordered list of pending messages to flush out when socket is writable - map keyValueMap; + std::list buffer; // Ordered list of pending messages to flush out when socket is writable + std::map keyValueMap; time_t createTime; }; // Manages connections. Unfortunately this is public because static callback for // libwebsockets is defined outside the instance and needs access to it. - map connections; + std::map &connections() { return _connections; } // Constructor / Destructor - WebSocketServer( int port, const string certPath = "", const string& keyPath = "" ); - ~WebSocketServer( ); + explicit WebSocketServer( int port, const std::string &certPath = "", const std::string& keyPath = "" ); + virtual ~WebSocketServer( ); - void run( uint64_t timeout = 50 ); - void wait( uint64_t timeout = 50 ); - void send( int socketID, string data ); - void broadcast( string data ); + void run( int timeout = 50 ); + void wait( int timeout = 50 ); + void send( int socketID, const std::string &data ); + void broadcast( const std::string &data ); // Key => value storage for each connection - string getValue( int socketID, const string& name ); - void setValue( int socketID, const string& name, const string& value ); - int getNumberOfConnections( ); + std::string getValue( int socketID, const std::string& name ) const; + void setValue( int socketID, const std::string& name, const std::string& value ); + int getNumberOfConnections( ) const; // Overridden by children virtual void onConnect( int socketID ) = 0; - virtual void onMessage( int socketID, const string& data ) = 0; + virtual void onMessage( int socketID, const std::string& data ) = 0; virtual void onDisconnect( int socketID ) = 0; - virtual void onError( int socketID, const string& message ) = 0; + virtual void onError( int socketID, const std::string& message ) = 0; // Wrappers, so we can take care of some maintenance void onConnectWrapper( int socketID ); void onDisconnectWrapper( int socketID ); - void onErrorWrapper( int socketID, const string& message ); + void onErrorWrapper( int socketID, const std::string& message ); protected: // Nothing, yet. private: int _port; - string _keyPath; - string _certPath; + std::string _keyPath; + std::string _certPath; struct lws_context *_context; + std::map _connections; void _removeConnection( int socketID ); + // private copy constructor to keep it from being used (the libwebsockets C library needs special care) + WebSocketServer(const WebSocketServer &rhs); + WebSocketServer(const WebSocketServer &&rhs) noexcept; + WebSocketServer& operator=(const WebSocketServer &rhs); + WebSocketServer& operator=(const WebSocketServer &&rhs) noexcept; }; // WebSocketServer.h diff --git a/examples/chatServer/chatServer.cpp b/examples/chatServer/chatServer.cpp index 7b6b771..ad5ab65 100644 --- a/examples/chatServer/chatServer.cpp +++ b/examples/chatServer/chatServer.cpp @@ -77,11 +77,15 @@ void ChatServer::onDisconnect( int socketID ) // Let everyone know the user has disconnected const string& message = handle + " has disconnected."; - for( map::const_iterator it = this->connections.begin( ); it != this->connections.end( ); ++it ) + for( auto it = connections().begin( ); it != this->connections().end( ); ++it ) + { if( it->first != socketID ) + { // The disconnected connection gets deleted after this function runs, so don't try to send to it // (It's still around in case the implementing class wants to perform any clean up actions) - this->send( it->first, message ); + send( it->first, message ); + } + } } void ChatServer::onError( int socketID, const string& message ) diff --git a/examples/echoServer/echoServer.cpp b/examples/echoServer/echoServer.cpp index 1e32117..05cdfb1 100644 --- a/examples/echoServer/echoServer.cpp +++ b/examples/echoServer/echoServer.cpp @@ -55,7 +55,7 @@ void EchoServer::onMessage( int socketID, const string& data ) { // Reply back with the same message Util::log( "Received: " + data ); - this->send( socketID, data ); + send( socketID, data ); } void EchoServer::onDisconnect( int socketID )