4 基于C/S结构的即时通信系统开发的系统详细设计
4.1 系统设计目标
随着计算机网络日新月异的发展,人们的交流方式越来越多,传统的交流 方式已经难以满足人们的交流要求,在互联网或局域网上即时的和好友取得联系,已经成为当今社会人们主流的联系方式。因此,两台计算机之间进行即时通讯、发送文件等交流方式已经成为必然潮流。作为整个计算机网络的基础,局域网用户之间进行即时通讯的需求更大,而且他们之间进行文件传输与共享的频率更高,因此有必要设计一款专门为局域网用户使用的即时通讯软件。
当前存在的即时通讯软件很多,但其中很多一部分由于嵌入了太多的娱乐功能而不很实用。对于局域网用户来说,首先,由于他们之间进行通讯的不确定性和随机性,所以如果专门为他们建立一个服务器端的话,势必会造成很大的浪费,因此,本软件选择实用点对点服务模式,不需要服务器支持。其次,局域网用户之间进行交流的主要方式就是文字信息交流,因此,本软件实现的最基本的功能就是文字信息的传输。再次,局域网用户要频繁的进行文件的传输,所以本软件还实现了两个局域网用户之间文件的传输。最后,由于采用跨平台的C++类库Qt4.8进行开发,因此本软件实现在不同的操作系统上运行,具有友好美观的界面。
在非功能需求方面,在开发软件时,考虑系统的安全性,采用了安全的设计理念,防止了“黑客”入侵后的盗窃行为,同时也遏制了病毒程序的攻击。系统主要应用于局域网中,解决局域网内信息公告、信息交流、文件传送等问题。考虑软件的“通用性”,“可扩展性”,“相对独立性”等方面问题,实现软件开发的灵活性。
4.2 功能模块划分
本设计可以实现局域网内用户的自动检测,用户之间的个人信息管理,文字信息交流, 文件传输等功能。据此,本设计的功能模块一共可以分为5个:广播用户信息,个人信息管理,用户列表管理,文字信息传输,文件传输。系统的总体功能模块图如图3.1所示:
图4.1 系统总体功能模块图
4.3 功能模块的说明
4.3.1 初始化(广播用户信息)
该模块的主要功能是搜集本机用户的相关信息,如计算机名,登陆时的用户名,初始化socket,开始监听端口,并把本机用户的信息通过广播消息发送出去。
4.3.2 个人信息管理管理
该模块的主要功能是对收到的用户信息进行管理。个人信息管理包括个人昵称、头像和个性签名的更新, 同时更新后还要对其他用户的广播消息进行反馈等。
4.3.3 用户列表管理
该模块的主要功能是对收到的用户信息进行管理和分类。子模块包括用户列表的更新, 用户分组等,同时还要对用户列表信息进行持久存储,以便下次启动系统能显示最新状态。
4.3.4 文本信息传输
该模块的主要功能是把当前文本框的内容发送给当前选中的用户,如果发送 成功,则把当前文本框的内容加上某些必须的信息在聊天记录文本框中显示,同 时清空输入文本框。该模块还要具有纠正用户为空(未选中用户)或者发送内容为空的情况。
4.3.5 文件传输
该模块的主要功能是实现两个用户之间的点对点的文件传输。由于使用TCP协议进行传输,所以该模块还负责文件块的确认和对文件块在传输过程中丢失的情况进行处理。同时,该模块还可以显示当前文件传输的进度,并可以粗略的计算文件传输速率。
#include <QSize>
#include <QSqlQuery>
#include <QSqlDatabase>
#include "shareData.h"
#include "chat_drawer.h"
/************************************************************
函数名: ChatDrawer
功能: 构造函数初始化图形显示界面
************************************************************/
ChatDrawer::ChatDrawer(QWidget *parent, Qt::WindowFlags f)
: QToolBox(parent, f)
{
setWindowTitle(QString::fromUtf8("LNQQ")); //界面的标题
if (!initGroup()) //初始化好友组
{
QMessageBox::information(0, QString::fromUtf8("错误"), QString::fromUtf8("初始化数据库失败!"));
}
creatGroup(); //创建好友组
this->setMinimumSize(QSize(182,450)); //设置界面最小尺寸
this->setMaximumSize(QSize(182,450)); //设置界面最大尺寸
// trayIcon=new QSystemTrayIcon(QIcon(":/img/radioButton.png"),this);
// //鼠标移至托盘的消息提示
// trayIcon->setToolTip(tr("这是一个Qt托盘测试程序"));
// //右击托盘图标的弹出菜单
// trayIcon->setContextMenu(CreateTrayMenu());
// //设置托盘可见
// trayIcon->setVisible(true);
// //添加托盘的鼠标事件
// connect(trayIcon,SIGNAL(activated(QSystemTrayIcon::ActivationReason)),this,SLOT(iconActived(QSystemTrayIcon::ActivationReason)));
}
//QMenu *ChatDrawer::CreateTrayMenu()
//{
// QMenu *menu=new QMenu;
// QAction *actionMinSize=new QAction(tr("最小化"),this);
// connect(actionMinSize,SIGNAL(triggered()),this,SLOT(showMinimized()));
// QAction *actionMaxSize=new QAction(tr("最大化"),this);
// connect(actionMaxSize,SIGNAL(triggered()),this,SLOT(showMaximized()));
// QAction *actionRestore=new QAction(tr("还原"),this);
// connect(actionRestore,SIGNAL(triggered()),this,SLOT(showNormal()));
// QAction *actionQuit=new QAction(tr("退出"),this);
// /*
// gui中qApp等同于QApplication::instance()
// 非gui中qApp等同于QCoreapplication::instance()
// 使用qApp需加头文件QApplication/QCoreapplication
// */
// connect(actionQuit,SIGNAL(triggered()),qApp,SLOT(quit()));
// menu->addAction(actionMinSize);
// menu->addAction(actionMaxSize);
// menu->addAction(actionRestore);
// menu->addSeparator();
// menu->addAction(actionQuit);
// return menu;
//}
//void ChatDrawer::iconActived(QSystemTrayIcon::ActivationReason reason)
//{
// switch(reason)
// {
// case QSystemTrayIcon::DoubleClick:
// QMessageBox::information(this,tr("托盘消息"),tr("双击"));
// break;
// case QSystemTrayIcon::Trigger:
// QMessageBox::information(this,tr("托盘消息"),tr("单击"));
// break;
// case QSystemTrayIcon::MiddleClick:
// QMessageBox::information(this,tr("托盘消息"),tr("鼠标中键"));
// break;
// }
//}
/************************************************************
函数名: initDatabase
功能: 初始化数据库
返回值 TRUE: 表示成功
FALSE: 表示失败
************************************************************/
bool ChatDrawer::initDatabase()
{
QSqlDatabase globDB = QSqlDatabase::addDatabase(DRIVER, DATABASE_NAME); //将加载数据库驱动以及连接名称
globDB.setDatabaseName(DATABASE_NAME); //设置数据库名
if (!globDB.open()) //打开数据库
{
QMessageBox::critical(0, QString::fromUtf8("错误"), QString::fromUtf8("连接数据库失败!"));
return false;
}
return true; //初始化数据库成功
}
/************************************************************
函数名: initGroup
功能: 初始化好友组
返回值 TRUE: 表示成功
FALSE: 表示失败
************************************************************/
bool ChatDrawer::initGroup()
{
if (!initDatabase()) //初始化数据库
{
return false;
}
QSqlDatabase db = QSqlDatabase::database(DATABASE_NAME);
QSqlQuery query(db); //创建查询结果集对象
QString sql = "select *from wn_user_group";
bool isRight = query.exec(sql); //查询好友分组
if (!isRight)
{
return false;
}
while (query.next())
{
groupName.append(query.value(1).toString()); //添加好友分组组名
QGroupBox * tmpGroup = new QGroupBox(); //添加好友分组
tmpGroup->setStyleSheet("\
QGroupBox {\
color: white;\
background-image: url(:/pics/gury.jpg);\
border-width: 0px;\
padding: 0px 0px;\
min-height: 25px;\
min-width: 60px;\
}\
QGroupBox::title {\
subcontrol-origin: margin;\
subcontrol-position: top center; /* position at the top center */\
padding: 0 3px;\
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,\
stop: 0 #FFOECE, stop: 1 #FFFFFF);\
}\
");
friendGroup.append(tmpGroup);
}
query.clear();
return true;
}
/************************************************************
函数名: creatGroup
功能: 创建好友组
************************************************************/
void ChatDrawer::creatGroup()
{
QVBoxLayout *tmpLayout;
QGroupBox *tmpGroup;
for (int i=0; i<friendGroup.count(); i++) //创建好友分组
{
tmpGroup = friendGroup.at(i);
tmpLayout = new QVBoxLayout(tmpGroup); //创建好友分组布局管理器
tmpLayout->setAlignment(Qt::AlignTop);
groupLayout.append(tmpLayout);
this->addItem((QWidget *)tmpGroup, groupName.at(i)); //添加好友分组
}
}
/************************************************************
函数名: ~ChatDrawer
功能: 释放图形界面资源
************************************************************/
ChatDrawer::~ChatDrawer()
{
for (int i=0; i<pButtonVector.count(); i++) //释放在线用户按钮
{
delete pButtonVector.at(i);
}
for (int i=0; i<friendGroup.count(); i++) //释放好友分组
{
delete friendGroup.at(i);
}
for (int i=0; i<groupLayout.count(); i++)
{
delete groupLayout.at(i);
}
}
/************************************************************
函数名: updateInfo
功能: 个人信息被修改发送信号
************************************************************/
void ChatDrawer::updateInfo(const User &user)
{
emit(update(user)); //发送个人信息被修改信号
}
/************************************************************
函数名: setUser
功能: 设置本地当前用户
输入参数 user: 源用户信息
************************************************************/
void ChatDrawer::setUser(const User &user)
{
this->user = user;
}
/************************************************************
函数名: drawUserFace
功能: 绘出特定的用户按钮
输入参数 newUser: 将要绘制按钮的用户信息
************************************************************/
void ChatDrawer::drawUserFace(const User &newUser)
{
QString info = newUser.getUserName(); //获得用户名
info += "\n" + newUser.getUserIp() +"\n" + newUser.getUserWrite(); //获得IP于个性签名
user_button = new ToolButton(user, newUser, groupName, this); //新建用户按钮
user_button->setText(info); //设置按钮显示信息
user_button->setIcon(QIcon(newUser.getUserHead())); //设置头像
user_button->setIconSize(QSize(40, 40)); //设置头像尺寸
user_button->setAutoRaise(true);
user_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); //设置按钮的风格
connect(user_button, SIGNAL(update(User)), this, SLOT(updateInfo(const User)));
connect(user_button, SIGNAL(setAction(ToolButton *)), this, SLOT(setActionSlot(ToolButton *)));
connect(user_button, SIGNAL(moveToOtherGroup(ToolButton *, int )), this, SLOT(moveFriend(ToolButton *, int)));
connect(user_button, SIGNAL(insertGroupName(const QString)), this, SLOT(updateGroupName(const QString)));
connect(user_button, SIGNAL(deleteGroupNameSignal(const QString)), this, SLOT(deleteGroupNameSlot(const QString)));
connect(user_button, SIGNAL(updateGroupNameSignal(const QVector<QString>)), this, SLOT(updateGroupNameSlot(const QVector<QString>)));
insertGroup(newUser, user_button); //将在线用户分组
user_button->creatChildMenu();
pButtonVector.append(user_button); //添加到在线用户容器中
setFunction(); //设置当前用户发送文件按钮失效
}
/************************************************************
函数名: updateGroupName
功能: 修改好友分组组名称
输入参数 name: 新增好友分组组名
************************************************************/
void ChatDrawer::updateGroupName(const QString name)
{
groupName.append(name); //新组好友组名添加进容器
for (int i=0; i<pButtonVector.count(); i++) //修改每个按钮中的好友组名容器
{
pButtonVector.at(i)->setGroupName(groupName);
}
QGroupBox * tmpGroup = new QGroupBox(); //添加好友分组
friendGroup.append(tmpGroup);
QVBoxLayout *tmpLayout = new QVBoxLayout(tmpGroup); //创建好友分组布局管理器
tmpLayout->setAlignment(Qt::AlignTop);
groupLayout.append(tmpLayout); //添加进布局管理器容器
this->addItem((QWidget *)tmpGroup, name); //添加好友分组 //重新创建好友分组框
}
void ChatDrawer::updateGroupNameSlot(const QVector<QString> groupName)
{
int index = 0;
for (int i=0; i<groupName.count(); i++)
{
if (this->groupName.at(i) != groupName.at(i))
{
index = i;
break;
}
}
this->groupName = groupName;
this->setItemText(index, this->groupName.at(index));
for (int i=0; i<pButtonVector.count(); i++) //修改每个按钮中的好友组名容器
{
pButtonVector.at(i)->setGroupName(groupName);
}
}
void ChatDrawer::deleteGroupNameSlot(const QString name)
{
int index = groupName.indexOf(name);
groupName.remove(index);
for (int i=0; i<pButtonVector.count(); i++) //修改每个按钮中的好友组名容器
{
ToolButton *tmpButton = pButtonVector.at(i);
tmpButton->setGroupName(groupName);
if (tmpButton->getGroupId() == index)
{
moveFriend(tmpButton, DEFAULT_GROUP_INDEX);
}
}
friendGroup.remove(index);
groupLayout.remove(index);
this->removeItem(index);
}
/************************************************************
函数名: queryGroup
功能: 查询用户所在组ID
输入参数 tmpUser: 用户对象
tmpButton: 用户按钮
************************************************************/
void ChatDrawer::insertGroup(const User &tmpUser, ToolButton *tmpButton)
{
QSqlDatabase db = QSqlDatabase::database(DATABASE_NAME);
QSqlQuery query(db); //创建查询数据库对象
QString sql = "select *from wn_user_ip where user_ip = '"+tmpUser.getUserIp()+"'";
bool isRight = query.exec(sql); //查询数据库
if (!isRight)
{
QMessageBox::information(0, QString::fromUtf8("错误"), QString::fromUtf8("访问数据库失败!"));
}
if (query.first()) //当前存在分组记录
{
int tmpId = query.value(1).toInt();
tmpButton->setGroupId(tmpId); //设置好友分组号
groupLayout.at(tmpId)->addWidget((QWidget *)tmpButton); //添加好友按钮
}
else
{
tmpButton->setGroupId(0); //默认分组
groupLayout.at(0)->addWidget((QWidget *)tmpButton); //采用默认分组记录
}
query.clear();
}
/************************************************************
函数名: moveFriend
功能: 移动好友
输入参数 friendButton: 用户按钮
newGroupId: 新组Id
************************************************************/
void ChatDrawer::moveFriend(ToolButton * friendButton, int newGroupId)
{
for (int i=0; i<pButtonVector.count(); i++) //查找将要移动按钮
{
if (friendButton == pButtonVector.at(i))
{
friendButton->hide(); //将要移动按钮隐藏
for (int j=0; j<groupLayout.count(); j++)
{
if (friendButton->getGroupId() == j) //找到移动的组号
{
groupLayout.at(j)->removeWidget(friendButton); //布局管理器中移除按钮
break;
}
}
break;
}
}
friendButton->setGroupId(newGroupId); //设置新组号
friendButton->creatChildMenu(); //创建新菜单
groupLayout.at(newGroupId)->addWidget(friendButton); //新组中添加按钮
friendButton->show();
}
/************************************************************
函数名: setActionSlot
功能: 设置按钮上下文菜单功能
输入参数 userButton: 用户按钮
************************************************************/
void ChatDrawer::setActionSlot(ToolButton *userButton)
{
for (int i=0; i<pButtonVector.count(); i++)
{
if ((userButton == pButtonVector.at(i)) && i == 0) //判断是否为当前用户按钮
{
userButton->setContextMenu(true); //设置当前用户上下文菜单公能
break;
}
else
{
userButton->setContextMenu(false); //设置非当前用户上下文菜单功能
break;
}
}
}
/************************************************************
函数名: setFunction
功能: 设置当前用户上下文菜单功能
************************************************************/
void ChatDrawer::setFunction()
{
if (!pButtonVector.isEmpty())
{
pButtonVector.at(0)->getChatWindow()->setSendFileButton(false); //将当前用户发送文件菜单失效
}
}
/************************************************************
函数名: removeUser
功能: 从在线用户容器中删除某个用户
输入参数 user: 将要删除的用户信息
************************************************************/
void ChatDrawer::removeUser(const User & user)
{
if (!pButtonVector.isEmpty()) //是否在线用户容器为空
{
ToolButton *tmpButton; //临时按钮
for(int i=0; i <pButtonVector.count(); i++) //检索在线用户容器
{
tmpButton = pButtonVector.at(i); //获得用户按钮
if (tmpButton->getUser() == user) //匹配是否将要删除对象
{
tmpButton->hide(); //隐藏按钮
pButtonVector.remove(i); //从在线用户容器中删除
break;
}
}
}
}
/************************************************************
函数名: removeAll
功能: 从在线用户容器中删除所有用户
************************************************************/
void ChatDrawer::removeAll()
{
int count = pButtonVector.count();
if (!pButtonVector.isEmpty()) //是否在线用户容器为空
{
for(int i=0; i<count; i++) //检索在线用户容器
{
pButtonVector.at(0)->hide(); //将按钮从容器中删除
pButtonVector.remove(0);
}
}
}
/************************************************************
函数名: getVector
功能: 获得在线用户容器
返回值: 在线用户的容器
************************************************************/
const QVector<ToolButton *> & ChatDrawer::getVector() const
{
return this->pButtonVector; //返回在线用户容器
}
/************************************************************
函数名: getFriendGroup
功能: 获得好友分组容器
返回值: 好友分组容器
************************************************************/
QVector<QGroupBox *> & ChatDrawer::getFriendGroup()
{
return this->friendGroup; //返回好友分组容器
}
//void ChatDrawer::closeEvent(QCloseEvent *e)
//{
this->hide();
e->ignore();
//}