在上2次文章Qt學習之路_5(Qt TCP的初步使用) Qt學習之路_4(Qt UDP的初步使用) 中已經初步介紹了群聊功能和文件傳輸功能,這一節中主要在這個基礎上加入一個私聊功能。
參考文獻依舊是:《Qt及Qt Quick開發實戰精解》一書中的第5個例子以及http://www.yafeilinux.com/ 網站上的源碼。另外這次的私聊功能也是參考網友http://www.qtcn.org/bbs/read-htm-tid-32609.html的,他的程序有些bug,其中最嚴重的bug是當私聊第二次聊天的時候對方會接收不到信息。這次主要是將這個bug和其它一些小bug修補了,但是仍然有一個漏洞就是:當第二次私聊時,后面那個的發送方收到信息的時候有可能會多一個窗口彈出來。目前還找不到其原因。猜想是:在第一次聊天接收時關閉聊天窗口后,其內存沒有釋放。但是當窗口關閉時我們覺得其內存釋放應該在Qt內部自己實現。
下面來講一下私聊發送端和接收端具體實現過程。
發送端流程圖如下:

接收端的流程圖如下:

下面來介紹下2者實現的具體過程:
A方(主動開始首次發送的一方):
- 在主窗口右側雙擊自己想與之聊天的B方,此時A方實際上完成的工作有:用B方的主機名和ip地址新建了私聊的類privatechat,在新建該類的過程中,已經設置了顯示頂端為:與***聊天中,對方IP:***,且綁定了本地ip和私聊的專用端口,同時設置了信號與槽的聯系,即該端口如果有數據輸入,則觸發槽函數processPendingDatagrams().該函數是char.cpp中的。
- 當上面的新建私聊類完成后,用通訊對方ip地址和其群聊專用的端口(但用的是主udp群聊的socket進行的)將以下內容分別發送出去:消息類型(Xchat),用戶名,主機名,本地ip地址。完成后,在屏幕中顯示私聊窗口。
- 在私聊窗口中輸入需要聊天的內容,單擊發送鍵。該過程玩成的內容有:分別將消息類型(Message)+用戶名+本地名+本地IP+消息內容本身通過私聊專用端口發送出去。在私聊窗口中顯示主機名+聊天時間,換行后顯示消息內容本身。
B方(第一次信息是他人發送過來的):
- 當A在2步驟中用群聊的方法發送其消息類型(Xchat),其用戶名,其主機名,其ip地址后,由於程序運行時已經初始化了widget.cpp中的構造函數,所以每個程序都綁定了本地地址+群聊專用的端口,一旦有數據傳入,就觸發widget.cpp中的槽函數processPendingDatagrams().
- 在processPendingDatagrams()函數中,判斷消息類型為Xchat后,接收緩存區內接收對方用戶名,對方主機名和對方ip地址。並用接收到的主機名和ip地址新建一個私聊類。新建該私聊的過程與A中的步驟1一樣。完后在程序中顯示私聊窗口。
- 當對方A按完發送按鈕后,通過私聊專用端口綁定槽函數來觸發chart.cpp中的processPendingDatagrams()函數,該函數中先讀取消息類型(Message),然后依次讀取用戶名,主機名,ip地址,消息內容本身,並將對方信息和消息內容顯示在聊天窗口中。
實驗結果如下:
群聊界面:

私聊界面:

文件傳輸過程截圖:

實驗總結(下面幾點只是暫時的理解):
- 使用類時,如果直接用構造函數定義該類的對象,則定義該類的函數接收時,該對象的生命也就結束了,所以如果要在其他函數中定義一個類的對象時並長久使用,可以使用new定義一個對象的初始指針。這樣就在內存中永存了。
- 如果某個窗口類需要顯示時直接調用其指針->show()或者其對象-.show(),這個函數只是將內存中該類的對象顯示出來而已(因為與界面有關),並不是重新建一個類對象。其表示該類的界面等可以顯示,所以一旦show過即使改變了界面的內容,后面也無需一直調用show函數,界面會自動顯示的。
- 當關閉某個窗口時,只是將其隱藏,並沒有釋放其內存。
程序源碼(附錄有工程code下載鏈接):
widget.h
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QtNetwork> #include <QtGui> #include "tcpclient.h" #include "tcpserver.h" #include "chat.h" using namespace std::tr1; namespace Ui { class Widget; } //enum MessageType //{ // Message, // NewParticipant, // ParticipantLeft, // FileName, // Refuse, // xchat //}; //枚舉變量標志信息的類型,分別為消息,新用戶加入,和用戶退出 class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); QString getUserName(); QString getMessage(); chat* privatechat; chat* privatechat1; protected: void changeEvent(QEvent *e); void sendMessage(MessageType type,QString serverAddress=""); void newParticipant(QString userName,QString localHostName,QString ipAddress); void participantLeft(QString userName,QString localHostName,QString time); void closeEvent(QCloseEvent *); void hasPendingFile(QString userName,QString serverAddress, QString clientAddress,QString fileName); bool eventFilter(QObject *target, QEvent *event);//事件過濾器 private: Ui::Widget *ui; QUdpSocket *udpSocket; qint32 port; qint32 bb; QString fileName; TcpServer *server; //chat *privatechat; QString getIP(); QColor color;//顏色 bool saveFile(const QString& fileName);//保存聊天記錄 void showxchat(QString name, QString ip); private slots: void on_tableWidget_doubleClicked(QModelIndex index); void on_textUnderline_clicked(bool checked); void on_clear_clicked(); void on_save_clicked(); void on_textcolor_clicked(); void on_textitalic_clicked(bool checked); void on_textbold_clicked(bool checked); void on_fontComboBox_currentFontChanged(QFont f); void on_fontsizecomboBox_currentIndexChanged(QString ); void on_close_clicked(); void on_sendfile_clicked(); void on_send_clicked(); void processPendingDatagrams(); void sentFileName(QString); void currentFormatChanged(const QTextCharFormat &format); signals: }; #endif // WIDGET_H
widget.cpp:
#include "widget.h" #include "ui_widget.h" using namespace std::tr1; Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); this->resize(850,550); ui->textEdit->setFocusPolicy(Qt::StrongFocus); ui->textBrowser->setFocusPolicy(Qt::NoFocus); ui->textEdit->setFocus(); ui->textEdit->installEventFilter(this);//設置完后自動調用其eventFilter函數 privatechat = NULL; privatechat1 = NULL; udpSocket = new QUdpSocket(this); port = 45454; bb = 0; udpSocket->bind(port,QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint); connect(udpSocket,SIGNAL(readyRead()),this,SLOT(processPendingDatagrams())); sendMessage(NewParticipant); server = new TcpServer(this); connect(server,SIGNAL(sendFileName(QString)),this,SLOT(sentFileName(QString))); connect(ui->textEdit,SIGNAL(currentCharFormatChanged(QTextCharFormat)),this,SLOT(currentFormatChanged(const QTextCharFormat))); } void Widget::currentFormatChanged(const QTextCharFormat &format) {//當編輯器的字體格式改變時,我們讓部件狀態也隨之改變 ui->fontComboBox->setCurrentFont(format.font()); if(format.fontPointSize()<9) //如果字體大小出錯,因為我們最小的字體為9 { ui->fontsizecomboBox->setCurrentIndex(3); //即顯示12 } else { ui->fontsizecomboBox->setCurrentIndex(ui->fontsizecomboBox->findText(QString::number(format.fontPointSize()))); } ui->textbold->setChecked(format.font().bold()); ui->textitalic->setChecked(format.font().italic()); ui->textUnderline->setChecked(format.font().underline()); color = format.foreground().color(); } void Widget::processPendingDatagrams() //接收數據UDP { 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->textBrowser->setTextColor(Qt::blue); ui->textBrowser->setCurrentFont(QFont("Times New Roman",12)); ui->textBrowser->append("[ " +localHostName+" ] "+ time); ui->textBrowser->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; } case Xchat: { in >>userName >>localHostName >>ipAddress; showxchat(localHostName,ipAddress);//顯示與主機名聊天中,不是用戶名 break; } } } } //處理新用戶加入 void Widget::newParticipant(QString userName,QString localHostName,QString ipAddress) { bool bb = ui->tableWidget->findItems(localHostName,Qt::MatchExactly).isEmpty(); if(bb) { QTableWidgetItem *user = new QTableWidgetItem(userName); QTableWidgetItem *host = new QTableWidgetItem(localHostName); QTableWidgetItem *ip = new QTableWidgetItem(ipAddress); ui->tableWidget->insertRow(0); ui->tableWidget->setItem(0,0,user); ui->tableWidget->setItem(0,1,host); ui->tableWidget->setItem(0,2,ip); ui->textBrowser->setTextColor(Qt::gray); ui->textBrowser->setCurrentFont(QFont("Times New Roman",10)); ui->textBrowser->append(tr("%1 在線!").arg(localHostName)); ui->onlineUser->setText(tr("在線人數:%1").arg(ui->tableWidget->rowCount())); sendMessage(NewParticipant); } } //處理用戶離開 void Widget::participantLeft(QString userName,QString localHostName,QString time) { int rowNum = ui->tableWidget->findItems(localHostName,Qt::MatchExactly).first()->row(); ui->tableWidget->removeRow(rowNum); ui->textBrowser->setTextColor(Qt::gray); ui->textBrowser->setCurrentFont(QFont("Times New Roman",10)); ui->textBrowser->append(tr("%1 於 %2 離開!").arg(localHostName).arg(time)); ui->onlineUser->setText(tr("在線人數:%1").arg(ui->tableWidget->rowCount())); } Widget::~Widget() { delete ui; // delete privatechat; // privatechat = NULL; //udpSocket //server } void Widget::changeEvent(QEvent *e) { QWidget::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); break; default: break; } } QString Widget::getIP() //獲取ip地址 { QList<QHostAddress> list = QNetworkInterface::allAddresses(); foreach (QHostAddress address, list) { if(address.protocol() == QAbstractSocket::IPv4Protocol) //我們使用IPv4地址 return address.toString(); } return 0; } 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 ParticipantLeft: { break; } case NewParticipant: { out << address; break; } case Message : { if(ui->textEdit->toPlainText() == "") { QMessageBox::warning(0,tr("警告"),tr("發送內容不能為空"),QMessageBox::Ok); return; } out << address << getMessage(); ui->textBrowser->verticalScrollBar()->setValue(ui->textBrowser->verticalScrollBar()->maximum()); break; } case FileName: { int row = ui->tableWidget->currentRow(); QString clientAddress = ui->tableWidget->item(row,2)->text(); out << address << clientAddress << fileName; break; } case Refuse: { out << serverAddress; break; } } udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast, port); } 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 false; } QString Widget::getMessage() //獲得要發送的信息 { QString msg = ui->textEdit->toHtml(); ui->textEdit->clear(); ui->textEdit->setFocus(); return msg; } void Widget::closeEvent(QCloseEvent *) { sendMessage(ParticipantLeft); } void Widget::sentFileName(QString fileName) { this->fileName = fileName; sendMessage(FileName); } 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); if(!name.isEmpty()) { TcpClient *client = new TcpClient(this); client->setFileName(name); client->setHostAddress(QHostAddress(serverAddress)); client->show(); } } else{ sendMessage(Refuse,serverAddress); } } } void Widget::on_send_clicked()//發送 { sendMessage(Message); } void Widget::on_sendfile_clicked() { if(ui->tableWidget->selectedItems().isEmpty()) { QMessageBox::warning(0,tr("選擇用戶"),tr("請先從用戶列表選擇要傳送的用戶!"),QMessageBox::Ok); return; } server->show(); server->initServer(); } void Widget::on_close_clicked()//關閉 { this->close(); } bool Widget::eventFilter(QObject *target, QEvent *event) { if(target == ui->textEdit) { if(event->type() == QEvent::KeyPress)//回車鍵 { QKeyEvent *k = static_cast<QKeyEvent *>(event); if(k->key() == Qt::Key_Return) { on_send_clicked(); return true; } } } return QWidget::eventFilter(target,event); } void Widget::on_fontComboBox_currentFontChanged(QFont f)//字體設置 { ui->textEdit->setCurrentFont(f); ui->textEdit->setFocus(); } //字體大小設置 void Widget::on_fontsizecomboBox_currentIndexChanged(QString size) { ui->textEdit->setFontPointSize(size.toDouble()); ui->textEdit->setFocus(); } void Widget::on_textbold_clicked(bool checked) { if(checked) ui->textEdit->setFontWeight(QFont::Bold); else ui->textEdit->setFontWeight(QFont::Normal); ui->textEdit->setFocus(); } void Widget::on_textitalic_clicked(bool checked) { ui->textEdit->setFontItalic(checked); ui->textEdit->setFocus(); } void Widget::on_textUnderline_clicked(bool checked) { ui->textEdit->setFontUnderline(checked); ui->textEdit->setFocus(); } void Widget::on_textcolor_clicked() { color = QColorDialog::getColor(color,this); if(color.isValid()) { ui->textEdit->setTextColor(color); ui->textEdit->setFocus(); } } void Widget::on_save_clicked()//保存聊天記錄 { if(ui->textBrowser->document()->isEmpty()) QMessageBox::warning(0,tr("警告"),tr("聊天記錄為空,無法保存!"),QMessageBox::Ok); else { //獲得文件名,注意getSaveFileName函數的格式即可 QString fileName = QFileDialog::getSaveFileName(this,tr("保存聊天記錄"),tr("聊天記錄"),tr("文本(*.txt);;All File(*.*)")); if(!fileName.isEmpty()) saveFile(fileName); } } bool Widget::saveFile(const QString &fileName)//保存文件 { QFile file(fileName); if(!file.open(QFile::WriteOnly | QFile::Text)) { QMessageBox::warning(this,tr("保存文件"), tr("無法保存文件 %1:\n %2").arg(fileName) .arg(file.errorString())); return false; } QTextStream out(&file); out << ui->textBrowser->toPlainText(); return true; } void Widget::on_clear_clicked()//清空聊天記錄 { ui->textBrowser->clear(); } void Widget::on_tableWidget_doubleClicked(QModelIndex index) { if(ui->tableWidget->item(index.row(),0)->text() == getUserName() && ui->tableWidget->item(index.row(),2)->text() == getIP()) { QMessageBox::warning(0,tr("警告"),tr("你不可以跟自己聊天!!!"),QMessageBox::Ok); } else { // else if(!privatechat){ // chat *privatechatTemp; privatechat = new chat(ui->tableWidget->item(index.row(),1)->text(), //接收主機名 ui->tableWidget->item(index.row(),2)->text()) ;//接收用戶IP } // if( privatechat->is_opened )delete privatechat;//如果其曾經顯示過則刪除掉 QByteArray data; QDataStream out(&data,QIODevice::WriteOnly); QString localHostName = QHostInfo::localHostName(); QString address = getIP(); out << Xchat << getUserName() << localHostName << address; udpSocket->writeDatagram(data,data.length(),QHostAddress::QHostAddress(ui->tableWidget->item(index.row(),2)->text()), port); // privatechat->xchat->writeDatagram(data,data.length(),QHostAddress::QHostAddress(ui->tableWidget->item(index.row(),2)->text()), 45456); // if(!privatechat->is_opened) privatechat->show(); privatechat->is_opened = true; // (privatechat->a) = 0; } } void Widget::showxchat(QString name, QString ip) { // if(!privatechat){ // chat *privatechatTemp; if(!privatechat1) privatechat1 = new chat(name,ip); // privatechat = privatechatTemp;} // chat privatechat(name,ip);//如果不用new函數,則程序運行時只是閃爍顯示一下就沒了,因為類的生命周期結束了 // privatechat->is_opened = false; // privatechat->show(); //privatechat.textBrowser.show(); //privatechat->is_opened = true; bb++; //delete privatechat; }
tcpclient.h:
#ifndef TCPCLIENT_H #define TCPCLIENT_H #include <QDialog> #include <QTcpSocket> #include <QHostAddress> #include <QFile> #include <QTime> 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){localFile = new QFile(fileName);} protected: void changeEvent(QEvent *e); 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 "tcpserver.h" #include "ui_tcpserver.h" #include <QTcpSocket> #include <QFileDialog> #include <QMessageBox> TcpServer::TcpServer(QWidget *parent):QDialog(parent), ui(new Ui::TcpServer) { ui->setupUi(this); this->setFixedSize(350,180); tcpPort = 6666; tcpServer = new QTcpServer(this); connect(tcpServer,SIGNAL(newConnection()),this,SLOT(sendMessage())); initServer(); } TcpServer::~TcpServer() { delete ui; } void TcpServer::changeEvent(QEvent *e) { QDialog::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); break; default: break; } } void TcpServer::sendMessage() //開始發送數據 { ui->serverSendBtn->setEnabled(false); clientConnection = tcpServer->nextPendingConnection(); connect(clientConnection,SIGNAL(bytesWritten(qint64)),SLOT(updateClientProgress(qint64))); ui->serverStatusLabel->setText(tr("開始傳送文件 %1 !").arg(theFileName)); localFile = new QFile(fileName); if(!localFile->open((QFile::ReadOnly))){//以只讀方式打開 QMessageBox::warning(this,tr("應用程序"),tr("無法讀取文件 %1:\n%2").arg(fileName).arg(localFile->errorString())); return; } TotalBytes = localFile->size(); QDataStream sendOut(&outBlock,QIODevice::WriteOnly); sendOut.setVersion(QDataStream::Qt_4_6); time.start(); //開始計時 QString currentFile = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1); sendOut<<qint64(0)<<qint64(0)<<currentFile; TotalBytes += outBlock.size(); sendOut.device()->seek(0); sendOut<<TotalBytes<<qint64((outBlock.size()-sizeof(qint64)*2)); bytesToWrite = TotalBytes - clientConnection->write(outBlock); qDebug()<<currentFile<<TotalBytes; outBlock.resize(0); } void TcpServer::updateClientProgress(qint64 numBytes)//更新進度條 { bytesWritten += (int)numBytes; if(bytesToWrite > 0){ outBlock = localFile->read(qMin(bytesToWrite,loadSize)); bytesToWrite -= (int)clientConnection->write(outBlock); outBlock.resize(0); } else{ localFile->close(); } ui->progressBar->setMaximum(TotalBytes); ui->progressBar->setValue(bytesWritten); float useTime = time.elapsed(); double speed = bytesWritten / useTime; ui->serverStatusLabel->setText(tr("已發送 %1MB (%2MB/s) \n共%3MB 已用時:%4秒\n估計剩余時間:%5秒") .arg(bytesWritten / (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));//剩余時間 //num.sprintf("%.1f KB/s", (bytesWritten*1000) / (1024.0*time.elapsed())); if(bytesWritten == TotalBytes) ui->serverStatusLabel->setText(tr("傳送文件 %1 成功").arg(theFileName)); } void TcpServer::on_serverOpenBtn_clicked() //打開 { fileName = QFileDialog::getOpenFileName(this); if(!fileName.isEmpty()) { theFileName = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1); ui->serverStatusLabel->setText(tr("要傳送的文件為:%1 ").arg(theFileName)); ui->serverSendBtn->setEnabled(true); ui->serverOpenBtn->setEnabled(false); } } void TcpServer::refused() //被對方拒絕 { tcpServer->close(); ui->serverStatusLabel->setText(tr("對方拒絕接收!!!")); } void TcpServer::on_serverSendBtn_clicked() //發送 { if(!tcpServer->listen(QHostAddress::Any,tcpPort))//開始監聽 { qDebug() << tcpServer->errorString(); close(); return; } ui->serverStatusLabel->setText(tr("等待對方接收... ...")); emit sendFileName(theFileName); } void TcpServer::on_serverCloseBtn_clicked()//退出 { if(tcpServer->isListening()) { tcpServer->close(); clientConnection->abort(); } this->close(); } void TcpServer::initServer()//初始化 { loadSize = 4*1024; TotalBytes = 0; bytesWritten = 0; bytesToWrite = 0; ui->serverStatusLabel->setText(tr("請選擇要傳送的文件")); ui->progressBar->reset(); ui->serverOpenBtn->setEnabled(true); ui->serverSendBtn->setEnabled(false); tcpServer->close(); }
tcpserver.h:
#ifndef TCPSERVER_H #define TCPSERVER_H #include <QDialog> #include <QTcpServer> #include <QFile> #include <QTime> namespace Ui { class TcpServer; } class TcpServer : public QDialog { Q_OBJECT public: explicit TcpServer(QWidget *parent = 0); ~TcpServer(); void refused(); void initServer(); protected: void changeEvent(QEvent *e); private: Ui::TcpServer *ui; qint16 tcpPort; QTcpServer *tcpServer; QString fileName; QString theFileName; QFile *localFile; qint64 TotalBytes; qint64 bytesWritten; qint64 bytesToWrite; qint64 loadSize; QByteArray outBlock;//緩存一次發送的數據 QTcpSocket *clientConnection; QTime time;//計時器 private slots: void on_serverSendBtn_clicked(); void on_serverCloseBtn_clicked(); void on_serverOpenBtn_clicked(); void sendMessage(); void updateClientProgress(qint64 numBytes); signals: void sendFileName(QString fileName); }; #endif // TCPSERVER_H
tcpserver.cpp:
#include "tcpserver.h" #include "ui_tcpserver.h" #include <QTcpSocket> #include <QFileDialog> #include <QMessageBox> TcpServer::TcpServer(QWidget *parent):QDialog(parent), ui(new Ui::TcpServer) { ui->setupUi(this); this->setFixedSize(350,180); tcpPort = 6666; tcpServer = new QTcpServer(this); connect(tcpServer,SIGNAL(newConnection()),this,SLOT(sendMessage())); initServer(); } TcpServer::~TcpServer() { delete ui; } void TcpServer::changeEvent(QEvent *e) { QDialog::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); break; default: break; } } void TcpServer::sendMessage() //開始發送數據 { ui->serverSendBtn->setEnabled(false); clientConnection = tcpServer->nextPendingConnection(); connect(clientConnection,SIGNAL(bytesWritten(qint64)),SLOT(updateClientProgress(qint64))); ui->serverStatusLabel->setText(tr("開始傳送文件 %1 !").arg(theFileName)); localFile = new QFile(fileName); if(!localFile->open((QFile::ReadOnly))){//以只讀方式打開 QMessageBox::warning(this,tr("應用程序"),tr("無法讀取文件 %1:\n%2").arg(fileName).arg(localFile->errorString())); return; } TotalBytes = localFile->size(); QDataStream sendOut(&outBlock,QIODevice::WriteOnly); sendOut.setVersion(QDataStream::Qt_4_6); time.start(); //開始計時 QString currentFile = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1); sendOut<<qint64(0)<<qint64(0)<<currentFile; TotalBytes += outBlock.size(); sendOut.device()->seek(0); sendOut<<TotalBytes<<qint64((outBlock.size()-sizeof(qint64)*2)); bytesToWrite = TotalBytes - clientConnection->write(outBlock); qDebug()<<currentFile<<TotalBytes; outBlock.resize(0); } void TcpServer::updateClientProgress(qint64 numBytes)//更新進度條 { bytesWritten += (int)numBytes; if(bytesToWrite > 0){ outBlock = localFile->read(qMin(bytesToWrite,loadSize)); bytesToWrite -= (int)clientConnection->write(outBlock); outBlock.resize(0); } else{ localFile->close(); } ui->progressBar->setMaximum(TotalBytes); ui->progressBar->setValue(bytesWritten); float useTime = time.elapsed(); double speed = bytesWritten / useTime; ui->serverStatusLabel->setText(tr("已發送 %1MB (%2MB/s) \n共%3MB 已用時:%4秒\n估計剩余時間:%5秒") .arg(bytesWritten / (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));//剩余時間 //num.sprintf("%.1f KB/s", (bytesWritten*1000) / (1024.0*time.elapsed())); if(bytesWritten == TotalBytes) ui->serverStatusLabel->setText(tr("傳送文件 %1 成功").arg(theFileName)); } void TcpServer::on_serverOpenBtn_clicked() //打開 { fileName = QFileDialog::getOpenFileName(this); if(!fileName.isEmpty()) { theFileName = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1); ui->serverStatusLabel->setText(tr("要傳送的文件為:%1 ").arg(theFileName)); ui->serverSendBtn->setEnabled(true); ui->serverOpenBtn->setEnabled(false); } } void TcpServer::refused() //被對方拒絕 { tcpServer->close(); ui->serverStatusLabel->setText(tr("對方拒絕接收!!!")); } void TcpServer::on_serverSendBtn_clicked() //發送 { if(!tcpServer->listen(QHostAddress::Any,tcpPort))//開始監聽 { qDebug() << tcpServer->errorString(); close(); return; } ui->serverStatusLabel->setText(tr("等待對方接收... ...")); emit sendFileName(theFileName); } void TcpServer::on_serverCloseBtn_clicked()//退出 { if(tcpServer->isListening()) { tcpServer->close(); clientConnection->abort(); } this->close(); } void TcpServer::initServer()//初始化 { loadSize = 4*1024; TotalBytes = 0; bytesWritten = 0; bytesToWrite = 0; ui->serverStatusLabel->setText(tr("請選擇要傳送的文件")); ui->progressBar->reset(); ui->serverOpenBtn->setEnabled(true); ui->serverSendBtn->setEnabled(false); tcpServer->close(); }
chat.h:
#ifndef CHAT_H #define CHAT_H #include <QDialog> #include <QtNetwork> #include <QtGui> #include "tcpclient.h" #include "tcpserver.h" namespace Ui { class chat; } enum MessageType { Message, NewParticipant, ParticipantLeft, FileName, Refuse, Xchat }; class chat : public QDialog { Q_OBJECT public: ~chat(); // chat(); chat(QString pasvusername, QString pasvuserip); QString xpasvuserip; QString xpasvusername; QUdpSocket *xchat; qint32 xport; void sendMessage(MessageType type,QString serverAddress=""); quint16 a; // static qint32 is_opened = 0; bool is_opened; public slots: protected: void hasPendingFile(QString userName,QString serverAddress, //接收文件 QString clientAddress,QString fileName); void participantLeft(QString userName,QString localHostName,QString time); bool eventFilter(QObject *target, QEvent *event); //事件過濾器 private: Ui::chat *ui; TcpServer *server; QColor color;//顏色 bool saveFile(const QString& fileName);//保存聊天記錄 QString getMessage(); QString getIP(); QString getUserName(); QString message; QString fileName; private slots: void sentFileName(QString); void on_sendfile_clicked(); void processPendingDatagrams(); void on_send_clicked(); void on_close_clicked(); void on_clear_clicked(); void on_save_clicked(); void on_textcolor_clicked(); void on_textUnderline_clicked(bool checked); void on_textitalic_clicked(bool checked); void on_textbold_clicked(bool checked); void on_fontComboBox_currentFontChanged(QFont f); void on_fontsizecomboBox_currentIndexChanged(QString ); void currentFormatChanged(const QTextCharFormat &format); }; #endif // CHAT_H
chat.cpp:
#include "chat.h" #include "ui_chat.h" //chat::chat():ui(new Ui::chat) //{ // is_opened = false; //} chat::chat(QString pasvusername, QString pasvuserip) : ui(new Ui::chat) { ui->setupUi(this); ui->textEdit->setFocusPolicy(Qt::StrongFocus); ui->textBrowser->setFocusPolicy(Qt::NoFocus); ui->textEdit->setFocus(); ui->textEdit->installEventFilter(this); a = 0; is_opened = false; // this->is_opened = false; xpasvusername = pasvusername; xpasvuserip = pasvuserip; ui->label->setText(tr("與%1聊天中 對方IP:%2").arg(xpasvusername).arg(pasvuserip)); //UDP部分 xchat = new QUdpSocket(this); xport = 45456; // xchat->bind(xport, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint); xchat->bind( QHostAddress::QHostAddress(getIP()), xport ); connect(xchat, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams())); //TCP部分 server = new TcpServer(this); connect(server,SIGNAL(sendFileName(QString)),this,SLOT(sentFileName(QString))); connect(ui->textEdit,SIGNAL(currentCharFormatChanged(QTextCharFormat)),this,SLOT(currentFormatChanged(const QTextCharFormat))); } chat::~chat() { is_opened = false; delete ui; } bool chat::eventFilter(QObject *target, QEvent *event) { if(target == ui->textEdit) { if(event->type() == QEvent::KeyPress)//按下鍵盤某鍵 { QKeyEvent *k = static_cast<QKeyEvent *>(event); if(k->key() == Qt::Key_Return)//回車鍵 { on_send_clicked(); return true; } } } return QWidget::eventFilter(target,event); } //處理用戶離開 void chat::participantLeft(QString userName,QString localHostName,QString time) { ui->textBrowser->setTextColor(Qt::gray); ui->textBrowser->setCurrentFont(QFont("Times New Roman",10)); ui->textBrowser->append(tr("%1 於 %2 離開!").arg(userName).arg(time)); } QString chat::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 false; } QString chat::getIP() //獲取ip地址 { QList<QHostAddress> list = QNetworkInterface::allAddresses(); foreach (QHostAddress address, list) { if(address.protocol() == QAbstractSocket::IPv4Protocol) //我們使用IPv4地址 return address.toString(); } return 0; } void chat::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); if(!name.isEmpty()) { TcpClient *client = new TcpClient(this); client->setFileName(name); client->setHostAddress(QHostAddress(serverAddress)); client->show(); } } else{ sendMessage(Refuse,serverAddress); } } } void chat::processPendingDatagrams() //接收數據UDP { while(xchat->hasPendingDatagrams()) { QByteArray datagram; datagram.resize(xchat->pendingDatagramSize()); xchat->readDatagram(datagram.data(),datagram.size()); QDataStream in(&datagram,QIODevice::ReadOnly); int messageType; in >> messageType; QString userName,localHostName,ipAddress,messagestr; QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); switch(messageType) { case Xchat: { // ui.show(); break; } case Message: { //這2條語句都沒有用。why??、 /*this->hide(); this->close();*/ in >>userName >>localHostName >>ipAddress >>messagestr; ui->textBrowser->setTextColor(Qt::blue); ui->textBrowser->setCurrentFont(QFont("Times New Roman",12)); ui->textBrowser->append("[ " +localHostName+" ] "+ time);//與主機名聊天中 ui->textBrowser->append(messagestr); // ui->textBrowser->show(); //this->textBrowser->setTextColor(Qt::blue); //this->textBrowser->setCurrentFont(QFont("Times New Roman",12)); //this->textBrowser->append("[ " +localHostName+" ] "+ time);//與主機名聊天中 //this->textBrowser->append(messagestr); // a ++; // if( is_opened == false )//加了這句,接收端B不顯示端口了 { this->show();////解決bug1.收到私聊消息后才顯示 // ui->textBrowser->show(); // this->show(); // ui->textBrowser->show(); // ui.show(); // if( this->show() ) // this->hide(); // 0 == a; is_opened = true; } 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; } case ParticipantLeft: { in >>userName >>localHostName; participantLeft(userName,localHostName,time); QMessageBox::information(0,tr("本次對話關閉"),tr("對方結束了對話"),QMessageBox::Ok); a = 1; ui->textBrowser->clear(); //is_opened = true; // this->is_opened = false; ui->~chat(); close(); // delete ui; // ui = 0; break; } } } } void chat::sentFileName(QString fileName) { this->fileName = fileName; sendMessage(FileName); } QString chat::getMessage() //獲得要發送的信息 { QString msg = ui->textEdit->toHtml(); qDebug()<<msg; ui->textEdit->clear(); ui->textEdit->setFocus(); return msg; } //通過私聊套接字發送到對方的私聊專用端口上 void chat::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 ParticipantLeft: { break; } case Message : { if(ui->textEdit->toPlainText() == "") { QMessageBox::warning(0,tr("警告"),tr("發送內容不能為空"),QMessageBox::Ok); return; } message = getMessage(); out << address << message; ui->textBrowser->verticalScrollBar()->setValue(ui->textBrowser->verticalScrollBar()->maximum()); break; } case FileName: { QString clientAddress = xpasvuserip; out << address << clientAddress << fileName; break; } case Refuse: { out << serverAddress; break; } } xchat->writeDatagram(data,data.length(),QHostAddress::QHostAddress(xpasvuserip), 45456); } void chat::currentFormatChanged(const QTextCharFormat &format) {//當編輯器的字體格式改變時,我們讓部件狀態也隨之改變 ui->fontComboBox->setCurrentFont(format.font()); if(format.fontPointSize()<9) //如果字體大小出錯,因為我們最小的字體為9 { ui->fontsizecomboBox->setCurrentIndex(3); //即顯示12 } else { ui->fontsizecomboBox->setCurrentIndex(ui->fontsizecomboBox->findText(QString::number(format.fontPointSize()))); } ui->textbold->setChecked(format.font().bold()); ui->textitalic->setChecked(format.font().italic()); ui->textUnderline->setChecked(format.font().underline()); color = format.foreground().color(); } void chat::on_fontComboBox_currentFontChanged(QFont f)//字體設置 { ui->textEdit->setCurrentFont(f); ui->textEdit->setFocus(); } void chat::on_fontsizecomboBox_currentIndexChanged(QString size) { ui->textEdit->setFontPointSize(size.toDouble()); ui->textEdit->setFocus(); } void chat::on_textbold_clicked(bool checked) { if(checked) ui->textEdit->setFontWeight(QFont::Bold); else ui->textEdit->setFontWeight(QFont::Normal); ui->textEdit->setFocus(); } void chat::on_textitalic_clicked(bool checked) { ui->textEdit->setFontItalic(checked); ui->textEdit->setFocus(); } void chat::on_save_clicked()//保存聊天記錄 { if(ui->textBrowser->document()->isEmpty()) QMessageBox::warning(0,tr("警告"),tr("聊天記錄為空,無法保存!"),QMessageBox::Ok); else { //獲得文件名 QString fileName = QFileDialog::getSaveFileName(this,tr("保存聊天記錄"),tr("聊天記錄"),tr("文本(*.txt);;All File(*.*)")); if(!fileName.isEmpty()) saveFile(fileName); } } void chat::on_clear_clicked()//清空聊天記錄 { ui->textBrowser->clear(); } bool chat::saveFile(const QString &fileName)//保存文件 { QFile file(fileName); if(!file.open(QFile::WriteOnly | QFile::Text)) { QMessageBox::warning(this,tr("保存文件"), tr("無法保存文件 %1:\n %2").arg(fileName) .arg(file.errorString())); return false; } QTextStream out(&file); out << ui->textBrowser->toPlainText(); return true; } void chat::on_textUnderline_clicked(bool checked) { ui->textEdit->setFontUnderline(checked); ui->textEdit->setFocus(); } void chat::on_textcolor_clicked() { color = QColorDialog::getColor(color,this); if(color.isValid()) { ui->textEdit->setTextColor(color); ui->textEdit->setFocus(); } } void chat::on_close_clicked() { sendMessage(ParticipantLeft); a = 1; ui->textBrowser->clear(); //is_opened = true; // this->is_opened = false; close(); ui->~chat(); //this->close(); /*delete ui; ui = 0;*/ } void chat::on_send_clicked() { sendMessage(Message); QString localHostName = QHostInfo::localHostName(); QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); ui->textBrowser->setTextColor(Qt::blue); ui->textBrowser->setCurrentFont(QFont("Times New Roman",12)); ui->textBrowser->append("[ " +localHostName+" ] "+ time); ui->textBrowser->append(message); // is_opened = true; } void chat::on_sendfile_clicked() { server->show(); server->initServer(); }
main:
#include <QtGui/QApplication> #include "widget.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; QTextCodec::setCodecForTr(QTextCodec::codecForLocale()); w.show(); return a.exec(); }
參考資料:
附錄:
