# QT 的 socket 服务端连接
对于一个联网的设备,socket 长连接再熟悉不过了。那么下位机的 demo 待我整理后给大家分享出来。
# 环境:
- 下位机:
- GD32F303(工作中用的)
- CAT14G 模块配合工作使用
- CAT1 配置为 socket 连接
- ESP32S3(个人的小爱好,做了一个小手表)
- wifi 连接,http 配网
- socket 连接
- GD32F303(工作中用的)
- 上位机:
- QT(平时改动比较大,所以界面很丑,大家不要介意哈)
- 串口连接
- socket 连接
那么我们开始吧
# 1、下位机
# GD32F303+CAT1 模组
请参考:CAT1 模组 AT 指令自动配置
通过AT指令配置CAT1 4G模组
https://zhuanlan.zhihu.com/p/646779417
# ESP32S3
后期整理好啦再更新到这
# 2、上位机
# 首先你需要有这个界面
<img src="../../../img.assets/23-8-7QTSocket%E8%BF%9E%E6%8E%A5.assets/image-20230802093630566.png" alt="image-20230802093630566" style="zoom:50%;" />
# 本地 IP 地址
用于 socket 连接
# 本地端口
用于 socket 连接
# 侦听按钮
触发侦听事件
# 消息发送接收
那么开始愉快的码代码吧!
项目中的.pro 文件中需要添加这个哦
QT += network
# 初始化 socke
//头文件要加上这三个
#include <QTcpserver>
#include <QTcpSocket>
#include <QNetworkInterface>
void MainWindow::vsocketserverInit(void){
//客户端
socket = new QTcpSocket();
//服务端
server = new QTcpServer();
//UDP可以广播的,实际没有使用
m_pUdpSocket = new QUdpSocket();
// 获取本地的IP
for (int i = 0; i < QNetworkInterface().allAddresses().length(); ++i) {
ui->comboBoxData_Socket_Server_IP->addItem(QNetworkInterface().allAddresses().at(i).toString());
}
ui->comboBoxData_Socket_Server_IP->setCurrentText("127.0.0.1");
ui->comboBoxData_Socket_Server_COM->addItem("8090");
ui->comboBoxData_Socket_Server_Select_Mode->addItem("指定模式");
ui->comboBoxData_Socket_Server_Select_Mode->addItem("广播模式");
QPalette pe;
pe.setColor(QPalette::WindowText, Qt::blue);
ui->label_Socket_Server_Status->setPalette(pe);
ui->label_Socket_Server_Status->setText("服务器未打开");
// 关联客户端连接信号newConnection
connect(server, &QTcpServer::newConnection, this, &MainWindow::server_New_Connect);
//创建一个查询的定时器
GetControllerID_Timer = new QTimer;
connect(GetControllerID_Timer, &QTimer::timeout,this,&MainWindow::vsocketGetControllerID_Send_timer);
GetControllerID_Timer->stop();
}
# 侦听按键
void MainWindow::on_pushButton_Socket_Server_Connect_clicked()
{
if (ui->pushButton_Socket_Server_Connect->text() == tr("侦听")) {
// 从输入端获取端口号
int port = ui->comboBoxData_Socket_Server_COM->currentText().toInt();
// 侦听指定的端口
if(!server->listen(QHostAddress::Any, port)) {
// 若出错,则输出错误信息
QMessageBox::information(this, tr("错误"), server->errorString(), QMessageBox::Yes);
return;
}
else {
// 修改按键文字
ui->pushButton_Socket_Server_Connect->setText("取消侦听");
QPalette pe;
pe.setColor(QPalette::WindowText, Qt::red);
ui->label_Socket_Server_Status->setPalette(pe);
ui->label_Socket_Server_Status->setText("服务器运行中...");
}
//-----------------------------启动FRPS-----------------------------
//这里我是自己搭建了内网穿透,将本地端口映射到服务器的公网上,相当于可以联网控制了
bfrpsstart(true);
}
else {
// 如果正在连接......
if(socket->state() == QAbstractSocket::ConnectedState) {
// 关闭连接
socket->disconnectFromHost();
}
// 取消侦听
server->close();
// 修改按键文字
ui->pushButton_Socket_Server_Connect->setText("侦听");
QPalette pe;
pe.setColor(QPalette::WindowText, Qt::blue);
ui->label_Socket_Server_Status->setPalette(pe);
ui->label_Socket_Server_Status->setText("服务器未打开");
ui->tableWidget_Socket_Server->clear();
//-----------------------------关闭FRPS-----------------------------
bfrpsstart(false);
}
}
# 新客户端连接
//连接
void MainWindow::server_New_Connect()
{
// 获取客户端连接
socket = server->nextPendingConnection();
clientSocket.append(socket);
// 把连接到的客户端添加入tableWidget中
int currentRow = ui->tableWidget_Socket_Server->rowCount();
ui->tableWidget_Socket_Server->insertRow(currentRow);
QTableWidgetItem *item = new QTableWidgetItem();
QTableWidgetItem *item_2 = new QTableWidgetItem();
QTableWidgetItem *item_3 = new QTableWidgetItem();
QTableWidgetItem *item_4 = new QTableWidgetItem();
QTableWidgetItem *item_5 = new QTableWidgetItem();
item->setText(tr("%1").arg(QString::number(ui->tableWidget_Socket_Server->rowCount())));
item_2->setText(clientSocket[currentRow]->peerAddress().toString().mid(7));
item_3->setText(QString::number(clientSocket[currentRow]->peerPort()));
item_4->setText("00000000");
item_5->setText("在线");
ui->tableWidget_Socket_Server->setItem(currentRow, 0, item);
ui->tableWidget_Socket_Server->setItem(currentRow, 1, item_2);
ui->tableWidget_Socket_Server->setItem(currentRow, 2, item_3);
ui->tableWidget_Socket_Server->setItem(currentRow, 3, item_4);
ui->tableWidget_Socket_Server->setItem(currentRow, 4, item_5);
// 连接QTcpSocket的信号槽,以读取新数据
connect(socket, SIGNAL(readyRead()), this, SLOT(Socket_Server_Recieve_Data()));
connect(socket, SIGNAL(disconnected()), this, SLOT(Socket_Server_Dis_Connected()));
//--------------------------根据自己的需求添加------------------------------
//连接上之后发配置时间,我这里是业务需要
QString checktimedata = "000000000000000000000000";
checktimedata.append(checktime());
checktimedata.append(qstrCrcCalc(checktimedata));
checktimedata.append("FE");
checktimedata.insert(0,"FB");
vSocket_Server_Send(checktimedata);
//----------------------------------------------------------------------
}
//取消连接
void MainWindow::Socket_Server_Dis_Connected()
{
// 遍历寻找断开连接的是哪一个客户端
for(int i = 0; i < clientSocket.length(); ++i) {
//判断连接状态
if(clientSocket[i]->state() == QAbstractSocket::UnconnectedState)
{
// 删除存储在tableWidget中的该客户端信息
for (int j = 0; j < ui->tableWidget_Socket_Server->rowCount(); ++j) {
//判断IP是否和显示数据的IP一致
if (clientSocket[i]->peerAddress().toString().mid(7) == ui->tableWidget_Socket_Server->item(j, 1)->text()) {
//判断端口是否一致,一致说明断开连接的就是这个客户端
QString socketport = QString::number(clientSocket[i]->peerPort());
if(socketport==ui->tableWidget_Socket_Server->item(j, 2)->text()){
ui->tableWidget_Socket_Server->removeRow(j);
}
}
}
// 删除存储在clientSocket列表中的客户端信息
clientSocket[i]->destroyed();
clientSocket.removeAt(i);
}
}
}
# 处理发送
//发送,我这里有自己的业务需求,根据控制器的ID来找到所对应的IP和端口,去发送
void MainWindow::vSocket_Server_Send_Flechazo(QString sendstr)
{
QString data = sendstr;
if (data.isEmpty()) {
QMessageBox::information(this, "提示", "请输入发送内容!", QMessageBox::Yes);
}
else {
if(ui->comboBoxData_Socket_Server_Select_Mode->currentText()=="指定模式"){
//获取控制器ID
QString ControllerID = ui->comboBox_ControlID->currentText();
//找控制器对应的端口
QString socketPort="";
QString socketIP="";
for(int i=0;i<ui->tableWidget_Socket_Server->rowCount();i++){
if(ControllerID == ui->tableWidget_Socket_Server->item(i, 3)->text()){
socketPort = ui->tableWidget_Socket_Server->item(i, 2)->text();
socketIP = ui->tableWidget_Socket_Server->item(i, 1)->text();
break;
}
}
if(socketPort==""){
QMessageBox::information(this, tr("提示消息"), tr("没有找到该控制器对应的socket端口!"), QMessageBox::Ok);
return;
}
//拿到端口后去找,因为服务器分配的端口是唯一的,所以就不判断IP啦,懒😎
for (int i = 0; i < clientSocket.length(); ++i) {
if (QString::number(clientSocket[i]->peerPort()) == socketPort) {
//以ASCII码形式发送文本框内容
clientSocket[i]->write(qstring_to_qbytearray(data));
//显示数据流
QString timeStrLine="["+QDateTime::currentDateTime().toString("hh:mm:ss")+"][发送]:🚀 ";
QString content_l = "<span style=\" color:red;\">"+timeStrLine +data+"\n\r</span>";
ui->textBrowser->append(content_l);
}
}
}else if(ui->comboBoxData_Socket_Server_Select_Mode->currentText()=="广播模式"){
for (int i = 0; i < clientSocket.length(); ++i) {
//以ASCII码形式发送文本框内容
clientSocket[i]->write(qstring_to_qbytearray(data));
}
}
}
}
# 处理接收
//接收
void MainWindow::Socket_Server_Recieve_Data()
{
// 由于readyRead信号并未提供SocketDecriptor,所以需要遍历所有客户端
for (int i = 0; i < clientSocket.length(); ++i) {
// 读取缓冲区数据
QByteArray buffer = clientSocket[i]->readAll();
if(buffer.isEmpty()) {
continue;
}
static QString IP_Port, IP_Port_Pre;
IP_Port = tr("[%1:%2]:").arg(clientSocket[i]->peerAddress().toString().mid(7)).arg(clientSocket[i]->peerPort());
// 若此次消息的地址与上次不同,则需显示此次消息的客户端地址
if (IP_Port != IP_Port_Pre) {
ui->textEdit_Socket_Server_RecvData->append(IP_Port);
}
//---------------------根据需要来处理接收-------------------------------
//buffer
//---------------------根据需要来处理接收-------------------------------
// 更新ip_port
IP_Port_Pre = IP_Port;
}
}
那么到这基本就结束了!
有了 socket,自己的小产品才有了灵魂好吧。随时随地,都可以远程控制它了!
# 关于 frp 内网穿透
教程请看云服务器搭建内网穿透
云服务器搭建Frps实现内网穿透
https://zhuanlan.zhihu.com/p/635462920
QT 对应的部分后期会整理成《QT 一键启动 frpc 内网穿透》