Qt学习之路_5(Qt TCP的初步使用)

来源:互联网 发布:c语言网 编辑:程序博客网 时间:2024/05/19 02:28

Qt学习之路_5(Qt TCP的初步使用)

       在上一篇博文Qt学习之路_4(Qt UDP的初步使用) 中,初步了解了Qt下UDP的使用,这一节就学习下TCP的使用。2者其实流程都差不多。当然了,本文还是参考的《Qt及Qt Quick开发实战精解》一书中的第5个例子,即局域网聊天工具中的UDP聊天和TCP文件传送部分。另外http://www.yafeilinux.com/ 上有其源码和相关教程下载。

         其发送端界面如下:

          

         接收端界面如下:

  

 

         发送端,也即承担服务器角色的操作:

         在主界面程序右侧选择一个需要发送文件的用户,弹出发送端界面后,点击打开按钮,在本地计算机中选择需要发送的文件,点击发送按钮,则进度条上会显示当前文件传送的信息,有已传送文件大小信息,传送速度等信息。如果想关闭发送过程,则单击关闭按钮。

         其流程图如下:

  

 

         接收端,也即承担客户端角色的操作:

         当在主界面中突然弹出一个对话框,问是否接自某个用户名和IP地址的文件传送信息,如果接受则单击yes按钮,否则就单击no按钮。当接收文件时,选择好接收文件所存目录和文件名后就开始接收文件了,其过程也会显示已接收文件的大小,接收速度和剩余时间的大小等信息。

         其流程图如下:

  

 

         TCP部分程序代码和注释如下:

 Widget.h:

