├── .github └── workflows │ └── build.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples ├── CMakeLists.txt └── first.cpp ├── include └── WebSocketClient.h └── src └── WebSocketClient.cpp /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | env: 6 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 7 | BUILD_TYPE: Release 8 | 9 | jobs: 10 | build: 11 | # The CMake configure and build commands are platform agnostic and should work equally 12 | # well on Windows or Mac. You can convert this to a matrix build if you need 13 | # cross-platform coverage. 14 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Create Build Environment 21 | # Some projects don't allow in-source building, so create a separate build directory 22 | # We'll use this as our working directory for all subsequent commands 23 | run: cmake -E make_directory ${{github.workspace}}/build 24 | 25 | - name: Configure CMake 26 | # Use a bash shell so we can use the same syntax for environment variable 27 | # access regardless of the host operating system 28 | shell: bash 29 | working-directory: ${{github.workspace}}/build 30 | # Note the current convention is to use the -S and -B options here to specify source 31 | # and build directories, but this is only available with CMake 3.13 and higher. 32 | # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 33 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE 34 | 35 | - name: Build 36 | working-directory: ${{github.workspace}}/build 37 | shell: bash 38 | # Execute the build. You can specify a specific target with "--target " 39 | run: cmake --build . --config $BUILD_TYPE 40 | 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | out/ 3 | CMakeSettings.json -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | project ("LightWSClient") 4 | 5 | set(CMAKE_CXX_STANDARD 11) 6 | 7 | aux_source_directory(./src SRCS) 8 | 9 | add_library(${PROJECT_NAME} STATIC ${SRCS}) 10 | 11 | target_include_directories(${PROJECT_NAME} PUBLIC include) 12 | 13 | option(LightWSClient_Build_Examples "Build examples" OFF) 14 | if(LightWSClient_Build_Examples) 15 | add_subdirectory(examples) 16 | endif(LightWSClient_Build_Examples) 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 cyanray 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 | # LightWebSocketClient 2 | A simple cross-platform WebSocket client. 3 | 4 | ![](https://github.com/cyanray/LightWebSocketClient/workflows/build/badge.svg) 5 | 6 | ## Features 7 | 8 | * C++11 9 | * Light 10 | * Cross-platform 11 | * Easy to use 12 | 13 | ## Usage 14 | 15 | ```c++ 16 | #include 17 | #include 18 | using namespace std; 19 | using namespace cyanray; 20 | 21 | int main() 22 | { 23 | const string ws_uri = "ws://localhost:5539/chat?sessionkey=123"; 24 | 25 | WebSocketClient client; 26 | try 27 | { 28 | client.Connect(ws_uri); 29 | cout << "working..." << endl; 30 | } 31 | catch (const std::exception& ex) 32 | { 33 | cout << ex.what() << endl; 34 | return 1; 35 | } 36 | 37 | client.OnTextReceived([](WebSocketClient& client, string text) 38 | { 39 | cout << "received: " << text << endl; 40 | }); 41 | 42 | client.OnLostConnection([ws_uri](WebSocketClient& client, int code) 43 | { 44 | cout << "Lost connection: " << code << endl; 45 | while (true) 46 | { 47 | try 48 | { 49 | client.Connect(ws_uri); 50 | cout << "Reconnected." << endl; 51 | break; 52 | } 53 | catch (const std::exception& ex) 54 | { 55 | cout << ex.what() << endl; 56 | } 57 | } 58 | }); 59 | 60 | string c; 61 | while (getline(std::cin, c);) 62 | { 63 | if (c == "quit") 64 | { 65 | client.Close(); 66 | break; 67 | } 68 | client.SendText(c); 69 | } 70 | 71 | return 0; 72 | } 73 | ``` 74 | 75 | ## Documentation 76 | 77 | See comments in **WebSocketClient.h**. 78 | 79 | 80 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | macro(api_exe target) 4 | add_executable(${target} ${target}.cpp) 5 | target_link_libraries(${target} PRIVATE LightWSClient) 6 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 7 | target_link_libraries(${target} PRIVATE pthread) 8 | endif() 9 | endmacro() 10 | 11 | api_exe(first) -------------------------------------------------------------------------------- /examples/first.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | using namespace std; 5 | using namespace cyanray; 6 | 7 | int main() 8 | { 9 | const string ws_uri = "ws://localhost:5539/command?authkey=RF4r8uEN"; 10 | 11 | WebSocketClient client; 12 | client.Connect(ws_uri); 13 | // client.Connect("127.0.0.1", 5539, "/"); 14 | cout << "working..." << endl; 15 | 16 | client.OnTextReceived([](WebSocketClient& client, string text) 17 | { 18 | cout << "received: " << text << endl; 19 | }); 20 | 21 | client.OnLostConnection([ws_uri](WebSocketClient& client, int code) 22 | { 23 | cout << "Lost connection: " << code << endl; 24 | while (true) 25 | { 26 | try 27 | { 28 | client.Connect(ws_uri); 29 | cout << "Reconnected." << endl; 30 | break; 31 | } 32 | catch (const std::exception& ex) 33 | { 34 | cout << ex.what() << endl; 35 | } 36 | } 37 | }); 38 | 39 | string c; 40 | while (getline(std::cin, c)) 41 | { 42 | if (c == "ping") 43 | { 44 | client.Ping(); 45 | } 46 | else if (c == "quit") 47 | { 48 | client.Close(); 49 | break; 50 | } 51 | else 52 | { 53 | client.SendText(c); 54 | } 55 | } 56 | return 0; 57 | } -------------------------------------------------------------------------------- /include/WebSocketClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef LightWSClient_include_WebSocketClient_H_ 3 | #define LightWSClient_include_WebSocketClient_H_ 4 | #include 5 | #include 6 | #include 7 | using std::vector; 8 | using std::string; 9 | 10 | namespace cyanray 11 | { 12 | class WebSocketClient 13 | { 14 | public: 15 | template 16 | using Callback = std::function; 17 | 18 | enum class Status 19 | { 20 | Open, Closing, Closed 21 | }; 22 | 23 | WebSocketClient(); 24 | WebSocketClient(const WebSocketClient&) = delete; 25 | WebSocketClient& operator=(const WebSocketClient&) = delete; 26 | ~WebSocketClient(); 27 | 28 | /** 29 | * @brief Get status. 30 | * @return WebSocketClient::Status 31 | */ 32 | Status GetStatus() const { return status; } 33 | 34 | /** 35 | * @brief Connect by websocket uri. 36 | * @param ws_uri e.g "ws://hostname[:port][/path/path][?query=value]" 37 | */ 38 | void Connect(const string& ws_uri); 39 | 40 | /** 41 | * @brief Connect by hostname, port and path 42 | * @param hostname e.g "localhost" or "example.com" 43 | * @param port e.g 80 44 | * @param path e.g "/chat?id=1234" 45 | */ 46 | void Connect(const string& hostname, int port, const string& path = "/"); 47 | 48 | /** 49 | * @brief Abort the connection and close tcp socket. 50 | */ 51 | void Shutdown(); 52 | 53 | /** 54 | * @brief After receving text data, the callback function will be called. 55 | * @param callback Callback funtion. 56 | */ 57 | void OnTextReceived(Callback callback); 58 | 59 | /** 60 | * @brief After receving binary data, the callback function will be called. 61 | * @param callback Callback funtion. 62 | */ 63 | void OnBinaryReceived(Callback> callback); 64 | 65 | /** 66 | * @brief If an error occurs during the receiving process (such as a wrong frame), 67 | this function will be called. 68 | * @param callback Callback funtion. 69 | */ 70 | void OnError(Callback callback); 71 | 72 | /** 73 | * @brief If tcp connection aborted or received close frame, 74 | * the callback function will be called. 75 | * @param callback Callback funtion. 76 | */ 77 | void OnLostConnection(Callback callback); 78 | 79 | /** 80 | * @brief Send text data. 81 | * @param text Text data. 82 | */ 83 | void SendText(const string& text); 84 | 85 | /** 86 | * @brief Send binary data. 87 | * @param data Binary data. 88 | * @param length Length of the binary data. 89 | */ 90 | void SendBinary(const char* data, size_t length); 91 | 92 | /** 93 | * @brief Send binary data. 94 | * @param data Binary data. 95 | * @param length Length of the binary data. 96 | */ 97 | void SendBinary(const uint8_t* data, size_t length); 98 | 99 | /** 100 | * @brief Send ping frame. 101 | */ 102 | void Ping(); 103 | 104 | /** 105 | * @brief Send pong frame. 106 | */ 107 | void Pong(); 108 | 109 | /** 110 | * @brief Send pong frame. 111 | * @param ping data 112 | */ 113 | void Pong(const vector& data); 114 | 115 | /** 116 | * @brief Send a close frame. 117 | If websocket server response a close frame, 118 | will close tcp socket. 119 | */ 120 | void Close(); 121 | private: 122 | void RecvLoop(); 123 | Callback TextReceivedCallback; 124 | Callback> BinaryReceivedCallback; 125 | Callback ErrorCallback; 126 | Callback LostConnectionCallback; 127 | Status status; 128 | // avoid including implementation-related headers. 129 | struct pimpl; 130 | pimpl* PrivateMembers; 131 | }; 132 | } 133 | 134 | 135 | #endif // !LightWSClient_include_WebSocketClient_H_ -------------------------------------------------------------------------------- /src/WebSocketClient.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "WebSocketClient.h" 10 | using namespace std; 11 | 12 | 13 | #if defined(_WIN32) 14 | 15 | #include 16 | #pragma comment (lib, "ws2_32.lib") 17 | 18 | #else 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #endif 28 | 29 | typedef unsigned char byte_t; 30 | 31 | #if defined(_WIN32) 32 | typedef SOCKET socket_t; 33 | #else 34 | typedef int socket_t; 35 | #ifndef INVALID_SOCKET 36 | #define INVALID_SOCKET (-1) 37 | #endif 38 | #ifndef SOCKET_ERROR 39 | #define SOCKET_ERROR (-1) 40 | #endif 41 | #define closesocket(s) ::close(s) 42 | #endif 43 | 44 | namespace cyanray 45 | { 46 | socket_t hostname_connect(const std::string& hostname, int port) { 47 | struct addrinfo* result; 48 | 49 | struct addrinfo hints; 50 | memset(&hints, 0, sizeof(hints)); 51 | hints.ai_family = AF_UNSPEC; 52 | hints.ai_socktype = SOCK_STREAM; 53 | 54 | int ret = getaddrinfo(hostname.c_str(), to_string(port).c_str(), &hints, &result); 55 | if (ret != 0) 56 | { 57 | throw std::runtime_error("getaddrinfo failed."); 58 | } 59 | 60 | socket_t sockfd = INVALID_SOCKET; 61 | for (struct addrinfo* p = result; p != NULL; p = p->ai_next) 62 | { 63 | sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); 64 | if (sockfd == INVALID_SOCKET) { continue; } 65 | if (connect(sockfd, p->ai_addr, (int)p->ai_addrlen) != SOCKET_ERROR) 66 | { 67 | break; 68 | } 69 | closesocket(sockfd); 70 | sockfd = INVALID_SOCKET; 71 | } 72 | freeaddrinfo(result); 73 | return sockfd; 74 | } 75 | 76 | enum class WebSocketOpcode 77 | { 78 | Continuation = 0x00, 79 | Text = 0x01, 80 | Binary = 0x02, 81 | Close = 0x08, 82 | Ping = 0x09, 83 | Pong = 0x0A 84 | }; 85 | 86 | struct WebSocketClient::pimpl 87 | { 88 | socket_t wsSocket = INVALID_SOCKET; 89 | std::thread recvLoop; 90 | 91 | void Send(WebSocketOpcode opcode) 92 | { 93 | array frame_data = { 0x00, 0x80, 0x00, 0x00, 0x00, 0x00 }; 94 | frame_data[0] = 0x80 | (byte_t)opcode; 95 | int sendResult = send(wsSocket, (char*)frame_data.data(), (int)frame_data.size(), 0); 96 | if (sendResult == SOCKET_ERROR) 97 | { 98 | throw std::runtime_error("socket error (send)."); 99 | } 100 | } 101 | 102 | template 103 | void Send(WebSocketOpcode opcode, Iterator begin, Iterator end) 104 | { 105 | size_t length = end - begin; 106 | vector frame_data; 107 | frame_data.reserve(2048); // TODO: replace with a const variable 108 | frame_data.push_back(0x80 | (byte_t)opcode); // Text Frame 109 | // MASK must be 1 110 | if (length <= 125) 111 | { 112 | frame_data.push_back(0x80 | (byte_t)length); 113 | } 114 | else if (length <= (std::numeric_limits::max)()) 115 | { 116 | uint16_t t = (uint16_t)length; 117 | frame_data.push_back(0xFE); 118 | frame_data.push_back((t >> 8) & 0xFF); 119 | frame_data.push_back((t >> 0) & 0xFF); 120 | } 121 | else if (length <= (std::numeric_limits::max)()) 122 | { 123 | uint64_t t = (uint64_t)length; 124 | frame_data.push_back(0xFF); 125 | frame_data.push_back((t >> 56) & 0xFF); 126 | frame_data.push_back((t >> 48) & 0xFF); 127 | frame_data.push_back((t >> 40) & 0xFF); 128 | frame_data.push_back((t >> 32) & 0xFF); 129 | frame_data.push_back((t >> 24) & 0xFF); 130 | frame_data.push_back((t >> 16) & 0xFF); 131 | frame_data.push_back((t >> 8) & 0xFF); 132 | frame_data.push_back((t >> 0) & 0xFF); 133 | } 134 | else 135 | { 136 | throw std::runtime_error("Data is too large. Does not support fragmentation."); 137 | } 138 | 139 | std::array mask_key{ 0xd2, 0x28, 0xb6, 0xde }; 140 | frame_data.insert(frame_data.end(), mask_key.begin(), mask_key.end()); 141 | 142 | int i = 0; 143 | for (auto it = begin; it != end; ++it) 144 | { 145 | frame_data.push_back(*it ^ mask_key[i++ % 4]); 146 | } 147 | 148 | int sendResult = send(wsSocket, (char*)frame_data.data(), (int)frame_data.size(), 0); 149 | if (sendResult == SOCKET_ERROR) 150 | { 151 | throw std::runtime_error("socket error (send)."); 152 | } 153 | 154 | } 155 | }; 156 | 157 | struct FrameInfo 158 | { 159 | bool Fin = false; 160 | bool Mask = false; 161 | WebSocketOpcode Opcode = WebSocketOpcode::Text; 162 | byte_t MaskKey[4] = { }; 163 | uint64_t PayloadLength = 0; 164 | }; 165 | 166 | int TryParseFrame(FrameInfo* frame_info, const byte_t* frame_data, size_t len) 167 | { 168 | if (len < 2) return -1; 169 | int offset = 2; 170 | FrameInfo& info = *frame_info; 171 | info.Fin = (frame_data[0] & 0x80) == 0x80; 172 | info.Mask = (frame_data[1] & 0x80) == 0x80; 173 | info.Opcode = static_cast(frame_data[0] & 0x0F); 174 | info.PayloadLength = frame_data[1] & 0x7F; 175 | if (info.PayloadLength == 126) 176 | { 177 | if (len < 4) return -1; 178 | info.PayloadLength = ((int64_t)frame_data[2] << 8) | frame_data[3]; 179 | offset = 4; 180 | } 181 | else if (info.PayloadLength == 127) 182 | { 183 | if (len < 10) return -1; 184 | memcpy(&info.PayloadLength, &frame_data[2], sizeof(uint64_t)); 185 | offset = 10; 186 | } 187 | if (info.Mask) 188 | { 189 | memcpy(&info.MaskKey, &frame_data[offset], sizeof(info.MaskKey)); 190 | offset += 4; 191 | } 192 | return offset; 193 | } 194 | 195 | WebSocketClient::WebSocketClient() : PrivateMembers(new pimpl()), status(Status::Closed) 196 | { 197 | #if defined(_WIN32) 198 | WSAData data; 199 | int WsResult = WSAStartup(MAKEWORD(2, 2), &data); 200 | if (WsResult != 0) 201 | { 202 | throw std::runtime_error("Can't start winsock."); 203 | } 204 | #endif 205 | } 206 | 207 | WebSocketClient::~WebSocketClient() 208 | { 209 | delete PrivateMembers; 210 | #if defined(_WIN32) 211 | WSACleanup(); 212 | #endif 213 | } 214 | 215 | void WebSocketClient::Connect(const string& ws_uri) 216 | { 217 | regex pattern(R"(ws:\/\/([^:\/]+):?(\d+)?(\/[\S]*)?)"); 218 | std::smatch matches; 219 | std::regex_search(ws_uri, matches, pattern); 220 | if (matches.size() == 0) 221 | { 222 | throw std::runtime_error("Unable to parse websocket uri."); 223 | } 224 | else 225 | { 226 | return Connect(matches[1].str(), 227 | matches[2].matched ? std::stoi(matches[2].str()) : 80, 228 | matches[3].matched ? matches[3].str() : "/"); 229 | } 230 | } 231 | 232 | void WebSocketClient::Connect(const string& hostname, int port, const string& path) 233 | { 234 | PrivateMembers->wsSocket = hostname_connect(hostname, port); 235 | if (PrivateMembers->wsSocket == INVALID_SOCKET) 236 | { 237 | throw std::runtime_error("Unable to connect to " + hostname); 238 | } 239 | 240 | string handshakingWords; 241 | handshakingWords.append("GET ").append(path).append(" HTTP/1.1").append("\r\n"); 242 | handshakingWords.append("Host: ").append(hostname).append(":").append(to_string(port)).append("\r\n"); 243 | handshakingWords.append("Connection: Upgrade").append("\r\n"); 244 | handshakingWords.append("Upgrade: websocket").append("\r\n"); 245 | //handshakingWords.append("Origin: http://example.com").append("\r\n"); 246 | handshakingWords.append("Sec-WebSocket-Version: 13").append("\r\n"); 247 | handshakingWords.append("Sec-WebSocket-Key: O7Tk4xI04v+X91cuvefLSQ==").append("\r\n"); 248 | handshakingWords.append("\r\n"); 249 | 250 | int sendResult = send(PrivateMembers->wsSocket, handshakingWords.c_str(), (int)handshakingWords.size(), 0); 251 | if (sendResult == SOCKET_ERROR) 252 | { 253 | throw std::runtime_error("An error occurred during the handshake."); 254 | } 255 | 256 | char response_buffer[4096] = { 0 }; 257 | int bytesReceived = recv(PrivateMembers->wsSocket, response_buffer, 4096, 0); 258 | // TODO: parse Response 259 | 260 | status = Status::Open; 261 | PrivateMembers->recvLoop = std::thread([this]() {RecvLoop(); }); 262 | PrivateMembers->recvLoop.detach(); 263 | } 264 | 265 | void WebSocketClient::Shutdown() 266 | { 267 | if (status == Status::Closed) return; 268 | status = Status::Closed; 269 | closesocket(PrivateMembers->wsSocket); 270 | } 271 | 272 | void WebSocketClient::OnTextReceived(Callback callback) 273 | { 274 | TextReceivedCallback = callback; 275 | } 276 | 277 | void WebSocketClient::OnBinaryReceived(Callback> callback) 278 | { 279 | BinaryReceivedCallback = callback; 280 | } 281 | 282 | void WebSocketClient::OnError(Callback callback) 283 | { 284 | ErrorCallback = callback; 285 | } 286 | 287 | void WebSocketClient::OnLostConnection(Callback callback) 288 | { 289 | LostConnectionCallback = callback; 290 | } 291 | 292 | void WebSocketClient::SendText(const string& text) 293 | { 294 | if (status == Status::Closed) 295 | throw std::runtime_error("WebSocket is closed."); 296 | PrivateMembers->Send(WebSocketOpcode::Text, text.begin(), text.end()); 297 | } 298 | 299 | void WebSocketClient::SendBinary(const char* data, size_t length) 300 | { 301 | if (status == Status::Closed) 302 | throw std::runtime_error("WebSocket is closed."); 303 | PrivateMembers->Send(WebSocketOpcode::Binary, data, data + length); 304 | } 305 | 306 | void WebSocketClient::SendBinary(const uint8_t* data, size_t length) 307 | { 308 | if (status == Status::Closed) 309 | throw std::runtime_error("WebSocket is closed."); 310 | PrivateMembers->Send(WebSocketOpcode::Binary, data, data + length); 311 | } 312 | 313 | void WebSocketClient::Ping() 314 | { 315 | if (status == Status::Closed) return; 316 | PrivateMembers->Send(WebSocketOpcode::Ping); 317 | } 318 | 319 | void WebSocketClient::Pong() 320 | { 321 | if (status == Status::Closed) return; 322 | PrivateMembers->Send(WebSocketOpcode::Pong); 323 | } 324 | 325 | void WebSocketClient::Pong(const vector& data) 326 | { 327 | if (status == Status::Closed) return; 328 | PrivateMembers->Send(WebSocketOpcode::Pong, data.begin(), data.end()); 329 | } 330 | 331 | void WebSocketClient::Close() 332 | { 333 | if (status == Status::Closed) return; 334 | status = Status::Closing; 335 | PrivateMembers->Send(WebSocketOpcode::Close); 336 | } 337 | 338 | void WebSocketClient::RecvLoop() 339 | { 340 | socket_t sock = PrivateMembers->wsSocket; 341 | vector buffer; 342 | buffer.reserve(8192); 343 | 344 | while (status == Status::Open) 345 | { 346 | struct timeval tv; 347 | tv.tv_sec = 0; 348 | tv.tv_usec = 200 * 1000; 349 | 350 | fd_set fds_read; 351 | FD_ZERO(&fds_read); 352 | FD_SET(sock, &fds_read); 353 | int ret = select((int)(sock + 1), &fds_read, NULL, NULL, &tv); 354 | if (ret < 0) 355 | { 356 | if (ErrorCallback != nullptr) 357 | { 358 | ErrorCallback(*this, "select error."); 359 | } 360 | } 361 | if (ret != 0 && FD_ISSET(sock, &fds_read)) 362 | { 363 | array buf = {}; 364 | int bytesReceived = recv(sock, buf.data(), (int)buf.size(), 0); 365 | if (bytesReceived > 0) 366 | { 367 | buffer.insert(buffer.end(), buf.begin(), buf.begin() + bytesReceived); 368 | } 369 | else 370 | { 371 | // If close socket manually(shutdown), should not call the callback function. 372 | if (status == Status::Open) 373 | { 374 | this->Shutdown(); 375 | if (LostConnectionCallback != nullptr) 376 | { 377 | LostConnectionCallback(*this, 1006); 378 | } 379 | } 380 | break; 381 | } 382 | } 383 | 384 | while (buffer.size()) 385 | { 386 | FrameInfo info; 387 | int offset = TryParseFrame(&info, buffer.data(), buffer.size()); 388 | if (offset < 0) 389 | { 390 | if (ErrorCallback != nullptr) 391 | { 392 | ErrorCallback(*this, "Failed to parse frame."); 393 | } 394 | break; 395 | } 396 | else 397 | { 398 | if (buffer.size() >= offset + info.PayloadLength) 399 | { 400 | auto payload_start = buffer.begin() + offset; 401 | auto payload_end = buffer.begin() + (offset + info.PayloadLength); 402 | if (info.Mask) 403 | { 404 | int i = 0; 405 | for (auto& it = payload_start; it != payload_end; ++it) 406 | { 407 | *it = *it ^ info.MaskKey[i++ % 4]; 408 | } 409 | } 410 | if (info.Opcode == WebSocketOpcode::Text) 411 | { 412 | string text = string(payload_start, payload_end); 413 | if (TextReceivedCallback != nullptr) 414 | { 415 | TextReceivedCallback(*this, text); 416 | } 417 | } 418 | else if (info.Opcode == WebSocketOpcode::Binary) 419 | { 420 | if (BinaryReceivedCallback != nullptr) 421 | { 422 | BinaryReceivedCallback(*this, vector(payload_start, payload_end)); 423 | } 424 | } 425 | else if (info.Opcode == WebSocketOpcode::Ping) 426 | { 427 | try 428 | { 429 | Pong(vector(payload_start, payload_end)); 430 | } 431 | catch (const std::exception& ex) 432 | { 433 | if (ErrorCallback != nullptr) 434 | { 435 | ErrorCallback(*this, string("An error occurs on sending pong frame. error: ")+ ex.what()); 436 | } 437 | } 438 | } 439 | else if (info.Opcode == WebSocketOpcode::Close) 440 | { 441 | if (status == Status::Closing) 442 | { 443 | Shutdown(); 444 | } 445 | else if (status == Status::Open) 446 | { 447 | // close frame from server, response a close frame and close socket. 448 | Close(); 449 | Shutdown(); 450 | if (LostConnectionCallback != nullptr) 451 | { 452 | LostConnectionCallback(*this, 1000); 453 | } 454 | } 455 | return; 456 | } 457 | else 458 | { 459 | if (ErrorCallback != nullptr) 460 | { 461 | ErrorCallback(*this, 462 | string("The opcode #") 463 | .append(to_string((int)info.Opcode) 464 | .append(" is not supported.") )); 465 | 466 | } 467 | } 468 | buffer.erase(buffer.begin(), buffer.begin() + (offset + info.PayloadLength)); 469 | } 470 | else 471 | { 472 | break; 473 | } 474 | } 475 | } 476 | } 477 | 478 | } 479 | 480 | 481 | } 482 | 483 | 484 | --------------------------------------------------------------------------------