本文已参与「新人创作礼」活动,一起开启掘金创作之路。
TCP原理
TCP是一种面向连接的、可靠的、基于字节流的通信协议。(相比于UDP的尽最大努力交付来说,TCP相对较为可靠。此处不得不提的一点是,UDP是报文传输,而TCP是字节流传输,在实际的使用过程中注意,如果TCP协议发送过快,可能会出现粘包现象,本人就遇到过,当时需求是20ms发送一次,导致了严重的粘包现象,处理方式不难,此处只是简单举例,关于粘包问题在本博客中不详细说明,有需要了解的可以在评论中提出。)所谓面向连接,就是数据在传输前要建立连接,然后进行数据数据传输,传输完毕后还要断开连接。
客户端在收发数据前要使用 connect() 函数和服务器建立连接。建立连接的目的是保证IP地址、端口、物理链路等正确无误,为数据的传输开辟通道。套接字十一全双工方式工作的,也就是说,它可以双向传递数据。因此收发数据前需要做一些准备。下面废话不多说,直接上代码。
先说一下客户端的代码,客户端实现相对较为繁琐一点,我在客户端代码实现了心跳包、重连机制,也就是丢失服务器之后自动重连。 项目结构如下
首先在.pro文件中需要加入network,其它的不需要改动
QT += core gui network
下面是.h文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpSocket>
#include <QHostAddress>
#include <QString>
#include <QDebug>
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void iniSocket();
void connetcTcp();
private:
Ui::Widget *ui;
// 心跳定时器
QTimer *timer_heart;
// 重连定时器
QTimer *timer_reConn;
// 初始化连接定时器
QTimer *timer_iniconn;
// TCP对象
QTcpSocket *TSocket;
// 服务器IP
QString ip="127.0.0.1";
// 服务器端口
quint16 port=3080;
// 设置重连次数
int count=1;
private slots:
// 心跳数据包
void doHeart();
// 是否处于连接状态
void isConnection();
// 是否处于未连接状态
void isDisConnection();
// 失去心跳包重连
void reConnection();
void on_sendMes_clicked();
};
#endif // WIDGET_H
接下来就是客户端的cpp代码
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
iniSocket();
connect(TSocket,&QTcpSocket::readyRead,[=](){
//从通信套接字中取出内容
QByteArray array = TSocket->readAll();
// 显示到文本框内
ui->recerived->append(array);
});
}
Widget::~Widget()
{
delete ui;
}
void Widget::iniSocket()
{
qDebug()<<"初始化客户端";
timer_heart=new QTimer(this);
timer_reConn=new QTimer(this);
timer_iniconn=new QTimer(this);
TSocket=new QTcpSocket(this);
timer_heart->setInterval(15000);//设置定时器间隔15秒发送心跳包
connect(timer_heart,SIGNAL(timeout()),this,SLOT(doHeart()));
connect(TSocket,SIGNAL(connected()),this,SLOT(isConnection()));//连接
connect(TSocket,SIGNAL(disconnected()),this,SLOT(isDisConnection()));//断线
connect(timer_iniconn,&QTimer::timeout,this,&Widget::connetcTcp);//初始化客户端未获得连接时,每两秒自动连接一次
timer_iniconn->start(2000);
}
// 连接到TCP
void Widget::connetcTcp()
{
// 未连接时,每隔两秒自动向指定服务器发送连接请求
if(TSocket->state() == QAbstractSocket::UnconnectedState)
{
qDebug()<<"connect state"<<TSocket->state();
TSocket->abort();
TSocket->connectToHost(ip, port);
}
// 连接成功后关闭初始化连接定时器,后面的断线重连与此无关
else if(TSocket->state() == QAbstractSocket::ConnectedState)
{
timer_iniconn->stop();
}
}
// 发送心跳报文
void Widget::doHeart()
{
qDebug()<<"时间到,发送心跳包";
// 心跳包的格式报文
QString heart="heartPackage$";
QByteArray ba=heart.toLocal8Bit();
TSocket->write(ba);
}
// 处于连接状态
void Widget::isConnection()
{
qDebug()<<"connect sucess";
count=1;
timer_heart->start();//开始心跳
}
// 处于断开状态
void Widget::isDisConnection()
{
count=1;
qDebug()<<"*******断开*******";
timer_heart->stop();//断开连接后心跳停止
connect(timer_reConn,SIGNAL(timeout()),this,SLOT(reConnection()));
timer_reConn->start(5000);//每5秒钟自动重连
}
// 断线重连
void Widget::reConnection()
{
if(TSocket->state() == QAbstractSocket::UnconnectedState)
{
TSocket->abort();
qDebug()<<"connect tring...("<<count++<<")";
TSocket->connectToHost(ip,port);
}
else if(TSocket->state() == QAbstractSocket::ConnectedState)
{
timer_reConn->stop();//
}
}
// 发送数据函数
void Widget::on_sendMes_clicked()
{
// 读取文本框内的内容
QString content=ui->send->toPlainText();
// 转换编码格式
QByteArray cont=content.toLocal8Bit();
// 发送出去
TSocket->write(cont);
}
然后是main文件
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
然后最后是客户端项目的UI界面
下面就是服务器端的,单独的两个项目,项目结构先发一下
然后是.h文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_buttonSend_clicked();
private:
Ui::Widget *ui;
QTcpServer *tcpServer;//监听套接字
QTcpSocket *tcpSocket;//通信套接字
};
#endif // WIDGET_H
接下来就是.cpp文件
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
tcpServer = NULL;
tcpSocket = NULL;
//监听套接字 指定父对象的目的,让其自动回收空间
tcpServer = new QTcpServer(this);
// 监听端口号
tcpServer->listen(QHostAddress::Any,3080);
// 设置窗口显示标题
setWindowTitle("服务器:3080");
connect(tcpServer,&QTcpServer::newConnection,[=](){
//取出建立好连接的的套接字
tcpSocket = tcpServer->nextPendingConnection();
//获取对方的IP和端口
QString ip = tcpSocket->peerAddress().toString();
qint16 port = tcpSocket->peerPort();
QString temp = QString("[%1:%2]:成功连接").arg(ip).arg(port);
ui->textEditRead->setText(temp);
connect(tcpSocket,&QTcpSocket::readyRead,[=](){
//从通信套接字中取出内容
QByteArray array = tcpSocket->readAll();
ui->textEditRead->append(array);
});
});
}
// 析构函数
Widget::~Widget()
{
delete ui;
}
// 发送函数
void Widget::on_buttonSend_clicked()
{
if(NULL == tcpSocket)
{
return ;
}
//获取编辑区的内容
QString str = ui->textEditWrite->toPlainText();
tcpSocket->write(str.toUtf8().data());
}
然后就是main
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
最后附上服务器的UI
在博客的最后,附上客户端与服务器的交互截图 首先,第一个是客户端开启,服务端未打开的时候,一直在自动连接服务器,结果如下
然后是,服务器开启,并到15秒时,发送心跳包的结果截图
接下来是双方通信,发送消息的结果截图。
最后是服务器端断开连接,客户端显示服务器断开并开始自动重连的结果,并在服务器重新开启后连接成功。
到这里就结束了,是不是没有想象的那么难!代码已经全部附上,直接粘贴即可运行,如有问题,评论留言,我看到会给大家回复。