收藏 分销(赏)

张依-16-河北金融学院信息管理与工程系课程设计报告.doc

上传人:精*** 文档编号:3418497 上传时间:2024-07-05 格式:DOC 页数:26 大小:817.50KB 下载积分:10 金币
下载 相关 举报
张依-16-河北金融学院信息管理与工程系课程设计报告.doc_第1页
第1页 / 共26页
张依-16-河北金融学院信息管理与工程系课程设计报告.doc_第2页
第2页 / 共26页


点击查看更多>>
资源描述
. 河北金融学院信息管理与工程系课程设计报告 题 目: 局域网聊天工具 学生姓名: 张依 学 号: 20111811016 系别班级:信息管理与工程系11级计算机科学 与技术本科班 专业(方向): 计算机科学与技术 指导者教师: 姜志旺 完 成 时 间: 2013年 5 月 16 日 目录 1. 任务与要求………………………………………………………………………3 1.1设计任务……………………………………………………………………… 3 1.2具体要求……………………………………………………………………… 3 2. 系统总体设计……………………………………………………………………4 2.1 设计目标及完成功能…………………………………………………………4 2.2 系统结构设计…………………………………………………………………4 3. 系统详细设………………………………………………………………………6 3.1窗口设计………………………………………………………………………7 3.2接收消息模块…………………………………………………………………16 3.3用户信息………………………………………………………………………18 4.测试………………………………………………………………………………20 5.课程设计与总结…………………………………………………………………25 6.参考文献…………………………………………………………………………25 1 任务与要求 1.1设计任务 本课程设计是利用可视化编程库QT进行一个可视化的网络通信程序设计,主要采用QUdpSocket、QTcpSocket、QTcpServer等类。 1.2具体要求 1.2.1系统的需求分析 ①  能够发送消息到不同的用户程序。 ②  用户程序能够接收来自不同用户程序的消息,并能识别出是哪个用户。 ③  通过协议可以找到局域网里面的用户。 ④  根据具体情况进行发挥,努力完善网络通讯程序。 1.2.2 完成系统设计 找出系统的对象,抽象完成分析类图的创建,根据情况画出顺序图,协作图,状态图,部署图,组件图,活动图。针对具体的思想语言要求写出具体的实现类图,类的属性和服务,标出类之间的关系。 1.2.3编码 完成需要编码完成的模块。 1.2.4测试 编写合适的测试用例完成系统的测试工作并分析结果 2 系统总体设计 2.1系统设计目标及完成功能 本项目的设计目标为一个高性能的,易于使用的,面向校园内部通信需求的局域网即时通信软件。它应具有如下特征:   1、具有高性能,可同时处理多个连接请求。   2、对硬件要求低,适应范围广,运行稳定。      3、具有一定的容错性能。 当用户登入聊天室时,用户输入的内容直接发送到其他有登入此聊天室的用户,用户与用户直接通信不需要经过服务器。 最终的软件产品应具有如下功能: (1) 能够随时改变自己的昵称。 (2) 能够自动更新其他用户的名单及在线人数。 (3) 随时获取系统的当前时间。 (4) 能够向其他用户传输文件。 (5) 能够保存或者删除聊天记录。 (6) 能够改变聊天的字体。 (7) 能够多人聊天。 (8) 只限于局域网内聊天。 (9) 美观的操作界面。 (10) 主界面显示聊天信息,在线用户信息。 2.2 系统结构设计 ①进入用户界面 软件构架 处理新用户加入 显示用户信息 显示新用户信息 发送文件 进入聊天室 退出 ②用户操作系统 显示用户离开 删除用户信息 显示新用户信息 用户处理 处理用户离开 处理新用户加入 接受文件 保存聊天记录 文件处理 聊天记录 发送文件 删除聊天记录 发送信息 接受信息 发送信息 ③聊天室活动图 处理用户离开 处理新用户加入 处理文字 消息处理 下划线 颜色 加粗 接受消息 发送消息 3 系统详细设计 3.1窗口设计 私聊窗口设计 群聊窗口设计 3.1.1聊天室窗口设计 聊天室窗口分为三个模块,模块一:发送消息;模块二:接收消息;模块三:接收在线用户的信息如(用户名、主机名、IP地址)。 1、模块一也可以称为用户发言区。专门用来处理用户所输入的发言等,可以对发言的字体大小,字体和颜色,粗体,下划线进行更改以及保存聊天记录、清屏等功能。用户发言后直接点击发送按钮,此时就会调用发送函数sendMessage(),将messageTextEdit组件中的内容发送出去。通过QByteArray型局部变量datagram中构建待发送的数据包,然后通过QUdpSocket类的 writeDatagram ( const QByteArray & datagram, const QHostAddress & host, quint16 port );函数将数据包发出。值得注意的是,这里的地址使用了QHostAddress::Broadcast值,它对应IPv4下的广播地址,如果将该值更换成单机地址(如本机地址QHostAddress::LocalHost),将变成一个普通的点对点的UDP程序。 发送函数实现的主要代码如下: void siliao::sendMessage(MessageType type , QString serverAddress) //发送信息 { QByteArray data; //定义一个存储数据的容器 QDataStream out(&data,QIODevice::WriteOnly); //将数据转化为同一类型 QString localHostName = QHostInfo::localHostName(); //获取主机名 QString address = getIP(); //获取IP地址 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; //将IP和消息发送到data容器中 ui->textBrowser->verticalScrollBar()->setValue(ui->textBrowser->verticalScrollBar()->maximum()); break; } } xsiliao->writeDatagram(data.data(),data.size(),QHostAddress::QHostAddress(xpasvuserip), 45457); } //将data容器中的数据发送出去 } 字体设置实现 字体样式转变设置实现的主要代码如下: void qunliao1::currentFormatChanged(const QTextCharFormat &format) { ui->fontComboBox->setCurrentFont(format.font()); if (format.fontPointSize() < 9) { ui->comboBox->setCurrentIndex(3); } else { ui->comboBox->setCurrentIndex(ui->comboBox ->findText(QString::number(format.fontPointSize()))); } color = format.foreground().color(); } 字体加粗样式设置实现的主要代码如下: void qunliao1::on_boldToolBtn_clicked(bool checked) { if(checked) ui->messageTextEdit->setFontWeight(QFont::Bold); else ui->messageTextEdit->setFontWeight(QFont::Normal); ui->messageTextEdit->setFocus(); } 字体添加下划线样式设置实现的主要代码如下: void qunliao1::on_underlineToolBtn_clicked(bool checked) { ui->messageTextEdit->setFontUnderline(checked); ui->messageTextEdit->setFocus(); } 清屏功能实现的主要代码如下: void qunliao1::on_clearToolBtn_clicked() { ui->messageBrowser->clear(); } 改变字体颜色实现的主要代码如下: void qunliao1::on_toolButton_clicked() { color = QColorDialog::getColor(color, this); if (color.isValid()) //判断是否创建对象实例,创建了就进入{ ui->messageTextEdit->setTextColor(color); ui->messageTextEdit->setFocus(); } } 保存聊天记录的主要代码如下: void qunliao1::on_saveToolBtn_clicked() { if (ui->messageBrowser->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); } }bool qunliao1::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->messageBrowser->toPlainText(); return true; } 3.1.2发送文件窗体设计 发送文件窗体中包涵了一些简单的组件,其中最主要的就是发送按钮,他实现了文件的传输。 1、用户在界面按下"打开"按钮后,openFile()槽函数将被调用。该函数通过Qt文件选择对画框QFileDialog所提供的静态函数getOpenFileName(),能够很容易地返回用户所选取的文件名,这里将其保存在私有成员变量fileName中。如果选中返回的文件名非空,将激活"发送"按钮。单击发送按钮就开启监听,并且等待接收者接受。当接收者接受时就开始建立连接。 一旦连接建立成功,QTcpServer类将发出newConnection ()消息,继而调用sendMessage()槽函数。该函数首先向接收方发送一个文件头结构。   文件头结构由三个字段组成,分别是64位的总长度(包括文件数据长度和文件头自身长度),64位的文件名长度和文件名。   函数sendMessage()首先以只读方式打开选中的文件,然后通过QFile类的size()函数获取待发送文件的大小,并将该值暂存于TotalBytes变量中。   接下来将发送缓冲区outBlock封装在一个QDataStream类型的变量中,这样做可以很方便的通过重载的"<<"操作符填写文件头结构。   设置文件头结构的操作有些小技巧,这里首先通过QString类的right()函数去掉文件的路径部分,仅将文件部分保存在currentFile变量中,然后通过sendOut << qint64(0) << qint64(0) << currentFile操作构造一个临时的文件头,将该值追加到TotalBytes字段,从而完成实际需发送字节数的记录。   接着通过sendOut.device()->seek(0)函数将读写操作指向从头开始,并且调用类似操作sendOut << TotalBytes << qint64((outBlock.size() - sizeof(qint64) * 2)),填写实际的总长度和文件长度。   需要注意的是,不能错误地通过QString::size()函数获取文件名的大小,该函数返回的是QString类型文件名所包含的字节数,而不是实际所占存储空间的大小,由于字节编码和QString类存储管理的原因,两者往往并不相等。 完成了文件头结构的填写后,调用tcpClient.write(outBlock)函数将该文件头发出,同时修改待发送字节数bytesToWrite。最后,调用outBlock.resize(0)函数清空发送缓冲区以备下次使用。 一旦数据发出,QTcpSocket类将会产生bytesWritten()信号,继而调用updateClientProgress(qint64)槽函数,参数表示实际已发出的字节数。如果待发送数据计数bytesToWritten大于0,将尽可能地从发送文件中读取数据,并将其发送,否则发送完毕关闭文件。还需要在此更新亦发和待发数据计数,并以此更新发送进度条和状态显示。 发送文件的代码如下: clientConnection = tcpServer->nextPendingConnection(); //nextPendingConnection获得socket 连接; 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); //将数据写入outBlock缓存; sendOut.setVersion(QDataStream::Qt_4_7); time.start(); // 开始计时 QString currentFile = fileName.right(fileName.size() - fileName.lastIndexOf('/')-1); sendOut << qint64(0) << qint64(0) << currentFile; //依次写入总大小信息空间,文件大小信息空间,文件名 TotalBytes += outBlock.size(); //此时总大小为实际文件大小加上文件名大小等信息 sendOut.device()->seek(0); //返回到数据缓冲区outBlock的起始地址 sendOut << TotalBytes << qint64((outBlock.size() - sizeof(qint64)*2)); bytesToWrite = TotalBytes - clientConnection->write(outBlock); outBlock.resize(0); } 更新进度条的代码如下: qApp->processEvents(); bytesWritten += (int)numBytes; if (bytesToWrite > 0) { outBlock = localFile->read(qMin(bytesToWrite, payloadSize)); //每次发送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)); } 1、添加所需要的组件 主要组件名称及用途如下: progressBar进度条,用于显示数据传输的过程 serverOpenBtn打开本地文件 serverSendBtn发送文件 serverCloseBtn关闭窗体 2、重要组件QTcpServer的使用 (1) 创建对象tcpServer。 (2) 通过tcpServer关联信号和槽。 (3) 每当发现新连接时信号newConnection就会触发槽函数sendMessage()实现文件的发送。tcpServer组件中的nextPendingConnection()方法是用来获得socket 连接的。 3.1.3接收窗体设计 接收窗体中也是一些简单的组件,其主要的功能还是在于文件接收的函数中。 1、当接收到其他用户发来的文件接收请求时,用户可以接受请求,也可以拒绝请求。当接收请求后,newConnect()函数将被调用。该函数的主要功能是连接服务器,它使用了QTcpSocket类的connectToHost()函数,其中的两个参数分别是服务器主机地址及其监听端口。 接收程序tcpclient完成的功能与发送程序恰恰相反,它负责从TCP连接上接收数据,并将其写入当前目录下的指定文件中。   其界面也是一个简单的对话框,上面布置一个QProgressBar进度条,一个用来显示状态的QLabel,两个QPushButton按钮分别用来取消接收和退出程序。 当建立的连接有新的可供读取的数据时,QTcpSocket类会发出readyRead()信号,从而触发readMessage()槽函数。该函数完成数据的接收、存储,并更新进度显示。 首先将返回的TCP连接tcpClient封装的QDataStream类型变量in中,同时设置流化数据格式类型为QDataStream::Qt_4_6,与客户端保持一致。现在可以很方便的通过重载后的"<<"操作符读取TCP连接上的数据了。 由于流数据是没有结构的,为了知道接收的文件名以及文件何时接收完毕,必须首先获取文件头结构,这里还有个小问题,由于开始时所传输文件名的长度是未知的,导致文件头结构的长度也是未知的,因此无法知道TCP数据流中前多少字节属于文件头结构部分。实际上文件头结构的接收可分两布完成: (1)从TCP数据流中接收前16个字节(两个qint64结构长),用来确定总共需接收的字节数和文件名长度,并将这两个值保存在私有成员TotalBytes和fileNameSize中,然后根据fileNameSize值接收文件名。值得注意的是,无法保证在上述接收文件头结构过程中,TCP连接上总是有足够的数据,因此在第一步中,需要通过tcpServerConnection->bytesAvailable() >= sizeof(qint64)*2) && (fileNameSize ==0)操作确保至少有16字节的可用数据且文件名长度为0(表示未从TCP连接接收文件名长度字段,仍处于第一步操作),然后调用in >> TotalBytes >> fileNameSize操作读取总共需接收的数据和文件名长度。 (2)类似的通过(tcpServerConnection->bytesAvailable() >= fileNameSize) && (fileNameSize !=0)操作确保连接上的数据已包含完整的文件名且文件名长度不为0(表示已从TCP连接接收文件名长度字段,处于第二步操作中),然后调用in >> fileName操作读取文件名,并根据该文件名在本地以只写方式打开一个同名文件localFile,用来保存接收到的数据。   接下来的工作是读取实际的文件数据并保存,以及更新进度显示,直到接收到完全的数据。由于所发送的文件内容自身也是无格式的流,因此在接收文件内容时,只要TCP连接上有数据,就调用tcpClient->readAll()操作将当前全部可读数据读入接收缓冲inBlock中,随后再将该缓冲中的数据写入文件localFile中。当已收到的数据bytesReceived等于TotalBytes时,接收完毕,这时通过tcpClient->close()操作关闭连接。 接收文件的主要代码如下: QDataStream in(tcpClient); in.setVersion(QDataStream::Qt_4_7); float useTime = time.elapsed(); if (bytesReceived <= sizeof(qint64)*2) { if ((tcpClient->bytesAvailable() >= sizeof(qint64)*2) && (fileNameSize == 0)) { in>>TotalBytes>>fileNameSize; bytesReceived += sizeof(qint64)*2; } if((tcpClient->bytesAvailable() >= fileNameSize) && (fileNameSize != 0)){ in>>fileName; bytesReceived +=fileNameSize; if(!localFile->open(QFile::WriteOnly)){ QMessageBox::warning(this,tr("应用程序"),tr("无法读取文件 %1:\n%2.") .arg(fileName).arg(localFile->errorString())); return; } } else { return; } } if (bytesReceived < TotalBytes) { bytesReceived += tcpClient->bytesAvailable(); inBlock = tcpClient->readAll(); localFile->write(inBlock); inBlock.resize(0); } ui->progressBar->setMaximum(TotalBytes); ui->progressBar->setValue(bytesReceived); double speed = bytesReceived / useTime; ui->tcpClientStatusLabel->setText(tr("已接收 %1MB (%2MB/s) " "\n共%3MB 已用时:%4秒\n估计剩余时间:%5秒") .arg(bytesReceived / (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(bytesReceived == TotalBytes) { localFile->close(); tcpClient->close(); ui->tcpClientStatusLabel->setText(tr("接收文件 %1 完毕") .arg(fileName)); } 1、 添加所需要的组件 一个lable,一个progressBar,两个pushButton。 主要组件名称及用途如下: progressBar进度条,用于显示数据接收的过程 tcpClientCancleBtn停止接收 tcpClientCloseBtn关闭窗体 2、 重要组件QTcpSocket的使用 (1) 创建对象tcpClient。 (2) 通过tcpClient关联信号和槽。 (3) 每当发送端将文件发送出来时,接收端就会设置服务器地址并通过tcpClient组件的connectToHost方法连接服务器,连接成功并发现有可接收的数据时,信号readyRead()就会触发槽函数readMessage( 开始接收数据。每读取一个字节时tcpClient组件中的 bytesAvailable 属性增加一 Tcpclient.ui tcpserver.ui 3.2接收消息模块 messageBrowser组件实现,接受数据函数首先调用QUdpSocket类的成员函数hasPendingDatagrams()以判断是否有可供读取的数据。如果有则通过pendingDatagramSize()获取当前可供读取的UDP报文大小,并据此大小分配接收缓冲区,最后读取相应数据。 接收函数中几条主要语句如下: void qunliao1::qlprocessPendingDatagrams() { while(udpSocket->hasPendingDatagrams()) //判断是否有可供读取的数据 { QByteArray datagram; //用于存放接收的数据报 datagram.resize(udpSocket->pendingDatagramSize()); //让datagram的大小为等待处理的数据报的大小,这样才能接收到完整的数据 udpSocket->readDatagram(datagram.data(), datagram.size()); //接收数据报,将其存放到datagram中; QDataStream in(&datagram, QIODevice::ReadOnly); // 从文件datagram中读取串行化的数据; int messageType; in >> messageType; QString userName,localHostName,ipAddress,message; QString time = QDateTime::currentDateTime() .toString("yyyy-MM-dd hh:mm:ss"); //获取系统时间 switch(messageType) { case QLMessage: 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(message); //显示接收的消息 break; } } } 3.3用户信息 用userTableWidget组件实现,在userTableWidget中显示了用户的,用户名、主机名、IP地址。有新用户加入时就调用newParticipant()函数处理新用户,有用户离开时就会调用participantLeft()函数处理离开的用户。用户加入时会把自己的用户名、主机名、IP地址发送出去,其他用户就会接收到并将其内容显示在userTabl
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传
相似文档                                   自信AI助手自信AI助手

当前位置:首页 > 教育专区 > 其他

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:0574-28810668  投诉电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服