Qt学习之路_6(Qt局域网聊天软件)

原文地址为: Qt学习之路_6(Qt局域网聊天软件)

  

  在上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_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();
}

   参考资料:

      http://www.yafeilinux.com/

  

   附录:

   实验工程code下载

  


转载请注明本文地址: Qt学习之路_6(Qt局域网聊天软件)

猜你喜欢

转载自blog.csdn.net/hong2511/article/details/81808456