C++ - Websocket Programming #1

 WebSocket allows you to exchange data while maintaining a connection between the server and the browser. At this point, the data is delivered in the form of a 'packet', and the transfer takes place in both directions without connection interruption and additional HTTP requests.

Because of this feature, WebSockets are ideal for services that require continuous data exchange, such as online games and stock trading systems.

For this reason, Websocket is mainly implemented using Javascript, a language for browsers.

However, sometimes you need to use WebSockets in C++. For example, if the server system is built assuming that the browser is the client, and a program developed in C++ needs to connect to this server and receive service, then you need to implement Websocket in your C++ program.


Simple Websocket Server (Node-RED)

For testing purposes, we will implement a simple websocket server. My favorite method is to use Node-RED.

Node-RED has already created Websocket nodes.


Install Node-RED

Install Node-RED on your computer. Node-RED can be installed on both Windows and Linux.

Refer to the installation guide on https://nodered.org/ and install it according to your OS. I will install it on Windows PC. For reference, since Node-RED is an application package of Node-js, Node-js is also installed on your PC. 

For installation information, refer to https://nodered.org/docs/getting-started/local.


Install Node-js

Download the Node-js installation file from https://nodejs.org/en/#home-downloadhead and install it. The current 16.X version is the LTS version. I will install this version. 

<Node-JS Download Page>


Install Node-RED with npm

Now, you can install Noed-RED using npm, the package management tool of Node-js.

npm install -g --unsafe-perm node-red




Run Node-RED

If you have installed Node-RED without any problems, you can install Node-RED as follows.


C:\Users\spypi>node-red
31 Jul 16:23:58 - [info]

Welcome to Node-RED
===================

31 Jul 16:23:58 - [info] Node-RED version: v3.0.1
31 Jul 16:23:58 - [info] Node.js  version: v16.16.0
31 Jul 16:23:58 - [info] Windows_NT 10.0.22621 x64 LE
31 Jul 16:23:59 - [info] Loading palette nodes
31 Jul 16:23:59 - [info] Settings file  : C:\Users\spypi\.node-red\settings.js
31 Jul 16:23:59 - [info] Context store  : 'default' [module=memory]
31 Jul 16:23:59 - [info] User directory : C:\Users\spypi\.node-red
31 Jul 16:23:59 - [warn] Projects disabled : editorTheme.projects.enabled=false
31 Jul 16:23:59 - [info] Flows file     : C:\Users\spypi\.node-red\flows.json
31 Jul 16:23:59 - [info] Creating new flow file
31 Jul 16:23:59 - [warn]

---------------------------------------------------------------------
Your flow credentials file is encrypted using a system-generated key.

If the system-generated key is lost for any reason, your credentials
file will not be recoverable, you will have to delete it and re-enter
your credentials.

You should set your own key using the 'credentialSecret' option in
your settings file. Node-RED will then re-encrypt your credentials
file using your chosen key the next time you deploy a change.
---------------------------------------------------------------------

31 Jul 16:23:59 - [info] Server now running at http://127.0.0.1:1880/
31 Jul 16:23:59 - [warn] Encrypted credentials not found
31 Jul 16:23:59 - [info] Starting flows
31 Jul 16:23:59 - [info] Started flows


You can see that Node-RED is in service on port 1880 of the local host. Now connect to port 1880 using a browser. If you enter the real IP address instead of the 127.0.0.1, you can also access it from a remote computer.

Now connect to Node-RED using a browser.

<Node-RED Page>


You can see that there are Websocket in and out on the left palette. If you connect using these nodes, you can easily implement Websocket. To make the work easier, we will use the pre-made flow.json file. If you look at the execution screen of Node-RED, you can see the following line. Your PC will probably show up in a different path. Overwrite this file with the flows.json file I created.

31 Jul 17:21:07 - [info] Node-RED version: v3.0.1
31 Jul 17:21:07 - [info] Node.js  version: v16.16.0
31 Jul 17:21:07 - [info] Windows_NT 10.0.22622 x64 LE
31 Jul 17:21:08 - [info] Loading palette nodes
31 Jul 17:21:09 - [info] Settings file  : C:\Users\spypi\.node-red\settings.js
31 Jul 17:21:09 - [info] Context store  : 'default' [module=memory]
31 Jul 17:21:09 - [info] User directory : \Users\spypi\.node-red
31 Jul 17:21:09 - [warn] Projects disabled : editorTheme.projects.enabled=false
31 Jul 17:21:09 - [info] Flows file     : \Users\spypi\.node-red\flows.json
31 Jul 17:21:09 - [info] Creating new flow file
31 Jul 17:21:09 - [warn]


