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

news2024/11/23 7:32:19

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_OBJECT

public:
    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.coloradmin.cn/o/748617.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

7.kafka+ELK连接

文章目录 kafkaELK连接部署Kafkakafka操作命令kafka架构深入FilebeatKafkaELK连接 kafkaELK连接 部署Kafka ###关闭防火墙systemctl stop firewalld systemctl disable firewalldsetenforce 0vim /etc/selinux/configSELINUXdisabled###下载安装包官方下载地址&#xff1a;ht…

python散记

"""字符串格式化的两种方法"""name"sans" age18 math_score90.56 english_score88.8print(f"这个学生的名字叫{name},年龄{age},数学分数是{math_score},总分是{math_scoreenglish_score}") print("这个学生的名字叫%s…

APP开发的未来:虚拟现实和增强现实的角色

移动应用程序越来越多地在我们的日常生活中发挥着重要作用。但是&#xff0c;随着技术的不断发展&#xff0c;未来的 APP开发会有什么新的发展方向呢&#xff1f;这是每个人都在关心的问题。在过去的几年中&#xff0c;移动应用程序领域发生了巨大变化。像 VR/AR这样的技术为人…

第63讲:Python编程案例之猴子吃桃

文章目录 1.需求描述以及分析2.递推方式实该该程序3.递归方式实现该程序 1.需求描述以及分析 需求描述&#xff1a; 猴子第一天摘了若干个桃子&#xff0c;第一天吃了若干个桃子中的一半&#xff0c;觉得不过瘾&#xff0c;又多吃了一个。 第二天早上又将第一天剩下的桃子吃…

Spring 项目创建和使用2 (Bean对象的存取)

目录 一、创建 Bean 对象 二、将Bean对象存储到 Spring容器中 三、创建 Spring 上下文&#xff08;得到一个Spring容器&#xff09; 1. 通过在启动类中 ApplicationContext 获取一个 Spring容器 2. 通过在启动类种使用 BeanFactory 的方式来得到 Spring 对象 &#xff08;此…

MAYA粒子目标goalV和goalU详细应用

一下就填充到点 一个一个点填充 nParticleShape1.goalV0.5; nParticleShape1.goalU0.5; 粒子向中心移动 V方向使用渐变 删除U方向表达式 也使用渐变 使用圆角 nParticleShape1.goalUrand(0,1); nParticleShape1.goalUnParticleShape1.goalU0.02; nParticleShape1.goalUnPartic…

Excel-公式VLOOKUP 使用方法-小记

个人愚见 表示 MongoDB列中的任意一条数据 在 MySQL列 精确查找 和MongoDB列 中一模一样的数据&#xff0c;有的话返回MongoDB列数据&#xff0c;没有话返回#N/A 官方解释

redis 三种缓存更新策略

今天聊聊redis 三种缓存更新策略分别是&#xff1a; Cache Aside&#xff08;旁路缓存&#xff09;策略&#xff1b; Read/Write Through&#xff08;读穿 / 写穿&#xff09;策略&#xff1b; Write Back&#xff08;写回&#xff09;策略&#xff1b; 其中 Cache Aside策略…

php通过IP获取用户当前所在城市

php获取当前用户所在城市 php通过ip免申请api获取所在城市的代码包括省市区sql数据 <?php function getName($pinyin,$lv){$servername "localhost";$username "root";$password "root";$dbname "ttx";try {$conn new PDO(…

Blazor前后端框架Known-V1.2.4

V1.2.4 Known是基于C#和Blazor开发的前后端分离快速开发框架&#xff0c;开箱即用&#xff0c;跨平台&#xff0c;一处代码&#xff0c;多处运行。 Gitee&#xff1a; https://gitee.com/known/KnownGithub&#xff1a;https://github.com/known/Known 概述 基于C#和Blazor…

一款开源的Hitomi-Downloader视频下载工具,几乎支持所有主流视频网站

一款开源的Hitomi-Downloader视频下载工具&#xff0c;几乎支持所有主流视频网站 用过IDM的朋友可能知道IDM有个强大的功能就是可以嗅探网站各种视频、音频等资源&#xff0c;然后提供快捷下载&#xff0c;可不巧的是IDM是收费软件。对于不愿意付费购买IDM的朋友&#xff0c;能…

AssetBundle.Unload(true)无法卸载图集

1&#xff09;AssetBundle.Unload(true)无法卸载图集 ​2&#xff09;关于Unity 2D游戏地图预加载的问题 3&#xff09;Addressables能否支持某些资源不打Bundle直接加载源文件 这是第342篇UWA技术知识分享的推送&#xff0c;精选了UWA社区的热门话题&#xff0c;涵盖了UWA问答…

如何在CSDN博客平台上吸引铁粉

&#x1f3c6;荣誉认证&#xff1a;51CTO博客专家博主、TOP红人、明日之星&#xff1b;阿里云开发者社区专家博主、技术博主、星级博主。 &#x1f4bb;微信公众号&#xff1a;微笑的段嘉许 &#x1f4cc;本文由微笑的段嘉许原创&#xff01; &#x1f389;欢迎关注&#x1f5…

照片文件大小怎么修改为200k?图片压缩怎么指定大小?

日常生活和工作中&#xff0c;经常用到图片&#xff0c;但是有时候需要将图片压缩指定大小来符合各种规定&#xff0c;比如图片压缩到200kb&#xff0c;那么有没有简单方便的图片压缩&#xff08; https://www.yasuotu.com/imagesize&#xff09;的方法呢&#xff1f;下面就拿压…

Centos 7 安装 Oracle 11G

Oracle 11G 安装教程 准备环境 p13390677_112040_Linux-x86-64_1of7.zipp13390677_112040_Linux-x86-64_2of7.zipCentos 7- rhel7-英文版的系统–不想换语言的执行(LANGen_US)– 传输 文件到服务器上 创建用户和组 [rootlocalhost ~]# groupadd oracle [rootlocalhost ~]…

用cmd命令刷新dns缓存

DNS flush with “ipconfig /flushdns” To flush the DNS via CMD, use the command ipconfig. To do this, enter the following command and confirm it with enter: ipconfig /flushdns After successful execution, you will receive a message that the DNS resolution…

【Go语言开发】将logrus日志送到elasticsearch构成elk体系

写在前面 这篇文章我们来讲讲怎么把logrus日志送到es。 使用的日志库是 github.com/sirupsen/logrus&#xff0c;由于这个包中的日志对象是可以接入很多个hook的&#xff0c;所以我们可以使用hook来接入 elasticsearch 来操作 。 hook 就是钩子&#xff0c;当设置hook在某个点…

第一章 SegFormer(语义分割篇)——SegFormer: 简单高效的基于Transformer的语义分割设计

0.摘要 我们提出了SegFormer&#xff0c;这是一个简单、高效且强大的语义分割框架&#xff0c;它将Transformer与轻量级多层感知机&#xff08;MLP&#xff09;解码器结合在一起。 SegFormer具有两个吸引人的特点&#xff1a; 1&#xff09;SegFormer包含一个新颖的层次结构的…

【C语言】猜数字游戏

问题描述 猜数字游戏是令游戏机随机产生一个100以内的正整数&#xff0c;用户输入一个数对其进行猜测&#xff0c;需要你编写程序自动对其与随机产生的被猜数进行比较&#xff0c;并提示大了&#xff08;“Too big”&#xff09;&#xff0c;还是小了&#xff08;“Too small”…

springboot项目target下面没有mapper.xml文件

文件结构是这个样子,mapper.xml文件在resources/mappers/fdms目录下面 通常来说, 将mapper打包到target目录下只需要在maven下面配置 <resources><resource><directory>src/main/resources</directory><filtering>true</filtering><inc…