Compare commits
	
		
			10 Commits
		
	
	
		
			feature/zl
			...
			feature/kq
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 0834198e74 | ||
|  | 22dd32d4e9 | ||
|  | b15c4189f5 | ||
|  | 74d3278258 | ||
|  | 831152b906 | ||
|  | 7c81a98632 | ||
|  | 6e47c62c06 | ||
|  | bcae7f326d | ||
|  | d719c41e31 | ||
|  | 6f0307fb35 | 
							
								
								
									
										4
									
								
								.github/workflows/unittest_uwp.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/unittest_uwp.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,12 +10,10 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v1 |     - uses: actions/checkout@v1 | ||||||
|     - uses: seanmiddleditch/gha-setup-vsdevenv@master |     - uses: seanmiddleditch/gha-setup-vsdevenv@master | ||||||
|     - run: | |  | ||||||
|         vcpkg install zlib:x64-uwp |  | ||||||
|     - run: | |     - run: | | ||||||
|         mkdir build |         mkdir build | ||||||
|         cd build |         cd build | ||||||
|         cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0" -DCMAKE_CXX_COMPILER=cl.exe -DUSE_TEST=1 .. |         cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0" -DCMAKE_CXX_COMPILER=cl.exe -DUSE_TEST=1 -DUSE_ZLIB=0 .. | ||||||
|     - run: cmake --build build |     - run: cmake --build build | ||||||
|  |  | ||||||
| # | # | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								.github/workflows/unittest_windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/unittest_windows.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,10 +10,11 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|     - uses: actions/checkout@v1 |     - uses: actions/checkout@v1 | ||||||
|     - uses: seanmiddleditch/gha-setup-vsdevenv@master |     - uses: seanmiddleditch/gha-setup-vsdevenv@master | ||||||
|     - run: | |  | ||||||
|         vcpkg install zlib:x64-windows |  | ||||||
|     - run: | |     - run: | | ||||||
|         mkdir build |         mkdir build | ||||||
|         cd build |         cd build | ||||||
|         cmake -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 .. |         cmake -DCMAKE_CXX_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=0 .. | ||||||
|     - run: cmake --build build |     - run: cmake --build build | ||||||
|  |  | ||||||
|  | #- run: ../build/test/ixwebsocket_unittest.exe | ||||||
|  | # working-directory: test | ||||||
|   | |||||||
| @@ -190,10 +190,16 @@ if (USE_TLS) | |||||||
|   endif() |   endif() | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
| # Use ZLIB_ROOT CMake variable if you need to use your own zlib | option(USE_ZLIB "Enable zlib support" TRUE) | ||||||
| find_package(ZLIB REQUIRED) |  | ||||||
| include_directories(${ZLIB_INCLUDE_DIRS}) | if (USE_ZLIB) | ||||||
| target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES}) |   # Use ZLIB_ROOT CMake variable if you need to use your own zlib | ||||||
|  |   find_package(ZLIB REQUIRED) | ||||||
|  |   include_directories(${ZLIB_INCLUDE_DIRS}) | ||||||
|  |   target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES}) | ||||||
|  |  | ||||||
|  |   target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB) | ||||||
|  | endif() | ||||||
|  |  | ||||||
| if (WIN32) | if (WIN32) | ||||||
|   target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi) |   target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi) | ||||||
|   | |||||||
| @@ -1,6 +1,23 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
| All changes to this project will be documented in this file. | All changes to this project will be documented in this file. | ||||||
|  |  | ||||||
|  | ## [10.1.5] - 2020-08-02 | ||||||
|  |  | ||||||
|  | (ws) Add a new ws sub-command, push_server. This command runs a server which sends many messages in a loop to a websocket client. We can receive above 200,000 messages per second (cf #235). | ||||||
|  |  | ||||||
|  | ## [10.1.4] - 2020-08-02 | ||||||
|  |  | ||||||
|  | (ws) Add a new ws sub-command, echo_client. This command sends a message to an echo server, and send back to a server whatever message it does receive. When connecting to a local ws echo_server, on my MacBook Pro 2015 I can send/receive around 30,000 messages per second. (cf #235) | ||||||
|  |  | ||||||
|  | ## [10.1.3] - 2020-08-02 | ||||||
|  |  | ||||||
|  | (ws) ws echo_server. Add a -q option to only enable warning and error log levels. This is useful for bench-marking so that we do not print a lot of things on the console. (cf #235) | ||||||
|  |  | ||||||
|  | ## [10.1.2] - 2020-07-31 | ||||||
|  |  | ||||||
|  | (build) make using zlib optional, with the caveat that some http and websocket features are not available when zlib is absent | ||||||
|  |  | ||||||
| ## [10.1.1] - 2020-07-29 | ## [10.1.1] - 2020-07-29 | ||||||
|  |  | ||||||
| (websocket client) onProgressCallback not called for short messages on a websocket (fix #233) | (websocket client) onProgressCallback not called for short messages on a websocket (fix #233) | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ There is a unittest which can be executed by typing `make test`. | |||||||
|  |  | ||||||
| Options for building: | Options for building: | ||||||
|  |  | ||||||
|  | * `-DUSE_ZLIB=1` will enable zlib support, required for http client + server + websocket per message deflate extension | ||||||
| * `-DUSE_TLS=1` will enable TLS support | * `-DUSE_TLS=1` will enable TLS support | ||||||
| * `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows) | * `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows) | ||||||
| * `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support | * `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								docs/performance.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								docs/performance.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  |  | ||||||
|  | ## WebSocket Client performance | ||||||
|  |  | ||||||
|  | We will run a client and a server on the same machine, connecting to localhost. This bench is run on a MacBook Pro from 2015. We can receive over 200,000 (small) messages per second, another way to put it is that it takes 5 micro-second to receive and process one message. This is an indication about the minimal latency to receive messages. | ||||||
|  |  | ||||||
|  | ### Receiving messages | ||||||
|  |  | ||||||
|  | By using the push_server ws sub-command, the server will send the same message in a loop to any connected client. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | ws push_server -q --send_msg 'yo' | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | By using the echo_client ws sub-command, with the -m (mute or no_send), we will display statistics on how many messages we can receive per second. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | $ ws echo_client -m ws://localhost:8008 | ||||||
|  | [2020-08-02 12:31:17.284] [info] ws_echo_client: connected | ||||||
|  | [2020-08-02 12:31:17.284] [info] Uri: / | ||||||
|  | [2020-08-02 12:31:17.284] [info] Headers: | ||||||
|  | [2020-08-02 12:31:17.284] [info] Connection: Upgrade | ||||||
|  | [2020-08-02 12:31:17.284] [info] Sec-WebSocket-Accept: byy/pMK2d0PtRwExaaiOnXJTQHo= | ||||||
|  | [2020-08-02 12:31:17.284] [info] Server: ixwebsocket/10.1.4 macos ssl/SecureTransport zlib 1.2.11 | ||||||
|  | [2020-08-02 12:31:17.284] [info] Upgrade: websocket | ||||||
|  | [2020-08-02 12:31:17.663] [info] messages received: 0 per second 2595307 total | ||||||
|  | [2020-08-02 12:31:18.668] [info] messages received: 79679 per second 2674986 total | ||||||
|  | [2020-08-02 12:31:19.668] [info] messages received: 207438 per second 2882424 total | ||||||
|  | [2020-08-02 12:31:20.673] [info] messages received: 209207 per second 3091631 total | ||||||
|  | [2020-08-02 12:31:21.676] [info] messages received: 216056 per second 3307687 total | ||||||
|  | [2020-08-02 12:31:22.680] [info] messages received: 214927 per second 3522614 total | ||||||
|  | [2020-08-02 12:31:23.684] [info] messages received: 216960 per second 3739574 total | ||||||
|  | [2020-08-02 12:31:24.688] [info] messages received: 215232 per second 3954806 total | ||||||
|  | [2020-08-02 12:31:25.691] [info] messages received: 212300 per second 4167106 total | ||||||
|  | [2020-08-02 12:31:26.694] [info] messages received: 212501 per second 4379607 total | ||||||
|  | [2020-08-02 12:31:27.699] [info] messages received: 212330 per second 4591937 total | ||||||
|  | [2020-08-02 12:31:28.702] [info] messages received: 216511 per second 4808448 total | ||||||
|  | ``` | ||||||
| @@ -16,7 +16,10 @@ | |||||||
| #include <random> | #include <random> | ||||||
| #include <sstream> | #include <sstream> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
| #include <zlib.h> | #include <zlib.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace ix | namespace ix | ||||||
| { | { | ||||||
| @@ -174,11 +177,13 @@ namespace ix | |||||||
|         ss << verb << " " << path << " HTTP/1.1\r\n"; |         ss << verb << " " << path << " HTTP/1.1\r\n"; | ||||||
|         ss << "Host: " << host << "\r\n"; |         ss << "Host: " << host << "\r\n"; | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         if (args->compress) |         if (args->compress) | ||||||
|         { |         { | ||||||
|             ss << "Accept-Encoding: gzip" |             ss << "Accept-Encoding: gzip" | ||||||
|                << "\r\n"; |                << "\r\n"; | ||||||
|         } |         } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|         // Append extra headers |         // Append extra headers | ||||||
|         for (auto&& it : args->extraHeaders) |         for (auto&& it : args->extraHeaders) | ||||||
| @@ -495,6 +500,7 @@ namespace ix | |||||||
|  |  | ||||||
|         downloadSize = payload.size(); |         downloadSize = payload.size(); | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         // If the content was compressed with gzip, decode it |         // If the content was compressed with gzip, decode it | ||||||
|         if (headers["Content-Encoding"] == "gzip") |         if (headers["Content-Encoding"] == "gzip") | ||||||
|         { |         { | ||||||
| @@ -513,6 +519,7 @@ namespace ix | |||||||
|             } |             } | ||||||
|             payload = decompressedPayload; |             payload = decompressedPayload; | ||||||
|         } |         } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|         return std::make_shared<HttpResponse>(code, |         return std::make_shared<HttpResponse>(code, | ||||||
|                                               description, |                                               description, | ||||||
| @@ -672,6 +679,7 @@ namespace ix | |||||||
|         return ss.str(); |         return ss.str(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|     bool HttpClient::gzipInflate(const std::string& in, std::string& out) |     bool HttpClient::gzipInflate(const std::string& in, std::string& out) | ||||||
|     { |     { | ||||||
|         z_stream inflateState; |         z_stream inflateState; | ||||||
| @@ -716,6 +724,7 @@ namespace ix | |||||||
|         inflateEnd(&inflateState); |         inflateEnd(&inflateState); | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|     void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args) |     void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args) | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -90,7 +90,9 @@ namespace ix | |||||||
|     private: |     private: | ||||||
|         void log(const std::string& msg, HttpRequestArgsPtr args); |         void log(const std::string& msg, HttpRequestArgsPtr args); | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         bool gzipInflate(const std::string& in, std::string& out); |         bool gzipInflate(const std::string& in, std::string& out); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|         // Async API background thread runner |         // Async API background thread runner | ||||||
|         void run(); |         void run(); | ||||||
|   | |||||||
| @@ -13,7 +13,10 @@ | |||||||
| #include <fstream> | #include <fstream> | ||||||
| #include <sstream> | #include <sstream> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
| #include <zlib.h> | #include <zlib.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace | namespace | ||||||
| { | { | ||||||
| @@ -41,6 +44,7 @@ namespace | |||||||
|         return std::make_pair(res.first, std::string(vec.begin(), vec.end())); |         return std::make_pair(res.first, std::string(vec.begin(), vec.end())); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|     std::string gzipCompress(const std::string& str) |     std::string gzipCompress(const std::string& str) | ||||||
|     { |     { | ||||||
|         z_stream zs; // z_stream is zlib's control structure |         z_stream zs; // z_stream is zlib's control structure | ||||||
| @@ -83,6 +87,7 @@ namespace | |||||||
|  |  | ||||||
|         return outstring; |         return outstring; | ||||||
|     } |     } | ||||||
|  | #endif | ||||||
| } // namespace | } // namespace | ||||||
|  |  | ||||||
| namespace ix | namespace ix | ||||||
| @@ -168,12 +173,14 @@ namespace ix | |||||||
|  |  | ||||||
|                 std::string content = res.second; |                 std::string content = res.second; | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|                 std::string acceptEncoding = request->headers["Accept-encoding"]; |                 std::string acceptEncoding = request->headers["Accept-encoding"]; | ||||||
|                 if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos) |                 if (acceptEncoding == "*" || acceptEncoding.find("gzip") != std::string::npos) | ||||||
|                 { |                 { | ||||||
|                     content = gzipCompress(content); |                     content = gzipCompress(content); | ||||||
|                     headers["Content-Encoding"] = "gzip"; |                     headers["Content-Encoding"] = "gzip"; | ||||||
|                 } |                 } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|                 // Log request |                 // Log request | ||||||
|                 std::stringstream ss; |                 std::stringstream ss; | ||||||
|   | |||||||
| @@ -35,19 +35,122 @@ namespace ix | |||||||
|         : _sockfd(fd) |         : _sockfd(fd) | ||||||
|         , _selectInterrupt(createSelectInterrupt()) |         , _selectInterrupt(createSelectInterrupt()) | ||||||
|     { |     { | ||||||
|         ; | #if defined(__APPLE__) | ||||||
|  |         _kqueuefd = kqueue(); | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     Socket::~Socket() |     Socket::~Socket() | ||||||
|     { |     { | ||||||
|         close(); |         close(); | ||||||
|  |  | ||||||
|  | #if defined(__APPLE__) | ||||||
|  |         ::close(_kqueuefd); | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     PollResultType Socket::poll(bool readyToRead, |     PollResultType Socket::poll(bool readyToRead, | ||||||
|                                 int timeoutMs, |                                 int timeoutMs, | ||||||
|                                 int sockfd, |                                 int sockfd, | ||||||
|                                 const SelectInterruptPtr& selectInterrupt) |                                 const SelectInterruptPtr& selectInterrupt, | ||||||
|  |                                 int kqueuefd) | ||||||
|     { |     { | ||||||
|  | #if defined(__APPLE__) | ||||||
|  |         // FIXME int kqueuefd = kqueue(); | ||||||
|  |  | ||||||
|  |         struct kevent ke; | ||||||
|  |         EV_SET(&ke, sockfd, (readyToRead) ? EVFILT_READ : EVFILT_WRITE, EV_ADD, 0, 0, NULL); | ||||||
|  |         if (kevent(kqueuefd, &ke, 1, NULL, 0, NULL) == -1) return PollResultType::Error; | ||||||
|  |  | ||||||
|  |         int retval, numevents = 0; | ||||||
|  |  | ||||||
|  |         int nfds = 1; | ||||||
|  | #if 0 | ||||||
|  |         if (selectInterrupt)  | ||||||
|  |         { | ||||||
|  |             nfds = 2; | ||||||
|  |             int interruptFd = selectInterrupt->getFd(); | ||||||
|  |  | ||||||
|  |             struct kevent ke; | ||||||
|  |             EV_SET(&ke, interruptFd, EVFILT_READ, EV_ADD, 0, 0, NULL); | ||||||
|  |             if (kevent(kqueuefd, &ke, 1, NULL, 0, NULL) == -1) return PollResultType::Error; | ||||||
|  |         } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |         struct kevent *events; | ||||||
|  |         events = (struct kevent*) malloc(sizeof(struct kevent) * nfds); | ||||||
|  |  | ||||||
|  |         if (timeoutMs != 0) | ||||||
|  |         { | ||||||
|  |             struct timespec timeout; | ||||||
|  |             timeout.tv_sec = timeoutMs / 1000; | ||||||
|  |             timeout.tv_nsec = (timeoutMs % 1000) * 1000 * 1000; | ||||||
|  |             retval = kevent(kqueuefd, NULL, 0, events, nfds, &timeout); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             retval = kevent(kqueuefd, NULL, 0, events, nfds, NULL); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | #if 0 | ||||||
|  |         if (retval > 0) { | ||||||
|  |             int j; | ||||||
|  |  | ||||||
|  |             numevents = retval; | ||||||
|  |             for(j = 0; j < numevents; j++) { | ||||||
|  |                 int mask = 0; | ||||||
|  |                 struct kevent *e = events+j; | ||||||
|  |  | ||||||
|  |                 if (e->filter == EVFILT_READ) mask |= AE_READABLE; | ||||||
|  |                 if (e->filter == EVFILT_WRITE) mask |= AE_WRITABLE; | ||||||
|  |                 eventLoop->fired[j].fd = e->ident; | ||||||
|  |                 eventLoop->fired[j].mask = mask; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | #else | ||||||
|  |         PollResultType pollResult = PollResultType::ReadyForRead; | ||||||
|  |         if (retval < 0) | ||||||
|  |         { | ||||||
|  |             pollResult = PollResultType::Error; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (retval > 0) { | ||||||
|  |             struct kevent *e = events; | ||||||
|  |             if (e->filter == EVFILT_READ) | ||||||
|  |             { | ||||||
|  |                 pollResult = PollResultType::ReadyForRead; | ||||||
|  |             } | ||||||
|  |             else if (e->filter == EVFILT_WRITE)  | ||||||
|  |             { | ||||||
|  |                 pollResult = PollResultType::ReadyForWrite; | ||||||
|  |  | ||||||
|  |                 int optval = -1; | ||||||
|  |                 socklen_t optlen = sizeof(optval); | ||||||
|  |  | ||||||
|  |                 // getsockopt() puts the errno value for connect into optval so 0 | ||||||
|  |                 // means no-error. | ||||||
|  |                 if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 || optval != 0) | ||||||
|  |                 { | ||||||
|  |                     pollResult = PollResultType::Error; | ||||||
|  |  | ||||||
|  |                     // set errno to optval so that external callers can have an | ||||||
|  |                     // appropriate error description when calling strerror | ||||||
|  |                     errno = optval; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             pollResult = PollResultType::Timeout; | ||||||
|  |         } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |         free(events); | ||||||
|  |  | ||||||
|  |         // ::close(kqueuefd); //FMXE | ||||||
|  |  | ||||||
|  |         return pollResult; | ||||||
|  | #else | ||||||
|         // |         // | ||||||
|         // We used to use ::select to poll but on Android 9 we get large fds out of |         // We used to use ::select to poll but on Android 9 we get large fds out of | ||||||
|         // ::connect which crash in FD_SET as they are larger than FD_SETSIZE. Switching |         // ::connect which crash in FD_SET as they are larger than FD_SETSIZE. Switching | ||||||
| @@ -142,6 +245,7 @@ namespace ix | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         return pollResult; |         return pollResult; | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     PollResultType Socket::isReadyToRead(int timeoutMs) |     PollResultType Socket::isReadyToRead(int timeoutMs) | ||||||
| @@ -152,7 +256,7 @@ namespace ix | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         bool readyToRead = true; |         bool readyToRead = true; | ||||||
|         return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt); |         return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt, _kqueuefd); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     PollResultType Socket::isReadyToWrite(int timeoutMs) |     PollResultType Socket::isReadyToWrite(int timeoutMs) | ||||||
| @@ -163,7 +267,7 @@ namespace ix | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         bool readyToRead = false; |         bool readyToRead = false; | ||||||
|         return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt); |         return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt, _kqueuefd); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Wake up from poll/select by writing to the pipe which is watched by select |     // Wake up from poll/select by writing to the pipe which is watched by select | ||||||
|   | |||||||
| @@ -13,6 +13,13 @@ | |||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
|  | // For kqueue | ||||||
|  | #if defined(__APPLE__) | ||||||
|  | #include <sys/types.h> | ||||||
|  | #include <sys/event.h> | ||||||
|  | #include <sys/time.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
| #include <BaseTsd.h> | #include <BaseTsd.h> | ||||||
| typedef SSIZE_T ssize_t; | typedef SSIZE_T ssize_t; | ||||||
| @@ -94,7 +101,8 @@ namespace ix | |||||||
|         static PollResultType poll(bool readyToRead, |         static PollResultType poll(bool readyToRead, | ||||||
|                                    int timeoutMs, |                                    int timeoutMs, | ||||||
|                                    int sockfd, |                                    int sockfd, | ||||||
|                                    const SelectInterruptPtr& selectInterrupt); |                                    const SelectInterruptPtr& selectInterrupt, | ||||||
|  |                                    int kqueuefd); | ||||||
|  |  | ||||||
|  |  | ||||||
|         // Used as special codes for pipe communication |         // Used as special codes for pipe communication | ||||||
| @@ -114,5 +122,9 @@ namespace ix | |||||||
|         static constexpr size_t kChunkSize = 1 << 15; |         static constexpr size_t kChunkSize = 1 << 15; | ||||||
|  |  | ||||||
|         SelectInterruptPtr _selectInterrupt; |         SelectInterruptPtr _selectInterrupt; | ||||||
|  |  | ||||||
|  | #if defined(__APPLE__) | ||||||
|  |         int _kqueuefd; | ||||||
|  | #endif | ||||||
|     }; |     }; | ||||||
| } // namespace ix | } // namespace ix | ||||||
|   | |||||||
| @@ -66,7 +66,10 @@ namespace ix | |||||||
|             int timeoutMs = 10; |             int timeoutMs = 10; | ||||||
|             bool readyToRead = false; |             bool readyToRead = false; | ||||||
|             auto selectInterrupt = std::make_unique<SelectInterrupt>(); |             auto selectInterrupt = std::make_unique<SelectInterrupt>(); | ||||||
|             PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt); |  | ||||||
|  |             int kqueuefd = kqueue(); | ||||||
|  |             PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt, kqueuefd); | ||||||
|  |             ::close(kqueuefd); | ||||||
|  |  | ||||||
|             if (pollResult == PollResultType::Timeout) |             if (pollResult == PollResultType::Timeout) | ||||||
|             { |             { | ||||||
|   | |||||||
| @@ -259,8 +259,11 @@ namespace ix | |||||||
|             int timeoutMs = 10; |             int timeoutMs = 10; | ||||||
|             bool readyToRead = true; |             bool readyToRead = true; | ||||||
|             auto selectInterrupt = std::make_unique<SelectInterrupt>(); |             auto selectInterrupt = std::make_unique<SelectInterrupt>(); | ||||||
|  |  | ||||||
|  |             int kqueuefd = kqueue(); | ||||||
|             PollResultType pollResult = |             PollResultType pollResult = | ||||||
|                 Socket::poll(readyToRead, timeoutMs, _serverFd, selectInterrupt); |                 Socket::poll(readyToRead, timeoutMs, _serverFd, selectInterrupt, kqueuefd); | ||||||
|  |             ::close(kqueuefd); | ||||||
|  |  | ||||||
|             if (pollResult == PollResultType::Error) |             if (pollResult == PollResultType::Error) | ||||||
|             { |             { | ||||||
|   | |||||||
| @@ -8,7 +8,9 @@ | |||||||
|  |  | ||||||
| #include "IXWebSocketVersion.h" | #include "IXWebSocketVersion.h" | ||||||
| #include <sstream> | #include <sstream> | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
| #include <zlib.h> | #include <zlib.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| // Platform name | // Platform name | ||||||
| #if defined(_WIN32) | #if defined(_WIN32) | ||||||
| @@ -77,8 +79,10 @@ namespace ix | |||||||
|         ss << " nossl"; |         ss << " nossl"; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         // Zlib version |         // Zlib version | ||||||
|         ss << " zlib " << ZLIB_VERSION; |         ss << " zlib " << ZLIB_VERSION; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|         return ss.str(); |         return ss.str(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -28,21 +28,26 @@ namespace ix | |||||||
|     WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor() |     WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor() | ||||||
|         : _compressBufferSize(kBufferSize) |         : _compressBufferSize(kBufferSize) | ||||||
|     { |     { | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         memset(&_deflateState, 0, sizeof(_deflateState)); |         memset(&_deflateState, 0, sizeof(_deflateState)); | ||||||
|  |  | ||||||
|         _deflateState.zalloc = Z_NULL; |         _deflateState.zalloc = Z_NULL; | ||||||
|         _deflateState.zfree = Z_NULL; |         _deflateState.zfree = Z_NULL; | ||||||
|         _deflateState.opaque = Z_NULL; |         _deflateState.opaque = Z_NULL; | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor() |     WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor() | ||||||
|     { |     { | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         deflateEnd(&_deflateState); |         deflateEnd(&_deflateState); | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits, |     bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits, | ||||||
|                                                     bool clientNoContextTakeOver) |                                                     bool clientNoContextTakeOver) | ||||||
|     { |     { | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         int ret = deflateInit2(&_deflateState, |         int ret = deflateInit2(&_deflateState, | ||||||
|                                Z_DEFAULT_COMPRESSION, |                                Z_DEFAULT_COMPRESSION, | ||||||
|                                Z_DEFLATED, |                                Z_DEFLATED, | ||||||
| @@ -57,6 +62,9 @@ namespace ix | |||||||
|         _flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH; |         _flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH; | ||||||
|  |  | ||||||
|         return true; |         return true; | ||||||
|  | #else | ||||||
|  |         return false; | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     template<typename T> |     template<typename T> | ||||||
| @@ -96,6 +104,7 @@ namespace ix | |||||||
|     template<typename T, typename S> |     template<typename T, typename S> | ||||||
|     bool WebSocketPerMessageDeflateCompressor::compressData(const T& in, S& out) |     bool WebSocketPerMessageDeflateCompressor::compressData(const T& in, S& out) | ||||||
|     { |     { | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         // |         // | ||||||
|         // 7.2.1.  Compression |         // 7.2.1.  Compression | ||||||
|         // |         // | ||||||
| @@ -152,6 +161,9 @@ namespace ix | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         return true; |         return true; | ||||||
|  | #else | ||||||
|  |         return false; | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // |     // | ||||||
| @@ -160,6 +172,7 @@ namespace ix | |||||||
|     WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor() |     WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor() | ||||||
|         : _compressBufferSize(kBufferSize) |         : _compressBufferSize(kBufferSize) | ||||||
|     { |     { | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         memset(&_inflateState, 0, sizeof(_inflateState)); |         memset(&_inflateState, 0, sizeof(_inflateState)); | ||||||
|  |  | ||||||
|         _inflateState.zalloc = Z_NULL; |         _inflateState.zalloc = Z_NULL; | ||||||
| @@ -167,16 +180,20 @@ namespace ix | |||||||
|         _inflateState.opaque = Z_NULL; |         _inflateState.opaque = Z_NULL; | ||||||
|         _inflateState.avail_in = 0; |         _inflateState.avail_in = 0; | ||||||
|         _inflateState.next_in = Z_NULL; |         _inflateState.next_in = Z_NULL; | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor() |     WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor() | ||||||
|     { |     { | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         inflateEnd(&_inflateState); |         inflateEnd(&_inflateState); | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits, |     bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits, | ||||||
|                                                       bool clientNoContextTakeOver) |                                                       bool clientNoContextTakeOver) | ||||||
|     { |     { | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         int ret = inflateInit2(&_inflateState, -1 * inflateBits); |         int ret = inflateInit2(&_inflateState, -1 * inflateBits); | ||||||
|  |  | ||||||
|         if (ret != Z_OK) return false; |         if (ret != Z_OK) return false; | ||||||
| @@ -186,10 +203,14 @@ namespace ix | |||||||
|         _flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH; |         _flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH; | ||||||
|  |  | ||||||
|         return true; |         return true; | ||||||
|  | #else | ||||||
|  |         return false; | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out) |     bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out) | ||||||
|     { |     { | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         // |         // | ||||||
|         // 7.2.2.  Decompression |         // 7.2.2.  Decompression | ||||||
|         // |         // | ||||||
| @@ -226,5 +247,8 @@ namespace ix | |||||||
|         } while (_inflateState.avail_out == 0); |         } while (_inflateState.avail_out == 0); | ||||||
|  |  | ||||||
|         return true; |         return true; | ||||||
|  | #else | ||||||
|  |         return false; | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
| } // namespace ix | } // namespace ix | ||||||
|   | |||||||
| @@ -6,7 +6,9 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
| #include "zlib.h" | #include "zlib.h" | ||||||
|  | #endif | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| @@ -34,7 +36,10 @@ namespace ix | |||||||
|         int _flush; |         int _flush; | ||||||
|         size_t _compressBufferSize; |         size_t _compressBufferSize; | ||||||
|         std::unique_ptr<unsigned char[]> _compressBuffer; |         std::unique_ptr<unsigned char[]> _compressBuffer; | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         z_stream _deflateState; |         z_stream _deflateState; | ||||||
|  | #endif | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     class WebSocketPerMessageDeflateDecompressor |     class WebSocketPerMessageDeflateDecompressor | ||||||
| @@ -50,7 +55,10 @@ namespace ix | |||||||
|         int _flush; |         int _flush; | ||||||
|         size_t _compressBufferSize; |         size_t _compressBufferSize; | ||||||
|         std::unique_ptr<unsigned char[]> _compressBuffer; |         std::unique_ptr<unsigned char[]> _compressBuffer; | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         z_stream _inflateState; |         z_stream _inflateState; | ||||||
|  | #endif | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| } // namespace ix | } // namespace ix | ||||||
|   | |||||||
| @@ -61,6 +61,7 @@ namespace ix | |||||||
|         _clientMaxWindowBits = kDefaultClientMaxWindowBits; |         _clientMaxWindowBits = kDefaultClientMaxWindowBits; | ||||||
|         _serverMaxWindowBits = kDefaultServerMaxWindowBits; |         _serverMaxWindowBits = kDefaultServerMaxWindowBits; | ||||||
|  |  | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         // Split by ; |         // Split by ; | ||||||
|         std::string token; |         std::string token; | ||||||
|         std::stringstream tokenStream(extension); |         std::stringstream tokenStream(extension); | ||||||
| @@ -112,6 +113,7 @@ namespace ix | |||||||
|                 sanitizeClientMaxWindowBits(); |                 sanitizeClientMaxWindowBits(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void WebSocketPerMessageDeflateOptions::sanitizeClientMaxWindowBits() |     void WebSocketPerMessageDeflateOptions::sanitizeClientMaxWindowBits() | ||||||
| @@ -126,6 +128,7 @@ namespace ix | |||||||
|  |  | ||||||
|     std::string WebSocketPerMessageDeflateOptions::generateHeader() |     std::string WebSocketPerMessageDeflateOptions::generateHeader() | ||||||
|     { |     { | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         std::stringstream ss; |         std::stringstream ss; | ||||||
|         ss << "Sec-WebSocket-Extensions: permessage-deflate"; |         ss << "Sec-WebSocket-Extensions: permessage-deflate"; | ||||||
|  |  | ||||||
| @@ -138,11 +141,18 @@ namespace ix | |||||||
|         ss << "\r\n"; |         ss << "\r\n"; | ||||||
|  |  | ||||||
|         return ss.str(); |         return ss.str(); | ||||||
|  | #else | ||||||
|  |         return std::string(); | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool WebSocketPerMessageDeflateOptions::enabled() const |     bool WebSocketPerMessageDeflateOptions::enabled() const | ||||||
|     { |     { | ||||||
|  | #ifdef IXWEBSOCKET_USE_ZLIB | ||||||
|         return _enabled; |         return _enabled; | ||||||
|  | #else | ||||||
|  |         return false; | ||||||
|  | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const |     bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const | ||||||
|   | |||||||
| @@ -6,4 +6,4 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #define IX_WEBSOCKET_VERSION "10.1.1" | #define IX_WEBSOCKET_VERSION "10.1.5" | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								makefile
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								makefile
									
									
									
									
									
								
							| @@ -34,7 +34,10 @@ ws: | |||||||
| 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install) | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install) | ||||||
|  |  | ||||||
| ws_install: | ws_install: | ||||||
| 	mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. && make -j 4 install) | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install) | ||||||
|  |  | ||||||
|  | ws_install_release: | ||||||
|  | 	mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install) | ||||||
|  |  | ||||||
| ws_openssl: | ws_openssl: | ||||||
| 	mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; make -j 4) | 	mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_PYTHON=1 -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; make -j 4) | ||||||
|   | |||||||
							
								
								
									
										189
									
								
								test/compatibility/cpp/libwebsockets/devnull_client.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								test/compatibility/cpp/libwebsockets/devnull_client.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | |||||||
|  | /* | ||||||
|  |  * lws-minimal-ws-client | ||||||
|  |  * | ||||||
|  |  * Written in 2010-2019 by Andy Green <andy@warmcat.com> | ||||||
|  |  * | ||||||
|  |  * This file is made available under the Creative Commons CC0 1.0 | ||||||
|  |  * Universal Public Domain Dedication. | ||||||
|  |  * | ||||||
|  |  * This demonstrates the a minimal ws client using lws. | ||||||
|  |  * | ||||||
|  |  * Original programs connects to https://libwebsockets.org/ and makes a | ||||||
|  |  * wss connection to the dumb-increment protocol there.  While | ||||||
|  |  * connected, it prints the numbers it is being sent by | ||||||
|  |  * dumb-increment protocol. | ||||||
|  |  * | ||||||
|  |  * This is modified to make a test client which counts how much messages | ||||||
|  |  * per second can be received. | ||||||
|  |  * | ||||||
|  |  * libwebsockets$ make && ./a.out | ||||||
|  |  * g++ --std=c++14 -I/usr/local/opt/openssl/include devnull_client.cpp -lwebsockets | ||||||
|  |  * messages received: 0 per second 0 total | ||||||
|  |  * [2020/08/02 19:22:21:4774] U: LWS minimal ws client rx [-d <logs>] [--h2] | ||||||
|  |  * [2020/08/02 19:22:21:4814] U: callback_dumb_increment: established | ||||||
|  |  * messages received: 0 per second 0 total | ||||||
|  |  * messages received: 180015 per second 180015 total | ||||||
|  |  * messages received: 172866 per second 352881 total | ||||||
|  |  * messages received: 176177 per second 529058 total | ||||||
|  |  * messages received: 174191 per second 703249 total | ||||||
|  |  * messages received: 193397 per second 896646 total | ||||||
|  |  * messages received: 196385 per second 1093031 total | ||||||
|  |  * messages received: 194593 per second 1287624 total | ||||||
|  |  * messages received: 189484 per second 1477108 total | ||||||
|  |  * messages received: 200825 per second 1677933 total | ||||||
|  |  * messages received: 183542 per second 1861475 total | ||||||
|  |  * ^C[2020/08/02 19:22:33:4450] U: Completed OK | ||||||
|  |  *  | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #include <libwebsockets.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <signal.h> | ||||||
|  |  | ||||||
|  | #include <atomic> | ||||||
|  | #include <thread> | ||||||
|  | #include <iostream> | ||||||
|  |  | ||||||
|  | static int interrupted; | ||||||
|  | static struct lws *client_wsi; | ||||||
|  |  | ||||||
|  | std::atomic<uint64_t> receivedCount(0); | ||||||
|  |  | ||||||
|  | static int | ||||||
|  | callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, | ||||||
|  |               void *user, void *in, size_t len) | ||||||
|  | { | ||||||
|  |         switch (reason) { | ||||||
|  |  | ||||||
|  |         /* because we are protocols[0] ... */ | ||||||
|  |         case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: | ||||||
|  |                 lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", | ||||||
|  |                          in ? (char *)in : "(null)"); | ||||||
|  |                 client_wsi = NULL; | ||||||
|  |                 break; | ||||||
|  |  | ||||||
|  |         case LWS_CALLBACK_CLIENT_ESTABLISHED: | ||||||
|  |                 lwsl_user("%s: established\n", __func__); | ||||||
|  |                 break; | ||||||
|  |  | ||||||
|  |         case LWS_CALLBACK_CLIENT_RECEIVE: | ||||||
|  |                 receivedCount++; | ||||||
|  |                 break; | ||||||
|  |  | ||||||
|  |         case LWS_CALLBACK_CLIENT_CLOSED: | ||||||
|  |                 client_wsi = NULL; | ||||||
|  |                 break; | ||||||
|  |  | ||||||
|  |         default: | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return lws_callback_http_dummy(wsi, reason, user, in, len); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static const struct lws_protocols protocols[] = { | ||||||
|  |         { | ||||||
|  |                 "dumb-increment-protocol", | ||||||
|  |                 callback_dumb_increment, | ||||||
|  |                 0, | ||||||
|  |                 0, | ||||||
|  |         }, | ||||||
|  |         { NULL, NULL, 0, 0 } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static void | ||||||
|  | sigint_handler(int sig) | ||||||
|  | { | ||||||
|  |         interrupted = 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int main(int argc, const char **argv) | ||||||
|  | { | ||||||
|  |         uint64_t receivedCountTotal(0); | ||||||
|  |         uint64_t receivedCountPerSecs(0); | ||||||
|  |  | ||||||
|  |         auto timer = [&receivedCountTotal, &receivedCountPerSecs] { | ||||||
|  |             while (!interrupted) | ||||||
|  |             { | ||||||
|  |                 std::cerr << "messages received: " | ||||||
|  |                           << receivedCountPerSecs | ||||||
|  |                           << " per second " | ||||||
|  |                           << receivedCountTotal  | ||||||
|  |                           << " total" | ||||||
|  |                           << std::endl; | ||||||
|  |  | ||||||
|  |                 receivedCountPerSecs = receivedCount - receivedCountTotal; | ||||||
|  |                 receivedCountTotal += receivedCountPerSecs; | ||||||
|  |  | ||||||
|  |                 auto duration = std::chrono::seconds(1); | ||||||
|  |                 std::this_thread::sleep_for(duration); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         std::thread t1(timer); | ||||||
|  |  | ||||||
|  |         struct lws_context_creation_info info; | ||||||
|  |         struct lws_client_connect_info i; | ||||||
|  |         struct lws_context *context; | ||||||
|  |         const char *p; | ||||||
|  |         int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE | ||||||
|  |                 /* for LLL_ verbosity above NOTICE to be built into lws, lws | ||||||
|  |                  * must have been configured with -DCMAKE_BUILD_TYPE=DEBUG | ||||||
|  |                  * instead of =RELEASE */ | ||||||
|  |                 /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ | ||||||
|  |                 /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ | ||||||
|  |                 /* | LLL_DEBUG */; | ||||||
|  |  | ||||||
|  |         signal(SIGINT, sigint_handler); | ||||||
|  |         if ((p = lws_cmdline_option(argc, argv, "-d"))) | ||||||
|  |                 logs = atoi(p); | ||||||
|  |  | ||||||
|  |         lws_set_log_level(logs, NULL); | ||||||
|  |         lwsl_user("LWS minimal ws client rx [-d <logs>] [--h2]\n"); | ||||||
|  |  | ||||||
|  |         memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ | ||||||
|  |         info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ | ||||||
|  |         info.protocols = protocols; | ||||||
|  |         info.timeout_secs = 10; | ||||||
|  |  | ||||||
|  |         /* | ||||||
|  |          * since we know this lws context is only ever going to be used with | ||||||
|  |          * one client wsis / fds / sockets at a time, let lws know it doesn't | ||||||
|  |          * have to use the default allocations for fd tables up to ulimit -n. | ||||||
|  |          * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we | ||||||
|  |          * will use. | ||||||
|  |          */ | ||||||
|  |         info.fd_limit_per_thread = 1 + 1 + 1; | ||||||
|  |  | ||||||
|  |         context = lws_create_context(&info); | ||||||
|  |         if (!context) { | ||||||
|  |                 lwsl_err("lws init failed\n"); | ||||||
|  |                 return 1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ | ||||||
|  |         i.context = context; | ||||||
|  |         i.port = 8008; | ||||||
|  |         i.address = "127.0.0.1"; | ||||||
|  |         i.path = "/"; | ||||||
|  |         i.host = i.address; | ||||||
|  |         i.origin = i.address; | ||||||
|  |         i.protocol = protocols[0].name; /* "dumb-increment-protocol" */ | ||||||
|  |         i.pwsi = &client_wsi; | ||||||
|  |  | ||||||
|  |         if (lws_cmdline_option(argc, argv, "--h2")) | ||||||
|  |                 i.alpn = "h2"; | ||||||
|  |  | ||||||
|  |         lws_client_connect_via_info(&i); | ||||||
|  |  | ||||||
|  |         while (n >= 0 && client_wsi && !interrupted) | ||||||
|  |                 n = lws_service(context, 0); | ||||||
|  |  | ||||||
|  |         lws_context_destroy(context); | ||||||
|  |  | ||||||
|  |         lwsl_user("Completed %s\n", receivedCount > 10 ? "OK" : "Failed"); | ||||||
|  |  | ||||||
|  |         t1.join(); | ||||||
|  |  | ||||||
|  |         return receivedCount > 10; | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								test/compatibility/csharp/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								test/compatibility/csharp/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | bin | ||||||
|  | obj | ||||||
							
								
								
									
										99
									
								
								test/compatibility/csharp/Main.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								test/compatibility/csharp/Main.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | |||||||
|  | // | ||||||
|  | // Main.cs | ||||||
|  | // Author: Benjamin Sergeant | ||||||
|  | // Copyright (c) 2020 Machine Zone, Inc. All rights reserved. | ||||||
|  | // | ||||||
|  | // In a different terminal, start a push server: | ||||||
|  | // $ ws push_server -q | ||||||
|  | // | ||||||
|  | // $ dotnet run | ||||||
|  | // messages received per second: 145157 | ||||||
|  | // messages received per second: 141405 | ||||||
|  | // messages received per second: 152202 | ||||||
|  | // messages received per second: 157149 | ||||||
|  | // messages received per second: 157673 | ||||||
|  | // messages received per second: 153594 | ||||||
|  | // messages received per second: 157830 | ||||||
|  | // messages received per second: 158422 | ||||||
|  | // | ||||||
|  |  | ||||||
|  | using System; | ||||||
|  | using System.Net.WebSockets; | ||||||
|  | using System.Threading; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  |  | ||||||
|  | public class DevNullClientCli | ||||||
|  | { | ||||||
|  |     private static int receivedMessage = 0; | ||||||
|  |  | ||||||
|  |     public static async Task<byte[]> ReceiveAsync(ClientWebSocket ws, CancellationToken token) | ||||||
|  |     { | ||||||
|  |         int bufferSize = 8192; // 8K | ||||||
|  |         var buffer = new byte[bufferSize]; | ||||||
|  |         var offset = 0; | ||||||
|  |         var free = buffer.Length; | ||||||
|  |  | ||||||
|  |         while (true) | ||||||
|  |         { | ||||||
|  |             var result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer, offset, free), token).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |             offset += result.Count; | ||||||
|  |             free -= result.Count; | ||||||
|  |             if (result.EndOfMessage) break; | ||||||
|  |  | ||||||
|  |             if (free == 0) | ||||||
|  |             { | ||||||
|  |                 // No free space | ||||||
|  |                 // Resize the outgoing buffer | ||||||
|  |                 var newSize = buffer.Length + bufferSize; | ||||||
|  |  | ||||||
|  |                 var newBuffer = new byte[newSize]; | ||||||
|  |                 Array.Copy(buffer, 0, newBuffer, 0, offset); | ||||||
|  |                 buffer = newBuffer; | ||||||
|  |                 free = buffer.Length - offset; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return buffer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static void OnTimedEvent(object source, EventArgs e) | ||||||
|  |     { | ||||||
|  |         Console.WriteLine($"messages received per second: {receivedMessage}"); | ||||||
|  |         receivedMessage = 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static async Task ReceiveMessagesAsync(string url) | ||||||
|  |     { | ||||||
|  |         var ws = new ClientWebSocket(); | ||||||
|  |  | ||||||
|  |         System.Uri uri = new System.Uri(url); | ||||||
|  |         var cancellationToken = CancellationToken.None; | ||||||
|  |  | ||||||
|  |         try  | ||||||
|  |         { | ||||||
|  |             await ws.ConnectAsync(uri, cancellationToken).ConfigureAwait(false); | ||||||
|  |             while (true) | ||||||
|  |             { | ||||||
|  |                 var data = await DevNullClientCli.ReceiveAsync(ws, cancellationToken); | ||||||
|  |                 receivedMessage += 1; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         catch (System.Net.WebSockets.WebSocketException e) | ||||||
|  |         { | ||||||
|  |             Console.WriteLine($"WebSocket error: {e}"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static async Task Main() | ||||||
|  |     { | ||||||
|  |         var timer = new System.Timers.Timer(1000); | ||||||
|  |         timer.Elapsed += OnTimedEvent; | ||||||
|  |         timer.Enabled = true; | ||||||
|  |         timer.Start(); | ||||||
|  |  | ||||||
|  |         var url = "ws://localhost:8008"; | ||||||
|  |         await ReceiveMessagesAsync(url); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								test/compatibility/csharp/devnull_client.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								test/compatibility/csharp/devnull_client.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <OutputType>Exe</OutputType> | ||||||
|  |     <TargetFramework>netcoreapp3.1</TargetFramework> | ||||||
|  |   </PropertyGroup> | ||||||
|  | </Project> | ||||||
							
								
								
									
										42
									
								
								test/compatibility/node/devnull_client.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								test/compatibility/node/devnull_client.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | // | ||||||
|  | // With ws@7.3.1 | ||||||
|  | // and  | ||||||
|  | // node --version | ||||||
|  | // v13.11.0 | ||||||
|  | // | ||||||
|  | // In a different terminal, start a push server: | ||||||
|  | // $ ws push_server -q | ||||||
|  | // | ||||||
|  | // $ node devnull_client.js | ||||||
|  | // messages received per second: 16643 | ||||||
|  | // messages received per second: 28065 | ||||||
|  | // messages received per second: 28432 | ||||||
|  | // messages received per second: 22207 | ||||||
|  | // messages received per second: 28805 | ||||||
|  | // messages received per second: 28694 | ||||||
|  | // messages received per second: 28180 | ||||||
|  | // messages received per second: 28601 | ||||||
|  | // messages received per second: 28698 | ||||||
|  | // messages received per second: 28931 | ||||||
|  | // messages received per second: 27975 | ||||||
|  | // | ||||||
|  | const WebSocket = require('ws'); | ||||||
|  |  | ||||||
|  | const ws = new WebSocket('ws://localhost:8008'); | ||||||
|  |  | ||||||
|  | ws.on('open', function open() { | ||||||
|  |   ws.send('hello from node'); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | var receivedMessages = 0; | ||||||
|  |  | ||||||
|  | setInterval(function timeout() { | ||||||
|  |   console.log(`messages received per second: ${receivedMessages}`) | ||||||
|  |   receivedMessages = 0; | ||||||
|  | }, 1000); | ||||||
|  |  | ||||||
|  | ws.on('message', function incoming(data) { | ||||||
|  |   receivedMessages += 1; | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										44
									
								
								test/compatibility/python/websockets/devnull_client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								test/compatibility/python/websockets/devnull_client.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  |  | ||||||
|  | # websocket send client | ||||||
|  |  | ||||||
|  | import argparse | ||||||
|  | import asyncio | ||||||
|  | import websockets | ||||||
|  |  | ||||||
|  | try: | ||||||
|  |     import uvloop | ||||||
|  |     uvloop.install() | ||||||
|  | except ImportError: | ||||||
|  |     print('uvloop not available') | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  | msgCount = 0 | ||||||
|  |  | ||||||
|  | async def timer(): | ||||||
|  |     global msgCount | ||||||
|  |  | ||||||
|  |     while True: | ||||||
|  |         print(f'Received messages: {msgCount}') | ||||||
|  |         msgCount = 0 | ||||||
|  |  | ||||||
|  |         await asyncio.sleep(1) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def client(url): | ||||||
|  |     global msgCount | ||||||
|  |  | ||||||
|  |     asyncio.ensure_future(timer()) | ||||||
|  |  | ||||||
|  |     async with websockets.connect(url) as ws: | ||||||
|  |         async for message in ws: | ||||||
|  |             msgCount += 1 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     parser = argparse.ArgumentParser(description='websocket proxy.') | ||||||
|  |     parser.add_argument('--url', help='Remote websocket url', | ||||||
|  |                         default='wss://echo.websocket.org') | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     asyncio.get_event_loop().run_until_complete(client(args.url)) | ||||||
| @@ -10,7 +10,7 @@ import websockets | |||||||
| async def echo(websocket, path): | async def echo(websocket, path): | ||||||
|     while True: |     while True: | ||||||
|         msg = await websocket.recv() |         msg = await websocket.recv() | ||||||
|         print(f'Received {len(msg)} bytes') |         # print(f'Received {len(msg)} bytes') | ||||||
|         await websocket.send(msg) |         await websocket.send(msg) | ||||||
|  |  | ||||||
| host = os.getenv('BIND_HOST', 'localhost') | host = os.getenv('BIND_HOST', 'localhost') | ||||||
|   | |||||||
| @@ -50,7 +50,9 @@ add_executable(ws | |||||||
|   ws_http_client.cpp |   ws_http_client.cpp | ||||||
|   ws_ping_pong.cpp |   ws_ping_pong.cpp | ||||||
|   ws_broadcast_server.cpp |   ws_broadcast_server.cpp | ||||||
|  |   ws_push_server.cpp | ||||||
|   ws_echo_server.cpp |   ws_echo_server.cpp | ||||||
|  |   ws_echo_client.cpp | ||||||
|   ws_chat.cpp |   ws_chat.cpp | ||||||
|   ws_connect.cpp |   ws_connect.cpp | ||||||
|   ws_transfer.cpp |   ws_transfer.cpp | ||||||
|   | |||||||
							
								
								
									
										57
									
								
								ws/ws.cpp
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								ws/ws.cpp
									
									
									
									
									
								
							| @@ -125,6 +125,7 @@ int main(int argc, char** argv) | |||||||
|     std::string logfile; |     std::string logfile; | ||||||
|     std::string scriptPath; |     std::string scriptPath; | ||||||
|     std::string republishChannel; |     std::string republishChannel; | ||||||
|  |     std::string sendMsg("hello world"); | ||||||
|     ix::SocketTLSOptions tlsOptions; |     ix::SocketTLSOptions tlsOptions; | ||||||
|     ix::CobraConfig cobraConfig; |     ix::CobraConfig cobraConfig; | ||||||
|     ix::CobraBotConfig cobraBotConfig; |     ix::CobraBotConfig cobraBotConfig; | ||||||
| @@ -147,6 +148,7 @@ int main(int argc, char** argv) | |||||||
|     bool version = false; |     bool version = false; | ||||||
|     bool verifyNone = false; |     bool verifyNone = false; | ||||||
|     bool disablePong = false; |     bool disablePong = false; | ||||||
|  |     bool noSend = false; | ||||||
|     int port = 8008; |     int port = 8008; | ||||||
|     int redisPort = 6379; |     int redisPort = 6379; | ||||||
|     int statsdPort = 8125; |     int statsdPort = 8125; | ||||||
| @@ -243,6 +245,19 @@ int main(int argc, char** argv) | |||||||
|     connectApp->add_option("--subprotocol", subprotocol, "Subprotocol"); |     connectApp->add_option("--subprotocol", subprotocol, "Subprotocol"); | ||||||
|     addTLSOptions(connectApp); |     addTLSOptions(connectApp); | ||||||
|  |  | ||||||
|  |     CLI::App* echoClientApp = | ||||||
|  |         app.add_subcommand("echo_client", "Echo messages sent by a remote server"); | ||||||
|  |     echoClientApp->fallthrough(); | ||||||
|  |     echoClientApp->add_option("url", url, "Connection url")->required(); | ||||||
|  |     echoClientApp->add_flag("-x", disablePerMessageDeflate, "Disable per message deflate"); | ||||||
|  |     echoClientApp->add_flag("-b", binaryMode, "Send in binary mode"); | ||||||
|  |     echoClientApp->add_option( | ||||||
|  |         "--ping_interval", pingIntervalSecs, "Interval between sending pings"); | ||||||
|  |     echoClientApp->add_option("--subprotocol", subprotocol, "Subprotocol"); | ||||||
|  |     echoClientApp->add_option("--send_msg", sendMsg, "Send message"); | ||||||
|  |     echoClientApp->add_flag("-m", noSend, "Do not send messages, only receive messages"); | ||||||
|  |     addTLSOptions(echoClientApp); | ||||||
|  |  | ||||||
|     CLI::App* chatApp = app.add_subcommand("chat", "Group chat"); |     CLI::App* chatApp = app.add_subcommand("chat", "Group chat"); | ||||||
|     chatApp->fallthrough(); |     chatApp->fallthrough(); | ||||||
|     chatApp->add_option("url", url, "Connection url")->required(); |     chatApp->add_option("url", url, "Connection url")->required(); | ||||||
| @@ -252,12 +267,25 @@ int main(int argc, char** argv) | |||||||
|     echoServerApp->fallthrough(); |     echoServerApp->fallthrough(); | ||||||
|     echoServerApp->add_option("--port", port, "Port"); |     echoServerApp->add_option("--port", port, "Port"); | ||||||
|     echoServerApp->add_option("--host", hostname, "Hostname"); |     echoServerApp->add_option("--host", hostname, "Hostname"); | ||||||
|     echoServerApp->add_flag("-g", greetings, "Verbose"); |     echoServerApp->add_flag("-q", quiet, "Quiet / only display warnings and errors"); | ||||||
|  |     echoServerApp->add_flag("-g", greetings, "Greet"); | ||||||
|     echoServerApp->add_flag("-6", ipv6, "IpV6"); |     echoServerApp->add_flag("-6", ipv6, "IpV6"); | ||||||
|     echoServerApp->add_flag("-x", disablePerMessageDeflate, "Disable per message deflate"); |     echoServerApp->add_flag("-x", disablePerMessageDeflate, "Disable per message deflate"); | ||||||
|     echoServerApp->add_flag("-p", disablePong, "Disable sending PONG in response to PING"); |     echoServerApp->add_flag("-p", disablePong, "Disable sending PONG in response to PING"); | ||||||
|     addTLSOptions(echoServerApp); |     addTLSOptions(echoServerApp); | ||||||
|  |  | ||||||
|  |     CLI::App* pushServerApp = app.add_subcommand("push_server", "Push server"); | ||||||
|  |     pushServerApp->fallthrough(); | ||||||
|  |     pushServerApp->add_option("--port", port, "Port"); | ||||||
|  |     pushServerApp->add_option("--host", hostname, "Hostname"); | ||||||
|  |     pushServerApp->add_flag("-q", quiet, "Quiet / only display warnings and errors"); | ||||||
|  |     pushServerApp->add_flag("-g", greetings, "Greet"); | ||||||
|  |     pushServerApp->add_flag("-6", ipv6, "IpV6"); | ||||||
|  |     pushServerApp->add_flag("-x", disablePerMessageDeflate, "Disable per message deflate"); | ||||||
|  |     pushServerApp->add_flag("-p", disablePong, "Disable sending PONG in response to PING"); | ||||||
|  |     pushServerApp->add_option("--send_msg", sendMsg, "Send message"); | ||||||
|  |     addTLSOptions(pushServerApp); | ||||||
|  |  | ||||||
|     CLI::App* broadcastServerApp = app.add_subcommand("broadcast_server", "Broadcasting server"); |     CLI::App* broadcastServerApp = app.add_subcommand("broadcast_server", "Broadcasting server"); | ||||||
|     broadcastServerApp->fallthrough(); |     broadcastServerApp->fallthrough(); | ||||||
|     broadcastServerApp->add_option("--port", port, "Port"); |     broadcastServerApp->add_option("--port", port, "Port"); | ||||||
| @@ -477,6 +505,11 @@ int main(int argc, char** argv) | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (quiet) | ||||||
|  |     { | ||||||
|  |         spdlog::set_level(spdlog::level::warn); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Cobra config |     // Cobra config | ||||||
|     cobraConfig.webSocketPerMessageDeflateOptions = ix::WebSocketPerMessageDeflateOptions(true); |     cobraConfig.webSocketPerMessageDeflateOptions = ix::WebSocketPerMessageDeflateOptions(true); | ||||||
|     cobraConfig.socketTLSOptions = tlsOptions; |     cobraConfig.socketTLSOptions = tlsOptions; | ||||||
| @@ -498,11 +531,33 @@ int main(int argc, char** argv) | |||||||
|                                   subprotocol, |                                   subprotocol, | ||||||
|                                   pingIntervalSecs); |                                   pingIntervalSecs); | ||||||
|     } |     } | ||||||
|  |     else if (app.got_subcommand("echo_client")) | ||||||
|  |     { | ||||||
|  |         ret = ix::ws_echo_client(url, | ||||||
|  |                                  disablePerMessageDeflate, | ||||||
|  |                                  binaryMode, | ||||||
|  |                                  tlsOptions, | ||||||
|  |                                  subprotocol, | ||||||
|  |                                  pingIntervalSecs, | ||||||
|  |                                  sendMsg, | ||||||
|  |                                  noSend); | ||||||
|  |     } | ||||||
|     else if (app.got_subcommand("echo_server")) |     else if (app.got_subcommand("echo_server")) | ||||||
|     { |     { | ||||||
|         ret = ix::ws_echo_server_main( |         ret = ix::ws_echo_server_main( | ||||||
|             port, greetings, hostname, tlsOptions, ipv6, disablePerMessageDeflate, disablePong); |             port, greetings, hostname, tlsOptions, ipv6, disablePerMessageDeflate, disablePong); | ||||||
|     } |     } | ||||||
|  |     else if (app.got_subcommand("push_server")) | ||||||
|  |     { | ||||||
|  |         ret = ix::ws_push_server(port, | ||||||
|  |                                  greetings, | ||||||
|  |                                  hostname, | ||||||
|  |                                  tlsOptions, | ||||||
|  |                                  ipv6, | ||||||
|  |                                  disablePerMessageDeflate, | ||||||
|  |                                  disablePong, | ||||||
|  |                                  sendMsg); | ||||||
|  |     } | ||||||
|     else if (app.got_subcommand("transfer")) |     else if (app.got_subcommand("transfer")) | ||||||
|     { |     { | ||||||
|         ret = ix::ws_transfer_main(port, hostname, tlsOptions); |         ret = ix::ws_transfer_main(port, hostname, tlsOptions); | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								ws/ws.h
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								ws/ws.h
									
									
									
									
									
								
							| @@ -35,6 +35,15 @@ namespace ix | |||||||
|                             bool disablePerMessageDeflate, |                             bool disablePerMessageDeflate, | ||||||
|                             bool disablePong); |                             bool disablePong); | ||||||
|  |  | ||||||
|  |     int ws_push_server(int port, | ||||||
|  |                        bool greetings, | ||||||
|  |                        const std::string& hostname, | ||||||
|  |                        const ix::SocketTLSOptions& tlsOptions, | ||||||
|  |                        bool ipv6, | ||||||
|  |                        bool disablePerMessageDeflate, | ||||||
|  |                        bool disablePong, | ||||||
|  |                        const std::string& sendMsg); | ||||||
|  |  | ||||||
|     int ws_broadcast_server_main(int port, |     int ws_broadcast_server_main(int port, | ||||||
|                                  const std::string& hostname, |                                  const std::string& hostname, | ||||||
|                                  const ix::SocketTLSOptions& tlsOptions); |                                  const ix::SocketTLSOptions& tlsOptions); | ||||||
| @@ -54,6 +63,15 @@ namespace ix | |||||||
|                         const std::string& subprotocol, |                         const std::string& subprotocol, | ||||||
|                         int pingIntervalSecs); |                         int pingIntervalSecs); | ||||||
|  |  | ||||||
|  |     int ws_echo_client(const std::string& url, | ||||||
|  |                        bool disablePerMessageDeflate, | ||||||
|  |                        bool binaryMode, | ||||||
|  |                        const ix::SocketTLSOptions& tlsOptions, | ||||||
|  |                        const std::string& subprotocol, | ||||||
|  |                        int pingIntervalSecs, | ||||||
|  |                        const std::string& sendMsg, | ||||||
|  |                        bool noSend); | ||||||
|  |  | ||||||
|     int ws_receive_main(const std::string& url, |     int ws_receive_main(const std::string& url, | ||||||
|                         bool enablePerMessageDeflate, |                         bool enablePerMessageDeflate, | ||||||
|                         int delayMs, |                         int delayMs, | ||||||
|   | |||||||
| @@ -160,7 +160,7 @@ namespace ix | |||||||
|             std::stringstream ss; |             std::stringstream ss; | ||||||
|             if (msg->type == ix::WebSocketMessageType::Open) |             if (msg->type == ix::WebSocketMessageType::Open) | ||||||
|             { |             { | ||||||
|                 log("ws_connect: connected"); |                 spdlog::info("ws_connect: connected"); | ||||||
|                 spdlog::info("Uri: {}", msg->openInfo.uri); |                 spdlog::info("Uri: {}", msg->openInfo.uri); | ||||||
|                 spdlog::info("Headers:"); |                 spdlog::info("Headers:"); | ||||||
|                 for (auto it : msg->openInfo.headers) |                 for (auto it : msg->openInfo.headers) | ||||||
|   | |||||||
							
								
								
									
										121
									
								
								ws/ws_echo_client.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								ws/ws_echo_client.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | |||||||
|  | /* | ||||||
|  |  *  ws_echo_client.cpp | ||||||
|  |  *  Author: Benjamin Sergeant | ||||||
|  |  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #include <iostream> | ||||||
|  | #include <ixcore/utils/IXCoreLogger.h> | ||||||
|  | #include <ixwebsocket/IXNetSystem.h> | ||||||
|  | #include <ixwebsocket/IXSetThreadName.h> | ||||||
|  | #include <ixwebsocket/IXWebSocket.h> | ||||||
|  | #include <spdlog/spdlog.h> | ||||||
|  | #include <sstream> | ||||||
|  | #include <thread> | ||||||
|  |  | ||||||
|  | namespace ix | ||||||
|  | { | ||||||
|  |     int ws_echo_client(const std::string& url, | ||||||
|  |                        bool disablePerMessageDeflate, | ||||||
|  |                        bool binaryMode, | ||||||
|  |                        const ix::SocketTLSOptions& tlsOptions, | ||||||
|  |                        const std::string& subprotocol, | ||||||
|  |                        int pingIntervalSecs, | ||||||
|  |                        const std::string& sendMsg, | ||||||
|  |                        bool noSend) | ||||||
|  |     { | ||||||
|  |         // Our websocket object | ||||||
|  |         ix::WebSocket webSocket; | ||||||
|  |  | ||||||
|  |         webSocket.setUrl(url); | ||||||
|  |         webSocket.setTLSOptions(tlsOptions); | ||||||
|  |         webSocket.setPingInterval(pingIntervalSecs); | ||||||
|  |  | ||||||
|  |         if (disablePerMessageDeflate) | ||||||
|  |         { | ||||||
|  |             webSocket.disablePerMessageDeflate(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!subprotocol.empty()) | ||||||
|  |         { | ||||||
|  |             webSocket.addSubProtocol(subprotocol); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         std::atomic<uint64_t> receivedCount(0); | ||||||
|  |         uint64_t receivedCountTotal(0); | ||||||
|  |         uint64_t receivedCountPerSecs(0); | ||||||
|  |  | ||||||
|  |         // Setup a callback to be fired (in a background thread, watch out for race conditions !) | ||||||
|  |         // when a message or an event (open, close, error) is received | ||||||
|  |         webSocket.setOnMessageCallback([&webSocket, &receivedCount, &sendMsg, noSend, binaryMode]( | ||||||
|  |                                            const ix::WebSocketMessagePtr& msg) { | ||||||
|  |             if (msg->type == ix::WebSocketMessageType::Message) | ||||||
|  |             { | ||||||
|  |                 if (!noSend) | ||||||
|  |                 { | ||||||
|  |                     webSocket.send(msg->str, msg->binary); | ||||||
|  |                 } | ||||||
|  |                 receivedCount++; | ||||||
|  |             } | ||||||
|  |             else if (msg->type == ix::WebSocketMessageType::Open) | ||||||
|  |             { | ||||||
|  |                 spdlog::info("ws_echo_client: connected"); | ||||||
|  |                 spdlog::info("Uri: {}", msg->openInfo.uri); | ||||||
|  |                 spdlog::info("Headers:"); | ||||||
|  |                 for (auto it : msg->openInfo.headers) | ||||||
|  |                 { | ||||||
|  |                     spdlog::info("{}: {}", it.first, it.second); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 webSocket.send(sendMsg, binaryMode); | ||||||
|  |             } | ||||||
|  |             else if (msg->type == ix::WebSocketMessageType::Pong) | ||||||
|  |             { | ||||||
|  |                 spdlog::info("Received pong {}", msg->str); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         auto timer = [&receivedCount, &receivedCountTotal, &receivedCountPerSecs] { | ||||||
|  |             setThreadName("Timer"); | ||||||
|  |             while (true) | ||||||
|  |             { | ||||||
|  |                 // | ||||||
|  |                 // We cannot write to sentCount and receivedCount | ||||||
|  |                 // as those are used externally, so we need to introduce | ||||||
|  |                 // our own counters | ||||||
|  |                 // | ||||||
|  |                 std::stringstream ss; | ||||||
|  |                 ss << "messages received: " << receivedCountPerSecs << " per second " | ||||||
|  |                    << receivedCountTotal << " total"; | ||||||
|  |  | ||||||
|  |                 CoreLogger::info(ss.str()); | ||||||
|  |  | ||||||
|  |                 receivedCountPerSecs = receivedCount - receivedCountTotal; | ||||||
|  |                 receivedCountTotal += receivedCountPerSecs; | ||||||
|  |  | ||||||
|  |                 auto duration = std::chrono::seconds(1); | ||||||
|  |                 std::this_thread::sleep_for(duration); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         std::thread t1(timer); | ||||||
|  |  | ||||||
|  |         // Now that our callback is setup, we can start our background thread and receive messages | ||||||
|  |         std::cout << "Connecting to " << url << "..." << std::endl; | ||||||
|  |         webSocket.start(); | ||||||
|  |  | ||||||
|  |         // Send a message to the server (default to TEXT mode) | ||||||
|  |         webSocket.send("hello world"); | ||||||
|  |  | ||||||
|  |         while (true) | ||||||
|  |         { | ||||||
|  |             std::string text; | ||||||
|  |             std::cout << "> " << std::flush; | ||||||
|  |             std::getline(std::cin, text); | ||||||
|  |  | ||||||
|  |             webSocket.send(text); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  | } // namespace ix | ||||||
							
								
								
									
										108
									
								
								ws/ws_push_server.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								ws/ws_push_server.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | |||||||
|  | /* | ||||||
|  |  *  ws_push_server.cpp | ||||||
|  |  *  Author: Benjamin Sergeant | ||||||
|  |  *  Copyright (c) 2020 Machine Zone, Inc. All rights reserved. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #include <ixwebsocket/IXNetSystem.h> | ||||||
|  | #include <ixwebsocket/IXWebSocketServer.h> | ||||||
|  | #include <spdlog/spdlog.h> | ||||||
|  | #include <sstream> | ||||||
|  |  | ||||||
|  | namespace ix | ||||||
|  | { | ||||||
|  |     int ws_push_server(int port, | ||||||
|  |                        bool greetings, | ||||||
|  |                        const std::string& hostname, | ||||||
|  |                        const ix::SocketTLSOptions& tlsOptions, | ||||||
|  |                        bool ipv6, | ||||||
|  |                        bool disablePerMessageDeflate, | ||||||
|  |                        bool disablePong, | ||||||
|  |                        const std::string& sendMsg) | ||||||
|  |     { | ||||||
|  |         spdlog::info("Listening on {}:{}", hostname, port); | ||||||
|  |  | ||||||
|  |         ix::WebSocketServer server(port, | ||||||
|  |                                    hostname, | ||||||
|  |                                    SocketServer::kDefaultTcpBacklog, | ||||||
|  |                                    SocketServer::kDefaultMaxConnections, | ||||||
|  |                                    WebSocketServer::kDefaultHandShakeTimeoutSecs, | ||||||
|  |                                    (ipv6) ? AF_INET6 : AF_INET); | ||||||
|  |  | ||||||
|  |         server.setTLSOptions(tlsOptions); | ||||||
|  |  | ||||||
|  |         if (disablePerMessageDeflate) | ||||||
|  |         { | ||||||
|  |             spdlog::info("Disable per message deflate"); | ||||||
|  |             server.disablePerMessageDeflate(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (disablePong) | ||||||
|  |         { | ||||||
|  |             spdlog::info("Disable responding to PING messages with PONG"); | ||||||
|  |             server.disablePong(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         server.setOnClientMessageCallback( | ||||||
|  |             [greetings, &sendMsg](std::shared_ptr<ConnectionState> connectionState, | ||||||
|  |                                   ConnectionInfo& connectionInfo, | ||||||
|  |                                   WebSocket& webSocket, | ||||||
|  |                                   const WebSocketMessagePtr& msg) { | ||||||
|  |                 auto remoteIp = connectionInfo.remoteIp; | ||||||
|  |                 if (msg->type == ix::WebSocketMessageType::Open) | ||||||
|  |                 { | ||||||
|  |                     spdlog::info("New connection"); | ||||||
|  |                     spdlog::info("remote ip: {}", remoteIp); | ||||||
|  |                     spdlog::info("id: {}", connectionState->getId()); | ||||||
|  |                     spdlog::info("Uri: {}", msg->openInfo.uri); | ||||||
|  |                     spdlog::info("Headers:"); | ||||||
|  |                     for (auto it : msg->openInfo.headers) | ||||||
|  |                     { | ||||||
|  |                         spdlog::info("{}: {}", it.first, it.second); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (greetings) | ||||||
|  |                     { | ||||||
|  |                         webSocket.sendText("Welcome !"); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     bool binary = false; | ||||||
|  |                     while (true) | ||||||
|  |                     { | ||||||
|  |                         webSocket.send(sendMsg, binary); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 else if (msg->type == ix::WebSocketMessageType::Close) | ||||||
|  |                 { | ||||||
|  |                     spdlog::info("Closed connection: client id {} code {} reason {}", | ||||||
|  |                                  connectionState->getId(), | ||||||
|  |                                  msg->closeInfo.code, | ||||||
|  |                                  msg->closeInfo.reason); | ||||||
|  |                 } | ||||||
|  |                 else if (msg->type == ix::WebSocketMessageType::Error) | ||||||
|  |                 { | ||||||
|  |                     spdlog::error("Connection error: {}", msg->errorInfo.reason); | ||||||
|  |                     spdlog::error("#retries: {}", msg->errorInfo.retries); | ||||||
|  |                     spdlog::error("Wait time(ms): {}", msg->errorInfo.wait_time); | ||||||
|  |                     spdlog::error("HTTP Status: {}", msg->errorInfo.http_status); | ||||||
|  |                 } | ||||||
|  |                 else if (msg->type == ix::WebSocketMessageType::Message) | ||||||
|  |                 { | ||||||
|  |                     spdlog::info("Received {} bytes", msg->wireSize); | ||||||
|  |                     webSocket.send(msg->str, msg->binary); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |         auto res = server.listen(); | ||||||
|  |         if (!res.first) | ||||||
|  |         { | ||||||
|  |             spdlog::error(res.second); | ||||||
|  |             return 1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         server.start(); | ||||||
|  |         server.wait(); | ||||||
|  |  | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  | } // namespace ix | ||||||
		Reference in New Issue
	
	Block a user