C++ - Websocket Programming #2
This time, we will implement a websocket client in a C++ program instead of a browser. There are various libraries that can implement WebSocket in C++ programs. In this article, I will introduce an example that can implement WebSocket with only one cpp file and one header file.
easywsclient
The websocket library I would recommend and use is https://github.com/dhbaird/easywsclient.
Download easywsclient.cpp and easywsclient.hpp files from the above site and save them in your project directory. And this site provides websocket example files example-client-cpp11.cpp and example-client.cpp files.
And this site provides websocket example files example-client-cpp11.cpp and example-client.cpp files. I will modify these files slightly and connect to Node-RED websocket.
Build working C++ example on node-red
First, download the source code from github.
[spypiggy@localhost study]$ git clone https://github.com/dhbaird/easywsclient.git Cloning into 'easywsclient'... remote: Enumerating objects: 292, done. remote: Counting objects: 100% (3/3), done. remote: Compressing objects: 100% (3/3), done. remote: Total 292 (delta 0), reused 2 (delta 0), pack-reused 289 Receiving objects: 100% (292/292), 94.27 KiB | 278.00 KiB/s, done. Resolving deltas: 100% (170/170), done. [spypiggy@localhost study]$ ll total 0 drwxrwxr-x. 3 spypiggy spypiggy 122 Jul 16 00:22 aria drwxrwxr-x. 2 spypiggy spypiggy 40 Jul 16 05:09 curl drwxrwxr-x. 4 spypiggy spypiggy 247 Jul 31 04:56 easywsclient drwxrwxr-x. 2 spypiggy spypiggy 69 Jul 15 08:45 json drwxrwxr-x. 2 spypiggy spypiggy 51 Jul 12 08:31 mmap drwxrwxr-x. 2 spypiggy spypiggy 63 Jul 12 07:52 pipe drwxrwxr-x. 3 spypiggy spypiggy 201 Jul 30 09:09 stt drwxrwxr-x. 2 spypiggy spypiggy 93 Jul 15 06:02 websocket [spypiggy@localhost study]$ cd easywsclient/ [spypiggy@localhost easywsclient]$ ll total 60 -rw-rw-r--. 1 spypiggy spypiggy 1092 Jul 31 04:56 COPYING -rw-rw-r--. 1 spypiggy spypiggy 21300 Jul 31 04:56 easywsclient.cpp -rw-rw-r--. 1 spypiggy spypiggy 2757 Jul 31 04:56 easywsclient.hpp -rw-rw-r--. 1 spypiggy spypiggy 958 Jul 31 04:56 example-client.cpp -rw-rw-r--. 1 spypiggy spypiggy 1207 Jul 31 04:56 example-client-cpp11.cpp -rw-rw-r--. 1 spypiggy spypiggy 959 Jul 31 04:56 example-server.js -rw-rw-r--. 1 spypiggy spypiggy 572 Jul 31 04:56 Makefile -rw-rw-r--. 1 spypiggy spypiggy 449 Jul 31 04:56 package.json -rw-rw-r--. 1 spypiggy spypiggy 4803 Jul 31 04:56 README.md drwxrwxr-x. 2 spypiggy spypiggy 85 Jul 31 04:56 test
And let's modify the example-client.cpp file a bit. The source code is very simple and very easy to understand. For reference, this example was built on a Linux virtual machine and it is different from the host where Node-RED is running, so the address of the host where Node-RED is running must be entered correctly.
#include "easywsclient.hpp" //#include "easywsclient.cpp" // <-- include only if you don't want compile separately #ifdef _WIN32 #pragma comment( lib, "ws2_32" ) #include <WinSock2.h> #endif #include <assert.h> #include <stdio.h> #include <string> using easywsclient::WebSocket; static WebSocket::pointer ws = NULL; void handle_message(const std::string & message) { printf(">>> %s\n", message.c_str()); if (message == "world") { ws->close(); } } int main() { #ifdef _WIN32 INT rc; WSADATA wsaData; rc = WSAStartup(MAKEWORD(2, 2), &wsaData); if (rc) { printf("WSAStartup Failed.\n"); return 1; } #endif ws = WebSocket::from_url("ws://192.168.150.1:1880/ws/simple"); assert(ws); ws->send("goodbye"); ws->send("hello"); while (ws->getReadyState() != WebSocket::CLOSED) { ws->poll(); ws->dispatch(handle_message); } delete ws; #ifdef _WIN32 WSACleanup(); #endif return 0; }
<modified exampleclient.cpp>
This source code specifies the server where Node-RED is running in the websocket address. And if the connection is successful, "goodbye" and "hello" are sent and the packet sent by the websocket server is output.
Build the program together with easywsclient.cpp. I'll try running it right away with the build.
[spypiggy@localhost easywsclient]$ g++ example-client.cpp easywsclient.cpp -o wstest [spypiggy@localhost easywsclient]$ ./wstest >>> Sun Jul 31 2022 21:04:08 GMT+0900 (Korean Standard Time) >>> Sun Jul 31 2022 21:04:13 GMT+0900 (Korean Standard Time) >>> Sun Jul 31 2022 21:04:18 GMT+0900 (Korean Standard Time) >>> Sun Jul 31 2022 21:04:23 GMT+0900 (Korean Standard Time)
It can be seen that the packets sent every 5 seconds from the websocket server are properly outputted. Similarly, on the Noed-RED screen, it can also be confirmed that "goodbye" and "hello" sent by the client were properly received.
I made a C++ example that can use websocket very easily.
easywsclient provides functions that can send not only string but also binary data. This time, we will create an example of sending binary data.
Send binary data from easywsclient
Install node-red-contrib-image-output which can output images to Node-RED. Information on this node and installation related information is available at https://flows.nodered.org/node/node-red-contrib-image-output.
In the c++ example, we will read the image file and send binary data using websocket. This node receives the contents of jpg and png files as msg.payload and displays the image contents.
The example code is a bit long, but there is nothing difficult about it.
The following example code is a slight variation of the easywsclient original code. Therefore, an error occurs when using David Baird's easywsclient.
#include "easywsclient.hpp" //#include "easywsclient.cpp" // <-- include only if you don't want compile separately #ifdef _WIN32 #pragma comment( lib, "ws2_32" ) #include <WinSock2.h> #endif #include <assert.h> #include <stdio.h> #include <iostream> #include <string> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> using easywsclient::WebSocket; static WebSocket::pointer ws = NULL; void handle_message(const std::string & message) { printf(">>> %s\n", message.c_str()); if (message == "world") { ws->close(); } } uint8_t * read_image(const char *imagefile, int *fsize){ struct stat st; stat(imagefile, &st); off_t size = st.st_size; *fsize = size; std::cout << "Fiel size:" << size << std::endl; FILE *fh = fopen(imagefile, "rb"); if (NULL == fh) return NULL; uint8_t *buf = (uint8_t *)malloc(size); fread(buf, sizeof(uint8_t), size, fh); fclose(fh); return buf; } int main(int argc, char * argv[]) { #ifdef _WIN32 INT rc; WSADATA wsaData; rc = WSAStartup(MAKEWORD(2, 2), &wsaData); if (rc) { printf("WSAStartup Failed.\n"); return 1; } #endif if(argc < 2) { printf("enter image filename"); return (0); } int fsize; uint8_t *buf = read_image(argv[1], &fsize); if(NULL == buf){ return 0; } std::vector<uint8_t> buf_vec(buf, buf + fsize); ws = WebSocket::from_url("ws://192.168.150.1:1880/ws/simple"); assert(ws); rc = ws->sendBinary(buf_vec);
std::cout << "websocket send :" << rc << " bytes" << endl;while (ws->getReadyState() != WebSocket::CLOSED) { ws->poll(); ws->dispatch(handle_message); } delete ws; free(buf); #ifdef _WIN32 WSACleanup(); #endif return 0; }
<websocket example that sends binary image data>
In general, uint8_t (unsigned char) is used for binary data transmission, but it is a bit unusual to use STL's std::vector<type> as a parameter to send binary data. Now build and run the above code.
[spypiggy@localhost easywsclient]$ g++ example-client2.cpp easywsclient.cpp -o wstest [spypiggy@localhost easywsclient]$ ./wstest sample.jpg Fiel size:417006 >>> Sun Jul 31 2022 21:48:52 GMT+0900 (Korean Standard Time) >>> Sun Jul 31 2022 21:48:57 GMT+0900 (Korean Standard Time) >>> Sun Jul 31 2022 21:49:02 GMT+0900 (Korean Standard Time)
In Node-RED, you can see that the image sent by the C++ program to the websocket is displayed properly.
easywsclient improvement
- Functions such as Send and SendBinary that send data are void types. It is recommended that these functions be returned as many bytes as sent.
- The sending function send does not need to be asynchronous.
- Finally, the close function does not close the socket. It only sends a packet to the server to terminate the websocket. Therefore, if you clear the websocet locally before shutting down on the server, the socket does not close. Therefore, if the socket is not closed in the destructor, it is recommended to close it.
Wrapping up
The easywsclient introduced in this article is made with very concise code and is very easy to use. If you need to implement a websocket client in a C++ program, we recommend using this code.
Finally, if you want to use it in an actual program, you can give the poll function a parameter in the example code above. The parameter is in ms and is returned if there is no packet to read within this time.
That is, implement it like this:
while (ws->getReadyState() != WebSocket::CLOSED) { ret = ws->poll(1); //1ms timeout if(0 == ret){ // no data to read } else{ //read data ws->dispatch(handle_message); }); } }
And if the dispatch callback implementation is awkward, you can handle reading the packet directly as follows.
while (ws->getReadyState() != WebSocket::CLOSED) {WebSocket::pointer wsp = &*(ws); // <-- because a unique_ptr cannot be copied into a lambda
ret = ws->poll(1); //1ms timeout if(0 == ret){ // no data to read } else{ //read data ws->dispatch([wsp](void *p, const std::string & message) { printf(">>> %s\n", message.c_str()); cout << (const char *)p <<endl; }); } }
And if you need more, you can directly edit the easywsclient.cpp and easywsclient.hpp files and use them.
댓글
댓글 쓰기