复制代码
#ifndef WIDGET_H#define WIDGET_H#include <QWidget>class QUdpSocket;class TcpServer;//可以这样定义类?不用保护头文件的?namespace Ui {class Widget;}// 枚举变量标志信息的类型,分别为消息,新用户加入,用户退出,文件名,拒绝接受文件enum MessageType{Message, NewParticipant, ParticipantLeft, FileName, Refuse};class Widget : public QWidget{    Q_OBJECTpublic:    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();    void hasPendingFile(QString userName, QString serverAddress,                        QString clientAddress, QString fileName);private:    Ui::Widget *ui;    QUdpSocket *udpSocket;    qint16 port;    QString fileName;    TcpServer *server;private slots:    void processPendingDatagrams();    void on_sendButton_clicked();    void getFileName(QString);    void on_sendToolBtn_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>#include "tcpserver.h"#include "tcpclient.h"#include <QFileDialog>Widget::Widget(QWidget *parent) :    QWidget(parent),    ui(new Ui::Widget){    ui->setupUi(this);    udpSocket = new QUdpSocket(this);    port = 45454;    udpSocket->bind(port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);    connect(udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));    sendMessage(NewParticipant);    //TcpServer是tcpserver.ui对应的类,上面直接用QUdpSocket是因为没有单独的udpserver.ui类    server = new TcpServer(this);    //sendFileName()函数一发送,则触发槽函数getFileName()    connect(server, SIGNAL(sendFileName(QString)), this, SLOT(getFileName(QString)));}Widget::~Widget(){    delete ui;}// 使用UDP广播发送信息void Widget::sendMessage(MessageType type, QString serverAddress){    QByteArray data;    QDataStream out(&data, QIODevice::WriteOnly);    QString localHostName = QHostInfo::localHostName();    QString address = getIP();    out << type << getUserName() << localHostName;    switch(type)    {    case Message :        if (ui->messageTextEdit->toPlainText() == "") {            QMessageBox::warning(0,tr("警告"),tr("发送内容不能为空"),QMessageBox::Ok);            return;        }        out << address << getMessage();        ui->messageBrowser->verticalScrollBar()                ->setValue(ui->messageBrowser->verticalScrollBar()->maximum());        break;    case NewParticipant :        out << address;        break;    case ParticipantLeft :        break;    case FileName : {        int row = ui->userTableWidget->currentRow();//必须选中需要发送的给谁才可以发送        QString clientAddress = ui->userTableWidget->item(row, 2)->text();//(row,,2)为ip地址        out << address << clientAddress << fileName;//发送本地ip,对方ip,所发送的文件名        break;    }    case Refuse :        out << serverAddress;        break;    }    udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast, port);}// 接收UDP信息void Widget::processPendingDatagrams(){    while(udpSocket->hasPendingDatagrams())    {        QByteArray datagram;        datagram.resize(udpSocket->pendingDatagramSize());        udpSocket->readDatagram(datagram.data(), datagram.size());        QDataStream in(&datagram, QIODevice::ReadOnly);        int messageType;        in >> messageType;        QString userName,localHostName,ipAddress,message;        QString time = QDateTime::currentDateTime()                .toString("yyyy-MM-dd hh:mm:ss");        switch(messageType)        {        case Message:            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(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: {            in >> userName >> localHostName >> ipAddress;            QString clientAddress, fileName;            in >> clientAddress >> fileName;            hasPendingFile(userName, ipAddress, clientAddress, fileName);            break;        }        case Refuse: {            in >> userName >> localHostName;            QString serverAddress;            in >> serverAddress;            QString ipAddress = getIP();            if(ipAddress == serverAddress)            {                server->refused();            }            break;        }        }    }}// 处理新用户加入void Widget::newParticipant(QString userName, QString localHostName, QString ipAddress){    bool isEmpty = ui->userTableWidget->findItems(localHostName, Qt::MatchExactly).isEmpty();    if (isEmpty) {        QTableWidgetItem *user = new QTableWidgetItem(userName);        QTableWidgetItem *host = new QTableWidgetItem(localHostName);        QTableWidgetItem *ip = new QTableWidgetItem(ipAddress);        ui->userTableWidget->insertRow(0);        ui->userTableWidget->setItem(0,0,user);        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));        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);    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地址QString Widget::getIP(){    QList<QHostAddress> list = QNetworkInterface::allAddresses();    foreach (QHostAddress address, list) {        if(address.protocol() == QAbstractSocket::IPv4Protocol)            return address.toString();    }    return 0;}// 获取用户名QString Widget::getUserName(){    QStringList envVariables;    envVariables << "USERNAME.*" << "USER.*" << "USERDOMAIN.*"                 << "HOSTNAME.*" << "DOMAINNAME.*";    QStringList environment = QProcess::systemEnvironment();    foreach (QString string, envVariables) {        int index = environment.indexOf(QRegExp(string));        if (index != -1) {            QStringList stringList = environment.at(index).split('=');            if (stringList.size() == 2) {                return stringList.at(1);                break;            }        }    }    return "unknown";}// 获得要发送的消息QString Widget::getMessage(){    QString msg = ui->messageTextEdit->toHtml();    ui->messageTextEdit->clear();    ui->messageTextEdit->setFocus();    return msg;}// 发送消息void Widget::on_sendButton_clicked(){    sendMessage(Message);}// 获取要发送的文件名void Widget::getFileName(QString name){    fileName = name;    sendMessage(FileName);}// 传输文件按钮void Widget::on_sendToolBtn_clicked(){    if(ui->userTableWidget->selectedItems().isEmpty())//传送文件前需选择用户    {        QMessageBox::warning(0, tr("选择用户"),                       tr("请先从用户列表选择要传送的用户!"), QMessageBox::Ok);        return;    }    server->show();    server->initServer();}// 是否接收文件,客户端的显示void Widget::hasPendingFile(QString userName, QString serverAddress,                            QString clientAddress, QString fileName){    QString ipAddress = getIP();    if(ipAddress == clientAddress)    {        int btn = QMessageBox::information(this,tr("接受文件"),                                           tr("来自%1(%2)的文件:%3,是否接收?")                                           .arg(userName).arg(serverAddress).arg(fileName),                                           QMessageBox::Yes,QMessageBox::No);//弹出一个窗口        if (btn == QMessageBox::Yes) {            QString name = QFileDialog::getSaveFileName(0,tr("保存文件"),fileName);//name为另存为的文件名            if(!name.isEmpty())            {                TcpClient *client = new TcpClient(this);                client->setFileName(name);    //客户端设置文件名                client->setHostAddress(QHostAddress(serverAddress));    //客户端设置服务器地址                client->show();            }        } else {            sendMessage(Refuse, serverAddress);        }    }}
复制代码

 

Tcpserver.h:

复制代码
#ifndef TCPSERVER_H#define TCPSERVER_H#include <QDialog>#include <QTime>class QFile;class QTcpServer;class QTcpSocket;namespace Ui {class TcpServer;}class TcpServer : public QDialog{    Q_OBJECTpublic:    explicit TcpServer(QWidget *parent = 0);    ~TcpServer();    void initServer();    void refused();protected:    void closeEvent(QCloseEvent *);private:    Ui::TcpServer *ui;    qint16 tcpPort;    QTcpServer *tcpServer;    QString fileName;    QString theFileName;    QFile *localFile;    qint64 TotalBytes;    qint64 bytesWritten;    qint64 bytesToWrite;    qint64 payloadSize;    QByteArray outBlock;    QTcpSocket *clientConnection;    QTime time;private slots:    void sendMessage();    void updateClientProgress(qint64 numBytes);    void on_serverOpenBtn_clicked();    void on_serverSendBtn_clicked();    void on_serverCloseBtn_clicked();signals:    void sendFileName(QString fileName);};#endif // TCPSERVER_H
复制代码

 

Tcpserver.cpp:

复制代码
#include "tcpserver.h"#include "ui_tcpserver.h"#include <QFile>#include <QTcpServer>#include <QTcpSocket>#include <QMessageBox>#include <QFileDialog>#include <QDebug>TcpServer::TcpServer(QWidget *parent) :    QDialog(parent),    ui(new Ui::TcpServer){    ui->setupUi(this);    //每一个新类都有一个自己的ui    setFixedSize(350,180);    //初始化时窗口显示固定大小    tcpPort = 6666;        //tcp通信端口    tcpServer = new QTcpServer(this);    //newConnection表示当tcp有新连接时就发送信号    connect(tcpServer, SIGNAL(newConnection()), this, SLOT(sendMessage()));    initServer();}TcpServer::~TcpServer(){    delete ui;}// 初始化void TcpServer::initServer(){    payloadSize = 64*1024;    TotalBytes = 0;    bytesWritten = 0;    bytesToWrite = 0;    ui->serverStatusLabel->setText(tr("请选择要传送的文件"));    ui->progressBar->reset();//进度条复位    ui->serverOpenBtn->setEnabled(true);//open按钮可用    ui->serverSendBtn->setEnabled(false);//发送按钮不可用    tcpServer->close();//tcp传送文件窗口不显示}// 开始发送数据void TcpServer::sendMessage()    //是connect中的槽函数{    ui->serverSendBtn->setEnabled(false);    //当在传送文件的过程中,发送按钮不可用    clientConnection = tcpServer->nextPendingConnection();    //用来获取一个已连接的TcpSocket    //bytesWritten为qint64类型,即长整型    connect(clientConnection, SIGNAL(bytesWritten(qint64)),    //?            this, SLOT(updateClientProgress(qint64)));    ui->serverStatusLabel->setText(tr("开始传送文件 %1 !").arg(theFileName));    localFile = new QFile(fileName);    //localFile代表的是文件内容本身    if(!localFile->open((QFile::ReadOnly))){        QMessageBox::warning(this, tr("应用程序"), tr("无法读取文件 %1:\n%2")                             .arg(fileName).arg(localFile->errorString()));//errorString是系统自带的信息        return;    }    TotalBytes = localFile->size();//文件总大小    //头文件中的定义QByteArray outBlock;    QDataStream sendOut(&outBlock, QIODevice::WriteOnly);//设置输出流属性    sendOut.setVersion(QDataStream::Qt_4_7);//设置Qt版本,不同版本的数据流格式不同    time.start();  // 开始计时    QString currentFile = fileName.right(fileName.size()    //currentFile代表所选文件的文件名                                         - fileName.lastIndexOf('/')-1);    //qint64(0)表示将0转换成qint64类型,与(qint64)0等价    //如果是,则此处为依次写入总大小信息空间,文件名大小信息空间,文件名    sendOut << qint64(0) << qint64(0) << currentFile;    TotalBytes += outBlock.size();//文件名大小等信息+实际文件大小    //sendOut.device()为返回io设备的当前设置,seek(0)表示设置当前pos为0    sendOut.device()->seek(0);//返回到outBlock的开始,执行覆盖操作    //发送总大小空间和文件名大小空间    sendOut << TotalBytes << qint64((outBlock.size() - sizeof(qint64)*2));    //qint64 bytesWritten;bytesToWrite表示还剩下的没发送完的数据    //clientConnection->write(outBlock)为套接字将内容发送出去,返回实际发送出去的字节数    bytesToWrite = TotalBytes - clientConnection->write(outBlock);    outBlock.resize(0);//why??}// 更新进度条,有数据发送时触发void TcpServer::updateClientProgress(qint64 numBytes){    //qApp为指向一个应用对象的全局指针    qApp->processEvents();//processEvents为处理所有的事件?    bytesWritten += (int)numBytes;    if (bytesToWrite > 0) {    //没发送完毕        //初始化时payloadSize = 64*1024;qMin为返回参数中较小的值,每次最多发送64K的大小        outBlock = localFile->read(qMin(bytesToWrite, payloadSize));        bytesToWrite -= (int)clientConnection->write(outBlock);        outBlock.resize(0);//清空发送缓冲区    } else {        localFile->close();    }    ui->progressBar->setMaximum(TotalBytes);//进度条的最大值为所发送信息的所有长度(包括附加信息)    ui->progressBar->setValue(bytesWritten);//进度条显示的进度长度为bytesWritten实时的长度    float useTime = time.elapsed();//从time.start()还是到当前所用的时间记录在useTime中    double speed = bytesWritten / useTime;    ui->serverStatusLabel->setText(tr("已发送 %1MB (%2MB/s) "                   "\n共%3MB 已用时:%4秒\n估计剩余时间:%5秒")                   .arg(bytesWritten / (1024*1024))    //转化成MB                   .arg(speed*1000 / (1024*1024), 0, 'f', 2)                   .arg(TotalBytes / (1024 * 1024))                   .arg(useTime/1000, 0, 'f', 0)    //0,‘f’,0是什么意思啊?                   .arg(TotalBytes/speed/1000 - useTime/1000, 0, 'f', 0));    if(bytesWritten == TotalBytes) {    //当需发送文件的总长度等于已发送长度时,表示发送完毕!        localFile->close();        tcpServer->close();        ui->serverStatusLabel->setText(tr("传送文件 %1 成功").arg(theFileName));    }}// 打开按钮void TcpServer::on_serverOpenBtn_clicked(){    //QString fileName;QFileDialog是一个提供给用户选择文件或目录的对话框    fileName = QFileDialog::getOpenFileName(this);    //filename为所选择的文件名(包含了路径名)    if(!fileName.isEmpty())    {        //fileName.right为返回filename最右边参数大小个字文件名,theFileName为所选真正的文件名        theFileName = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1);        ui->serverStatusLabel->setText(tr("要传送的文件为:%1 ").arg(theFileName));        ui->serverSendBtn->setEnabled(true);//发送按钮可用        ui->serverOpenBtn->setEnabled(false);//open按钮禁用    }}// 发送按钮void TcpServer::on_serverSendBtn_clicked(){    //tcpServer->listen函数如果监听到有连接,则返回1,否则返回0    if(!tcpServer->listen(QHostAddress::Any,tcpPort))//开始监听6666端口    {        qDebug() << tcpServer->errorString();//此处的errorString是指?        close();        return;    }    ui->serverStatusLabel->setText(tr("等待对方接收... ..."));    emit sendFileName(theFileName);//发送已传送文件的信号,在widget.cpp构造函数中的connect()触发槽函数}// 关闭按钮,服务器端的关闭按钮void TcpServer::on_serverCloseBtn_clicked(){    if(tcpServer->isListening())    {        //当tcp正在监听时,关闭tcp服务器端应用,即按下close键时就不监听tcp请求了        tcpServer->close();        if (localFile->isOpen())//如果所选择的文件已经打开,则关闭掉            localFile->close();        clientConnection->abort();//clientConnection为下一个连接?怎么理解    }    close();//关闭本ui,即本对话框}// 被对方拒绝void TcpServer::refused(){    tcpServer->close();    ui->serverStatusLabel->setText(tr("对方拒绝接收!!!"));}// 关闭事件void TcpServer::closeEvent(QCloseEvent *){    on_serverCloseBtn_clicked();}
复制代码

 

