【嵌入式Qt开发入门】Qt如何网络编程——建立TCP通信服务端(附项目代码)

TCP 简介

        TCP 协议(Transmission Control Protocol)全称是传输控制协议是一种面向连接的、可靠的、 基于字节流的传输层通信协议。

        TCP 通信必须先建立 TCP 连接,通信端分为客户端和服务端。服务端通过监听某个端口来监听是否有客户端连接到来,如果有连接到来,则建立新的 socket 连接;客户端通过 ip 和 port 连接服务端,当成功建立连接之后,就可进行数据的收发了。需要注意的是,在 Qt 中, Qt把 socket 当成输入输出流来对待的,数据的收发是通过 read()和 write()来进行的,需要与我们常见的 send()与 recv()进行区分。

        TCP 客户端与服务端通信示意图如下。

        更多关于网络的知识可以阅读网络专栏。

TCP 服务端应用实例

        本例目的:了解 TCP 服务端的使用。

        项目名称:tcpserver

       流程:首先获取本地 IP 地址。创建一个 tcpSocket 套接字,一个 tcpServer 服务端。点击监听即监听本地的主机 IP 地址和端口,同时等待服务端的连接。此程序需要结合客户端一起使用。

        项目文件tcpserver.pro 文件第一行添加的代码部分如下。

QT +=     core gui network

        在头文件“mainwindow.h”具体代码如下。

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTextBrowser>
#include <QLabel>
#include <QComboBox>
#include <QSpinBox>
#include <QHostInfo>
#include <QLineEdit>
#include <QNetworkInterface>
#include <QDebug>class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private:/* tcp服务器 */QTcpServer *tcpServer;/* 通信套接字 */QTcpSocket *tcpSocket;/* 按钮 */QPushButton *pushButton[4];/* 标签文本 */QLabel *label[2];/* 水平容器 */QWidget *hWidget[3];/* 水平布局 */QHBoxLayout *hBoxLayout[3];/* 垂直容器 */QWidget *vWidget;/* 垂直布局 */QVBoxLayout *vBoxLayout;/* 文本浏览框 */QTextBrowser *textBrowser;/* 用于显示本地ip */QComboBox *comboBox;/* 用于选择端口 */QSpinBox  *spinBox;/* 文本输入框 */QLineEdit *lineEdit;/* 存储本地的ip列表地址 */QList<QHostAddress> IPlist;/* 获取本地的所有ip */void getLocalHostIP();private slots:/* 客户端连接处理槽函数 */void clientConnected();/* 开始监听槽函数 */void startListen();/* 停止监听槽函数 */void stopListen();/* 清除文本框时的内容 */void clearTextBrowser();/* 接收到消息 */void receiveMessages();/* 发送消息 */void sendMessages();/* 连接状态改变槽函数 */void socketStateChange(QAbstractSocket::SocketState);
};
#endif // MAINWINDOW_H

        头文件里主要是声明界面用的元素,及一些槽函数。重点是声明 tcpServer 和 tcpSocket。

        在源文件“mainwindow.cpp”具体代码如下。

