在上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方(主动开始首次发送的一方):

  1. 在主窗口右侧双击自己想与之聊天的B方,此时A方实际上完成的工作有:用B方的主机名和ip地址新建了私聊的类privatechat,在新建该类的过程中,已经设置了显示顶端为:与***聊天中,对方IP:***,且绑定了本地ip和私聊的专用端口,同时设置了信号与槽的联系,即该端口如果有数据输入,则触发槽函数processPendingDatagrams().该函数是char.cpp中的。
  2. 当上面的新建私聊类完成后,用通讯对方ip地址和其群聊专用的端口(但用的是主udp群聊的socket进行的)将以下内容分别发送出去:消息类型(Xchat),用户名,主机名,本地ip地址。完成后,在屏幕中显示私聊窗口。
  3. 在私聊窗口中输入需要聊天的内容,单击发送键。该过程玩成的内容有:分别将消息类型(Message)+用户名+本地名+本地IP+消息内容本身通过私聊专用端口发送出去。在私聊窗口中显示主机名+聊天时间,换行后显示消息内容本身。

  B方(第一次信息是他人发送过来的):

  1. 当A在2步骤中用群聊的方法发送其消息类型(Xchat),其用户名,其主机名,其ip地址后,由于程序运行时已经初始化了widget.cpp中的构造函数,所以每个程序都绑定了本地地址+群聊专用的端口,一旦有数据传入,就触发widget.cpp中的槽函数processPendingDatagrams().
  2. 在processPendingDatagrams()函数中,判断消息类型为Xchat后,接收缓存区内接收对方用户名,对方主机名和对方ip地址。并用接收到的主机名和ip地址新建一个私聊类。新建该私聊的过程与A中的步骤1一样。完后在程序中显示私聊窗口。
  3. 当对方A按完发送按钮后,通过私聊专用端口绑定槽函数来触发chart.cpp中的processPendingDatagrams()函数,该函数中先读取消息类型(Message),然后依次读取用户名,主机名,ip地址,消息内容本身,并将对方信息和消息内容显示在聊天窗口中。

  实验结果如下

  群聊界面:

  

  

  私聊界面:

  

  

  文件传输过程截图:

  

  

  实验总结(下面几点只是暂时的理解):

  1. 使用类时,如果直接用构造函数定义该类的对象,则定义该类的函数接收时,该对象的生命也就结束了,所以如果要在其他函数中定义一个类的对象时并长久使用,可以使用new定义一个对象的初始指针。这样就在内存中永存了。
  2. 如果某个窗口类需要显示时直接调用其指针->show()或者其对象-.show(),这个函数只是将内存中该类的对象显示出来而已(因为与界面有关),并不是重新建一个类对象。其表示该类的界面等可以显示,所以一旦show过即使改变了界面的内容,后面也无需一直调用show函数,界面会自动显示的。
  3. 当关闭某个窗口时,只是将其隐藏,并没有释放其内存。

程序源码(附录有工程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_OBJECTpublic: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);elseui->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{//    elseif(!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_OBJECTpublic: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_OBJECTpublic: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_OBJECTpublic:~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);elseui->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();
}

  参考资料:

http://www.yafeilinux.com/

  附录:

   实验工程code下载。

Qt学习之路_6(Qt局域网聊天软件)相关推荐

  1. Qt学习之路(17): Qt标准对话框之QMessageBox

    好久没有更新博客,主要是公司里面还在验收一些东西,所以没有及时更新.而且也在写一个基于Qt的画图程序,基本上类似于PS的东西,主要用到的是Qt Graphics View Framework.好了,现 ...

  2. Qt学习之路(35): Qt容器类之顺序存储容器

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://devbean.blog.51cto.com/448512/245988 本来计划 ...

  3. Qt 学习之路 :Qt 线程相关类

      希望上一章有关事件循环的内容还没有把你绕晕.本章将重新回到有关线程的相关内容上面来.在前面的章节我们了解了有关QThread类的简单使用.不过,Qt 提供的有关线程的类可不那么简单,否则的话我们也 ...

  4. Qt学习之路(37): Qt容器类之关联存储容器

    2019独角兽企业重金招聘Python工程师标准>>> 今天我们来说说Qt容器类中的关联存储容器.所谓关联存储容器,就是容器中存储的一般是二元组,而不是单个的对象.二元组一般表述为& ...

  5. c语言编一个dll 用message box 弹出一个对话框,Qt学习之路(17): Qt标准对话框之QMessageBox...

    2015-06-25 16:41 下面的是vc6中创建的dll程序|@||@|// TestDll2QT.cpp : Defines the initialization routines for t ...

  6. 转载: Qt 学习之路 2归档

    Qt 学习之路 2归档 http://www.devbean.net/2012/08/qt-study-road-2-catelog/

  7. 对QT学习之路12-14的源代码补充与修正

    QT学习之路12-14的源代码有些不完整,为了更好的让大家学习,本人做了一点修正与补充,谢谢.源代码如下: 头文件: #ifndef MAINWINDOW_H #define MAINWINDOW_H ...

  8. java qt gui_工控编程,Qt 学习之路

    原标题:工控编程,Qt 学习之路 Qt 是一个著名的 C++ 库--或许并不能说这只是一个 GUI 库,因为 Qt 十分庞大,并不仅仅是 GUI.使用 Qt,在一定程序上你获得的是一个"一站 ...

  9. Qt学习之路_12(简易数据管理系统)

    原文地址为: Qt学习之路_12(简易数据管理系统) 前言 最近从大陆来到台湾,之间杂事很多,挤不出时间来更新博客- 这次主要是通过做一个简易的数据库管理系统,来学习在Qt中对数据库,xml,界面的各 ...

  10. QT学习之路2 学习笔记

    QT学习之路2 学习笔记 1.Qt 是一个著名的 C++ 应用程序框架.你并不能说它只是一个 GUI 库,因为 Qt 十分庞大,并不仅仅是 GUI 组件.使用 Qt,在一定程度上你获得的是一个&quo ...

最新文章

  1. Interview Q A 程序会在哪一行死掉
  2. iOS学习笔记---oc语言第八天
  3. flask html css文件更改后(谷歌)浏览器不及时更新样式文件怎么办?(ctrl+shift+delete清除缓存的图片和文件)
  4. MySQL InnoDB Cluster安装
  5. 能用python做信号处理吗_Python中的信号处理
  6. centos7如何安装cloud-init
  7. 产品经理应聘之感受漫谈
  8. json 转对象函数_JSON_QUERY()函数从JSON数据提取对象
  9. python是什么课程-Python课程包括哪些内容?
  10. win10如何找计算机管理员密码,win10怎么修改administrator账户密码 win10修改管理员账户密码方法...
  11. 圆圆曲(清.吴伟业)
  12. 渗透测试思路 - 关于黑灰产的那些事(番外篇)
  13. OTU/ASV/Feature tabel 表格 过滤 相对丰度 微生物
  14. Python学习笔记--文件操作
  15. 宽带和光纤宽带的区别
  16. 微信模拟登陆php投票,PHP爬虫和微信自动投票
  17. 如何区分好老板和坏老板?
  18. Failing Recovery Loader
  19. 4月20日----4月24日二年级课程表
  20. PCIe 链路训练学习---------Training Sequence (TS1 TS2序列)

热门文章

  1. 逐步完善自己的3D引擎
  2. 练习四十四:整数的排序
  3. Eolinker——前置用例返回的reponse值进行传递
  4. greenplum数据库建表及分区
  5. Hadoop可视化与交互式工具:Zeppelin和Hue
  6. 条件注释判断IE浏览器
  7. Mountain Lion 10.8
  8. Silverlight学习之贪吃蛇游戏
  9. 一文带你彻底理解ROC曲线和AUC值
  10. R_ggplot2地理信息可视化_史上最全(二)