This is the new flows.json file

[
    {
        "id": "3f466f75b126dd57",
        "type": "tab",
        "label": "Flow1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "985ecbc7.67a138",
        "type": "websocket-listener",
        "z": "3f466f75b126dd57",
        "path": "/ws/simple",
        "wholemsg": "false"
    },
    {
        "id": "fd6546df.632a18",
        "type": "inject",
        "z": "3f466f75b126dd57",
        "name": "Tick every 5 secs",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "5",
        "crontab": "",
        "once": false,
        "onceDelay": "",
        "topic": "test",
        "payload": "",
        "payloadType": "date",
        "x": 130,
        "y": 240,
        "wires": [
            [
                "6525495f.1eecd8"
            ]
        ]
    },
    {
        "id": "370263af.e0203c",
        "type": "websocket out",
        "z": "3f466f75b126dd57",
        "name": "",
        "server": "985ecbc7.67a138",
        "client": "",
        "x": 560,
        "y": 300,
        "wires": []
    },
    {
        "id": "6525495f.1eecd8",
        "type": "function",
        "z": "3f466f75b126dd57",
        "name": "format time nicely",
        "func": "const d = new Date();\nmsg.payload = d.toString();\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 350,
        "y": 240,
        "wires": [
            [
                "370263af.e0203c"
            ]
        ]
    },
    {
        "id": "734ecc43.6050a4",
        "type": "websocket in",
        "z": "3f466f75b126dd57",
        "name": "",
        "server": "985ecbc7.67a138",
        "client": "",
        "x": 120,
        "y": 460,
        "wires": [
            [
                "340e1c45.9716c4"
            ]
        ]
    },
    {
        "id": "340e1c45.9716c4",
        "type": "debug",
        "z": "3f466f75b126dd57",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 330,
        "y": 460,
        "wires": []
    },
    {
        "id": "80d75e74.6d99a",
        "type": "inject",
        "z": "3f466f75b126dd57",
        "name": "Websocket Output Trigger",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "Test Packet",
        "payloadType": "str",
        "x": 150,
        "y": 340,
        "wires": [
            [
                "370263af.e0203c"
            ]
        ]
    },
    {
        "id": "68eef96b.216138",
        "type": "template",
        "z": "3f466f75b126dd57",
        "name": "Simple Web Page",
        "field": "payload.scriptSocket",
        "fieldType": "msg",
        "format": "html",
        "syntax": "mustache",
        "template": "        var ws;\n        var wsUri = \"ws:\";\n        var loc = window.location;\n        console.log(loc);\n        if (loc.protocol === \"https:\") { wsUri = \"wss:\"; }\n        // This needs to point to the web socket in the Node-RED flow\n        // ... in this case it's ws/simple\n        wsUri += \"//\" + loc.host + loc.pathname.replace(\"simple\",\"ws/simple\");\n\n        function wsConnect() {\n            console.log(\"connect\",wsUri);\n            ws = new WebSocket(wsUri);\n            //var line = \"\";    // either uncomment this for a building list of messages\n            ws.onmessage = function(msg) {\n                var line = \"\";  // or uncomment this to overwrite the existing message\n                // parse the incoming message as a JSON object\n                var data = msg.data;\n                console.log(data);\n                // build the output from the topic and payload parts of the object\n                line += \"<p>\"+data+\"</p>\";\n                // replace the messages div with the new \"line\"\n                document.getElementById('messages').innerHTML = line;\n                //ws.send(JSON.stringify({data:data}));\n            }\n            ws.onopen = function() {\n                // update the status div with the connection status\n                document.getElementById('status').innerHTML = \"connected\";\n                //ws.send(\"Open for data\");\n                console.log(\"connected\");\n            }\n            ws.onclose = function() {\n                // update the status div with the connection status\n                document.getElementById('status').innerHTML = \"not connected\";\n                // in case of lost connection tries to reconnect every 3 secs\n                setTimeout(wsConnect,3000);\n            }\n        }\n        \n        function doit(m) {\n            if (ws) { ws.send(m); }\n        }",
        "x": 290,
        "y": 100,
        "wires": [
            [
                "2ee6d3d5.235dec"
            ]
        ]
    },
    {
        "id": "2ee6d3d5.235dec",
        "type": "template",
        "z": "3f466f75b126dd57",
        "name": "Main html",
        "field": "payload",
        "fieldType": "msg",
        "format": "html",
        "syntax": "mustache",
        "template": "<!DOCTYPE HTML>\n<html>\n<head>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <script type=\"text/javascript\"> {{{payload.scriptSocket}}} </script>\n</head>\n    <body onload=\"wsConnect();\" onunload=\"ws.disconnect();\">\n        <h1>WebSocket Demo</h1>\n        <div id=\"messages\"></div>\n        <div id=\"status\">unknown</div>\n        <hr/>\n        <button type=\"button\" onclick='doit(\"Press\");'>Click to send message</button>\n    </body>\n</html>\n\n",
        "x": 480,
        "y": 100,
        "wires": [
            [
                "42a28745.bd5d78"
            ]
        ]
    },
    {
        "id": "42a28745.bd5d78",
        "type": "http response",
        "z": "3f466f75b126dd57",
        "name": "",
        "statusCode": "",
        "headers": {},
        "x": 630,
        "y": 100,
        "wires": []
    },
    {
        "id": "1787be40.e87842",
        "type": "http in",
        "z": "3f466f75b126dd57",
        "name": "",
        "url": "/simple",
        "method": "get",
        "upload": false,
        "swaggerDoc": "",
        "x": 90,
        "y": 100,
        "wires": [
            [
                "68eef96b.216138"
            ]
        ]
    },
    {
        "id": "7b12a12d4d95f240",
        "type": "comment",
        "z": "3f466f75b126dd57",
        "name": "Inbound Websocket",
        "info": "",
        "x": 130,
        "y": 420,
        "wires": []
    },
    {
        "id": "9b1de891.a8df68",
        "type": "comment",
        "z": "3f466f75b126dd57",
        "name": "Websocket Example Web Page (http://localhost:1880/simple)",
        "info": "",
        "x": 240,
        "y": 60,
        "wires": []
    },
    {
        "id": "344bdb54429d1602",
        "type": "comment",
        "z": "3f466f75b126dd57",
        "name": "send \"Test Packet\"",
        "info": "",
        "x": 130,
        "y": 300,
        "wires": []
    },
    {
        "id": "0921a42a3d413b26",
        "type": "comment",
        "z": "3f466f75b126dd57",
        "name": "send time every 5 seconds",
        "info": "",
        "x": 150,
        "y": 200,
        "wires": []
    }
]

