Skip to content

Instantly share code, notes, and snippets.

@micjabbour
Created February 17, 2017 17:04
Show Gist options
  • Save micjabbour/3fdd561bd129a25e33725d6618e6de21 to your computer and use it in GitHub Desktop.
Save micjabbour/3fdd561bd129a25e33725d6618e6de21 to your computer and use it in GitHub Desktop.
Tcp Socket with heartbeats
#include <QtWidgets>
#include <QtNetwork>
class SyncTcpSocket : public QTcpSocket{
Q_OBJECT
public:
explicit SyncTcpSocket(QObject* parent= nullptr):QTcpSocket(parent){
setSocketOption(QAbstractSocket::LowDelayOption, 1);
heartbeatTimer.setInterval(100);
connect(&heartbeatTimer, &QTimer::timeout,
this, &SyncTcpSocket::SendHeartBeat);
timeoutTimer.setInterval(500);
connect(&timeoutTimer, &QTimer::timeout,
this, &SyncTcpSocket::HeartbeatTimeout);
connect(this, &QTcpSocket::readyRead,
this, &SyncTcpSocket::Receive);
connect(this, &QAbstractSocket::stateChanged,
this, &SyncTcpSocket::StateChanged);
StateChanged();
}
~SyncTcpSocket(){}
Q_SLOT void SendHeartBeat(){
QDataStream ds(this);
ds << (qint16)0; //heartbeat value
flush();
lastSendMs= lastSendTime.elapsed();
lastSendTime.start();
if(lastSendMs < minSendMs) minSendMs= lastSendMs;
if(lastSendMs > maxSendMs) maxSendMs= lastSendMs;
sumSendMs+= lastSendMs;
countSendMs++;
emit SentHeartbeat();
}
Q_SLOT void HeartbeatTimeout(){
qDebug() << "heartbeat timeout, aborting. . .";
abort();
close();
}
Q_SLOT void Receive(){
if(bytesAvailable() < 2) return;
QDataStream ds(this);
qint16 val;
ds >> val;
timeoutTimer.start();
lastReceiveMs= lastReceiveTime.elapsed();
lastReceiveTime.start();
if(lastReceiveMs < minReceiveMs) minReceiveMs= lastReceiveMs;
if(lastReceiveMs > maxReceiveMs) maxReceiveMs= lastReceiveMs;
sumReceiveMs+= lastReceiveMs;
countReceiveMs++;
emit ReceivedHeartbeat();
}
Q_SLOT void StateChanged(){
if(state() == QAbstractSocket::ConnectedState){
heartbeatTimer.start();
timeoutTimer.start();
lastReceiveTime.start();
lastSendTime.start();
} else {
heartbeatTimer.stop();
timeoutTimer.stop();
}
}
//connection properties getters
int GetLastReceiveMs(){ return lastReceiveMs; }
int GetLastSendMs(){ return lastSendMs; }
double GetAvgReceiveMs(){ return sumReceiveMs/countReceiveMs; }
double GetAvgSendMs(){ return sumSendMs/countSendMs; }
int GetMaxReceiveMs(){ return maxReceiveMs; }
int GetMaxSendMs(){ return maxSendMs; }
int GetMinReceiveMs(){ return minReceiveMs; }
int GetMinSendMs(){ return minSendMs; }
signals:
void SentHeartbeat();
void ReceivedHeartbeat();
private:
QTimer heartbeatTimer{this};
QTimer timeoutTimer{this};
QTime lastReceiveTime;
QTime lastSendTime;
int lastReceiveMs{0}, lastSendMs{0};
double sumReceiveMs{0}, sumSendMs{0};
int countReceiveMs{0}, countSendMs{0};
int maxReceiveMs{0}, maxSendMs{0};
int minReceiveMs{INT_MAX}, minSendMs{INT_MAX};
};
class SyncTcpServer : public QTcpServer{
public:
static const quint16 defaultListenPort = 9955;
explicit SyncTcpServer(QObject* parent= nullptr):QTcpServer(parent){}
~SyncTcpServer(){}
SyncTcpSocket* nextPendingConnection(){
SyncTcpSocket* pendingConn=
dynamic_cast<SyncTcpSocket*>(QTcpServer::nextPendingConnection());
if(!pendingConn) return NULL;
return pendingConn;
}
bool Listen(){
return listen(QHostAddress::Any, defaultListenPort);
}
protected:
void incomingConnection(qintptr socketDescriptor){
SyncTcpSocket* newSocket= new SyncTcpSocket(this);
newSocket->setSocketDescriptor(socketDescriptor);
addPendingConnection(newSocket);
}
};
class SyncSocketWatcherWidget : public QWidget{
Q_OBJECT
public:
explicit SyncSocketWatcherWidget(QWidget* parent= nullptr):QWidget(parent){
layout.addWidget(&sendLabel, 0, 0, Qt::AlignCenter);
layout.addWidget(&sendLast, 1, 0);
layout.addWidget(&sendMax, 2, 0);
layout.addWidget(&sendAvg, 3, 0);
layout.addWidget(&sendMin, 4, 0);
layout.addWidget(&receiveLabel, 0, 1, Qt::AlignCenter);
layout.addWidget(&receiveLast, 1, 1);
layout.addWidget(&receiveMax, 2, 1);
layout.addWidget(&receiveAvg, 3, 1);
layout.addWidget(&receiveMin, 4, 1);
layout.addWidget(&socketState, 5, 0, 1, 2, Qt::AlignCenter);
}
~SyncSocketWatcherWidget(){}
void SetWatchedSocket(SyncTcpSocket* watchedSocket){
if(this->watchedSocket)
disconnect(this->watchedSocket, 0, this, 0);
this->watchedSocket = watchedSocket;
if(watchedSocket){
connect(watchedSocket, &SyncTcpSocket::ReceivedHeartbeat,
this, &SyncSocketWatcherWidget::UpdateValues);
connect(watchedSocket, &SyncTcpSocket::SentHeartbeat,
this, &SyncSocketWatcherWidget::UpdateValues);
socketState.setText("Connected");
} else {
socketState.setText("Disconnected");
}
}
Q_SLOT void UpdateValues(){
sendLast.setText(QString("Interval Last: %0 ms").arg(watchedSocket->GetLastSendMs()));
sendMax.setText(QString("Interval Max: %0 ms").arg(watchedSocket->GetMaxSendMs()));
sendAvg.setText(QString("Interval Avg: %0 ms").arg(watchedSocket->GetAvgSendMs()));
sendMin.setText(QString("Interval Min: %0 ms").arg(watchedSocket->GetMinSendMs()));
receiveLast.setText(QString("Interval Last: %0 ms").arg(watchedSocket->GetLastReceiveMs()));
receiveMax.setText(QString("Interval Max: %0 ms").arg(watchedSocket->GetMaxReceiveMs()));
receiveAvg.setText(QString("Interval Avg: %0 ms").arg(watchedSocket->GetAvgReceiveMs()));
receiveMin.setText(QString("Interval Min: %0 ms").arg(watchedSocket->GetMinReceiveMs()));
}
private:
QGridLayout layout{this};
SyncTcpSocket* watchedSocket{nullptr};
QLabel receiveLabel{"Receive Intervals"};
QLabel receiveLast{"Interval Last: "};
QLabel receiveMax{"Interval Max: "};
QLabel receiveAvg{"Interval Avg: "};
QLabel receiveMin{"Interval Min: "};
QLabel sendLabel{"Send Intervals"};
QLabel sendLast{"Interval Last: "};
QLabel sendMax{"Interval Max: "};
QLabel sendAvg{"Interval Avg: "};
QLabel sendMin{"Interval Min: "};
QLabel socketState{"Disconnected"};
};
class ServerWindow : public QWidget{
Q_OBJECT
public:
explicit ServerWindow(QWidget* parent= nullptr):QWidget(parent){
layout.addWidget(&buttonStart);
layout.addWidget(&watcherWidget);
connect(&buttonStart, &QPushButton::clicked,
this, &ServerWindow::StartServer);
connect(&server, &SyncTcpServer::newConnection,
this, &ServerWindow::AcceptTheOnlyConnection);
}
~ServerWindow(){}
Q_SLOT void AcceptTheOnlyConnection(){
socket= server.nextPendingConnection();
watcherWidget.SetWatchedSocket(socket);
connect(socket, &QTcpSocket::disconnected,
this, &ServerWindow::ShowError);
connect(socket, static_cast<void(QTcpSocket::*)(QTcpSocket::SocketError)>(&QTcpSocket::error),
this, &ServerWindow::ShowError);
connect(socket, &QTcpSocket::disconnected,
this, [=](){qDebug() << "server socket disconnected";});
connect(socket, static_cast<void(QTcpSocket::*)(QTcpSocket::SocketError)>(&QTcpSocket::error),
this, [=](QAbstractSocket::SocketError e){qDebug() << "server socket error " << e;});
connect(socket, &QObject::destroyed, this, []{qDebug() << "server socket destroyed.";});
server.close();
}
Q_SLOT void StartServer(){
if(!server.Listen()){
QMessageBox::critical(this, "Error Listening",
"Could Not Start Server", QMessageBox::Ok);
return;
}
watcherWidget.SetWatchedSocket(nullptr);
if(socket){
socket->abort();
socket->deleteLater();
socket= nullptr;
}
buttonStart.setEnabled(false);
}
Q_SLOT void ShowError(){
if(socket){
socket->deleteLater();
socket= nullptr;
}
watcherWidget.SetWatchedSocket(nullptr);
buttonStart.setEnabled(true);
}
private:
QVBoxLayout layout{this};
QPushButton buttonStart{"Start Server"};
SyncSocketWatcherWidget watcherWidget;
SyncTcpServer server;
SyncTcpSocket* socket{nullptr};
};
class ClientWindow : public QWidget{
Q_OBJECT
public:
explicit ClientWindow(QWidget* parent= nullptr):QWidget(parent){
layout.addLayout(&hLayout);
hLayout.addWidget(&lineEditAddress);
hLayout.addWidget(&buttonConnect);
layout.addWidget(&watcherWidget);
lineEditAddress.setPlaceholderText("Server Address");
connect(&buttonConnect, &QPushButton::clicked, this, &ClientWindow::ConnectToServer);
}
~ClientWindow(){
delete socket;
}
Q_SLOT void ConnectToServer(){
socket = new SyncTcpSocket(this);
socket->connectToHost(lineEditAddress.text(), SyncTcpServer::defaultListenPort);
watcherWidget.SetWatchedSocket(socket);
connect(socket, &SyncTcpSocket::disconnected, this, &ClientWindow::DisconnectedFromServer);
connect(socket, static_cast<void(SyncTcpSocket::*)(SyncTcpSocket::SocketError)>(&SyncTcpSocket::error),
this, &ClientWindow::DisconnectedFromServer);
connect(socket, &SyncTcpSocket::disconnected,
this, [=](){qDebug() << "client socket disconnected";});
connect(socket, static_cast<void(SyncTcpSocket::*)(SyncTcpSocket::SocketError)>(&SyncTcpSocket::error),
this, [=](QAbstractSocket::SocketError e){qDebug() << "client socket error " << e;});
connect(socket, &QObject::destroyed, this, []{qDebug() << "client socket destroyed.";});
lineEditAddress.setEnabled(false);
buttonConnect.setEnabled(false);
}
Q_SLOT void DisconnectedFromServer(){
if(socket){
socket->deleteLater();
socket= nullptr;
}
lineEditAddress.setEnabled(true);
buttonConnect.setEnabled(true);
watcherWidget.SetWatchedSocket(nullptr);
}
private:
QVBoxLayout layout{this};
QHBoxLayout hLayout;
QLineEdit lineEditAddress{"127.0.0.1"};
QPushButton buttonConnect{"Connect To Server"};
SyncSocketWatcherWidget watcherWidget;
SyncTcpSocket* socket{nullptr};
};
int main(int argc, char* argv[]){
QApplication a(argc, argv);
ServerWindow serverW;
serverW.show();
ClientWindow clientW;
clientW.show();
return a.exec();
}
#include "main.moc"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment