Repository: mnisjk/cppWebSockets Branch: master Commit: fbd2358d77f9 Files: 15 Total size: 31.2 KB Directory structure: gitextract_agtrf0tl/ ├── LICENSE ├── README.md ├── Util.cpp ├── Util.h ├── WebSocketServer.cpp ├── WebSocketServer.h └── examples/ ├── chatServer/ │ ├── Makefile │ ├── chatServer.cpp │ └── index.html ├── echoServer/ │ ├── Makefile │ ├── echoServer.cpp │ └── index.html └── multiPollServer/ ├── Makefile ├── index.html └── multiPollServer.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ Copyright (c) 2014, Jason Kruse All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JASON KRUSE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ********************************* Please note this is a C++ wrapper around Andy Green's libwebsockets C library. libwebsockets is licensed under the MIT license. ================================================ FILE: README.md ================================================ cppWebSockets =========== A simple, lightweight c++ WebSockets server library wrapped around the popular libwebsockets c library. ### Usage Create a class that extends `WebSocketServer` and implement the following callbacks ``` void onConnect( int socketID ); // New websocket connection void onMessage( int socketID, const string& data ); // Message received from connected client void onDisconnect( int socketID ); // Client disconnect void onError( int socketID, const string& message ); // Networking error ``` Then simply instantiate your server and call `run()` ``` MyServer s = MyServer( 8080 ); // MyServer extends WebSocketServer listening on port 8080 s.run( ); ``` At any arbitrary time, you can push a message to a client by calling `send( int socketID, string data )`. If your server is more complex and needs to monitor its own connections in addition to WebSocket connections, you can manage your own event loop. Instead of calling `s.run( )`, use the `s.wait( )` function. A good illustration of this is located in [examples/multiPollServer/multiPollServer.cpp](https://github.com/mnisjk/cppWebSockets/blob/master/examples/multiPollServer/multiPollServer.cpp) ### Features * Implement your own web socket server in less than 50 lines of c++. * OpenSSL support * WebSocket RFC6455 implementation * Abstracts away all c pointers and managing when sockets are writable * Push data to any client at any time * Key => Value storage for any socket with `setValue( int socketID, const string& name, const string& value );` and `getValue( int socketID, const string& name );` ### Examples Check out the [examples](https://github.com/mnisjk/cppWebSockets/blob/master/examples/) directory for fully implemented illustrations. There is a basic echo and chat server as well as a more complex server that manages multiple `poll( )` loops. They should demonstrate how easy this library is to use and serve as basic scaffolding for your projects. ### Dependencies This is built on top of [warmcat](http://warmcat.com/)'s wonderful, lightweight [libwebsocket](http://libwebsockets.org/) c library. To install: ``` mnisjk@localdev ~ $ git clone git://git.libwebsockets.org/libwebsockets mnisjk@localdev ~ $ cd libwebsockets mnisjk@localdev libwebsockets $ cmake . mnisjk@localdev libwebsockets $ make mnisjk@localdev libwebsockets $ sudo make install ``` ### Compile and run All examples have Makefiles, so simply run `make` and then run the example. When creating your own projects, your compile commands will look like the following: ``` mnisjk@localdev ~ $ g++ -w -DLOG_TO_STDOUT=1 -omyserver Util.cpp WebSocketServer.cpp myserver.cpp -lwebsockets mnisjk@localdev ~ $ ./myserver ``` ================================================ FILE: Util.cpp ================================================ /** -------------------------------------------------------------------------- * Util.cpp * * A few very basic utility functions for WebSocketServer * * Author : Jason Kruse or @mnisjk * Copyright : 2014 * License : BSD (see LICENSE) * -------------------------------------------------------------------------- **/ #include #include "Util.h" using namespace std; #define LOG_PREFIX "[cppWebSockets] " void Util::log( const string& message ) { const string& logMessage = LOG_PREFIX + message; syslog( LOG_WARNING, "%s", logMessage.c_str( ) ); #ifdef LOG_TO_STDOUT printf( "%s\n", logMessage.c_str( ) ); #endif } void Util::log( const char* message ) { log( string( message ) ); } ================================================ FILE: Util.h ================================================ /** -------------------------------------------------------------------------- * Util.h * * A few very basic utility functions for WebSocketServer * * Author : Jason Kruse or @mnisjk * Copyright : 2014 * License : BSD (see LICENSE) * -------------------------------------------------------------------------- **/ #ifndef _UTIL_H #define _UTIL_H #include #include #include #include using namespace std; ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// /// Util /// --------- class Util { public: static void log( const string& message ); static void log( const char* message ); template static inline string toString(T t) { stringstream s; s << t; return s.str(); } }; // Util.h #endif ================================================ FILE: WebSocketServer.cpp ================================================ /** -------------------------------------------------------------------------- * WebSocketServer.cpp * * Base class that WebSocket implementations must inherit from. Handles the * client connections and calls the child class callbacks for connection * events like onConnect, onMessage, and onDisconnect. * * Author : Jason Kruse or @mnisjk * Copyright : 2014 * License : BSD (see LICENSE) * -------------------------------------------------------------------------- **/ #include #include #include #include #include #include "libwebsockets.h" #include "Util.h" #include "WebSocketServer.h" using namespace std; // 0 for unlimited #define MAX_BUFFER_SIZE 0 // Nasty hack because certain callbacks are statically defined WebSocketServer *self; static int callback_main( struct lws *wsi, enum lws_callback_reasons reason, 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]; switch( reason ) { case LWS_CALLBACK_ESTABLISHED: self->onConnectWrapper( lws_get_socket_fd( wsi ) ); lws_callback_on_writable( wsi ); break; case LWS_CALLBACK_SERVER_WRITEABLE: fd = lws_get_socket_fd( wsi ); 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 ); if( charsSent < msgLen ) self->onErrorWrapper( fd, string( "Error writing to socket" ) ); else // Only pop the message if it was sent successfully. 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 ) ); break; case LWS_CALLBACK_CLOSED: self->onDisconnectWrapper( lws_get_socket_fd( wsi ) ); break; default: break; } 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 ) { this->_port = port; this->_certPath = certPath; this->_keyPath = keyPath; 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 ); info.port = this->_port; info.iface = NULL; info.protocols = protocols; #ifndef LWS_NO_EXTENSIONS info.extensions = lws_get_internal_extensions( ); #endif if( !this->_certPath.empty( ) && !this->_keyPath.empty( ) ) { Util::log( "Using SSL certPath=" + this->_certPath + ". keyPath=" + this->_keyPath + "." ); info.ssl_cert_filepath = this->_certPath.c_str( ); info.ssl_private_key_filepath = this->_keyPath.c_str( ); } else { Util::log( "Not using SSL" ); info.ssl_cert_filepath = NULL; info.ssl_private_key_filepath = NULL; } info.gid = -1; info.uid = -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 ) ); // Some of the libwebsocket stuff is define statically outside the class. This // allows us to call instance variables from the outside. Unfortunately this // means some attributes must be public that otherwise would be private. self = this; } WebSocketServer::~WebSocketServer( ) { // Free up some memory for( map::const_iterator it = this->connections.begin( ); it != this->connections.end( ); ++it ) { Connection* c = it->second; this->connections.erase( it->first ); delete c; } } void WebSocketServer::onConnectWrapper( int socketID ) { Connection* c = new Connection; c->createTime = time( 0 ); this->connections[ socketID ] = c; this->onConnect( socketID ); } void WebSocketServer::onDisconnectWrapper( int socketID ) { this->onDisconnect( socketID ); this->_removeConnection( socketID ); } void WebSocketServer::onErrorWrapper( int socketID, const 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 ) { // Push this onto the buffer. It will be written out when the socket is writable. this->connections[socketID]->buffer.push_back( data.c_str() ); } void WebSocketServer::broadcast(string data ) { for( map::const_iterator it = this->connections.begin( ); it != this->connections.end( ); ++it ) this->send( it->first, data ); } void WebSocketServer::setValue( int socketID, const string& name, const string& value ) { this->connections[socketID]->keyValueMap[name] = value; } string WebSocketServer::getValue( int socketID, const string& name ) { return this->connections[socketID]->keyValueMap[name]; } int WebSocketServer::getNumberOfConnections( ) { return this->connections.size( ); } void WebSocketServer::run( uint64_t timeout ) { while( 1 ) { this->wait( timeout ); } } void WebSocketServer::wait( uint64_t timeout ) { if( lws_service( this->_context, timeout ) < 0 ) throw "Error polling for socket activity."; } void WebSocketServer::_removeConnection( int socketID ) { Connection* c = this->connections[ socketID ]; this->connections.erase( socketID ); delete c; } ================================================ FILE: WebSocketServer.h ================================================ /** -------------------------------------------------------------------------- * WebSocketServer.h * * Base class that WebSocket implementations must inherit from. Handles the * client connections and calls the child class callbacks for connection * events like onConnect, onMessage, and onDisconnect. * * Author : Jason Kruse or @mnisjk * Copyright : 2014 * License : BSD (see LICENSE) * -------------------------------------------------------------------------- **/ #ifndef _WEBSOCKETSERVER_H #define _WEBSOCKETSERVER_H #include #include #include #include #include #include #include #include #include #include "libwebsockets.h" using namespace std; ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// /// WebSocketServer /// --------- class WebSocketServer { public: // Represents a client connection struct Connection { list buffer; // Ordered list of pending messages to flush out when socket is writable 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; // Constructor / Destructor WebSocketServer( int port, const string certPath = "", const string& keyPath = "" ); ~WebSocketServer( ); void run( uint64_t timeout = 50 ); void wait( uint64_t timeout = 50 ); void send( int socketID, string data ); void broadcast( 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( ); // Overridden by children virtual void onConnect( int socketID ) = 0; virtual void onMessage( int socketID, const string& data ) = 0; virtual void onDisconnect( int socketID ) = 0; virtual void onError( int socketID, const 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 ); protected: // Nothing, yet. private: int _port; string _keyPath; string _certPath; struct lws_context *_context; void _removeConnection( int socketID ); }; // WebSocketServer.h #endif ================================================ FILE: examples/chatServer/Makefile ================================================ hellomake: g++ -w -DLOG_TO_STDOUT=1 -ochatserver ../../Util.cpp ../../WebSocketServer.cpp chatServer.cpp -lwebsockets ================================================ FILE: examples/chatServer/chatServer.cpp ================================================ /** -------------------------------------------------------------------------- * chatServer.cpp * * A basic example of how to implement a WebSocketServer. Creats a WS server * bound to port 8080 for basic chatting. It listens for connections and * assigns them a random handle based on their socket FD. Whenever a message * is received on the server, it is sent out to all connected clients. * received, it echos the same message back to the client. * * Author : Jason Kruse or @mnisjk * Copyright : 2014 * License : BSD (see LICENSE) * -------------------------------------------------------------------------- **/ // Log to stdout for easy debugging. #define LOG_TO_STDOUT 1 #include "../../Util.h" #include "../../WebSocketServer.h" using namespace std; // For any real project this should be defined separately in a header file class ChatServer : public WebSocketServer { public: ChatServer( int port ); ~ChatServer( ); virtual void onConnect( int socketID ); virtual void onMessage( int socketID, const string& data ); virtual void onDisconnect( int socketID ); virtual void onError( int socketID, const string& message ); }; int main( int argc, char **argv ) { ChatServer cs = ChatServer( 8080 ); cs.run( ); } ChatServer::ChatServer( int port ) : WebSocketServer( port ) { } ChatServer::~ChatServer( ) { } void ChatServer::onConnect( int socketID ) { // Give this connection a random user ID const string& handle = "User #" + Util::toString( socketID ); Util::log( "New connection: " + handle ); // Associate this handle with the connection this->setValue( socketID, "handle", handle ); // Let everyone know the new user has connected this->broadcast( handle + " has connected." ); } void ChatServer::onMessage( int socketID, const string& data ) { // Send the received message to all connected clients in the form of 'User XX: message...' Util::log( "Received: " + data ); const string& message = this->getValue( socketID, "handle" ) + ": " + data; this->broadcast( message ); } void ChatServer::onDisconnect( int socketID ) { const string& handle = this->getValue( socketID, "handle" ); Util::log( "Disconnected: " + handle ); // 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 ) 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 ); } void ChatServer::onError( int socketID, const string& message ) { Util::log( "Error: " + message ); } ================================================ FILE: examples/chatServer/index.html ================================================ cppWebSockets chat server example

Basic chat server

This is a super basic example of a chat server using lwebsockets++. This also shows how to use the key => value storage to associate data with a connection.


  • Compile and run the server in examples/chatServer.
  • Open this page in multiple tabs.
  • Start chatting with yourself


Note: There is no flash fallback, so make sure you have a modern browser with WebSocket support.

Demo
Server responses
================================================ FILE: examples/echoServer/Makefile ================================================ hellomake: g++ -w -DLOG_TO_STDOUT=1 -oechoserver ../../Util.cpp ../../WebSocketServer.cpp echoServer.cpp -lwebsockets ================================================ FILE: examples/echoServer/echoServer.cpp ================================================ /** -------------------------------------------------------------------------- * echoServer.cpp * * A basic example of how to implement a WebSocketServer. Creats a WS server * bound to port 8080. It listens for connections, and when a message is * received, it echos the same message back to the client. * * Author : Jason Kruse or @mnisjk * Copyright : 2014 * License : BSD (see LICENSE) * -------------------------------------------------------------------------- **/ // Log to stdout for easy debugging. #define LOG_TO_STDOUT 1 #include "../../Util.h" #include "../../WebSocketServer.h" using namespace std; // For any real project this should be defined separately in a header file class EchoServer : public WebSocketServer { public: EchoServer( int port ); ~EchoServer( ); virtual void onConnect( int socketID ); virtual void onMessage( int socketID, const string& data ); virtual void onDisconnect( int socketID ); virtual void onError( int socketID, const string& message ); }; int main( int argc, char **argv ) { EchoServer es = EchoServer( 8080 ); es.run( ); } EchoServer::EchoServer( int port ) : WebSocketServer( port ) { } EchoServer::~EchoServer( ) { } void EchoServer::onConnect( int socketID ) { Util::log( "New connection" ); } void EchoServer::onMessage( int socketID, const string& data ) { // Reply back with the same message Util::log( "Received: " + data ); this->send( socketID, data ); } void EchoServer::onDisconnect( int socketID ) { Util::log( "Disconnect" ); } void EchoServer::onError( int socketID, const string& message ) { Util::log( "Error: " + message ); } ================================================ FILE: examples/echoServer/index.html ================================================ cppWebSockets echo example

Send a message to the server

This is a super basic example of how to connect to the websocket server.


  • Compile and run the server in examples/echoServer.
  • Enter a message in the text box below; it will be sent to the server which will reply back with the same message.


Note: There is no flash fallback, so make sure you have a modern browser with WebSocket support.

Demo
Server responses
================================================ FILE: examples/multiPollServer/Makefile ================================================ hellomake: g++ -w -omultipollserver ../../Util.cpp ../../WebSocketServer.cpp multiPollServer.cpp -lwebsockets ================================================ FILE: examples/multiPollServer/index.html ================================================ cppWebSockets multipoll example

Multi Poll server

A basic example of how to implement a WebSocketServer and listen to other fds.


This is useful when your server needs to handle connections from WebSocket clients and other TCP connections at the same time.


  • Compile and run the server in examples/multiPollServer.
  • Enter messages into standard input and they will be sent out to all connected clients.


Note: There is no flash fallback, so make sure you have a modern browser with WebSocket support.

Demo
Server messages
================================================ FILE: examples/multiPollServer/multiPollServer.cpp ================================================ /** -------------------------------------------------------------------------- * multiPollServer.cpp * * A basic example of how to implement a WebSocketServer *AND* listen to other * fds. This is useful when your server needs to handle connections from * WebSocket clients and other TCP connections at the same time. * * It also demonstrates how to send to an arbitrary WebSocket connection at * any time. * * This program listens for input on stdin and WebSockets on port 8080 * simultaneously. When new data is available on stdin (line buffered), * it sends that string out to all connected WebSocket clients. * * Author : Jason Kruse or @mnisjk * Copyright : 2014 * License : BSD (see LICENSE) * -------------------------------------------------------------------------- **/ #include #include #include "../../Util.h" #include "../../WebSocketServer.h" #define TIMEOUT 50 #define PROMPT "message> " using namespace std; // For any real project this should be defined separately in a header file class MultiPollServer : public WebSocketServer { public: MultiPollServer( int port ); ~MultiPollServer( ); virtual void onConnect( int socketID ); virtual void onMessage( int socketID, const string& data ); virtual void onDisconnect( int socketID ); virtual void onError( int socketID, const string& message ); }; void showPrompt( ) { cout << PROMPT; fflush( stdout ); } int main( int argc, char **argv ) { // Set up the stdin polling int p; char buf[1024]; struct pollfd pfds[1]; pfds[0].fd = 0; pfds[0].events = POLLIN; // Start the WebSocket server MultiPollServer s = MultiPollServer( 8080 ); cout << "Type a message followed by enter to send to all connected clients." << endl << endl; showPrompt( ); while( 1 ) { // Handle websocket stuff s.wait( TIMEOUT ); // Handle stdin p = poll( pfds, 1, TIMEOUT ); if( p > 0 && pfds[0].revents & POLLIN ) { int len = read( 0, buf, 1024 ); if( !len ) return 0; buf[len] = 0; // make sure the c-string terminator is where we want it to be since this buf is re-used. string input = string( buf ); input.resize( input.size( ) - 1 ); // Remove the trailing \n // Send it out to WS clients. Util::log( "Sending '" + input + "' to " + Util::toString( s.getNumberOfConnections( ) ) + " client(s)." ); s.broadcast( input ); // Diplay the prompt. showPrompt( ); } } } MultiPollServer::MultiPollServer( int port ) : WebSocketServer( port ) { } MultiPollServer::~MultiPollServer( ) { } void MultiPollServer::onConnect( int socketID ) { Util::log( "New connection" ); } void MultiPollServer::onMessage( int socketID, const string& data ) { // Should never get hit. } void MultiPollServer::onDisconnect( int socketID ) { Util::log( "Disconnect" ); } void MultiPollServer::onError( int socketID, const string& message ) { Util::log( "Error: " + message ); }