资源描述
课 程 设 计 报 告
题 目 : 基于QT的局域网聊天室
—UI界面和消息发送、接收模块
课 程 名 称 : 嵌入式系统应用开发
院 部 名 称 : 计算机工程学院
专 业 : 计算机科学与技术
班 级 : 计算机科学与技术(2)班
课程设计书写要求
课程设计报告原则上要求学生手写,要求书写工整。若因课程特点需打印的,要遵照以下字体、字号、间距等的具体要求。纸张一律采用A4的纸张。
课程设计报告书写说明
课程设计报告应包含一下七部分内容:1、摘要 2、目录 3、前言/引言 4、正文 5、结论 、6、参考文献 7、附录,每部分的书写要求参见具体条目要求。
填写注意事项
(1)准确说明,层次清晰。
(2)尽量采用专用术语来说明事物。
(3)外文、符号、公式要准确,应使用统一规定的名词和符号。
(4)应独立完成实验报告的书写,严禁抄袭、复印,一经发现,以零分论处。
课程设计报告批改说明
实验报告的批改要及时、认真、仔细,一律用红色笔批改。实验报告的批改成绩采用百分制,具体评分标准由各院部自行制定。
实验报告装订要求
实验批改完毕后,任课老师将每门课程的每个实验项目的实验报告以自然班为单位、按学号升序排列,装订成册,并附上一份该门课程的实验大纲。
金陵科技学院课程设计报告
课程设计题目:
基于QT的局域网聊天室 —UI界面和消息发送、接收模块
一、 摘要
嵌入式系统是软件和硬件的综合体,嵌入式系统是与应用紧密结合的,具有很强专用性,必须结合实际系统需求进行合理的裁减利用。
嵌入式在电子行业的应用很广泛,学习嵌入式显得非常重要。这次课程设计的主要目的是检验学习qt软件后的效果。通过基础题的代码编写,熟悉C++语言编程技巧。通过完成聊天程序的设计,熟悉嵌入式底层编程。利用QT设计界面,缩短开发时间。
关键字:嵌入式、tcp SOCKET、QT软件
二、目录
一、 摘要 1
二、目录 2
三、前言/引言 3
四、正文 4
4.1 原理与关键技术 4
4.1.1条件与限制 4
4.1.2开发环境说明 4
4.1.3运行环境说明 4
4.1.4设计思路的分析和可行性 4
4.1.5信号和槽机制 4
4.1.6聊天平台的设计思想 5
4.1.7技术路线 5
4.2 系统的总体设计 6
4.2.1界面设计 6
4.2.2服务器端的设计 7
4.2.3客户端的设计 8
4.3各个模块的具体实现过程 9
4.3.1主界面设计主要代码分析 9
4.3.2服务器端主要程序代码分析 11
4.3.3客户端主要程序代码分析 12
五、结论 14
六、参考文献 15
七、附录 16
三、前言/引言
局域网聊天工具,是在局域网内部使用的,用户之间用来交流的一个工具,一般都具有文本聊天和文件传输功能。局域网聊天软件因其使用简单,系统资源消耗少等优点,成为各企事业单位等的局域网内广泛应用的软件之一。
随着中小型企事业的不断发展,在企业内部实现局域网通信是必不可少的,随之出现了企业即时通讯,它是一种面向企业终端使用者的网络沟通工具服务,使用者可以通过安装了即时通信的终端机进行两人或多人之间的实时沟通。交流内容包括文字、界面、语音、视频及文件互发等。
四、正文
4.1 原理与关键技术
4.1.1条件与限制
本系统为普通聊天系统,主要是局域网内成员使用,要求界面简洁,操作简单,以处理事物为核心,保证程序的可靠性。本系统处理数据量有限,适用于局域网用户,但性能良好,能满足局域网成员的基本需求。
可以应对各种由于系统产生的错误,比如信息发送失败,不能接收信息等,要求尽可能多的预防此类事件的发生,并准备好应对措施和向用户提供提示信息。
要保证程序有一定的容错性,当用户进行非法操作或系统本身出现错误时要能以合理的方式退出程序。
4.1.2开发环境说明
1. 编程语言:C++
2. 操作系统:windows xp 32位系统
3. 开发平台:QT creator
4.1.3运行环境说明
1.CPU: 酷睿i5
2.内存:2GB
3.硬盘:500G
4.操作系统:windows xp 32位系统
4.1.4设计思路的分析和可行性
本如软件采用服务器和客户端的形式。服务器负责监听客户端和接收客户端发送过来的信息,并在显示在界面上。客户端则向服务端发送信息,在本地呈现的同时,通过网络实时发往服务器。用户在服务器器上也可以看见客户端发送的消息,并获取最新客户端发送的消息。
网络编程技术的成熟和界面设计技术使得客户端与服务端通信的设计成为可能,而且此软件同过设置IP是聊天通信更为方便。
4.1.5信号和槽机制
信号:当对象改变其状态时,信号就由该对象发射 (emit) 出去,而且对象只负责发送信号,它不知道另一端是谁在接收这个信号。这样就做到了真正的信息封装,能确保对象被当作一个真正的软件组件来使用。
槽:用于接收信号,而且槽只是普通的对象成员函数。一个槽并不知道是否有任何信号与自己相连接。而且对象并不了解具体的通信机制。
信号与槽的连接:所有从 QObject 或其子类 ( 例如 Qwidget ) 派生的类都能够包含信号和槽。因为信号与槽的连接是通过 QObject 的 connect() 成员函数来实现的。
4.1.6聊天平台的设计思想
本系统采用服务器和客户端的形式。
服务器主要模拟服务器的控制端,来对客户端的各种状态进行监听。可以通过网络向客户端发送信息,并接受客户端传来的信息,供用户参考。
客户端主要通过IP连接服务器。客户端在显示自己状态的同时,通过网络实时将自己的状态发送到服务器端。
4.1.7技术路线
UDP协议:
UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。
UDP协议的全称是用户数据报协议[1] ,在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在第四层——传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但是即使是在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。
与所熟知的TCP(传输控制协议)协议一样,UDP协议直接位于IP(网际协议)协议的顶层。根据OSI(开放系统互连)参考模型,UDP和TCP都属于传输层协议。
UDP协议的主要作用是将网络数据流量压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。
TCP协议:
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接(连接导向)的、可靠的、基于IP的传输层协议,由IETF的RFC 793说明(specified)。
TCP/IP(Transmission Control Protocol/Internet Protocol) 即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WAN)设计的。它是由ARPANET网的研究机构发展起来的。
TCP/IP的标准在一系列称为RFC的文档中公布。文档由技术专家、特别工作组、或RFC编辑修订。公布一个文档 时,该文档被赋予一个RFC编号,如RFC959(FTP的说明文档)、RFC793(TCP的说明文档)、RFC791(IP的说明文档)等。最初的 RFC一直保留而从来不会被更新,如果修改了该文档,则该文档又以一个新号码公布。因此,重要的是要确认你拥有了关于某个专题的最新RFC文档。通常在 RFC的开头部分,有相关RFC的更新(update)、修改(errata)、作废(obsolete)信息,提示读者信息的时效性。
TCP与UDP的区别:
1.TCP协议面向连接,UDP协议面向非连接;
2.TCP协议传输速度慢,UDP协议传输速度快
3.TCP有丢包重传机制,UDP没有;
4.TCP协议保证数据正确性,UDP协议可能丢包
4.2 系统的总体设计
4.2.1界面设计
图1主界面
软件左边那大块是聊天内容显示界面,这里局域网相当于qq中的qq群,即群聊。
该程序实现的是每个用户登录既是客户端又是服务器端,这就需要看你站在哪个角度看问题了。简单的说,当用户发送信息给别人时就是客户端,当接收别人的信息是就可以看做是服务器端。
4.2.2服务器端的设计
图2发送文件界面
服务器端界面主要如图所示,包括界面部分和控制部分。
界面执行显示功能,包括打开、发送、关闭功能。
发送端流程:
图3发送端流程
4.2.3客户端的设计
图4接收文件界面
客户端界面如图所示,采用的是界面驱动的方式。客户端有自己的窗口,都可以单独向服务器端发送状态信息。
客户端主要包括发送和清除当前信息的功能呢,还有通过IP连接服务器的功能。
客户端的界面响应用户的操作时,可以向服务器报告自己的信息。
客户端也会将必要的信息定时报告给服务器。
接收端流程:
图5接收端流程
4.3各个模块的具体实现过程
4.3.1主界面设计主要代码分析
// 接收UDP信息
void Widget::processPendingDatagrams()
{
//hasPendingDatagrams返回true时表示至少有一个数据报在等待被读取
while(udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
//pendingDatagramSize为返回第一个在等待读取报文的size,resize函数是把datagram的size归一化到参数size的大小一样
datagram.resize(udpSocket->pendingDatagramSize());
//将读取到的不大于datagram.size()大小数据输入到datagram.data()中,datagram.data()返回的是一个字节数组中存储
//数据位置的指针
udpSocket->readDatagram(datagram.data(), datagram.size());
QDataStream in(&datagram, QIODevice::ReadOnly);//因为其属性为只读,所以是输入
int messageType; //此处的int为qint32,在Qt中,qint8为char,qint16为uint
in >> messageType; //读取1个32位长度的整型数据到messageTyep中
QString userName,localHostName,ipAddress,message;
QString time = QDateTime::currentDateTime()
.toString("yyyy-MM-dd hh:mm:ss");//将当前的时间转化到括号中的形式
switch(messageType)
{
case Message:
//in>>后面如果为Qstring,则表示读取一个直到出现'\0'的字符串
in >> userName >> localHostName >> ipAddress >> message;
ui->messageBrowser->setTextColor(Qt::blue);//设置文本颜色
ui->messageBrowser->setCurrentFont(QFont("Times New Roman",12));//设置字体大小
// ui->messageBrowser->append("[ " +userName+" ] "+ time);//输出的格式为用户名加时间显示
//输出的格式为主机名加时间显示,但输出完后为什么会自动换行呢?
ui->messageBrowser->append("[ " +localHostName+" ] "+ time);
ui->messageBrowser->append(message);//消息输出
break;
case NewParticipant:
in >>userName >>localHostName >>ipAddress;
newParticipant(userName,localHostName,ipAddress);
break;
case ParticipantLeft:
in >>userName >>localHostName;
participantLeft(userName,localHostName,time);
break;
case FileName:
break;
case Refuse:
break;
}
}
}
4.3.2服务器端主要程序代码分析
// 开始发送数据
void TcpServer::sendMessage() //是connect中的槽函数
{
ui->serverSendBtn->setEnabled(false); //当在传送文件的过程中,发送按钮不可用
clientConnection = tcpServer->nextPendingConnection(); //用来获取一个已连接的TcpSocket
//bytesWritten为qint64类型,即长整型
connect(clientConnection, SIGNAL(bytesWritten(qint64)), //?
this, SLOT(updateClientProgress(qint64)));
ui->serverStatusLabel->setText(tr("开始传送文件 %1 !").arg(theFileName));
localFile = new QFile(fileName); //localFile代表的是文件内容本身
if(!localFile->open((QFile::ReadOnly))){
QMessageBox::warning(this, tr("应用程序"), tr("无法读取文件 %1:\n%2")
.arg(fileName).arg(localFile->errorString()));//errorString是系统自带的信息
return;
}
TotalBytes = localFile->size();//文件总大小
//头文件中的定义QByteArray outBlock;
QDataStream sendOut(&outBlock, QIODevice::WriteOnly);//设置输出流属性
sendOut.setVersion(QDataStream::Qt_4_7);//设置Qt版本,不同版本的数据流格式不同
time.start(); // 开始计时
QString currentFile = fileName.right(fileName.size() //currentFile代表所选文件的文件名
- fileName.lastIndexOf('/')-1);
//qint64(0)表示将0转换成qint64类型,与(qint64)0等价
//如果是,则此处为依次写入总大小信息空间,文件名大小信息空间,文件名
sendOut << qint64(0) << qint64(0) << currentFile;
TotalBytes += outBlock.size();//文件名大小等信息+实际文件大小
//sendOut.device()为返回io设备的当前设置,seek(0)表示设置当前pos为0
sendOut.device()->seek(0);//返回到outBlock的开始,执行覆盖操作
//发送总大小空间和文件名大小空间
sendOut << TotalBytes << qint64((outBlock.size() - sizeof(qint64)*2));
//qint64 bytesWritten;bytesToWrite表示还剩下的没发送完的数据
//clientConnection->write(outBlock)为套接字将内容发送出去,返回实际发送出去的字节数
bytesToWrite = TotalBytes - clientConnection->write(outBlock);
outBlock.resize(0);//why??
}
4.3.3客户端主要程序代码分析
// 创建新连接
void TcpClient::newConnect()
{
blockSize = 0;
tcpClient->abort(); //取消已有的连接
tcpClient->connectToHost(hostAddress, tcpPort);//连接到指定ip地址和端口的主机
time.start();
}
// 读取数据
void TcpClient::readMessage()
{
QDataStream in(tcpClient); //这里的QDataStream可以直接用QTcpSocket对象做参数
in.setVersion(QDataStream::Qt_4_7);
float useTime = time.elapsed();
if (bytesReceived <= sizeof(qint64)*2) { //说明刚开始接受数据
if ((tcpClient->bytesAvailable() //bytesAvailable为返回将要被读取的字节数
>= sizeof(qint64)*2) && (fileNameSize == 0))
{
五、结论
本文使用面向对象程序设计思想,设计开发QT聊天系统。聊天系统要完成的任务就是提供用户操作界面,方便用户之间在局域网的环境下相互通信。程序的核心是通过基于TCP/IP的Socket来实现消息传递的通信部分。通过此次课程设计的锻炼,提高了我们解决实际问题的能力,让我们能更加从容地面对未来的工作。
六、参考文献
[1] 霍亚飞.Qt及 Qt Quick开发实战精解.北京航空航天大学出版社,2012;155-12
[2] 布兰切特 (Jasmin Blanchette)等.C++GUI Programming
with Qt4[M].第二版.电子工业出版社,2008:223-30
七、附录
21
关键代码:
Tcpserver.cpp:
#include "tcpserver.h"
#include "ui_tcpserver.h"
#include <QFile>
#include <QTcpServer>
#include <QTcpSocket>
#include <QMessageBox>
#include <QFileDialog>
#include <QDebug>
TcpServer::TcpServer(QWidget *parent) :
QDialog(parent),
ui(new Ui::TcpServer)
{
ui->setupUi(this);
setFixedSize(350,180);
tcpPort = 6666;
tcpServer = new QTcpServer(this);
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(sendMessage()));
initServer();
}
TcpServer::~TcpServer()
{
delete ui;
}
// 初始化
void TcpServer::initServer()
{
payloadSize = 64*1024;
TotalBytes = 0;
bytesWritten = 0;
bytesToWrite = 0;
ui->serverStatusLabel->setText(tr("请选择要传送的文件"));
ui->progressBar->reset();
ui->serverOpenBtn->setEnabled(true);
ui->serverSendBtn->setEnabled(false);
tcpServer->close();
}
// 开始发送数据
void TcpServer::sendMessage()
{
ui->serverSendBtn->setEnabled(false);
clientConnection = tcpServer->nextPendingConnection();
connect(clientConnection, SIGNAL(bytesWritten(qint64)),
this, 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_8);
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);
outBlock.resize(0);
}
// 更新进度条
void TcpServer::updateClientProgress(qint64 numBytes)
{
qApp->processEvents();
bytesWritten += (int)numBytes;
if (bytesToWrite > 0) {
outBlock = localFile->read(qMin(bytesToWrite, payloadSize));
bytesToWrite -= (int)clientConnection->write(outBlock);
outBlock.resize(0);
} else {
localFile->close();
}
ui->progressBar->setMaximum(TotalBytes);
ui->progressBar->setValue(bytesWritten);
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));
if(bytesWritten == TotalBytes) {
localFile->close();
tcpServer->close();
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::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();
if (localFile->isOpen())
localFile->close();
clientConnection->abort();
}
close();
}
// 被对方拒绝
void TcpServer::refused()
{
tcpServer->close();
ui->serverStatusLabel->setText(tr("对方拒绝接收!!!"));
}
// 关闭事件
void TcpServer::closeEvent(QCloseEvent *)
{
on_serverCloseBtn_clicked();
}
Tcoclient.cpp:
#include "tcpclient.h"
#include "ui_tcpclient.h"
#include <QTcpSocket>
#include <QDebug>
#include <QMessageBox>
TcpClient::TcpClient(QWidget *parent) :
QDialog(parent),
ui(new Ui::TcpClient)
{
ui->setupUi(this);
setFixedSize(350,180);
TotalBytes = 0;
bytesReceived = 0;
fileNameSize = 0;
tcpClient = new QTcpSocket(this);
tcpPort = 6666;
connect(tcpClient, SIGNAL(readyRead()), this, SLOT(readMessage()));
connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)), this,
SLOT(displayError(QAbstractSocket::SocketError)));
}
TcpClient::~TcpClient()
{
delete ui;
}
// 设置文件名
void TcpClient::setFileName(QString fileName)
{
localFile = new QFile(fileName);
}
// 设置地址
void TcpClient::setHostAddress(QHostAddress address)
{
hostAddress = address;
newConnect();
}
// 创建新连接
void TcpClient::newConnect()
{
blockSize = 0;
tcpClient->abort();
tcpClient->connectToHost(hostAddress, tcpPort);
time.start();
}
// 读取数据
void TcpClient::readMessage()
{
QDataStream in(tcpClient);
in.setVersion(QDataStream::Qt_4_7);
float useTime = time.elapsed();
if (bytesReceived <= sizeof(qint64)*2) {
if ((tcpClient->byt
展开阅读全文