├── LICENSE ├── README.md ├── capi └── c_api.cpp ├── example ├── README.md ├── main.cpp ├── main_capi.cpp └── test-server.py ├── include └── websocket_client.h ├── src ├── WebSocketClientImplCurl.cpp └── WebSocketClientImplCurl.h └── test ├── endian ├── README.md └── main.cpp └── websocket └── main.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 YouRancestor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebSocketClient 2 | 3 | A lightweight websocket client implemented in c++, based on libcurl(introduced for HTTP implementation). 4 | 5 | ## Usage 6 | 7 | Just copy files under src/ into your project, and add curl include directory, link libcurl library. 8 | Note: also link Ws2_32.lib on Windows. 9 | 10 | ## Dependencies 11 | 12 | - [libcurl](https://curl.haxx.se) (required) [download releases](https://curl.haxx.se/download.html) 13 | - [openssl](https://www.openssl.org) (optional, depending on your curl library) [Windows releases](https://curl.haxx.se/windows/) 14 | -------------------------------------------------------------------------------- /capi/c_api.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | using namespace ws; 4 | struct websocket_client_t : public WebSocketClientImplCurl 5 | { 6 | websocket_client_t() :conn_cb(NULL), recv_cb(NULL), opaque(NULL) {} 7 | virtual void OnConnect(ConnectResult result)override; 8 | virtual void OnRecv(Message msg, bool fin) override; 9 | 10 | websocket_client_connect_callback conn_cb; 11 | websocket_client_receive_callback recv_cb; 12 | void* opaque; 13 | }; 14 | 15 | websocket_client_t* websocket_client_create() 16 | { 17 | return new websocket_client_t; 18 | } 19 | void websocket_client_destroy(websocket_client_t* client) 20 | { 21 | delete client; 22 | } 23 | 24 | void websocket_client_connect_server(websocket_client_t* client, const char* url) 25 | { 26 | client->Connect(url); 27 | } 28 | 29 | int websocket_client_send_sessage(websocket_client_t* client, websocket_message_t msg) 30 | { 31 | return client->Send(ws::Message((FrameType)msg.type, msg.data, msg.len)); 32 | } 33 | 34 | void websocket_client_set_callbacks( 35 | websocket_client_t* client, 36 | websocket_client_connect_callback conn_cb, 37 | websocket_client_receive_callback recv_cb, 38 | void* opaque) 39 | { 40 | client->conn_cb = conn_cb; 41 | client->recv_cb = recv_cb; 42 | client->opaque = opaque; 43 | } 44 | 45 | void websocket_client_t::OnConnect(ConnectResult result) 46 | { 47 | if(this->conn_cb) 48 | this->conn_cb((websocket_client_connect_result_t)result, this->opaque); 49 | } 50 | 51 | void websocket_client_t::OnRecv(Message msg, bool fin) 52 | { 53 | if (this->recv_cb) 54 | { 55 | websocket_message_t message; 56 | message.type = (websocket_frame_type_t)msg.type; 57 | message.data = msg.data; 58 | message.len = msg.len; 59 | this->recv_cb(message, fin, this->opaque); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | ## Usage 4 | 5 | ### Client 6 | 7 | - Install libcurl 8 | 9 | ```sh 10 | $ sudo apt install libcurl4-openssl-dev # install libcurl 11 | ``` 12 | 13 | - Compile main.cpp with files under src/ into an excutable file. 14 | 15 | #### Linux 16 | 17 | ```sh 18 | $ g++ main.cpp ../src/WebSocketClientImplCurl.cpp -I../src/ -fpermissive -pthread -lcurl -o example 19 | $ ./example 20 | ``` 21 | 22 | ### Server (python is required) 23 | 24 | - Install tornado 5.0.2 package. (other version may be incompatible) 25 | 26 | ```sh 27 | $ pip install tornado==5.0.2 28 | ``` 29 | 30 | - Start test-server with the follow command. This server will listen on port 8000 by default. 31 | 32 | ```sh 33 | $ python test-server.py 34 | ``` 35 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | #include "WebSocketClientImplCurl.h" 2 | #include 3 | #include 4 | 5 | static const char* message = "Hello!"; 6 | using namespace ws; 7 | 8 | class MyWsClient : public WebSocketClientImplCurl 9 | { 10 | virtual void OnConnect(ConnectResult result)override; 11 | virtual void OnRecv(Message msg, bool fin) override; 12 | }; 13 | 14 | static const char g_url[] = "http://127.0.0.1:8000/ws"; 15 | 16 | int main(int argc, char *argv[]) 17 | { 18 | MyWsClient cl; 19 | 20 | char * url = new char[sizeof(g_url)]; 21 | memcpy(url, g_url, sizeof(g_url)); 22 | cl.Connect(url); 23 | delete [] url; 24 | 25 | std::this_thread::sleep_for(std::chrono::seconds(1)); 26 | 27 | cl.Close(); 28 | 29 | std::this_thread::sleep_for(std::chrono::seconds(5)); 30 | 31 | return 0; 32 | } 33 | 34 | void MyWsClient::OnConnect(ConnectResult result) 35 | { 36 | if (result == ws::WebSocketClientImplCurl::Success) 37 | { 38 | Message msg(ws::Text, message, strlen(message)); 39 | 40 | Send(msg); 41 | 42 | } 43 | else 44 | { 45 | printf("Websocket connect failed.\n"); 46 | } 47 | } 48 | 49 | void MyWsClient::OnRecv(Message msg, bool fin) 50 | { 51 | printf("receive message(type %d): %s\n", msg.type, msg.data); 52 | } 53 | -------------------------------------------------------------------------------- /example/main_capi.cpp: -------------------------------------------------------------------------------- 1 | #include "websocket_client.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static const char g_url[] = "http://127.0.0.1:8000/ws"; 8 | static const char* message = "Hello!"; 9 | 10 | void on_connect(websocket_client_connect_result_t result, void* opaque); 11 | 12 | void on_receive(websocket_message_t msg, int fin, void* opaque); 13 | 14 | void send_close(websocket_client_t* wsclient) 15 | { 16 | websocket_message_t msg={Close, nullptr, 0}; 17 | websocket_client_send_sessage(wsclient, msg); 18 | } 19 | 20 | int main(int argc, char* argv[]) 21 | { 22 | websocket_client_t* wsclient = websocket_client_create(); 23 | char* url = (char* )malloc(sizeof(g_url)); 24 | if (url) 25 | { 26 | strcpy_s(url, sizeof(g_url), g_url); 27 | } 28 | else 29 | { 30 | printf("not enough memory"); 31 | return 1; 32 | } 33 | 34 | websocket_client_set_callbacks(wsclient, on_connect, on_receive, wsclient); 35 | 36 | websocket_client_connect_server(wsclient, url); 37 | free(url); 38 | 39 | std::this_thread::sleep_for(std::chrono::seconds(2)); 40 | 41 | send_close(wsclient); 42 | 43 | std::this_thread::sleep_for(std::chrono::seconds(5)); 44 | 45 | return 0; 46 | } 47 | 48 | void on_connect(websocket_client_connect_result_t result, void* opaque) 49 | { 50 | websocket_client_t* wsclient = (websocket_client_t*)opaque; 51 | if (result == Success) 52 | { 53 | websocket_message_t msg; 54 | msg.type = Text; 55 | msg.data = message; 56 | msg.len = strlen(message); 57 | 58 | websocket_client_send_sessage(wsclient, msg); 59 | 60 | } 61 | else 62 | { 63 | printf("Websocket connect failed.\n"); 64 | } 65 | 66 | } 67 | 68 | void on_receive(websocket_message_t msg, int fin, void* opaque) 69 | { 70 | printf("receive message(type %d): %s\n", msg.type, msg.data); 71 | } 72 | -------------------------------------------------------------------------------- /example/test-server.py: -------------------------------------------------------------------------------- 1 | import tornado 2 | import tornado.websocket 3 | from tornado.httpserver import HTTPServer 4 | from tornado.ioloop import IOLoop 5 | from tornado.web import Application 6 | 7 | 8 | class WSHandler(tornado.websocket.WebSocketHandler): 9 | def open(self): 10 | self.write_message("Hello") 11 | print('new connection!') 12 | 13 | def on_message(self, msg): 14 | self.write_message("you said: "+msg) 15 | 16 | def on_close(self): 17 | print ("closed") 18 | 19 | class HTTPHandler(tornado.web.RequestHandler): 20 | def get(self): 21 | pass 22 | 23 | def post(self): 24 | pass 25 | 26 | if __name__ == '__main__': 27 | app = Application([ 28 | (r"/ws", WSHandler), 29 | (r"/", HTTPHandler) 30 | ]) 31 | server = HTTPServer(app) 32 | server.listen(8000) 33 | IOLoop.current().start() -------------------------------------------------------------------------------- /include/websocket_client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef WEBSOCKET_CLIENT_STATIC 5 | #define WEBSOCKET_CLIENT_API 6 | #else 7 | #if defined _WIN32 || defined __CYGWIN__ 8 | #ifdef WEBSOCKET_CLIENT_EXPORTS 9 | #ifdef __GNUC__ 10 | #define WEBSOCKET_CLIENT_API __attribute__ ((dllexport)) 11 | #else 12 | #define WEBSOCKET_CLIENT_API __declspec(dllexport) // Note: actually gcc seems to also supports this syntax. 13 | #endif 14 | #else 15 | #ifdef __GNUC__ 16 | #define WEBSOCKET_CLIENT_API __attribute__ ((dllimport)) 17 | #else 18 | #define WEBSOCKET_CLIENT_API __declspec(dllimport) // Note: actually gcc seems to also supports this syntax. 19 | #endif 20 | #endif 21 | #else 22 | #if __GNUC__ >= 4 23 | #define WEBSOCKET_CLIENT_API __attribute__ ((visibility ("default"))) 24 | #define DLL_LOCAL __attribute__ ((visibility ("hidden"))) 25 | #else 26 | #define WEBSOCKET_CLIENT_API 27 | #endif 28 | #endif 29 | #endif 30 | 31 | #ifdef __cplusplus 32 | extern "C" 33 | { 34 | #endif // __cplusplus 35 | 36 | typedef enum websocket_frame_type_t 37 | { 38 | // Non-control frame. 39 | Continuation = 0x0, 40 | Text = 0x1, 41 | Binary = 0x2, 42 | NonControlReserved1 = 0x3, 43 | NonControlReserved2 = 0x4, 44 | NonControlReserved3 = 0x5, 45 | NonControlReserved4 = 0x6, 46 | NonControlReserved5 = 0x7, 47 | 48 | // Control frame. 49 | Close = 0x8, 50 | Ping = 0x9, 51 | Pong = 0xA, 52 | ControlReserved1 = 0xB, 53 | ControlReserved2 = 0xC, 54 | ControlReserved3 = 0xD, 55 | ControlReserved4 = 0xE, 56 | ControlReserved5 = 0xF, 57 | } websocket_frame_type_t; 58 | 59 | typedef struct websocket_message_t 60 | { 61 | websocket_frame_type_t type; 62 | const char* data; 63 | int len; // size of data in bytes 64 | }websocket_message_t; 65 | 66 | typedef enum websocket_client_connect_result_t 67 | { 68 | Success = 0, 69 | Timeout = 1, 70 | Reject = 2 71 | } websocket_client_connect_result_t; 72 | 73 | typedef struct websocket_client_t websocket_client_t; 74 | 75 | typedef void (*websocket_client_connect_callback)(websocket_client_connect_result_t result, void* opaque); 76 | 77 | typedef void (*websocket_client_receive_callback)(websocket_message_t msg, int fin, void* opaque); 78 | 79 | /** 80 | * @brief create a websocket client instance 81 | * @return websocket client instance 82 | * @sa @anchor websocket_client_create_with_custom_http_header @anchor websocket_client_destroy 83 | */ 84 | WEBSOCKET_CLIENT_API websocket_client_t* websocket_client_create(); 85 | 86 | /** 87 | * @brief create a websocket client instance 88 | * @param custom_header array for customize HTTP header, one array item represents one line in HTTP 89 | * header, including the request line 90 | * @param nlines @em custom_header array size 91 | * @return websocket client instance 92 | * @sa @anchor websocket_client_create @anchor websocket_client_destroy 93 | */ 94 | WEBSOCKET_CLIENT_API websocket_client_t* websocket_client_create_with_custom_http_header(const char** custom_header, int nlines); 95 | 96 | /** 97 | * @brief destroy the specific websocket client instance 98 | * @param client websocket client instance created by @anchor websocket_client_create 99 | * or @anchor websocket_client_create_with_custom_http_header 100 | * @sa @anchor websocket_client_create @anchor websocket_client_create_with_custom_http_header 101 | */ 102 | WEBSOCKET_CLIENT_API void websocket_client_destroy(websocket_client_t* client); 103 | 104 | /** 105 | * @brief connect to websocket server 106 | * @param client websocket client instance 107 | * @param url the websocket server's url to connect 108 | * @note This function is non-blocking, it starts a thread to establish the connection and returns immediately. \n 109 | * The @em url string will be saved as a copy, you can release it after this function returns. \n 110 | * Set callbacks by calling @anchor websocket_client_set_callbacks before connect to server. 111 | */ 112 | WEBSOCKET_CLIENT_API void websocket_client_connect_server(websocket_client_t* client, const char* url); 113 | 114 | /** 115 | * @brief send message to server 116 | * @param client websocket client instance 117 | * @param msg the message to send 118 | * @return Remaining bytes to be sent, 0 means the hole message was sent, -1 means failure. 119 | * @note You should make sure the connection has established or the message will not be sent. 120 | * This function won't block. If this function returns a positive value, please call this function again and again 121 | * until it returns 0 or -1. The em msg will be buffed, you don't have to pass it again. Before finish sending the 122 | * last message, new messages won't be sent. 123 | */ 124 | WEBSOCKET_CLIENT_API int websocket_client_send_sessage(websocket_client_t* client, websocket_message_t msg); 125 | 126 | /** 127 | * @brief websocket_client_set_callbacks 128 | * @param client websocket client instance 129 | * @param conn_cb callback to check connetion result 130 | * @param recv_cb callback to receive message 131 | * @param opaque private pointer for the above callbacks 132 | */ 133 | WEBSOCKET_CLIENT_API void websocket_client_set_callbacks(websocket_client_t* client, 134 | websocket_client_connect_callback conn_cb, 135 | websocket_client_receive_callback recv_cb, 136 | void* opaque ); 137 | 138 | #ifdef __cplusplus 139 | } 140 | #endif // __cplusplus 141 | -------------------------------------------------------------------------------- /src/WebSocketClientImplCurl.cpp: -------------------------------------------------------------------------------- 1 |  2 | /* The WebSocket Protocol 3 | The WebSocket Protocol enables two-way communication between a client 4 | running untrusted code in a controlled environment to a remote host 5 | that has opted-in to communications from that code. The security 6 | model used for this is the origin-based security model commonly used 7 | by web browsers. The protocol consists of an opening handshake 8 | followed by basic message framing, layered over TCP. The goal of 9 | this technology is to provide a mechanism for browser-based 10 | applications that need two-way communication with servers that does 11 | not rely on opening multiple HTTP connections (e.g., using 12 | XMLHttpRequest or