<flows.json>

Note: The original author of this file is Professor Dongil Kim, Doowon Institute of Technology, Korea. The original file can be downloaded from https://github.com/kdi6033/node-red/blob/master/README.md.

After overwriting this file and restarting the node-red program, the browser screen changes as follows.


Test Websocket using Browser

The upper part of the Node_RED page above provides an html page for testing websockets.  When accessing the http://localhost:1880/simple page, a page for testing websocket appears as follows. The original html source of this page is created in the "Simple Web Page", "Main html" node.

<Websocket Test Page>

When this web page loads, it connects to the localhost:1880/ws/simple websocket. And Node-RED sends the current time every 5 seconds to the connected websocket client. In the figure above, the time displayed on the browser screen is sent at 5 second intervals from the Node-RED websocket. 

And click "Click to send message" to send data to Node-RED websocket.


<Node-RED inbound websocket result>

In the debugger window on the right, you can see the text ("Press") sent by the browser's client websocket.


Wrapping up

Using Node-RED, I created an environment for testing websockets simply. In the next article, we will continue the example of accessing Node-RED websocket and receiving services using a C++ program instead of a browser.

댓글

이 블로그의 인기 게시물

MQTT - C/C++ Client

RabbitMQ - C++ Client #1 : Installing C/C++ Libraries

C/C++ - Everything about time, date