qt的udp的初步使用

久龄 久龄     2022-09-05     601

关键词:

该程序实现的功能是:局域网内,每个用户登录到聊天软件,则软件界面的右端可以显示在线用户列表,分别显示的是用户名,主机名,ip地址。软件左边那大块是聊天内容显示界面,这里局域网相当于qq中的qq群,即群聊。每个人可以在聊天输入界面中输入文字并发送。其聊天界面如下:

    

     该程序实现的是每个用户登录既是客户端又是服务器端,这就需要看你站在哪个角度看问题了。简单的说,当用户发送信息给别人时就是客户端,当接收别人的信息是就可以看做是服务器端。

  下面分服务器端和客户端2部分来介绍。

  服务器端:建立一个UDP Socket并绑定在固定端口后,用信号与槽的方式进行监听是否有数据来临。如果用,接收其数据并分析数据的消息类型,如果消息是新用户登录则更新用户列表并在聊天显示窗口中添加新用户上线通知;同理,如果是用户下线,则在用户列表中删除该用户且在聊天显示窗口中显示下线通知;如果是聊天消息,则接收该消息并且在窗口中显示。其流程图如下:

    

  客户端:首先当客户端登录时,获取本机的用户名,计算机名和ip地址,并广播给局域网的服务器更新用户列表。然后当客户端需要发送信息时,则在聊天输入栏中输入信息并按发送键发送聊天内容,当然于此同时也广播本地系统的各种信息。其流程图如下:

   

  程序主要代码和注释如下:

widget.h:

复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
class QUdpSocket;

namespace Ui {
class Widget;
}

// 枚举变量标志信息的类型,分别为消息,新用户加入,用户退出,文件名,拒绝接受文件
enum MessageType{Message, NewParticipant, ParticipantLeft, FileName, Refuse};


class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

protected:
    void newParticipant(QString userName,
                        QString localHostName, QString ipAddress);
    void participantLeft(QString userName,
                         QString localHostName, QString time);
    void sendMessage(MessageType type, QString serverAddress="");

    QString getIP();
    QString getUserName();
    QString getMessage();

private:
    Ui::Widget *ui;
    QUdpSocket *udpSocket;
    qint16 port;

private slots:
    void processPendingDatagrams();

    void on_sendButton_clicked();
};

#endif // WIDGET_H
复制代码

widget.cpp:

复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QUdpSocket>
#include <QHostInfo>
#include <QMessageBox>
#include <QScrollBar>
#include <QDateTime>
#include <QNetworkInterface>
#include <QProcess>


Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    udpSocket = new QUdpSocket(this);//创建一个QUdpSocket类对象,该类提供了Udp的许多相关操作
    port = 45454;
    //此处的bind是个重载函数,连接本机的port端口,采用ShareAddress模式(即允许其它的服务连接到相同的地址和端口,特别是
    //用在多客户端监听同一个服务器端口等时特别有效),和ReuseAddressHint模式(重新连接服务器)
    udpSocket->bind(port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
    //readyRead()信号是每当有新的数据来临时就被触发
    connect(udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));
    sendMessage(NewParticipant);//打开软件时就向外发射本地信息,让其他在线用户得到通知
}

Widget::~Widget()
{
    delete ui;
}

// 使用UDP广播发送信息,MessageType是指头文件中的枚举数据类型
//sendMessage即把本机的主机名,用户名+(消息内容后ip地址)广播出去
void Widget::sendMessage(MessageType type, QString serverAddress)
{
    QByteArray data;    //字节数组
    //QDataStream类是将序列化的二进制数据送到io设备,因为其属性为只写
    QDataStream out(&data, QIODevice::WriteOnly);
    QString localHostName = QHostInfo::localHostName();//返回主机名,QHostInfo包含了一些关于主机的静态函数
    QString address = getIP();    //调用自己类中的getIP()函数
    //将type,getUserName(),localHostName按照先后顺序送到out数据流中,消息类型type在最前面
    out << type << getUserName() << localHostName;

    switch(type)
    {
    case Message :
        if (ui->messageTextEdit->toPlainText() == "") {    //将输入框里的文字转化成纯文本发送
            //当发送的文本为空时创建一个警告信息窗口,tr函数为译本函数,即译码后面的text内容
            QMessageBox::warning(0,tr("警告"),tr("发送内容不能为空"),QMessageBox::Ok);    
            return;
        }
        out << address << getMessage();//将ip地址和得到的消息内容输入out数据流
        ui->messageBrowser->verticalScrollBar()    //返回垂直条
                ->setValue(ui->messageBrowser->verticalScrollBar()->maximum());//设置垂直滑动条的最大值
        break;

    case NewParticipant :
        out << address;    //为什么此时只是输出地址这一项呢?因为此时不需要传递聊天内容
        break;

    case ParticipantLeft :
        break;

    case FileName :
        break;

    case Refuse :
        break;
    }
    //一个udpSocket已经于一个端口bind在一起了,这里的data是out流中的data,最多可以传送8192个字节,但是建议不要超过
    //512个字节,因为这样虽然可以传送成功,但是这些数据需要在ip层分组,QHostAddress::Broadcast是指发送数据的目的地址
    //这里为本机所在地址的广播组内所有机器,即局域网广播发送
    udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast, port);//将data中的数据发送
}