#include "mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent)
{/* 设置主窗体的位置与大小 */this->setGeometry(0, 0, 800, 480);/* 实例化tcp服务器与tcp套接字 */tcpServer = new QTcpServer(this);tcpSocket = new QTcpSocket(this);/* 开始监听按钮 */pushButton[0] = new QPushButton();/* 停止监听按钮 */pushButton[1] = new QPushButton();/* 清空聊天文本按钮 */pushButton[2] = new QPushButton();/* 发送消息按钮 */pushButton[3] = new QPushButton();/* 水平布局一 */hBoxLayout[0] = new QHBoxLayout();/* 水平布局二 */hBoxLayout[1] = new QHBoxLayout();/* 水平布局三 */hBoxLayout[2] = new QHBoxLayout();/* 水平布局四 */hBoxLayout[3] = new QHBoxLayout();/* 水平容器一 */hWidget[0] =  new QWidget();/* 水平容器二 */hWidget[1] =  new QWidget();/* 水平容器三 */hWidget[2] =  new QWidget();vWidget = new QWidget();vBoxLayout = new QVBoxLayout();/* 标签实例化 */label[0] = new QLabel();label[1] = new QLabel();lineEdit = new QLineEdit();comboBox = new QComboBox();spinBox = new QSpinBox();textBrowser = new QTextBrowser();label[0]->setText("监听IP地址:");label[1]->setText("监听端口:");/* 设置标签根据文本文字大小自适应大小  */label[0]->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);label[1]->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);/* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */spinBox->setRange(10000, 99999);pushButton[0]->setText("开始监听");pushButton[1]->setText("停止监听");pushButton[2]->setText("清空文本");pushButton[3]->setText("发送消息");/* 设置停止监听状态不可用 */pushButton[1]->setEnabled(false);/* 设置输入框默认的文本 */lineEdit->setText("www.openedv.com正点原子论坛");/* 水平布局一添加内容 */hBoxLayout[0]->addWidget(pushButton[0]);hBoxLayout[0]->addWidget(pushButton[1]);hBoxLayout[0]->addWidget(pushButton[2]);/* 设置水平容器一的布局为水平布局一 */hWidget[0]->setLayout(hBoxLayout[0]);/* 水平布局二添加内容 */hBoxLayout[1]->addWidget(label[0]);hBoxLayout[1]->addWidget(comboBox);hBoxLayout[1]->addWidget(label[1]);hBoxLayout[1]->addWidget(spinBox);/* 设置水平容器二的布局为水平布局二 */hWidget[1]->setLayout(hBoxLayout[1]);/* 水平布局三添加内容 */hBoxLayout[2]->addWidget(lineEdit);hBoxLayout[2]->addWidget(pushButton[3]);/* 设置水平容器三的布局为水平布局一 */hWidget[2]->setLayout(hBoxLayout[2]);/* 垂直布局添加内容 */vBoxLayout->addWidget(textBrowser);vBoxLayout->addWidget(hWidget[1]);vBoxLayout->addWidget(hWidget[0]);vBoxLayout->addWidget(hWidget[2]);/* 设置垂直容器的布局为垂直布局 */vWidget->setLayout(vBoxLayout);/* 居中显示 */setCentralWidget(vWidget);/* 获取本地ip */getLocalHostIP();/* 信号槽连接 */connect(pushButton[0], SIGNAL(clicked()),this, SLOT(startListen()));connect(pushButton[1], SIGNAL(clicked()),this, SLOT(stopListen()));connect(pushButton[2], SIGNAL(clicked()),this, SLOT(clearTextBrowser()));connect(pushButton[3], SIGNAL(clicked()),this, SLOT(sendMessages()));connect(tcpServer, SIGNAL(newConnection()),this, SLOT(clientConnected()));
}MainWindow::~MainWindow()
{
}/* 新的客户端连接 */
void MainWindow::clientConnected()
{/* 获取客户端的套接字 */tcpSocket = tcpServer->nextPendingConnection();/* 客户端的ip信息 */QString ip = tcpSocket->peerAddress().toString();/* 客户端的端口信息 */quint16 port = tcpSocket->peerPort();/* 在文本浏览框里显示出客户端的连接信息 */textBrowser->append("客户端已连接");textBrowser->append("客户端ip地址:"+ ip);textBrowser->append("客户端端口:"+ QString::number(port));connect(tcpSocket, SIGNAL(readyRead()),this, SLOT(receiveMessages()));connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(socketStateChange(QAbstractSocket::SocketState)));
}/* 获取本地IP */
void MainWindow::getLocalHostIP()
{// /* 获取主机的名称 */// QString hostName = QHostInfo::localHostName();// /* 主机的信息 */// QHostInfo hostInfo = QHostInfo::fromName(hostName);// /* ip列表,addresses返回ip地址列表,注意主机应能从路由器获取到// * IP,否则可能返回空的列表(ubuntu用此方法只能获取到环回IP) */// IPlist = hostInfo.addresses();// qDebug()<<IPlist<<endl;// /* 遍历IPlist */// foreach (QHostAddress ip, IPlist) {//      if (ip.protocol() == QAbstractSocket::IPv4Protocol)//          comboBox->addItem(ip.toString());//    }/* 获取所有的网络接口,* QNetworkInterface类提供主机的IP地址和网络接口的列表 */QList<QNetworkInterface> list= QNetworkInterface::allInterfaces();/* 遍历list */foreach (QNetworkInterface interface, list) {/* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */QList<QNetworkAddressEntry> entryList= interface.addressEntries();/* 遍历entryList */foreach (QNetworkAddressEntry entry, entryList) {/* 过滤IPv6地址,只留下IPv4 */if (entry.ip().protocol() ==QAbstractSocket::IPv4Protocol) {comboBox->addItem(entry.ip().toString());/* 添加到IP列表中 */IPlist<<entry.ip();}}}
}/* 开始监听 */
void MainWindow::startListen()
{/* 需要判断当前主机是否有IP项 */if (comboBox->currentIndex() != -1) {qDebug()<<"start listen"<<endl;tcpServer->listen(IPlist[comboBox->currentIndex()],spinBox->value());/* 设置按钮与下拉列表框的状态 */pushButton[0]->setEnabled(false);pushButton[1]->setEnabled(true);comboBox->setEnabled(false);spinBox->setEnabled(false);/* 在文本浏览框里显示出服务端 */textBrowser->append("服务器IP地址:"+ comboBox->currentText());textBrowser->append("正在监听端口:"+ spinBox->text());}
}/* 停止监听 */
void MainWindow::stopListen()
{qDebug()<<"stop listen"<<endl;/* 停止监听 */tcpServer->close();/* 如果是连接上了也应该断开,如果不断开客户端还能继续发送信息,* 因为socket未断开,还在监听上一次端口 */if (tcpSocket->state() == tcpSocket->ConnectedState)tcpSocket->disconnectFromHost();/* 设置按钮与下拉列表框的状态  */pushButton[1]->setEnabled(false);pushButton[0]->setEnabled(true);comboBox->setEnabled(true);spinBox->setEnabled(true);/* 将停止监听的信息添加到文本浏览框中 */textBrowser->append("已停止监听端口:"+ spinBox->text());
}/* 清除文本浏览框里的内容 */
void MainWindow::clearTextBrowser()
{/* 清除文本浏览器的内容 */textBrowser->clear();
}/* 服务端接收消息 */
void MainWindow::receiveMessages()
{/* 读取接收到的消息 */QString messages = "客户端:" + tcpSocket->readAll();textBrowser->append(messages);
}/* 服务端发送消息 */
void MainWindow::sendMessages()
{if(NULL == tcpSocket)return;/* 如果已经连接 */if(tcpSocket->state() == tcpSocket->ConnectedState) {/* 发送消息 */tcpSocket->write(lineEdit->text().toUtf8().data());/* 在服务端插入发送的消息 */textBrowser->append("服务端:" + lineEdit->text());}
}/* 服务端状态改变 */
void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
{switch (state) {case QAbstractSocket::UnconnectedState:textBrowser->append("scoket状态:UnconnectedState");break;case QAbstractSocket::ConnectedState:textBrowser->append("scoket状态:ConnectedState");break;case QAbstractSocket::ConnectingState:textBrowser->append("scoket状态:ConnectingState");break;case QAbstractSocket::HostLookupState:textBrowser->append("scoket状态:HostLookupState");break;case QAbstractSocket::ClosingState:textBrowser->append("scoket状态:ClosingState");break;case QAbstractSocket::ListeningState:textBrowser->append("scoket状态:ListeningState");break;case QAbstractSocket::BoundState:textBrowser->append("scoket状态:BoundState");break;default:break;}
}

        上面的代码主要是服务端开启监听,如果有客户端连到服务端,就会发射 newConnection() 信号,同时也连接到接收消息的信号与槽函数。点击发送消息按钮就可以使用 tcpSocket 发送消 息。注意发送消息和接收消息都是通过 tcpSocket 的 read()和 write()进行。


        限于篇幅,tcp客户端的编写放在下篇

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/22921.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java正则表达式MatchResult的接口、Pattern类、Matcher类