Tcpclient.h:

复制代码
#ifndef TCPCLIENT_H#define TCPCLIENT_H#include <QDialog>#include <QHostAddress>#include <QFile>#include <QTime>class QTcpSocket;namespace Ui {class TcpClient;}class TcpClient : public QDialog{    Q_OBJECTpublic:    explicit TcpClient(QWidget *parent = 0);    ~TcpClient();    void setHostAddress(QHostAddress address);    void setFileName(QString fileName);protected:    void closeEvent(QCloseEvent *);private:    Ui::TcpClient *ui;    QTcpSocket *tcpClient;    quint16 blockSize;    QHostAddress hostAddress;    qint16 tcpPort;    qint64 TotalBytes;    qint64 bytesReceived;    qint64 bytesToReceive;    qint64 fileNameSize;    QString fileName;    QFile *localFile;    QByteArray inBlock;    QTime time;private slots:    void on_tcpClientCancleBtn_clicked();    void on_tcpClientCloseBtn_clicked();    void newConnect();    void readMessage();    void displayError(QAbstractSocket::SocketError);};#endif // TCPCLIENT_H
复制代码

 

Tcpclient.cpp:

复制代码
#include "tcpclient.h"#include "ui_tcpclient.h"#include <QTcpSocket>#include <QDebug>#include <QMessageBox>TcpClient::TcpClient(QWidget *parent) :    QDialog(parent),    ui(new Ui::TcpClient){    ui->setupUi(this);    setFixedSize(350,180);    TotalBytes = 0;    bytesReceived = 0;    fileNameSize = 0;    tcpClient = new QTcpSocket(this);    tcpPort = 6666;    connect(tcpClient, SIGNAL(readyRead()), this, SLOT(readMessage()));    connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)), this,            SLOT(displayError(QAbstractSocket::SocketError)));}TcpClient::~TcpClient(){    delete ui;}// 设置文件名void TcpClient::setFileName(QString fileName){    localFile = new QFile(fileName);}// 设置地址void TcpClient::setHostAddress(QHostAddress address){    hostAddress = address;    newConnect();}// 创建新连接void TcpClient::newConnect(){    blockSize = 0;    tcpClient->abort();        //取消已有的连接    tcpClient->connectToHost(hostAddress, tcpPort);//连接到指定ip地址和端口的主机    time.start();}// 读取数据void TcpClient::readMessage(){    QDataStream in(tcpClient);    //这里的QDataStream可以直接用QTcpSocket对象做参数    in.setVersion(QDataStream::Qt_4_7);    float useTime = time.elapsed();    if (bytesReceived <= sizeof(qint64)*2) {    //说明刚开始接受数据        if ((tcpClient->bytesAvailable()    //bytesAvailable为返回将要被读取的字节数             >= sizeof(qint64)*2) && (fileNameSize == 0))        {            //接受数据总大小信息和文件名大小信息            in>>TotalBytes>>fileNameSize;            bytesReceived += sizeof(qint64)*2;        }        if((tcpClient->bytesAvailable() >= fileNameSize) && (fileNameSize != 0)){            //开始接受文件,并建立文件            in>>fileName;            bytesReceived +=fileNameSize;            if(!localFile->open(QFile::WriteOnly)){                QMessageBox::warning(this,tr("应用程序"),tr("无法读取文件 %1:\n%2.")                                     .arg(fileName).arg(localFile->errorString()));                return;            }        } else {            return;        }    }    if (bytesReceived < TotalBytes) {        bytesReceived += tcpClient->bytesAvailable();//返回tcpClient中字节的总数        inBlock = tcpClient->readAll();    //返回读到的所有数据        localFile->write(inBlock);        inBlock.resize(0);    }    ui->progressBar->setMaximum(TotalBytes);    ui->progressBar->setValue(bytesReceived);    double speed = bytesReceived / useTime;    ui->tcpClientStatusLabel->setText(tr("已接收 %1MB (%2MB/s) "                                         "\n共%3MB 已用时:%4秒\n估计剩余时间:%5秒")                                      .arg(bytesReceived / (1024*1024))                                      .arg(speed*1000/(1024*1024),0,'f',2)                                      .arg(TotalBytes / (1024 * 1024))                                      .arg(useTime/1000,0,'f',0)                                      .arg(TotalBytes/speed/1000 - useTime/1000,0,'f',0));    if(bytesReceived == TotalBytes)    {        localFile->close();        tcpClient->close();        ui->tcpClientStatusLabel->setText(tr("接收文件 %1 完毕")                                          .arg(fileName));    }}// 错误处理//QAbstractSocket类提供了所有scoket的通用功能,socketError为枚举型void TcpClient::displayError(QAbstractSocket::SocketError socketError){    switch(socketError)    {    //RemoteHostClosedError为远处主机关闭了连接时发出的错误信号    case QAbstractSocket::RemoteHostClosedError : break;    default : qDebug() << tcpClient->errorString();    }}// 取消按钮void TcpClient::on_tcpClientCancleBtn_clicked(){    tcpClient->abort();    if (localFile->isOpen())        localFile->close();}// 关闭按钮void TcpClient::on_tcpClientCloseBtn_clicked(){    tcpClient->abort();    if (localFile->isOpen())        localFile->close();    close();}// 关闭事件void TcpClient::closeEvent(QCloseEvent *){    on_tcpClientCloseBtn_clicked();}
复制代码

 

Main.cpp:

复制代码
#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();}
复制代码
0 0