// 接收UDP信息
void Widget::processPendingDatagrams()
{
    //hasPendingDatagrams返回true时表示至少有一个数据报在等待被读取
    while(udpSocket->hasPendingDatagrams())
    {
        QByteArray datagram;
        //pendingDatagramSize为返回第一个在等待读取报文的size,resize函数是把datagram的size归一化到参数size的大小一样
        datagram.resize(udpSocket->pendingDatagramSize());
        //将读取到的不大于datagram.size()大小数据输入到datagram.data()中,datagram.data()返回的是一个字节数组中存储
        //数据位置的指针
        udpSocket->readDatagram(datagram.data(), datagram.size());
        QDataStream in(&datagram, QIODevice::ReadOnly);//因为其属性为只读,所以是输入
        int messageType;    //此处的int为qint32,在Qt中,qint8为char,qint16为uint
        in >> messageType;    //读取1个32位长度的整型数据到messageTyep中
        QString userName,localHostName,ipAddress,message;
        QString time = QDateTime::currentDateTime()
                .toString("yyyy-MM-dd hh:mm:ss");//将当前的时间转化到括号中的形式

        switch(messageType)
        {
        case Message:
            //in>>后面如果为Qstring,则表示读取一个直到出现'\0'的字符串
            in >> userName >> localHostName >> ipAddress >> message;
            ui->messageBrowser->setTextColor(Qt::blue);//设置文本颜色
            ui->messageBrowser->setCurrentFont(QFont("Times New Roman",12));//设置字体大小
       //     ui->messageBrowser->append("[ " +userName+" ] "+ time);//输出的格式为用户名加时间显示
            //输出的格式为主机名加时间显示,但输出完后为什么会自动换行呢?
            ui->messageBrowser->append("[ " +localHostName+" ] "+ time);
            ui->messageBrowser->append(message);//消息输出
            break;

        case NewParticipant:
            in >>userName >>localHostName >>ipAddress;
            newParticipant(userName,localHostName,ipAddress);
            break;

        case ParticipantLeft:
            in >>userName >>localHostName;
            participantLeft(userName,localHostName,time);
            break;

        case FileName:
            break;

        case Refuse:
            break;
        }
    }
}

// 处理新用户加入
void Widget::newParticipant(QString userName, QString localHostName, QString ipAddress)
{
    //此处的findItems表示找到与内容localHostName匹配的item,其匹配是基于变体的匹配模式
    bool isEmpty = ui->userTableWidget->findItems(localHostName, Qt::MatchExactly).isEmpty();
    if (isEmpty) {    //没有找到相应的主机名
        //新建3个小的item,分别为user,host,ip
        QTableWidgetItem *user = new QTableWidgetItem(userName);
        QTableWidgetItem *host = new QTableWidgetItem(localHostName);
        QTableWidgetItem *ip = new QTableWidgetItem(ipAddress);

        ui->userTableWidget->insertRow(0);//先设置的是第0行,即新来的用户放在最上面
        ui->userTableWidget->setItem(0,0,user);//第0行的第1列...
        ui->userTableWidget->setItem(0,1,host);
        ui->userTableWidget->setItem(0,2,ip);
        ui->messageBrowser->setTextColor(Qt::gray);
        ui->messageBrowser->setCurrentFont(QFont("Times New Roman",10));
        //arg为返回后面文本的一个副本,%1表示输出的内容按照第1个.arg后面的输出?
        ui->messageBrowser->append(tr("%1 在线!").arg(userName));
        ui->userNumLabel->setText(tr("在线人数:%1").arg(ui->userTableWidget->rowCount()));//在线人数为条目的行数

        sendMessage(NewParticipant);//该句的功能是让新来的用户也能收到其它在线用户的信息,可拥于更新自己的好友列表
    }
}

