I am trying to send TCP messages over my local network that exceed the MTU limit of ~1500 bytes. I know that TCP protocol splits messages exceeding 1500 bytes into individual packets at some level, but am unclear if it's something I need to deal with at the application level. So I wrote a test application in ROS and Qt, C++, to test the behavior. I picked these because they are what my overall project is written in.
My test_fragmentation_test node sets up a server, then the test_client node establishes a connection and sends a message exceeding 1500 bytes. The goal is to get the server to receive this full message as one cohesive unit.
When I test these nodes on the same computer, the server receives the full messages as one unit. However, when I put the server and client on separate computers (still same network), the server receives the message as multiple packets. The packets are either 1500 bytes, or multiples thereof, so I believe they are sometimes getting squashed together in the read buffer in pairs of two or three.
Here is my code:
tcp_fragmentation_test.cpp
#include "ros/ros.h"
#include <QtCore/QCoreApplication>
#include <QTcpSocket>
#include <QTcpServer>
#include <QDebug>
//prototype functions
void initialConnect();
void receiveMessage();
//global(ish) variables
extern QTcpServer* subserver;
extern QTcpSocket* subsocket;
QTcpServer* subserver;
QTcpSocket* subsocket;
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
//initiate ROS
ros::init(argc, argv, "tcp_fragmentation_test_node");
ros::NodeHandle n;
subserver = new QTcpServer();
//call initialConnect on first connection attempt
QObject::connect(subserver, &QTcpServer::newConnection,initialConnect);
quint16 server_port = 9998;
//start listening
QHostAddress server_IP = QHostAddress("127.0.0.1");
//start listening for incoming C2 connections
if(!subserver->listen(server_IP, server_port))
{
qDebug().noquote() << "subserver failed to start on: " + server_IP.toString() + "/" + QString::number(server_port);
}
else
{
qDebug().noquote() << "subserver started on: " + server_IP.toString() + "/" + QString::number(server_port);
}
return app.exec(); //inifite loop that starts qt event listener
}
//accept incoming connection
void initialConnect()
{
//accept the incoming connection
subsocket = subserver->nextPendingConnection();
QObject::connect(subsocket, &QTcpSocket::readyRead, receiveMessage);
}
void receiveMessage()
{
//read data in
QByteArray received_message = subsocket->readAll();
qDebug() << "message received: ";
qDebug() << received_message;
}
test_client.cpp
#include "ros/ros.h"
#include <QtCore/QCoreApplication>
#include <QTcpSocket>
#include <QTcpServer>
#include <QDebug>
//function prototypes
bool initialConnect(QString server_IP, quint16 server_port);
void sendMessage(QByteArray message);
//global(ish) variables
QTcpSocket* test_socket;
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
ros::init(argc, argv, "test_client");
ros::NodeHandle n;
initialConnect("127.0.0.1",9998);
//send 1500+ byte message, to test IP fragmentation
sendMessage("<message large than 1500 bytes>");
return app.exec(); //inifite loop that starts qt event listener
}
bool initialConnect(QString server_IP, quint16 server_port)
{
//create test_socket
test_socket = new QTcpSocket();
//try to connect, then wait a bit to make sure it was successful
test_socket->connectToHost(server_IP, server_port);
if(test_socket->waitForConnected(1000))
{
return true;
}
else
{
qDebug() << "Failed to connect to server";
return false;
}
}
//send test message
void sendMessage(QByteArray message)
{
//send message
test_socket->write(message);
test_socket->waitForBytesWritten(1000);
return;
}
I talked to one of my colleagues about this, and they said that they had not run into this issue when sending messages that exceed the MTU in both Tcl scripting language, and HTML websockets. They sent me their code to check out, but unfortunately it is a small part of a really large and not well documented codebase, so I am having parsing out what they did and why they did not run into the same issues as me.
I know that I could probably avoid this issue by including identifying headers for each message, that include the length, and then combine read messages into an overall buffer until the correct number of bytes have been read. However, I'm trying to see if there's a simpler way than this, especially after my colleague telling me that he never ran into this issue. It seems like something that should be handled behind the scenes by the TCP protocol, and I'm trying to avoid dealing with it at the application level if at all possible.
Any ideas?
Related
I have a server which uses a ZMQ_ROUTER to communicate with ZMQ_DEALER clients. I set the ZMQ_HEARTBEAT_IVL and ZMQ_HEARTBEAT_TTL options on the client socket to make the client and server ping pong each other. Beside, because of the ZMQ_HEARTBEAT_TTL option, the server will timeout the connection if it does not receive any pings from the client in a time period, according to zmq man page:
The ZMQ_HEARTBEAT_TTL option shall set the timeout on the remote peer for ZMTP heartbeats. If
this option is greater than 0, the remote side shall time out the connection if it does not
receive any more traffic within the TTL period. This option does not have any effect if
ZMQ_HEARTBEAT_IVL is not set or is 0. Internally, this value is rounded down to the nearest
decisecond, any value less than 100 will have no effect.
Therefore, what I expect the server to behave is that, when it does not receive any traffic from a client in a time period, it will close the connection to that client and discard all the messages in the outgoing queue after the linger time expires. I create a toy example to check if my hypothesis is correct and it turns out that it is not. The chain of events is as followed:
The server sends a bunch of data to the client.
The client receives and processes the data, which is slow.
All send commands return successfully.
While the client is still receiving the data, I unplug the internet cable.
After a few seconds (set by the ZMQ_HEARTBEAT_TTL option), the server starts sending FIN signals to the client, which are not being ACKed back.
The outgoing messages are not discarded (I check the memory consumption) even after a while. They are discarded only if I call zmq_close on the router socket.
So my question is, is this suppose to be how one should use the ZMQ heartbeat mechanism? If it is not then is there any solution for what I want to achieve? I figure that I can do heartbeat myself instead of using ZMQ's built in. However, even if I do, it seems that ZMQ does not provide a way to close a connection between a ZMQ_ROUTER and a ZMQ_DEALER, although that another version of ZMQ_ROUTER - ZMQ_STREAM provides a way to do this by sending an identity frame followed by an empty frame.
The toy example is below, any help would be thankful.
Server's side:
#include <zmq.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv)
{
void *context = zmq_ctx_new();
void *router = zmq_socket(context, ZMQ_ROUTER);
int router_mandatory = 1;
zmq_setsockopt(router, ZMQ_ROUTER_MANDATORY, &router_mandatory, sizeof(router_mandatory));
int hwm = 0;
zmq_setsockopt(router, ZMQ_SNDHWM, &hwm, sizeof(hwm));
int linger = 3000;
zmq_setsockopt(router, ZMQ_LINGER, &linger, sizeof(linger));
char bind_addr[1024];
sprintf(bind_addr, "tcp://%s:%s", argv[1], argv[2]);
if (zmq_bind(router, bind_addr) == -1) {
perror("ERROR");
exit(1);
}
// Receive client identity (only 1)
zmq_msg_t identity;
zmq_msg_init(&identity);
zmq_msg_recv(&identity, router, 0);
zmq_msg_t dump;
zmq_msg_init(&dump);
zmq_msg_recv(&dump, router, 0);
printf("%s\n", (char *) zmq_msg_data(&dump)); // hello
zmq_msg_close(&dump);
char buff[1 << 16];
for (int i = 0; i < 50000; ++i) {
if (zmq_send(router, zmq_msg_data(&identity),
zmq_msg_size(&identity),
ZMQ_SNDMORE) == -1) {
perror("ERROR");
exit(1);
}
if (zmq_send(router, buff, 1 << 16, 0) == -1) {
perror("ERROR");
exit(1);
}
}
printf("OK IM DONE SENDING\n");
// All send commands have returned successfully
// While the client is still receiving data, I unplug the intenet cable on the client machine
// After a while, the server starts sending FIN signals
printf("SLEEP before closing\n"); // At this point, the messages are not discarded (memory usage is high).
getchar();
zmq_close(router);
zmq_ctx_destroy(context);
}
Client's side:
#include <zmq.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
void *context = zmq_ctx_new();
void *dealer = zmq_socket(context, ZMQ_DEALER);
int heartbeat_ivl = 3000;
int heartbeat_timeout = 6000;
zmq_setsockopt(dealer, ZMQ_HEARTBEAT_IVL, &heartbeat_ivl, sizeof(heartbeat_ivl));
zmq_setsockopt(dealer, ZMQ_HEARTBEAT_TIMEOUT, &heartbeat_timeout, sizeof(heartbeat_timeout));
zmq_setsockopt(dealer, ZMQ_HEARTBEAT_TTL, &heartbeat_timeout, sizeof(heartbeat_timeout));
int hwm = 0;
zmq_setsockopt(dealer, ZMQ_RCVHWM, &hwm, sizeof(hwm));
char connect_addr[1024];
sprintf(connect_addr, "tcp://%s:%s", argv[1], argv[2]);
zmq_connect(dealer, connect_addr);
zmq_send(dealer, "hello", 6, 0);
size_t size = 0;
int i = 0;
while (size < (1ll << 16) * 50000) {
zmq_msg_t msg;
zmq_msg_init(&msg);
if (zmq_msg_recv(&msg, dealer, 0) == -1) {
perror("ERROR");
exit(1);
}
size += zmq_msg_size(&msg);
printf("i = %d, size = %ld, total = %ld\n", i, zmq_msg_size(&msg), size); // This causes the cliet to be slow
// Somewhere in this loop I unplug the internet cable.
// The client starts sending FIN signals as well as trying to reconnect. The recv command hangs forever.
zmq_msg_close(&msg);
++i;
}
zmq_close(dealer);
zmq_ctx_destroy(context);
}
PS: I know that setting the highwater mark to unlimited is bad practice, however I figure that the problem will be the same even if the highwater mark is low so let's ignore it for now.
I'm trying to use QUdpSocket to send some data to a well-defined host:port destination. When the destination port is not being listened on (e.g. the client is not running), ICMP messages appear that say "port unreachable".
I'd like to detect this condition in my program and act when it happens. I read somewhere that if I directly "connect" to the host instead of just doing "fire and forget" with my datagrams, then the application should receive these ICMP notifications. But the following code doesn't appear to get any errors.
#include <QTimer>
#include <QString>
#include <QUdpSocket>
#include <QCoreApplication>
class DataSender : public QObject
{
public:
DataSender(const QString& host, quint16 port);
void send(const char* data, unsigned size);
private:
void onSocketError(QUdpSocket::SocketError error);
private:
QUdpSocket socket;
};
DataSender::DataSender(const QString& host, const quint16 port)
{
connect(&socket, &QUdpSocket::connected, this, []{qDebug() << "connected";});
connect(&socket, &QUdpSocket::disconnected, this, []{qDebug() << "disconnected";});
connect(&socket, &QUdpSocket::hostFound, this, []{qDebug() << "hostFound";});
connect(&socket, qOverload<QUdpSocket::SocketError>(&QUdpSocket::error), this, &DataSender::onSocketError);
socket.connectToHost(host, port, QIODevice::WriteOnly);
qDebug() << "UDP server started";
}
void DataSender::send(const char*const data, const unsigned size)
{
socket.write(data, size);
}
void DataSender::onSocketError(const QUdpSocket::SocketError)
{
qDebug() << "UDP socket error:" << socket.errorString();
}
int main(int argc, char** argv)
{
QCoreApplication app(argc, argv);
DataSender sender("127.0.0.1", 33333);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&sender]{ sender.send("Test", 4); });
timer.start(1000);
return app.exec();
}
In the output I only get
hostFound
connected
UDP server started
At the same time, when running this test, I do see the "Port unreachable" ICMP messages in Wireshark. How can I actually receive and handle them?
I've found that in the Linux socket API the write(2) syscall returns -1 with errno=ECONNREFUSED in this case. But with QUdpSocket I'd like to get a cross-platform solution. I've also found that syscall-set errno does appear to be preserved after QUdpSocket::write() returns, so I might be able to use this, but this is specific to Linux, while the list of my target platforms also includes Windows.
I'm working on an application that needs to perform network communication and decided to use the poco c++ libraries. After going through the network tutorial I can't seem to find any forms of validation on establishing a network connection.
In the following example a client tries to connect to a server using a tcp socket stream:
#include "Poco/Net/SocketAddress.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/SocketStream.h"
#include "Poco/StreamCopier.h"
#include <iostream>
int main(int argc, char** argv)
{
Poco::Net::SocketAddress sa("www.appinf.com", 80);
Poco::Net::StreamSocket socket(sa);
Poco::Net::SocketStream str(socket);
str << "GET / HTTP/1.1\r\n"
"Host: www.appinf.com\r\n"
"\r\n";
str.flush();
Poco::StreamCopier::copyStream(str, std::cout);
return 0;
}
However, I couldn't find any information related to:
Error checking(what if www.appinf.com is unavailable or doesn't exist for that matter)
The type of exception these calls may raise
The only mention is that a SocketStream may hang if the receive timeout is not set for the socket when using formated inputs.
How can I check if a host is alive and may set up a tcp connection, implement a method such as:
void TCPClient::connectTo(std::string host, bool& connected, unsigned int port) {
std::string hi = "hi";
Poco::Net::SocketAddress clientSocketAddress(host, port);
Poco::Net::StreamSocket clientStreamSocket;
// try to connect and avoid hang by setting a timeout
clientStreamSocket.connect(clientSocketAddress, timeout);
// check if the connection has failed or not,
// set the connected parameter accordingly
// additionally try to send bytes over this connection
Poco::Net::SocketStream clientSocketStream(clientStreamSocket);
clientSocketStream << hi << std::endl;
clientSocketStream.flush();
// close the socket stream
clientSocketStream.close();
// close stream
clientStreamSocket.shutdown();
}
i'm developing an app for Raspberry PI based on socket interface. The main idea is that Raspberry will be connected to a sensor, collect data and send it via WiFi to Android device. From Android I can communicate with sensor sending some commands. I'm a beginner in this kind of development and following some tutorials about QTcpSocket I have created a simple client-server app but it is only in one direction. Server listens for what client is sending. Could you help me to improve it into two way communication? I've read that QTcpSocket doesn't require threading for this kind of problem but I didn't find any solution.
I would appreciate any help!
server.cpp:
#include "server.h"
#include <QTcpServer>
#include <QTcpSocket>
#include <cstdio>
#include <QtDebug>
Server::Server(QObject *parent) :
QObject(parent)
{
server = new QTcpServer(this);
connect(server, SIGNAL(newConnection()),
this, SLOT(on_newConnection()));
}
void Server::listen()
{
server->listen(QHostAddress::Any, 5100);
}
void Server::on_newConnection()
{
socket = server->nextPendingConnection();
if(socket->state() == QTcpSocket::ConnectedState)
{
printf("New connection established.\n");
qDebug()<<socket->peerAddress();
}
connect(socket, SIGNAL(disconnected()),
this, SLOT(on_disconnected()));
connect(socket, SIGNAL(readyRead()),
this, SLOT(on_readyRead()));
}
void Server::on_readyRead()
{
while(socket->canReadLine())
{
QByteArray ba = socket->readLine();
if(strcmp(ba.constData(), "!exit\n") == 0)
{
socket->disconnectFromHost();
break;
}
printf(">> %s", ba.constData());
}
}
void Server::on_disconnected()
{
printf("Connection disconnected.\n");
disconnect(socket, SIGNAL(disconnected()));
disconnect(socket, SIGNAL(readyRead()));
socket->deleteLater();
}
client.cpp
#include "client.h"
#include <QTcpSocket>
#include <QHostAddress>
#include <cstdio>
Client::Client(QObject *parent) : QObject(parent)
{
socket = new QTcpSocket(this);
printf("try to connect.\n");
connect(socket, SIGNAL(connected()),
this, SLOT(on_connected()));
}
void Client::on_connected()
{
printf("Connection established.\n");
char buffer[1024];
forever
{
while(socket->canReadLine())
{
QByteArray ba = socket->readLine();
printf("from server: %s", ba.constData());
}
printf(">> ");
gets(buffer);
int len = strlen(buffer);
buffer[len] = '\n';
buffer[len+1] = '\0';
socket->write(buffer);
socket->flush();
}
}
void Client::connectToServer()
{
socket->connectToHost(QHostAddress::LocalHost, 5100);
}
From architectural point of view you should define some communication rules (message flow) between your server and client first.
Then just read(write) from(to) instance of QTCPSocket according to defined flow.
You can, for instance, read data on server side, check what you should answer and write response to the same socket from which you have read. For line oriented messages (and only for them) code could look like:
void Server::on_readyRead()
{
// "while" loop would block until at least one whole line arrived
// I would use "if" instead
if(socket->canReadLine())
{
QByteArray ba = socket->readLine();
QByteArray response;
// some code which parses arrived message
// and prepares response
socket->write(response);
}
//else just wait for more data
}
Personally, I would move parsing and sending responses out from on_readyRead() slot to avoid blocking event loop for too long time, but since you are a beginner in network programming I just wanted to clarify what could be done to implement two way communication.
For more details you can see http://qt-project.org/doc/qt-4.8/qtnetwork.html
Remember about checking if whole message has arrived on both client and server side. If you use your own protocol (not HTTP, FTP nor other standarized) you can add message length on the beginning of message.
All you need to do is to write/read from the socket from the client or server. A TCP connection is already two way connection.
You may be having some issues with your forever loop in Client. You should really use the readyRead signal on the socket in the same way that you do for the server and drop that forever loop.
Handle keyboard input in a non-blocking manner rather than using gets(). gets() will block the main thread and prevent it from running the event loop. This is not the best way to handle things in your case since you want to be able to handle data from the server and from the user at the same time.
Maybe take a look at this with respect to keyboard handling from a console app:
using-qtextstream-to-read-stdin-in-a-non-blocking-fashion
Alternatively make it a GUI app and use a QPlainTextEdit.
I am trying to implement a bidirectional client-server program, where clients and servers can pass serialized objects between one another. I am trying to do this using Qt (QTcpSocket and QTcpServer). I have implemented programs like this in java, but I can't figure out how to do it using Qt. I've checked out the fortune client and fortune server examples...but from what I can see, the client is simply signaling the server, and the server sends it some data. I need for the client and server to send objects back and forth. I am not looking for a complete solution, all I am looking for is some guidance in the right direction.
I wrote some code, which accepts a connection, but does not accept the data.
SERVER
this class is the server; it should be accepting a connection and outputting the size of the buffer which is being sent. However it is outputting 0
#include "comms.h"
Comms::Comms(QString hostIP, quint16 hostPort)
{
server = new QTcpServer(this);
hostAddress.setAddress(hostIP);
this->hostPort = hostPort;
}
void Comms::attemptConnection(){
connect(server, SIGNAL(newConnection()), this, SLOT(connectionAccepted()));
//socket = server->nextPendingConnection();
server->listen(hostAddress,hostPort);
//receivedData = socket->readAll();
}
void Comms::connectionAccepted(){
qDebug()<<"Connected";
socket = new QTcpSocket(server->nextPendingConnection());
char* rec = new char[socket->readBufferSize()];
qDebug()<<socket->readBufferSize();
}
CLIENT
This class is the client. It should be sending the string 'hello'. It sends it successfully (to my knowledge)
#include "toplevelcomms.h"
#include "stdio.h"
TopLevelComms::TopLevelComms(QString hostIP, quint16 hostPort)
{
tcpSocket = new QTcpSocket();
hostAddress.setAddress(hostIP);
this->hostPort = hostPort;
}
void TopLevelComms::connect(){
tcpSocket->connectToHost(hostAddress,hostPort,QIODevice::ReadWrite);
//tcpSocket->waitForConnected(1);
QString string = "Hello";
QByteArray array;
array.append(string);
qDebug()<<tcpSocket->write(array);
}
Please tell me what I'm doing wrong, or tell me the general logic of establishing what I want in Qt.
QTcpSocket is asynchronous by default, so when you call connectToHost and write in same context it won't be sent, as socket is not connected. You should change your "client" code:
void TopLevelComms::connect(){
tcpSocket->connectToHost(hostAddress,hostPort,QIODevice::ReadWrite);
if(tcpSocket->waitForConnected()) // putting 1 as parameter isn't reasonable, using default 3000ms value
{
QString string = "Hello";
QByteArray array;
array.append(string);
qDebug()<<tcpSocket->write(array);
}
else
{
qDebug() << "couldn't connect";
}
}
Note: you also didn't check if you're able to listen
void Comms::attemptConnection(){
connect(server, SIGNAL(newConnection()), this, SLOT(connectionAccepted()));
//socket = server->nextPendingConnection();
if(server->listen(hostAddress,hostPort))
{
qDebug() << "Server listening";
}
else
{
qDebug() << "Couldn't listen to port" << server->serverPort() << ":" << server->errorString();
}
//receivedData = socket->readAll();
}
And last thing. Note that QTcpServer::nextPendingConnection() return QTcpSocket, so instead of taking that new connection you create new QTcpSocket with nextPendingConnection as parent
void Comms::connectionAccepted(){
qDebug()<<"Connected";
// WRONG! it will use QTcpSocket::QTcpSocket(QObject * parent)
//socket = new QTcpSocket(server->nextPendingConnection());
// use simple asign
socket = server->nextPendingConnection();
// move reading to slot
connect(socket, SIGNAL(readyRead()), this, SLOT(readSocket()));
}
now we will move reading to separate slot
void Comms::readSocket()
{
// note that dynamic size array is incompatible with some compilers
// we will use Qt data structure for that
//char* rec = new char[socket->readBufferSize()];
qDebug()<<socket->readBufferSize();
// note that QByteArray can be casted to char * and const char *
QByteArray data = socket->readAll();
}
I must admit, that it is a lot of errors as for such small code sample. You need to get some knowledge about TCP/IP connections. Those are streams and there is no warranty that whole data chunk will get to you at once
It looks like you have a timing issue. Since your client and server are different processes, there's no way you can guarantee that the entirety of TopLevelComms::connect() is being executed (along with the network data transfer) before your server's connectionAccepted() function tries to read from the socket.
I suspect that if you take advantage of QTcpSocket's waitForReadyRead() function, you should have better luck:
void Comms::connectionAccepted(){
qDebug()<<"Connected";
socket = new QTcpSocket(server->nextPendingConnection());
if( socket->waitForReadyRead() ) {
char* rec = new char[socket->readBufferSize()];
qDebug()<<socket->readBufferSize();
}
}