I'm working on a project that is relying fairly heavily on packet latency. The architect for the overall system wants to use the Poco::Net::Websocket protocol as the transport layer between the different nodes. We have been running the application with ZMQ till this point, but there's too much overhead and we are not seeing the speeds we need. So I'm tasked with converting the ZMQ portion of the system over to WebSockets.
I've never used WebSockets before and I'm having issues understanding what's going on. I know WebSockets were originally designed as a way to speed up communication between the web browser and the page server decreasing latency through the use of full-duplex bi-directional communication.
There are a lot of tutorials for javascript, nodejs, and even python, but it's more limited when you're talking about C++, which is what I'm working with. The examples that I have found are geared more towards browser requests, which I'm not surprised by. Rather than general TCP packet transfer, which is what our use case would be.
Here are my thoughts and questions.
Seeing how I couldn't really understand what was happening, I decided to start with a Poco::Net::SocketStream configuration first. I chose to do it this way, based on the thought that Websockets are nothing more than a TCP socket inserted into a higher-level protocol framework. I figured transitioning would be easier than trying from scratch. So I spun up a server and client application using standard Poco socket libraries.
I used the example here as a base. I just ended up moving everything to a single file. I also spun up a Poco::Net::TCPServer as well, a sample found here.
I modified both to manage the fact that I'm sending JSON packets between the client and server. I took snippets of our original code to format the JSON packets and encode them with CBOR. I ended up keeping the zmq::message_t format that I was originally using, as I ran into issues receiving the packets on the other end. So as it wasn't the major issue I just skipped that part for now. So I'm aware the ZMQ portions of the code below aren't necessary, a small problem for another time.
Here is the Client code for reference.
#include "Poco/Net/HTTPRequest.h"
#include "Poco/Net/HTTPResponse.h"
#include "Poco/Net/HTTPMessage.h"
#include "Poco/Net/WebSocket.h"
#include "Poco/Net/HTTPClientSession.h"
#include <iostream>
#include "Poco/Net/SocketAddress.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/SocketStream.h"
#include "Poco/Net/WebSocket.h"
#include <jsoncons/json.hpp>
#include <jsoncons/json.hpp>
#include <jsoncons_ext/cbor/cbor.hpp>
#include <jsoncons_ext/jsonpath/json_query.hpp>
#include <jsoncons_ext/jsonpath/jsonpath.hpp>
#include <iostream>
#include <string>
#include <zmq.hpp>
using namespace jsoncons;
using Poco::Net::HTTPClientSession;
using Poco::Net::HTTPMessage;
using Poco::Net::HTTPRequest;
using Poco::Net::HTTPResponse;
using Poco::Net::WebSocket;
class ClientHandler
{
private:
std::string host;
int port;
// IP endpoint/socket address (consists of host addr and port #)
Poco::Net::SocketAddress socketAddr;
// Interface to a TCP stream socket
// Poco::Net::StreamSocket socket;
Poco::Net::Socket socket;
// Stream for reading from / writing to a socket (accepts a socket)
Poco::Net::SocketStream stream;
public:
ClientHandler(std::string h, int p) : host(h), port(p), socketAddr(h, p), socket(), stream(socket)
{
std::cout << "Host: " << host << "\tPort: " << port << std::endl;
}
// Connect to the initialized socket address' hostname and port
bool connected()
{
std::cout << "Creating a connection with ["
<< socketAddr.host().toString()
<< "] through port [" << socketAddr.port() << "] ...";
try
{
// socket.connect(socketAddr);
// std::cout << "Success!" << std::endl;
}
catch (Poco::Exception err)
{
std::cout << std::endl;
std::cout << "Socket connection error: [" << err.displayText() << "]" << std::endl;
return false;
}
return true;
}
// Send a message to the connected server
bool sendMessage()
{
std::cout << std::endl;
const char channel[] = "fool";
std::string channelPort = std::to_string(5577);
try
{
std::string message;
std::cout << "Enter a message to send to the server: ";
std::cin >> message;
json msg = json(json_object_arg, {{"$op", "create_channel"}, {"$svc", "zmq"}, {"channel_name", channel}, {"port", channelPort}});
std::vector<uint8_t> buffer;
jsoncons::cbor::encode_cbor(msg, buffer);
zmq::const_buffer msg_out = zmq::buffer(buffer);
if (message.compare("exit") != 0)
{
std::cout << "Sending the message to the server!\n\t";
// socket.sendBytes(msg_out.data(), msg_out.size());
return true;
}
else
return false;
}
catch (Poco::Exception err)
{
std::cout << "Data send error: [" << err.displayText() << "]" << std::endl;
return false;
}
}
};
int main(int argc, char **argv)
{
int port = 2001;
std::string hostname = "10.0.12.97";
// Handle the server-client connection and send some JSON
try
{
ClientHandler handler(hostname, port);
if (handler.connected())
while (handler.sendMessage())
;
}
catch (Poco::Exception err)
{
std::cout << "Handler Error -> " << err.displayText() << std::endl;
}
return 0;
}
So, How do I get from this ^ to Websockets?
I have been reading and digging through the documentation for Poco Websockets. At first, I thought I needed to figure out the HTTPClientSession, HTTPRequest, and HTTPResponse objects I saw in so many examples. However, looking at Documentation for Poco Websockets constructors, it appeared all I needed to do was pass it a socket connection. So spent some time trying to figure that out, but after going back to the documentation it appears that it specifies that I need to send it a socket that is already a Websocket.
Creates a WebSocket from another Socket, which must be a WebSocket,
That's what I get for not actually reading it. So I spent some more time reading through the constructors and it appears that I do in fact have to send at least 3 arguments.
WebSocket
WebSocket(
HTTPClientSession & cs,
HTTPRequest & request,
HTTPResponse & response
);
Creates a client-side WebSocket, using the given HTTPClientSession and HTTPRequest for the initial handshake (HTTP Upgrade request).
So I'm now back to HTTPClientSession, HTTPRequest, and HTTPResponse objects.
I think I understand HTTPClientSession. Here I should provide the Server name and port. So updating the code above should look something like this
...
Poco::Net::HTTPClientSession cs;
public:
ClientHandler(std::string h, int p) : host(h), port(p), cs(h, p)
...
HTTPResponse looks to be a pointer to a response object for the WebSocket to return the HTTPRequest response. Looks to contain a status code, date, reason(not sure what reason is)
The HTTPRequest is a point of interest. I'm lost as to what I need to format this as. There are three arguments for the request.
Method - this looks like nothing more than telling the server that you are requesting something or looking to hand something over.
HTTP_POST - I would assume this is what I want?
HTTP_GET
URI - for HTTP_GET this would be the path of the resource I'm looking to retrieve. And for Post, this would be the path that I would like to post something to on the server
Version - looks to be self-explanatory, as this is the HTTP version I would like to use.
but here is the rub of it, I do not want to post/get anything that's associated with a URI, I'm just looking to transfer a JSON packet and manage it on the other end. I've found a sample code of a client that uses this information, but I don't understand why it's using HTTP_GET, and the reason for the /?encode=text reference. I can't seem to find details anywhere on it. That leads me to the set value of the request object, don't understand what the "set()" call is for or how it would work in my situation.
With the new information, I updated my constructor to this.
...
Poco::Net::WebSocket *ws;
public:
ClientHandler(std::string h, int p) : host(h), port(p)
{
std::cout << "TEST" << std::endl;
// Poco::Net::HTTPClientSession cs(h, p);
Poco::Net::HTTPClientSession cs(host, port);
Poco::Net::HTTPRequest req(HTTPRequest::HTTP_GET, "/?encoding=text", HTTPMessage::HTTP_1_1);
// req.set("origin", "http://www.websocket.org");
Poco::Net::HTTPResponse resp;
ws = new WebSocket(cs, req, resp);
std::cout << "Host: " << host << "\tPort: " << port << std::endl;
}
...
However, I enter the constructor but never leave it. It gets hung up when I attempt to create the WebSocket. I'm assuming it doesn't like something. I tried the HTTPClientSesseion cs two ways. and leaving the req.set() in as well. Just hangs.
Could someone lend me some insight as to how I can get Poco::Net::Websocket running? and some information so I can understand what's going on a bit better. So when I attempt the server-side I will have some more information that will help.
Related
In my application I want to stream jpg images through a HTTP web server using the POCO library, and for this I am using multipart/x-mixed-replace content type in my response body. This means when a GET request arrives to the HTTP server, it is continously sending images to the client in parts in a single http response.
The stream should close and the request handler should return when the client closes the window. But my problem with the HTTPServerResponse.send() stream is that it only gets destroyed, when the response object is destroyed, so I do not know when the client has left, and when do I have to stop sending the images.
That is how the code looks like:
#pragma once
#include <fstream>
#include <sstream>
#include <string>
#include "Poco/Net/HTTPRequestHandler.h"
class StreamHandler : public Poco::Net::HTTPRequestHandler {
public:
void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) override {
std::ifstream imgFile("../../assets/imgs/random.jpg");
std::stringstream ss{};
ss << imgFile.rdbuf();
std::string buf = ss.str();
std::string boundary = "--BOUNDARY--";
response.setVersion(request.getVersion());
response.setStatus(Poco::Net::HTTPServerResponse::HTTP_OK);
response.setChunkedTransferEncoding(false);
response.setKeepAlive(false);
response.setContentType("multipart/x-mixed-replace; boundary=" + boundary);
response.set("Access-Control-Allow-Origin", "*");
response.set("Connection", "Close");
response.set("Cache-Control",
"no-cache, no-store, must-revalidate, pre-check=0, post-check=0, max-age=0, false");
response.set("Pragma", "no-cache");
std::ostream& ostr = response.send();
while (true) { // <-- What is the stop condition?
ostr << boundary << "\r\n";
ostr << "Content-Type: image/jpeg\r\n"
"Content-Length: " +
std::to_string(buf.length()) + "\r\n\r\n";
ostr << buf;
ostr << "\r\n";
}
}
};
Is there a way to detect whether the client has left?
PS: Possibly I would like a solution within the boundaries of Poco::Net::HTTPRequestHandler. I do not want any solution that requires opening another port, or using another third party library only for the streaming. For example, I am already using nadjieb/cpp-mjpeg-streamer which is working great, but I want to simplify my code and depend only on POCO. In lower level libraries I saw good implementations that are using the following strategy:
while (true) {
if (res) {
res = http_res_send_chunk(req, part_buf, part_len);
} else {
break;
}
}
The send command returns with some positive integer in case of success, and with 0 if the send function failed. However, I cannot replicate this with POCO unfortunately. Any ideas how to get around this?
I finally found a solution and posted it here. If my answer gets accepted, I will flag this question as a duplicate.
I am using uWebSockets to do a project. What I need to do is get the sender IP address out of incoming HTTP Requests. In the documentation I can see IP address can be taken out from the WebSockets. Do anybody have an idea to cast uWS to WebSockets to get the data or is there an other way to get it?
#include <iostream>
#include "App.h"
int main() {
/* Overly simple hello world app */
uWS::App().get("/*", [](auto *res, auto *req) {
// **this is the place i need to access the ip address of the incoming HttpRequest**
res->end("Hello world!");
}).listen(3000, [](auto *listen_socket) {
if (listen_socket) {
std::cout << "Listening on port " << 3000 << std::endl;
}
}).
run();
std::cout << "Failed to listen on port 3000" << std::endl;
}
Link of the library: https://unetworking.github.io/uWebSockets.js/generated/interfaces/WebSocketBehavior.html
According to the documentation, the remote address is an attribute of the response. Ergo:
std::string_view remote_ip = res->getRemoteAddressAsText();
I have one machine running simultaniously some C++ application and a Node.js server.
Use-case:
I want to be able to trigger my C++ application and make it pass some data (lets say a string) into a socket file. Then my Node.js server shall fetch that data from the socket and print it on some web page via a TCP-port (Code not included here/yet). The same should happen the other way around.
What I've done so far:
I was able to write some strings from my Node.js server into to the socket file with the following code:
server.js
var net = require('net');
var fs = require('fs');
var socketPath = '/tmp/sock';
fs.stat(socketPath, function(err) {
if (!err) fs.unlinkSync(socketPath);
var unixServer = net.createServer(function(localSerialConnection) {
localSerialConnection.on('data', function(data) {
// data is a buffer from the socket
console.log('Something happened!');
});
// write to socket with localSerialConnection.write()
localSerialConnection.write('HELLO\n');
localSerialConnection.write('I\'m\n');
localSerialConnection.write('DOING something!\n');
localSerialConnection.write('with the SOCKS\n');
});
unixServer.listen(socketPath);
});
reading the content with nc -U /tmp/sock and with the following output https://i.stack.imgur.com/ye2Dx.png.
When I run my C++ code:
cpp_socket.cpp
#include <boost/asio.hpp>
#include <iostream>
int main() {
using boost::asio::local::stream_protocol;
boost::system::error_code ec;
::unlink("/tmp/sock"); // Remove previous binding.
boost::asio::io_service service;
stream_protocol::endpoint ep("/tmp/sock");
stream_protocol::socket s(service);
std::cout << "passed setup section" << std::endl;
s.connect(ep);
std::cout << "passed connection" << std::endl;
std::string message = "Hello from C++!";
std::cout << "before sending" << std::endl;
boost::asio::write(s, boost::asio::buffer(message), boost::asio::transfer_all());
/* s.write_some(boost::asio::buffer("hello world!"), ec); */
std::cout << "after sending" << std::endl;
I get the following output:
/cpp_socket
passed setup section
terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
what(): connect: No such file or directory
Aborted (core dumped)
Even though the /tmp/sock file still exists.
When I remove ::unlink("/tmp/sock"); // Remove previous binding. with comments it runs through, but my Node.js server stops running and nc -U /tmp/sock looses its connection.
Neither the .write() nor the .write_some() function seems to work.
I assume that I miss something trivial or I'm not following basic concepts of unix socket communication.
Questions:
Is it even possible to listen with one Node.js server application to a TCP-port and a UNIX-socket at the same time?
Am I understanding the concept of unix socket communication correctly, judging from my input?
How can I read or write from C++ from/into a socket, preferably with C++ boost/asio library. But not necessarily necessary :-)
Am I asking the right questions?
As you might see, I'm not too experienced with these subjects. If I haven't addressed my issues accordingly and not precisely enough,it's due to my lack of experience.
Thanks a lot in advance. Lets have a fruitful discussion.
Oh oops. The error was in plain sight:
::unlink("/tmp/sock"); // Remove previous binding.
Removes the socket. That's not good if you wanted to connect to it.
Removing that line made it work:
passed setup section
passed connection: Success
before sending
after sending
And on the listener side:
Which is, I guess, to be expected because the client isn't complete yet.
Disclaimer:
I made it work with TCP sockets, but I would like to see how its possible with unix sockets. One more open port could lead to potential security threats (correct me if I'm wrong). So if you (sehe) or someone knows how to achieve this, please feel free to share. Since I wasn't able to find this in my searches over the internet, it could be helpful for others, too.
What I did now:
Creating a NodeJS server which is listening to two ports. One port for the web-browser and one for the C++ application
Connect the C++ application with one port
Sending strings using telnet
server.js
const net = require('net');
const express = require('express');
const app = express();
const c_port = 6666;
const si_port = 8888;
//------------- From here Browser stream is handled -------------//
app.get('/', (req, res)=>{
res.send('Hello from Node!');
});
app.get('/index.html', (req, res) => {
res.sendFile(__dirname + "/" + "index.html");
});
app.listen(si_port,(req, res)=>{
console.log(`Listening on http://localhost:${si_port}`);
});
//------------- From here C++ stream is handled -------------//
var server = net.createServer(function(c) { //'connection' listener
console.log('client connected');
c.on('end', function() {
console.log('client disconnected');
});
c.write('hello\r\n');
c.on('data', function(data){
var read = data.toString();
console.log(read);
// var message = c.read();
// console.log(message);
})
// c.pipe(c);
c.write('Hello back to C++'); // But only if you shut down the server
});
server.listen(c_port, function() { //'listening' listener
console.log(`Listening for input from C++ application on port:${c_port}`);
});
client.cpp
#include <iostream>
#include <boost/asio.hpp>
int main(int argc, char* argv[])
{
if(argc != 4){
std::cout<<"Wrong parameter\n"<<"Example usage ./client 127.0.0.1 1234 hello"<<std::endl;
return -1;
}
auto const address = boost::asio::ip::make_address(argv[1]);
auto const port = std::atoi(argv[2]);
std::string msg = argv[3];
msg = msg + '\n';
boost::asio::io_service io_service;
//socket creation
boost::asio::ip::tcp::socket socket(io_service);
//connection
boost::system::error_code ec;
socket.connect( boost::asio::ip::tcp::endpoint( address, port ),ec);
if(ec){std::cout<<ec.message()<<std::endl; return 1;}
// request/message from client
//const string msg = "Hello from Client!\n";
boost::system::error_code error;
boost::asio::write( socket, boost::asio::buffer(msg), error );
if(error){
std::cout << "send failed: " << error.message() << std::endl;
}
// getting response from server
boost::asio::streambuf receive_buffer;
boost::asio::read(socket, receive_buffer, boost::asio::transfer_all(), error);
if( error && error != boost::asio::error::eof ){
std::cout << "receive failed: " << error.message() << std::endl;
}
else{
const char* data = boost::asio::buffer_cast<const char*>(receive_buffer.data());
std::cout << data << std::endl;
}
return 0;
}
With telnet localhost 6666 I can easily on that port and send random strings.
Executing my binary with additional arguments and a string I was able to send some data from my C++: ./clientcpp 127.0.0.1 6666 "HELLO from C++". And here is the output:
Thanks a lot again.
I'm new to ASIO and I'm trying to get a relatively simple UDP broadcast to work and I'm not seeing any packets leave my PC in Wireshark. Is there a configuraton on the io_service or socket that I'm missing? Here's my complete code:
#include <iostream>
#include <boost/asio.hpp>
#include <array>
using boost::asio::ip::udp;
namespace asio = boost::asio;
const char* idnMsg = "*IDN?;";
int main(int argc, char* argv[])
{
try
{
asio::io_service serv;
boost::system::error_code err;
udp::socket socket(serv);
socket.open(asio::ip::udp::v4(), err);
if (!err)
{
socket.set_option(udp::socket::reuse_address(true));
socket.set_option(asio::socket_base::broadcast(true));
asio::ip::udp::endpoint senderEndpoint(asio::ip::address_v4::broadcast(), 7777);
socket.send_to(asio::buffer(idnMsg, 6), senderEndpoint);
//socket.close(err);
std::array<char, 128> buf;
while (socket.available())
{
asio::ip::udp::endpoint remote;
size_t len = socket.receive_from(asio::buffer(buf), remote);
std::cout << "Received: ";
std::cout.write(buf.data(), len);
buf.fill(0);
}
}
else
std::cerr << "Error connecting: " << err.message() << std::endl;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
You may notice that it bears a striking resemblance to a combination of an asio example and another SO answer.
I'm using Boost.Asio from NuGet in Visual Studio 2015 in Windows 7 x64. If switching to manually-compiled standalone Asio will help, I will. I plan on doing that in the future anyway, as I have no need for the rest of boost in my current plans.
As I said, I saw no packets leave my PC in Wireshark, and I even have several devices on my network that would respond to that packet, and I saw no responses.
EDIT: This code with standalone ASIO in Linux works fine. But I'm going to need ASIO working on Windows if I'm eventually going to be shipping code with it; cross-platform is great and I'm aiming for that, but Windows is the biggest market.
It looks to me that the best you can expect is one small packet. You should check the return from the send_to call, which will tell you how many bytes were sent.
if( socket.send_to(asio::buffer(idnMsg, 6), senderEndpoint) != 6 )
{ std::cerr << "send_to failed" << std::endl; exit(1); }
else
{ std::cerr << "one packet sent" << std::endl; }
I compiled and ran your code: it works just fine -
Perhaps you missed this single packet in a flood of others?
Answering my own question to provide information to others who may have the same issue. I determined that the culprit was the "VirtualBox Host-Only Network" adapter added by VirtualBox. It looks like it was swallowing up all UDP broadcast packets sent from my machine. Disabling that adapter allowed my Asio code to work, and didn't event interfere with my VMs set to Bridged Adapter.
I am trying to communicate with a telnet server via socket programming with wxWidgets however, after I connect to the server I don't get any message back, even though I know there is a welcome message from the server, I just get nothing, is there a certain message I have to send to initiate a telnet session with the server, I thought just connecting would be good enough that it would then send the welcome message. When I connect via putty the server gives a welcome message right away before any input from me.
Can I get a link to a specification which will detail how to initiate a telnet session cause I haven't been able to find a useful article yet from searching.
The following program connects to a telnet server and prints the data it receives. Notice that the data is binary, not text -- you need to study the RFC854 to understand how to parse the telnet protocol.
#include <wx/wx.h>
#include <wx/socket.h>
#include <iostream>
#include <iomanip>
int main () {
wxInitialize();
wxSocketClient sockConn(wxSOCKET_WAITALL);
wxIPV4address addr;
addr.Hostname(wxT("shell.theworld.com"));
addr.Service(23);
if(!sockConn.Connect(addr, true)) {
std::cout << "Connect failed\n";
return 1;
}
std::string s(' ', 10);
sockConn.Read(&s[0], 10);
for(unsigned char c : s)
std::cout << std::hex << (unsigned int)c << " ";
std::cout << "\n";
wxUninitialize();
return 0;
}