一、设计要求
二、开发环境与工具
三、设计原理
四、系统功能描述及软件模块划分
五、设计步骤
本次系统的设计主要是为了实现三个功能,一是使用TCP协议完成用户一对一的私聊;二是使用UDP协议完成广播信息的处理;三是使用TCP协议完成用户一对一之间的文件传输。
六、关键问题及其解决方法
七:部分源码
#ifndef WIDGET_H
#define WIDGET_H
#include
#include //监听套接字头文件
#include
#include //通信套接字头文件
#include //容器头文件
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
void upDateMsg(); //更新用户信息
private:
Ui::Widget *ui;
private slots:
void udp_ReadyRead();
void process_Data(void); //tcp 消息处理函数
void tcp_ReadyRead();
private:
QString getIP(); //得到本地IP地址
private:
int usrNum; //用户总数
QTcpServer *tcpServer;
QTcpSocket *tcpSocket;
QUdpSocket *udpSocket;
int tPort; //TCP端口
QMap<QString,QString> usrMap;
QMap<QString,QTcpSocket*> tcpusr;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
#include
#include
#include
#include //数据流头文件
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
ui->textBrowser->setText("开始!");
usrNum = 0;
//处理用户表格
QStringList header;
header<<tr("用户名")<<tr("IP地址");
ui->tableWidget->setHorizontalHeaderLabels(header);
ui->tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
ui->tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->tableWidget->show();
//udp初设设置,允许其他服务绑定同样的地址和端口
udpSocket =new QUdpSocket(this);
udpSocket->bind(5555,QUdpSocket::ShareAddress|QUdpSocket::ReuseAddressHint);
//udp消息处理函数
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(udp_ReadyRead()));
//QTcpserver设置
tcpSocket =new QTcpSocket(this);
tcpServer=new QTcpServer(this);
//有新连接请求时执行
connect(tcpServer, SIGNAL(newConnection()),this,SLOT(process_Data()));
tPort = 6666;
if (!tcpServer->listen(QHostAddress::Any,tPort))
{
qDebug() << tcpServer->errorString();
close();
return;
}
}
//!析构函数,释放申请的内存
Widget::~Widget()
{
delete ui;
usrMap.clear();
tcpusr.clear();
}
//*消息处理函数
//.完成得功能有:
//1.新用户注册处理
//2.用户离开处理
//3.用户转发消息处理
//tcp 连接成功触发newConnection ,取出套接字
void Widget::process_Data()
{
//获得当前套接字
tcpSocket = tcpServer->nextPendingConnection();
//qDebug()<<"收到一个新的客户连接\r\n客户端的IP:"<peerAddress().toString();
QString ip = tcpSocket->peerAddress().toString();
quint16 port = tcpSocket->peerPort();
QString str = QString("收到一个新的客户连接客户端的IP:[%1],端口号:[%2]").arg(ip).arg(port);
ui->textBrowser->append(str);
//获得对方IP并储存套接字
tcpusr.insert(tcpSocket->peerAddress().toString().mid(7),tcpSocket);
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(tcp_ReadyRead()));
}
//连接请求处理函数
void Widget::tcp_ReadyRead()
{
QTcpSocket * tcp ;
QTcpSocket * tcp_to ;
//获得那个信号过来的
tcp=qobject_cast <QTcpSocket*>(sender());
//qDebug()<<"即将转发一条消息\r\n"<<"消息来自:"<localAddress();
QString ip = tcp->peerAddress().toString();
QString str = QString("即将转发一条消息,消息来自:[%1]").arg(ip);
ui->textBrowser->append(str);
//消息可读
if(tcp->isReadable())
{
quint16 blockSize = 0;
QByteArray array = tcp->readAll();
array.resize(array.size());
QDataStream in(&array,QIODevice::ReadOnly);
QByteArray data;
QDataStream out(&data,QIODevice::WriteOnly);
if (blockSize == 0) {
in >> blockSize;
}
/* MsgType:信息类型
* fromname:源用户名称
* fromip:源IP地址
* msg:转发内容
* toname:目标用户名称
* toip:目的IP地址
*/
QString MsgType,fromname,fromip,msg,toname,toip;
in>>MsgType>>fromname>>fromip>>msg>>toname>>toip;
if(MsgType == "transfer")
{
//可能是fromip
tcp_to = tcpusr.value(fromip);
//把来源和信息发送过去
out<<fromname<<fromip<<msg<<toname<<toip;
tcp_to->write(data);
//qDebug()<<"消息转发完成\r\n"<<"内容:"<
QString str = QString("消息转发完成,内容:[%1]转发的目的地址:[%2]").arg(msg).arg(toip);
ui->textBrowser->append(str);
}
}
}
//udp消息处理函数
//功能:处理新加如的用户和断开连接的用户
void Widget::udp_ReadyRead()
{
while(udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
QByteArray data;
QDataStream out(&data,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_6);
datagram.resize(udpSocket->pendingDatagramSize());
//读取UDP消息
udpSocket->readDatagram(datagram.data(),datagram.size());
//读io流管理
QDataStream in(&datagram,QIODevice::ReadOnly);
//取出信息类型
qint16 msgType;
in>>msgType;
switch(msgType)
{
//处理新用户加入信息
case (qint16)2:
{
usrNum++;
QString ip;
QString usrName;
in>>ip;
//防止重复IP加入
foreach (QString value, usrMap)
{
if(value==ip)
{
if (QMessageBox::Yes==QMessageBox::warning(this,tr("提示"),tr("有重复的IP地址,请检查!")
,QMessageBox::Yes))
{
return;
}
}
}
//分配用户名
usrName =tr("Usr_") +QString::number(usrNum);
//返还服务器信息 QByteArray data
out<<(qint16)3<<usrName<<getIP();
//存入容器
usrMap.insert(usrName,ip);
//获得在线用户的总数,放入数据
out<<usrMap.size();
//Map的迭代器
QMapIterator<QString, QString> i(usrMap);
//迭代器返回所有用户
while (i.hasNext())
{
i.next();
out << i.key() << i.value();
}
QHostAddress addr;
addr.setAddress(ip);
//发送数据
udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast,5555);
//udpSocket->writeDatagram(data,data.length(),addr,5555);
//服务器数据存储
ui->tableWidget->insertRow(0);
ui->tableWidget->setItem(0,0,new QTableWidgetItem(usrName));
ui->tableWidget->setItem(0,1,new QTableWidgetItem(ip));
upDateMsg();
break;
}
//客户断开连接,清除注册的信息
case (qint16)4:
{
QString name;
in>>name;
tcpusr[usrMap.value(name)]->disconnectFromHost();
tcpusr[usrMap.value(name)]->close();
tcpusr[usrMap.value(name)]->abort();
tcpusr.remove(usrMap.value(name));
usrMap.remove(name);
//清空用户列表
ui->tableWidget->setRowCount(0);
//Map的迭代器
QMapIterator<QString, QString> i(usrMap);
//刷新用户信息
while (i.hasNext())
{
i.next();
//服务器重新数据存储
ui->tableWidget->insertRow(0);
ui->tableWidget->setItem(0,0,new QTableWidgetItem(i.key()));
ui->tableWidget->setItem(0,1,new QTableWidgetItem(i.value()));
}
upDateMsg();
break;
}
default:{break;}
}
}
}
//更新现有的用户
void Widget::upDateMsg()
{
QByteArray data;
QDataStream out(&data,QIODevice::WriteOnly);
//获得在线用户的总数,放入数据
out<<(qint16)6<<(qint16)usrMap.size();
//Map的迭代器
QMapIterator<QString, QString> i(usrMap);
//刷新用户信息
while (i.hasNext())
{
i.next();
out << i.key() << i.value();
}
//Map的迭代器
QMapIterator<QString, QString> j(usrMap);
//发送数据
udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast,5555);
//刷新用户信息
while (j.hasNext())
{
j.next();
QHostAddress addr;
addr.setAddress(j.value());
udpSocket->writeDatagram(data,data.length(),addr,5555);
}
}
//本地IP地址
QString Widget:: getIP()
{
QList<QHostAddress> addrs = QNetworkInterface::allAddresses();
for(int i = 0; i < addrs.size(); i++)
{
if (addrs.at(i).protocol() == QAbstractSocket::IPv4Protocol &&
addrs.at(i) != QHostAddress::Null &&
addrs.at(i) != QHostAddress::LocalHost &&
!addrs.at(i).toString().contains(QRegExp("^169.*$")))
{
QString a;
a = addrs.at(i).toString();
return addrs.at(i).toString();
}
}
}
#ifndef CHAT_H
#define CHAT_H
#include
#include
#include
#include
#include
#include
#include"filesend.h"
namespace Ui {
class chat;
}
class chat : public QDialog
{
Q_OBJECT
public:
explicit chat(QWidget *parent = 0);
~chat();
void showMsg(); //将接受到的消息弹出显示在窗口
public:
QString chat_ip; //对方的IP
QString chat_name; //对方的名称
QString server_ip; //服务器的IP
QString ipself; //自己的IP
QString nameself; //自己的名称
QTcpSocket *tClnt; //私聊用的TCP通信套接字
QString msg_receive; //接收的对方聊天内容
QTcpSocket *fileTcpSocket; //发送文件用的通信套接字
private:
qint16 totalBytes; //选择传送文件的大小
qint64 fileNameSize; //文件名字长度
qint64 bytesTowrite; //剩余未发送的文件大小
qint64 byteWritten; //已传送的文件大小
qint64 loadSize; //文件每次传送的大小
QFile *locFile; //文件操作
QByteArray outBlock; //发送数据缓存区
QString fileName; //传送的文件名字
FileSend *filesnd; //文件传输界面
private:
Ui::chat *ui;
private slots:
void on_sendBtn_clicked();
void on_exitBtn_clicked();
void on_FileSendBtn_clicked();
void updateData(qint64); //发送剩余文件
void isOK(); //是否收到回复信息
};
#endif
#include "chat.h"
#include "ui_chat.h"
#include
#include
#include
#include
#include
chat::chat(QWidget *parent) :
QDialog(parent),
ui(new Ui::chat)
{
ui->setupUi(this);
//给输入框输入箭头
ui->textInput->setFocus();
//4K大小的文件
loadSize =200*1024;
setWindowFlags(Qt::WindowMinimizeButtonHint);
}
//将接受到的消息弹出显示在窗口
void chat::showMsg()
{
QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
ui->MsgBrowser->setAlignment(Qt::AlignLeft);
ui->MsgBrowser->setTextColor(Qt::blue);
ui->MsgBrowser->setCurrentFont(QFont("Times New Roman",10));
ui->MsgBrowser->append("["+chat_name+"--"+time+"]");
ui->MsgBrowser->append(msg_receive+"\r\n");
this->setWindowTitle(chat_name);
}
chat::~chat()
{
delete ui;
}
//发送按钮
//获取编译区的内容并封装,发给服务器端
void chat::on_sendBtn_clicked()
{
QByteArray outBlock;
QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
QDataStream out(&outBlock,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_6);
//读取信息
QString msg =ui->textInput->toPlainText();
if(msg.isEmpty())
{
QMessageBox::warning(0,tr("warning"),tr("发送的消息不能为空!"),
QMessageBox::Ok);
return ;
}
//马上清空方面下一次输入
ui->textInput->clear();
//将发送内容显示在内容显示栏的右边
ui->MsgBrowser->setAlignment(Qt::AlignRight);
ui->MsgBrowser->setTextColor(Qt::green);
ui->MsgBrowser->setCurrentFont(QFont("Times New Roman",10));
ui->MsgBrowser->append("["+nameself+"--"+time+"]");
ui->MsgBrowser->append(msg+"\r\n");
//设置发送的数据的长度
out.device()->seek(0);
out << (quint16) 0;
//设置发送内容
//消息类型为消息转发
QString a("transfer");
out<<a;
//自己的昵称
out<<nameself;
//自己得IP
out<<ipself ;
//发送的消息
out<<msg;
//发送的目的昵称
out<<chat_name;
//送的目的ip
out<<chat_ip;
//回到字节流起始位置 重置字节流长度
out.device()->seek(0);
out << (quint16) (outBlock.size()-sizeof(quint16));
//发送消息给服务器让其中转
tClnt->write(outBlock);
}
//文件传输按钮
void chat::on_FileSendBtn_clicked()
{
QString fileTosendName;
//创建文件传输的TcpSocket,文件的端口为7777
fileTcpSocket =new QTcpSocket;
fileName = QFileDialog::getOpenFileName(this);
//获得文件按名字后开始传送
if (!fileName.isEmpty())
{
fileTosendName = fileName.right(fileName.size()-fileName.lastIndexOf('/')-1);
locFile =new QFile(fileName);
if(!locFile->open(QFile::ReadOnly))
{
QMessageBox::warning(this,tr("应用程序"),tr("无法读取文件%1:\n%2.")
.arg(fileTosendName)
.arg(locFile->errorString()));
}
totalBytes =locFile->size();
QDataStream sendOut(&outBlock,QIODevice::WriteOnly);
QString currentFile =fileName.right(fileName.size()-
fileName.lastIndexOf('/')-1);
sendOut<<qint64(0)<<qint64(0)<<currentFile;
qDebug()<<"namesize:"<<currentFile;
totalBytes += outBlock.size();
qDebug()<<"totalBytes:"<<totalBytes<<qint64((outBlock.size()-sizeof(qint64)*2));
sendOut.device()->seek(0);
sendOut<<(qint64)totalBytes<<qint64((outBlock.size()-sizeof(qint64)*2));
//开始连接客户端的IP,等待对方响应
fileTcpSocket->connectToHost(chat_ip,7777);
//最多等待20s
if(fileTcpSocket->waitForConnected(20000))
{
qDebug()<<"文件传输端口已经连接上对方服务器!";
connect(fileTcpSocket,SIGNAL(bytesWritten(qint64)),this,SLOT(updateData(qint64)));
connect(fileTcpSocket,SIGNAL(readyRead()),this,SLOT(isOK()));
}
else
{
qDebug()<<"无法连接对方的服务器!";
return;
}
}
}
//收到确认接收信息后开始发送文件
void chat::isOK()
{
qDebug()<<"收到了确认消息:";
QString msg;
QDataStream in(fileTcpSocket);
in>>msg;
if(msg =="IamReady")
{
qDebug()<<"收到了确认消息:"<<"IamReady";
//发送出去,等待响应
bytesTowrite=totalBytes - fileTcpSocket->write(outBlock);
qDebug()<<"bytesTowrite:"<<bytesTowrite;
outBlock.resize(0);
//建立文件传输界面
filesnd =new FileSend;
filesnd->LabelState("等待对方接收……");
filesnd->show();
}
if(msg =="Refuse")
{
QMessageBox::information(this,tr("提示"),tr("对方拒绝了您的文件请求"),QMessageBox::Yes);
}
}
//发送后续的文件
void chat::updateData(qint64 numBytes)
{
byteWritten += (int)numBytes;
qDebug()<<"bytesTowrite:"<<bytesTowrite;
if(bytesTowrite>0)
{
outBlock = locFile->read(qMin(bytesTowrite,loadSize));
bytesTowrite -= (int)fileTcpSocket->write(outBlock);
outBlock.resize(0);
//更新进度条
filesnd->showProcess(totalBytes,byteWritten);
filesnd->LabelState(tr("已发送 %1Mb")
.arg(byteWritten/(1024*1024)));
filesnd->show();
}
else
{
locFile->close();
filesnd->close();
QMessageBox::information(this,tr("提示"),tr("文件传送成功"),QMessageBox::Yes);
fileTcpSocket->disconnectFromHost();
fileTcpSocket->close();
}
}
//退出按钮槽函数
void chat::on_exitBtn_clicked()
{
accept();
close();
}
#include "widget.h"
#include "ui_widget.h"
#include
#include"chat.h"
#include
#include
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
tClnt = new QTcpSocket(this);
//初始化变量和窗口状态
flag = 0;
fileNameSize = 0;
bytesReceived = 0;
//udp端口
port = 5555;
setWindowTitle(tr("聊天工具"));
this->resize(650,550);
ui->groupChatBtn->setText(tr("开始群聊"));
ui->usrNameLine->setText(tr("正在连接服务器……"));
ui->groupBox->hide();
ui->groupBox->hide();
setWindowFlags(Qt::WindowMinimizeButtonHint);
//定时器
timer = new QTimer(this);
// 两秒钟后重连接服务器
timer->start(2000);
connect(timer,SIGNAL(timeout()),this,SLOT(recall(void)));
//客户端udp初始化
udpSocket = new QUdpSocket;
udpSocket->bind(port,QUdpSocket::ShareAddress|QUdpSocket::ReuseAddressHint);
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(udp_ReadyRead()));
//文件传输服务器配置
fileTcpServer = new QTcpServer(this);
connect(fileTcpServer, SIGNAL(newConnection()),this,SLOT(file_NewConc()));
//占用端口7777
if (!fileTcpServer->listen(QHostAddress::Any,7777))
{
qDebug() << fileTcpServer->errorString();
close();
return;
}
}
//!析构函数,清除内存
Widget::~Widget()
{
delete ui;
delete udpSocket;
delete timer;
delete fileTcpServer;
delete fileSocket;
dialogChat.clear();
delete locFile;
}
//文件接收连接
void Widget::file_NewConc()
{
fileSocket = fileTcpServer->nextPendingConnection();
int btn = QMessageBox::information(this,tr("接收文件"),tr("来自 %1的文件 ,是否接收?")
.arg(fileSocket->peerAddress().toString().mid(7)),
QMessageBox::Yes,QMessageBox::No);
if(btn == QMessageBox::Yes)
{
QByteArray block;
QDataStream out(&block,QIODevice::WriteOnly);
//定义文件消息标志
QString msg = "IamReady";
out<<msg;
//发送确认消息,准备接收
fileSocket->write(block);
qDebug()<<"确认消息已经发出!";
connect(fileSocket,SIGNAL(readyRead()),this,SLOT(fileRcv()));
//新建一个发送显示窗口
filercv =new FileRcv;
}
else
{
//拒绝文件传输请求
QByteArray block;
QDataStream out(&block,QIODevice::WriteOnly);
QString msg = "Refuse";
out<<msg;
fileSocket->write(block);
qDebug()<<"确认消息已经发出!";
fileSocket->disconnectFromHost();
fileSocket->close();
}
}
//文件接受槽函数
void Widget::fileRcv()
{
qDebug()<<"开始接收文件";
QDataStream in(fileSocket);
if(fileSocket->bytesAvailable() >= sizeof(qint64)*2
&&fileNameSize==0)
{
in>>TotalByte>>fileNameSize;
qDebug()<<"文件大小"<<TotalByte<<"文件名长度"<<fileNameSize;
bytesReceived += sizeof(qint64)*2;
}
if(fileSocket->bytesAvailable() >= fileNameSize
&&fileNameSize != 0)
{
in>>fileName;
qDebug()<<"文件名字"<<fileName;
bytesReceived += fileNameSize;
QString name = QFileDialog::getSaveFileName(0,tr("保存文件"),fileName);
locFile = new QFile(name);
if(!locFile->open(QFile::WriteOnly))
{
QMessageBox::warning(this,tr("应用程序"),tr("无法写入文件 %1:\n%2.")
.arg(fileName)
.arg(locFile->errorString()));
return;
}
}
//接收过程
if(bytesReceived < TotalByte)
{
bytesReceived += fileSocket->bytesAvailable();
inBlock = fileSocket->readAll();
locFile->write(inBlock);
inBlock.resize(0);
}
//实时更新进度条
filercv->showProcess(TotalByte,bytesReceived);
filercv->showMsg(tr("已接收%1MB")
.arg(bytesReceived/(1024*1024)));
filercv->show();
if(bytesReceived == TotalByte)
{
qDebug()<<"接收完成!";
QMessageBox::information(this,tr("提示"),tr("文件接收成功"),QMessageBox::Yes);
locFile->close();
filercv->close();
fileSocket->disconnectFromHost();
fileSocket->close();
}
}
//连接服务器重连接函数
//每次重播时间为2秒
void Widget::recall()
{
static int times = 0;
QByteArray data;
QDataStream out(&data,QIODevice::WriteOnly);
QString iplocal =getIP();
//写入自己得IP地址
out<<(qint16)2<<iplocal;
times++;
qDebug()<<tr("进入中断");
if(0 == flag)
{
udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast,port);
}
else
{
timer->stop();
times = 0;
}
if(2 == times)
{
timer->stop();
times = 0;
if(QMessageBox::Yes == QMessageBox::warning(this,tr("提示"),tr("请先检查服务器是否连接,或者本地网络接入,是否重新连接?"),
QMessageBox::Yes,QMessageBox::No))
{
timer->start(2000);
}
else
{
close();
}
}
}
//处理udp收到的广播消息
//1.对服务器返回的信息进行处理
//2.处理群聊信息
//3.接受服务器各种广播消息
void Widget::udp_ReadyRead()
{
qDebug()<<"收到UDP返回的广播消息";
while(udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
//QByteArray data;
//QDataStream out(&data,QIODevice::WriteOnly);
datagram.resize(udpSocket->pendingDatagramSize());
udpSocket->readDatagram(datagram.data(),datagram.size());
QDataStream in(&datagram,QIODevice::ReadOnly);
in.setVersion(QDataStream::Qt_5_6);
qint16 msgType;
in>>msgType;
switch(msgType)
{
//该类型为服务器的返回消息类型
case 3:
{
//获得服务器分配的用户名
in>>usrName;
ui->usrNameLine->setText(usrName);
//获得服务器地址
in>>serverIP;
qDebug()<<tr("服务器IP地址:")<<serverIP;
//标志位置1,通知重播窗口连接上服务器,关闭定时器
flag = 1;
timer->stop();
//获得所有在线用户信息
int num =0;
in>>num;
//建立Tcp连接
tClnt->connectToHost(serverIP,6666);
if(tClnt->waitForConnected(2000))
{
qDebug()<<"客户端以Tcp方式连接上服务器";
connect(tClnt,SIGNAL(readyRead()),this,SLOT(tcp_ReadyRead()));
}
else
{
qDebug()<<"无法连接连接上服务器!";
}
break;
}
//提交时注意加上来自自己消息的屏蔽
case 5:
{
QString msg,name;
in>>msg;
in>>name;
if(name != usrName){
QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
if(!(ui->groupBox->isHidden())){
ui->groupMsg->setAlignment(Qt::AlignLeft);
ui->groupMsg->setTextColor(Qt::blue);
ui->groupMsg->setCurrentFont(QFont("Times New Roman",10));
ui->groupMsg->append("["+name+"--"+time+"]");
ui->groupMsg->append(msg+"\r\n");
}
}
break;
}
//更新在线用户
case 6:
{
qint16 size;
in>>size;
ui->allUsrTable->setRowCount(0);
for(int i=0; i<size; i++)
{
QString usr,ip;
in>>usr;
in>>ip;
//将在线用户信息显示在表格
ui->allUsrTable->insertRow(0);
ui->allUsrTable->setItem(0,0,new QTableWidgetItem(usr));
ui->allUsrTable->setItem(0,1,new QTableWidgetItem(ip));
}
}
}
}
}
//接受服务器返回的Tcp消息处理函数
void Widget::tcp_ReadyRead()
{
// QTextCodec *tc=QTextCodec::codecForName("UTF-8");
qDebug()<<tr("接受到了服务器返回信息!");
QByteArray data = tClnt->readAll();
QDataStream in(&data,QIODevice::ReadOnly);
QString fromUsr,ip,msg,toUsr,toip;
in>>fromUsr>>ip>>msg>>toUsr>>toip;
chat *mychat;
char flag = 1;
//首先检查要传达的窗口数据是否存在
QList< chat *>::iterator i;
for(i = dialogChat.begin(); i != dialogChat.end(); ++i)
{
if((*i)->chat_ip == ip)
{
mychat = *i;
flag = 0;
qDebug()<<"已经存在窗口";
mychat->msg_receive = msg;
mychat->showMsg();
mychat->show();
break;
}
}
//如果窗口不存在就自己新建
if(flag)
{
qDebug()<<"新建窗口";
mychat = new chat(this);
mychat->chat_ip = ip;
mychat->chat_name = fromUsr;
mychat->server_ip = serverIP;
mychat->nameself = usrName;
mychat->ipself = getIP();
mychat->tClnt = tClnt;
mychat->msg_receive = msg;
mychat->showMsg();
dialogChat.append(mychat);
mychat->show();
}
}
//.获得本机IP地址
QString Widget:: getIP()
{
QList<QHostAddress> addrs = QNetworkInterface::allAddresses();
for(int i = 0; i < addrs.size(); i++)
{
if (addrs.at(i).protocol() == QAbstractSocket::IPv4Protocol &&
addrs.at(i) != QHostAddress::Null &&
addrs.at(i) != QHostAddress::LocalHost &&
!addrs.at(i).toString().contains(QRegExp("^169.*$")))//正则表达式,不包含169开头的默认无网络连接IP
{
qDebug() << addrs.at(i).toString();
return addrs.at(i).toString();
}
}
}
//双击表格后弹出聊天窗口
void Widget::on_allUsrTable_cellDoubleClicked(int row, int column)
{
QString usr;
QString IP;
usr = ui->allUsrTable->item(row,0)->text();
IP = ui->allUsrTable->item(row,1)->text();
//新对话框需要的参数信息
chat *mychat = new chat;
mychat->chat_ip = IP;
mychat->chat_name = usr;
mychat->server_ip = serverIP;
mychat->nameself = usrName;
mychat->ipself = getIP();
mychat->tClnt = tClnt;
mychat->msg_receive = " ";
mychat->showMsg();
dialogChat.append(mychat);
mychat->show();
// if(mychat->exec()==QDialog::Accepted)
// {
// dialogChat.removeOne(mychat);
// delete mychat;
// mychat =NULL;
// }
}
//退出按钮槽函数
void Widget::on_exitBtn_clicked()
{
QByteArray data;
QDataStream out(&data,QIODevice::WriteOnly);
out<<(qint16)4<<usrName;//写入自己得IP地址
qDebug()<<tr("用户退出");
udpSocket->writeDatagram(data,data.length(),QHostAddress::Broadcast,port);
tClnt->abort();
tClnt->disconnectFromHost();
tClnt->close();
fileTcpServer->close();
close();
}
//群聊功能
void Widget::on_groupChatBtn_clicked()
{
static int flag =1;
flag = 1 - flag;
if(flag == 1){
ui->groupBox->hide();
ui->groupChatBtn->setText(tr("开始群聊"));
}
else
{
ui->groupBox->show();
ui->groupChatBtn->setText(tr("关闭群聊"));
}
}
//群聊发送按钮槽函数
void Widget::on_SendBtn_clicked()
{
QByteArray outBlock;
QDataStream out(&outBlock,QIODevice::WriteOnly);
//out.setVersion(QDataStream::Qt_5_6);
QString msg =ui->groupChatInput->toPlainText();
//信息发送不能为空判断
if(msg.isEmpty())
{
QMessageBox::warning(0,tr("warning"),tr("发送的消息不能为空!"),
QMessageBox::Ok);
return ;
}
ui->groupChatInput->clear();
QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
//将发送内容显示在内容显示栏的右边
ui->groupMsg->setAlignment(Qt::AlignRight);
ui->groupMsg->setTextColor(Qt::green);
ui->groupMsg->setCurrentFont(QFont("Times New Roman",10));
ui->groupMsg->append("["+usrName+"--"+time+"]");
ui->groupMsg->append(msg+"\r\n");
//加上消息类型后发送出去
out<<qint16(5)<<msg<<usrName;
// QHostAddress addr;
// addr.setAddress(tr("192.168.43.12"));//这里必须用指定的IP不然不可以发出消息,不知为何
// udpSocket->writeDatagram(outBlock,outBlock.length(),addr,5555);//发送数据
udpSocket->writeDatagram(outBlock,outBlock.length(),QHostAddress::Broadcast,5555);
}
完整项目源码文件:https://download.csdn.net/download/WXY19990803/12450932
八、设计结果
软件正常运行是先打开服务端,再打开客户端,服务端界面如图7.1所示,客户端界面如图7.2所示。
图7.1:服务端界面
图7.2:客户端界面
在先后打开服务端和客户端后,服务端会有消息显示已有用户连接,并返回自动生成的用户名在客户端显示(Usr_1)。私聊功能只需要在客户端双击你想私聊的用户即可进入私聊界面,如图7.3所示。
图7.3:私聊界面
思考题:简述单播、广播、多播的区别与联系,以及各自的优缺点和适应范围。
八、软件使用说明
软件中有两个工程文件,一个是客户端工程文件chat_client,一个是服务端工程文件chat_server,使用QT打开各个工程文件的.pro文件即可。在正常使用软件时需要先打开服务端在打开客户端,否则先打开客户端会出现出错提示,注意,软件设计时是采用一台PC机只能注册一个用户的原则,采用的是唯一IP地址进行注册,但没有限制自己与自己私聊和传输文件,目的在于测试。在正常打开软件后,如果要私聊,双击用户列表中的用户即可进入私聊界面,在私聊界面中可以进行文件传输。如果要群聊,点击“开始群聊”按钮即可进入群聊界面。退出软件点击“退出”即可。服务端打开后是不需要任何操作的,他自动显示用户一对一之间的私聊信息,但是不能显示文件传输信息和群聊信息。
九、参考资料
书籍资料:
[1]张会勇. WinSock网络编程经络. 电子工业出版社,2012.
[2]王艳平. Windows网络与通信程序设计. 人民邮电出版社,2009
[3]唐文超. Visual C++网络编程. 清华大学出版社,2013
视频资料:
[1]网址:https://www.bilibili.com/video/av20446734?p=1
看完觉得有帮助就顺手点个赞呗^_^
!!!