// 处理用户离开
void Widget::participantLeft(QString userName, QString localHostName, QString time)
{
    //找到第一个对应的主机名
    int rowNum = ui->userTableWidget->findItems(localHostName, Qt::MatchExactly).first()->row();
    ui->userTableWidget->removeRow(rowNum);    //此句执行完后,rowCount()内容会自动减1
    ui->messageBrowser->setTextColor(Qt::gray);//设置文本颜色为灰色
    ui->messageBrowser->setCurrentFont(QFont("Times New Roman", 10));
    ui->messageBrowser->append(tr("%1 于 %2 离开!").arg(userName).arg(time));
    ui->userNumLabel->setText(tr("在线人数:%1").arg(ui->userTableWidget->rowCount()));
}

// 获取ip地址,获取本机ip地址(其协议为ipv4的ip地址)
QString Widget::getIP()
{
    //QList是Qt中一个容器模板类,是一个数组指针?
    QList<QHostAddress> list = QNetworkInterface::allAddresses();//此处的所有地址是指ipv4和ipv6的地址
    //foreach (variable, container),此处为按照容器list中条目的顺序进行迭代
    foreach (QHostAddress address, list) {    
        if(address.protocol() == QAbstractSocket::IPv4Protocol)
            return address.toString();
    }
    return 0;
}

// 获取用户名
QString Widget::getUserName()
{
    QStringList envVariables;
    //将后面5个string存到envVariables环境变量中
    envVariables << "USERNAME.*" << "USER.*" << "USERDOMAIN.*"
                 << "HOSTNAME.*" << "DOMAINNAME.*";
    //系统中关于环境变量的信息存在environment中
    QStringList environment = QProcess::systemEnvironment();
    foreach (QString string, envVariables) {
        //indexOf为返回第一个匹配list的索引,QRegExp类是用规则表达式进行模式匹配的类
        int index = environment.indexOf(QRegExp(string));
        if (index != -1) {
            //stringList中存的是environment.at(index)中出现'='号前的字符串
            QStringList stringList = environment.at(index).split('=');
            if (stringList.size() == 2) {
                return stringList.at(1);//at(0)为文字"USERNAME.",at(1)为用户名
                break;
            }
        }
    }
    return "unknown";
}

// 获得要发送的消息
QString Widget::getMessage()
{
    QString msg = ui->messageTextEdit->toHtml();//转化成html语言进行发送

    ui->messageTextEdit->clear();//发送完后清空输入框
    ui->messageTextEdit->setFocus();//重新设置光标输入焦点,即焦点保持不变
    return msg;
}


// 发送消息
void Widget::on_sendButton_clicked()
{
    sendMessage(Message);
}
复制代码

main:

复制代码
#include <QtGui/QApplication>
#include "widget.h"
#include <QTextCodec>    //处理不同语言编码的类

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTextCodec::setCodecForTr(QTextCodec::codecForLocale());//对不同的文字选择不同的编码
    Widget w;
    w.show();

    return a.exec();
}

qt初步学习

一、Qt的环境与工具 1、工具 a、Qt助手:里面详细说明了Qt框架中的每一个类、函数、宏等 是开发的必备工具,在终端可以使用assistant开启 b、构建器:负责把源码文件、头文件构建成一个Qt工程,并且生成Makefile文件 c、Qt设计... 查看详情

qt学习笔记1.初步接触

...的过程中对C++的各种特性有能深刻的认识。此外,我在Qt初步的学习过程中,所用的环境应该是VS2015IDE二、安装官网下载Qt安装包和QtforVSaddin,分别安装,并配置。650)this.width=650;"src="http://s5.51cto.c 查看详情

qt下udp套接字通信——qudpsocket简单使用(代码片段)

...数据报、无连接的协议。它可以在可靠性不重要的情况下使用。QUdpSocket是QAbstractSocket的一个子类,允许您发送和接收UDP数据报。注意:使用时需要在pro文件中添加network组件QT+=network例子效果展示代码发送端.h#ifnd 查看详情

使用qt获取udp数据并显示成图片(代码片段)

一个项目,要接收UDP数据包,解析并获取其中的数据,主要根据解析出来的行号和序号将数据拼接起来,然后将拼接起来的数据(最重要的数据是R、G、B三个通道的像素值)显示在窗口中。考虑到每秒钟要接收的数据包的数量较... 查看详情

qt的udp组播技术

...话,明天再看。客户端:classSender:publicQObject{Q_OBJECT//可以使用信号和槽public:explicitSender(QO 查看详情

vc++下使用qt初步入门学习

初步了解,VC++下使用QT,一种方式是,用QT的工具或命令行,生成VS的工程,然后导入VC进行构建;下面来看一下,下载一个QT免费试用版本;安装之后,先设置一下环境变量;test3目录... 查看详情

qt网络编程二(udp版本)

QT的UdpSocket接收消息使用原则第一步:new一个UdpSocket第二步:调用UdpSocket的bind方法,同时指定端口号第三步:使用connect将接收消息函数和UdpSocket对象做关联第四步:在接受消息槽函数当中调用readDatagram接收消息 接收消息#ifnd... 查看详情

qt3d教程初步显示3d的内容

Qt3D教程(二)初步显示3D的内容      前一篇很easy,全然就没有牵涉到3D的内容,它仅仅是我们搭建3D应用的基本框架而已,而这一篇。我们将要利用它来初步地显示3D的内容了!本次目的是将程序中间的内... 查看详情

如何在 Qt 中进行 UDP 打孔?

...:你知道,这真的,真的复杂。只是为了确保:您想通过使用您的服务器在同一程序的两个实例(在不同的客户端上运行)之间打一个洞?【参考方案1】:您必须 查看详情

使用 C++ 和 Qt 通过 UDP 发送 float var

】使用C++和Qt通过UDP发送floatvar【英文标题】:SendfloatvarviaUDPwithC++andQt【发布时间】:2015-03-3111:24:16【问题描述】:你好,你可以看到我是新来的(在C++和Qt中)。请帮助我,我想通过UDP将一些命令从我的应用程序发送到X-Plane(... 查看详情

初步剖析qt事件处理全过程(windows)

一、说起Qt事件处理,在windows平台下,当然离不开Win32:Win32程序的基本结构:  1.注册窗口;  2.创建窗口;  3.启动由GetMessage和DispatchMessage构成的事件循环;  4.被注册的回调函数WndProc负责相应各类事件;Windows会为当... 查看详情

qt之udp通信(代码片段)

...快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。UDP通信示意图如下:UDP消息传送有三种模式,分别是单播、广播和组... 查看详情

使用 UDP 协议发送数据包

】使用UDP协议发送数据包【英文标题】:SendingpacketswithUDPprotocol【发布时间】:2014-12-0522:39:54【问题描述】:我正在使用Qt构建一个网络项目。我的项目以数据包的形式发送文件,每个数据包大小为1K,我使用的是UDP协议。问题是... 查看详情

qtsocket网络通信

...P的简单通信。socket简介在LINUX下进行网络编程,我们可以使用LINUX提供的统一的套接字接口。但是这种方法牵涉到太多的结构体,比如IP地址,端口转换等,不熟练的人往往容易犯这样那样的错误。QT中提供的SOCKET完全使用了类的... 查看详情

使用 UDP 协议和 writeDatagram 发送结构体

】使用UDP协议和writeDatagram发送结构体【英文标题】:SendastructurewithUDPprotocolandwriteDatagram【发布时间】:2014-08-0103:49:25【问题描述】:我的目标是通过UDP协议发送一个结构,我的问题是我用来做它的函数与他参数中的结构不兼容... 查看详情

qt使用qaudioinputqaudiooutput实现局域网的音频通话(代码片段)

Qt使用QAudioInput、QAudioOutput实现局域网的音频通话本文旨在介绍一下用Qt来实现局域网音频通话功能文章目录Qt使用QAudioInput、QAudioOutput实现局域网的音频通话项目背景技术实现QAudioFormat(音频采样格式)QAudioDeviceInfoQAudioInp... 查看详情

qt之udp通信

...分别为单播、组播和广播,下面将一一为大家介绍。同样的我们都需要在工程文件中添加networkQT+=coreguinetwork进行UDP通信需要用到的头文件#include<QUdpSocket>这里我们把UDP通信分为两个部分写,一个是发送端,另一个是接收端,... 查看详情

编写第一个qt程序(代码片段)

...序。我们也用QtCreator编写一个“HelloWorld”程序,以初步了解QtCreator设计应用程序的基本过程,对使用QtCreator编写QtC++应用程序建立初步的了解。新建一个项目单 查看详情