Java正则表达式MatchResult的接口 java.util.regex.MatchResult接口表示匹配操作的结果。 此接口包含用于确定与正则表达式匹配的结果的查询方法。可以看到匹配边界&#xff0c;组和组边界&#xff0c;但不能通过MatchResult进行修改。 接口声明 以下是java.util.regex.Matc…

Flask结合gunicorn和nginx反向代理的生产环境部署及踩坑记录

个人博客&#xff1a;https://xzajyjs.cn 前言 之前自己写的flask使用gunicorn上线生产环境没有什么问题&#xff0c;但是最近搭建了一个现成的flask项目&#xff0c;当使用python直接运行时不会有问题&#xff0c;而使用gunicorn时则会出现一些问题。 部署过程 运行测试 这…

XUbuntu22.04之解决蓝牙鼠标不停掉线问题(追凶过程)(一百八十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

JDY-31 蓝牙传输模块的使用、调试和传输

JDY-31 蓝牙基于蓝牙 3.0 SPP 设计&#xff0c;这样可以支持 Windows、Linux、android 数据透传&#xff0c; 工作频段 2.4GHZ&#xff0c;调制方式 GFSK&#xff0c;最大发射功率 8db&#xff0c;最大发射距离 30 米&#xff0c;支持用户通过 AT 命令修改设备名、波特率等指令…

2023年Mac上有哪些优质的工具(一)

Downie 4 专门用来下载视频的&#xff0c;各大视频网站上的视频只要粘贴地址就可以直接下载&#xff0c;并且可以选择清晰度&#xff0c;再也不用受百度云的气了&#xff0c;下载速度非常快&#xff0c;让工作效率大大提高。 Xmind 大家熟知的一款思维导图软件&#xff0c;他…

亚马逊云科技,加速生成式AI的落地

编辑&#xff1a;阿冒 设计&#xff1a;沐由 “展望今天的世界&#xff0c;在机遇之外&#xff0c;更多事后我们看到的是前所未有的巨大挑战。事实证明&#xff0c;惟有通过创新、专注创新&#xff0c;方能挖掘和发现更多的增长机会。” 在2023亚马逊云科技中国峰会的第二天&am…

Mysql 数据库开发及企业级应用

文章目录 1、Mysql 数据库开发及企业级应用1.1、为什么要使用数据库1.1.1、数据库概念&#xff08;Database&#xff09;1.1.2、为什么需要数据库 1.2、程序员为什么要学习数据库1.3、数据库的选择1.3.1、主流数据库简介1.3.2、使用 MySQL 的优势1.3.3、版本选择 1.4、Windows …

万字长文 | Hadoop 上云: 存算分离架构设计与迁移实践

一面数据原有的技术架构是在线下机房中使用 CDH 构建的大数据集群。自公司成立以来&#xff0c;每年都保持着高速增长&#xff0c;业务的增长带来了数据量的剧增。 在过去几年中&#xff0c;我们按照每 1 到 2 年的规划扩容硬件&#xff0c;但往往在半年之后就不得不再次扩容。…

构建新型电力系统,需要什么样的数字能源底座?

在迈向“碳中和”的进程中&#xff0c;全球能源产业变革已经进入了关键时期&#xff0c;其中电力系统的价值从未像今天这样重要。 国际能源署数据显示&#xff0c;2022年全球与能源相关的二氧化碳排放量超过368亿吨&#xff0c;创下历史新高。其中&#xff0c;电力领域碳排放占…

内网环境使用docker部署微服务系统记录

背景 内网环境部署一套微服务应用系统&#xff0c;采用docker方式部署。包括mysql、redis、nginx、nacos、gateway以及应用程序的jar包。下面记录部署的过程和遇到的问题。 一、内网dockcer部署mysql服务 内网生成mysql镜像 在一个可以连接外网的环境中&#xff0c;下载mys…

基于51单片机的红外测温系统的设计与实现

功能框图 功能描述 本设计以STC89C52单片机为核心控制器&#xff0c;加上其他的模块一起组成非接触人体红外测温的整个系统&#xff0c;其中包含中控部分、输入部分和输出部分。中控部分采用了STC89C52单片机&#xff0c;其主要作用是获取输入部分数据&#xff0c;经过内部处理…

PostgreSQL的进程架构和内存架构

文章首发地址 PostgreSQL的进程架构 PostgreSQL的进程架构是由多个进程组成的&#xff0c;每个进程都有不同的作用和职责。下面是PostgreSQL的进程架构的详细说明&#xff1a; 后台进程(Postmaster) 后台进程是PostgreSQL启动时创建的第一个进程&#xff0c;它负责管理和控…