Chinaunix首页 | 论坛 | 博客
  • 博客访问: 38583
  • 博文数量: 20
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 145
  • 用 户 组: 普通用户
  • 注册时间: 2016-03-25 09:44
个人简介

unix

文章分类

全部博文(20)

文章存档

2016年(20)

我的朋友

分类: 嵌入式

2016-05-18 10:39:51

本文还是参考的《Qt及Qt Quick开发实战精解》一书中的第5个例子,即局域网聊天工具中的UDP聊天和TCP文件传送部分。另外 上有其源码和相关教程下载。

         其发送端界面如下:

          

         接收端界面如下:

  

 

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

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

         其流程图如下:

  

 

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

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

         其流程图如下:

  

 

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

 Widget.h:

复制代码
#ifndef WIDGET_H #define WIDGET_H #include  class QUdpSocket; class TcpServer;//可以这样定义类?不用保护头文件的? 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(); 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  #include  #include  #include  #include  #include  #include  #include "tcpserver.h" #include "tcpclient.h" #include  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 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  #include  class QFile; class QTcpServer; class QTcpSocket; namespace Ui { class TcpServer;
} class TcpServer : public QDialog
{
    Q_OBJECT public: 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  #include  #include  #include  #include  #include  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  #include  #include  #include  class QTcpSocket; namespace Ui { class TcpClient;
} class TcpClient : public QDialog
{
    Q_OBJECT public: 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  #include  #include  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  #include "widget.h" #include  int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTextCodec::setCodecForTr(QTextCodec::codecForLocale());
    Widget w;
    w.show(); return a.exec();
}
复制代码

 

阅读(1669) | 评论(0) | 转发(0) |
0

上一篇:perror()函数

下一篇:Qt: Session management error:

给主人留下些什么吧!~~