整体代码
注册
登录
好友列表
好友对话
服务器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w0ZNOPrF-1687012526102)(C:Users28558AppDataRoamingTyporatypora-user-imagesimage-20230617091508231.png)]
socket = MySocket::getIns();
//接收数据的连接函数 套接字发送readyRead信号connect(socket,SIGNAL(readyRead()),this,SLOT(slot_read()));
//获取文本内容QString nickname = ui->lineEdit_nickname->text();QString pass = ui->lineEdit_pass->text();
/*注册按钮的槽函数,作用是将昵称和密码中的文本构建出字符型发送给服务器*/
void RegistDialog::on_pushButton_regist_clicked()
{//获取文本内容QString nickname = ui->lineEdit_nickname->text();QString pass = ui->lineEdit_pass->text();//构建json串QString jsonstr = MsgBuilder::buildRegisterUserMsg(pass,0,nickname);//发送内容给服务器 QString->QByteArraysocket->Local8Bit());
}
/*readyRead()信号的槽函数,当服务器发送消息的时候调用,作用为解析返回的json串,并显示返回的id(账号)*/
void RegistDialog::slot_read()
{//获取接收信息QByteArray data = socket->readAll();//QByteArray->QStringQString jsonStr = QString::fromLocal8Bit(data);qDebug()<<jsonStr;//解析jsonint id = MsgBuilder::parseRegisterUserReturnMsg(jsonStr);//显示在画面上ui->label_id->setNum(id);
}
/*退出界面时,断开本界面对socket中readyRead()信号的监听*/
void RegistDialog::closeEvent(QCloseEvent *)
{//断开连接函数disconnect(socket,SIGNAL(readyRead()),this,SLOT(slot_read()));
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g83u1rZM-1687012526104)(C:Users28558AppDataRoamingTyporatypora-user-imagesimage-20230617103401937.png)]
//连接服务器成功套接字会发送connected信号connect(socket,SIGNAL(connected()),this,SLOT(slot_con()));
//接收数据 套接字会发送readyRead信号connect(socket,SIGNAL(readyRead()),this,SLOT(slot_read()));
/*connect按钮的槽函数,负责与服务器进行连接,连接成功后服务器会返回connected信号*/
void LoginDialog::on_pushButton_connect_clicked()
{//获取文本内容QString ip = ui->lineEdit_ip->text();//连接服务器socket->connectToHost(QHostAddress(ip),12345);
}
/*服务器返回连接信号调用对应槽函数,将界面的按钮恢复活性*/
void LoginDialog::slot_con()
{qDebug()<<"连接成功";//把失活的按钮变成活性ui->lineEdit_id->setEnabled(true);ui->lineEdit_pass->setEnabled(true);ui->pushButton_login->setEnabled(true);ui->pushButton_regist->setEnabled(true);
}
/*点击注册按钮弹出注册界面,此时登录界面断开对readyread()信号的监听*/
void LoginDialog::on_pushButton_regist_clicked()
{//页面切换RegistDialog *r = new RegistDialog(this);//断开连接函数 断开哪个信号 断开哪个槽函数disconnect(socket,SIGNAL(readyRead()),this,SLOT(slot_read()));//显示页面 (带有阻塞效果)r->exec();//连接函数connect(socket,SIGNAL(readyRead()),this,SLOT(slot_read()));
}
/*登录按钮的槽函数,负责将先前输入的id与密码发送给服务器*/
void LoginDialog::on_pushButton_login_clicked()
{//获取文本内容QString id = ui->lineEdit_id->text();QString pass = ui->lineEdit_pass->text();//构建jsonQString jsonstr = MsgBuilder::Int(), pass);qDebug()<<jsonstr;//发送给服务器socket->Local8Bit());
}
/*服务器返回readyread信号,对应的槽函数slot_read根据返回的消息类型进行处理*/
void LoginDialog::slot_read()
{//获取接收数据QByteArray data = socket->readAll();//QByteArray->QStringQString jsonstr = QString::fromLocal8Bit(data);qDebug()<<jsonstr;//获取消息类型int type = MsgBuilder::msgType(jsonstr);//判断消息类型switch (type) {case MsgBuilder::loginSucReturn://登录成功 type =3deelUserLoginSuc(jsonstr);//处理登录成功函数break;case MsgBuilder::loginLoseReturn://登录失败 type =4QMessageBox::information(this,"提示","登陆失败!请确认账号和密码!");//处理登录失败break;default:break;}
}
/*登录成功打开好友列表界面,保存自己和好友的数据并将这些数据传给好友列表的类*/
void LoginDialog::deelUserLoginSuc(QString jsonstr)
{//定义结构体接收自己的数据UserData hostData;//解析jsonvector<UserData> friends = MsgBuilder::parseLoginSucReturnMsg(hostData,jsonstr);//页面切换FriendList *f = new FriendList(hostData,friends);f->show();//关掉登录页面this->close();
}
/*这个页面关闭后,停止对服务器信号的监听*/
void LoginDialog::closeEvent(QCloseEvent *)
{//断开连接 断开哪个信号 断开哪个槽函数disconnect(socket,SIGNAL(readyRead()),this,SLOT(slot_read()));
}
登录成功后,由登录界面进入本界面
/*自定义的构造函数,接收由服务器发送的用户信息和好友列表信息*/
FriendList::FriendList(UserData user,vector<UserData> friends,QWidget *parent) :QDialog(parent),ui(new Ui::FriendList)
{ui->setupUi(this);//初始化this->user = user;this->friends = friends;socket = MySocket::getIns();socket->setSelf(user);//保存到全局使用的类中//连接函数connect(socket,SIGNAL(readyRead()),this,SLOT(slot_read()));//显示自己的信息QString str = QString("%1(%2)").arg(user.nickname).arg(user.id);ui->label->setText(str);//显示好友信息showFriends();
}
/*单个自定义控件中显示的都是好友信息,因此此处的data储存的是单个好友的信息*/
FriendForm::FriendForm(UserData data,QWidget *parent) :QWidget(parent),ui(new Ui::FriendForm),fdata(data)
{ui->setupUi(this);//显示图片QPixmap p(":/heads/head1.jpg");p = p.scaled(ui->label_img->size());ui->label_img->setPixmap(p);//显示好友信息QString str = QString("%1(%2)").arg(data.nickname).arg(data.id);ui->label_name->setText(str);
}
/*在列表中通过储存着好友信息的容器显示好友信息*/
void FriendList::showFriends()
{//遍历好友容器for(vector<UserData>::iterator it=friends.begin();it!d();it++){/*FriendForm继承QWidge*///创建自定义控件FriendForm *f = new FriendForm(*it,ui->listWidget);//创建行对象QListWidgetItem *i = new QListWidgetItem(ui->listWidget);//修改行对象的尺寸i->setSizeHint(f->size());//三者绑定ui->listWidget->setItemWidget(i,f);}
}
/*有好友上线,服务器发出信号,调用slot_read函数*/
void FriendList::slot_read()
{//获取接收消息QByteArray data = socket->readAll();//QByteArray->QStringQString jsonstr = QString::fromLocal8Bit(data);qDebug()<<jsonstr;//获取消息类型int type = MsgBuilder::msgType(jsonstr);switch (type) {case MsgBuilder::receiveMsg://接收消息 type = 6;deelUserReceiveMsg(jsonstr);//处理接收消息break;case MsgBuilder::userOnline://用户上线 type = 7;deelUserOnline(jsonstr);//处理用户上线break;case MsgBuilder::userOffline://用户下线 type = 8;deelUserOffline(jsonstr);//处理用户下线break;default:break;}
}
/*根据消息类型,选择处理用户上线的函数*/
void FriendList::deelUserOnline(QString jsonstr)
{//解析jsonUserData data = MsgBuilder::parseUserOnline(jsonstr);//更新好友容器friends.push_back(data);//创建自定义控件FriendForm *f = new FriendForm(data,ui->listWidget);//创建行对象QListWidgetItem *i = new QListWidgetItem(ui->listWidget);//修改行对象的尺寸i->setSizeHint(f->size());//三者绑定ui->listWidget->setItemWidget(i,f);
}
/*根据消息类型,选择处理用户下线的函数*/
void FriendList::deelUserOffline(QString jsonstr)
{//解析jsonUserData data = MsgBuilder::parseUserOffline(jsonstr);//遍历好友(listwidget)for(int i=0;i<ui->listWidget->count();i++){//获取行对象QListWidgetItem *it = ui->listWidget->item(i);//获取行对象内的控件QWidget *w = ui->listWidget->itemWidget(it);//父子类型转换获取到自定义控件FriendForm *f = dynamic_cast<FriendForm*>(w);//判断是否是下线的用户if(f->getData().id == data.id){//移除下线用户ui->listWidget->takeItem(i);delete f;delete it;}}
}
/*当好友通过服务器发送了一条消息后,根据消息类型调用本函数*/
void FriendList::deelUserReceiveMsg(QString jsonstr)
{//准备空的结构体类接收解析的数据UserData from;UserData to;//解析jsonQString msg = MsgBuilder::parseReceiveMsg(jsonstr,from,to);//判断单聊窗口是否被打开/*如果是已经打开的聊天窗口,则直接在窗口的聊天框上显示*/unt(from)){//找到的单聊窗口ChatDialog *chat = chatFridends[from];//显示接收信息chat->addReceiveMsg(msg);}/*未读的消息会在好友列表显示出未读*/else{//遍历好友(listwidget)for(int i=0;i<ui->listWidget->count();i++){//获取行对象QListWidgetItem *it = ui->listWidget->item(i);//获取行对象内的控件QWidget *w = ui->listWidget->itemWidget(it);//父子类型转换获取到自定义控件FriendForm *f = dynamic_cast<FriendForm*>(w);//判断是否是接收信息的用户if(f->getData().id == from.id){//保存未读信息f->addWaitMsg(msg);//消息提醒f->msgTip(true);}}}
}
/*追加写入未读消息的容器*/inline void addWaitMsg(QString msg){waitMsgs.append(msg);//waitMsgs:QStringList类型}
/*判断新消息的状态,如果未读就在列表中显示出未读,如果已读就恢复默认显示*/
void FriendForm::msgTip(bool flag)
{//判断是否是提示的状态if(flag){QString str = QString("(新消息)%1(%2)").arg(fdata.nickname).arg(fdata.id);ui->label_name->setText(str);}else{QString str = QString("%1(%2)").arg(fdata.nickname).arg(fdata.id);ui->label_name->setText(str);}
}
/*双击好友列表中的行对象,如果还未打开过这个窗口的话就进入聊天窗口*/
void FriendList::on_listWidget_itemDoubleClicked(QListWidgetItem *item)
{//获取自定义控件QWidget *w = ui->listWidget->itemWidget(item);//父子转换FriendForm *f = dynamic_cast<FriendForm*>(w);//获取好友信息UserData fdata = f->getData();//判断是否已经打开单聊窗口unt(fdata)){return;}//去除消息提醒提示f->msgTip(false);//获取未读信息QStringList msgs = f->getWaitMsg();//创建单聊窗口(传递好友信息)ChatDialog *c = new ChatDialog(fdata,this);c->show();//连接函数 当单聊窗口点击X关闭的时候,会发送rejected信号connect(c,SIGNAL(rejected()),this,SLOT(slot_rejected()));//显示未读信息c->showWaitMsg(msgs);//保存单聊相关信息chatFridends[fdata] = c;
}
/*获取第一条未读消息,并将这条消息从队列中删除*/
QStringList FriendForm::getWaitMsg()
{QStringList tmp = waitMsgs;waitMsgs.clear();return tmp;
}
/*找到关闭的是与谁聊天的窗口,在储存好友信息的容器中将他抹去*/
void FriendList::slot_rejected()
{//当前发出信号 的是哪个窗口 --》 获取窗口里边的好友是谁QObject *send = QObject::sender();//父子转换 找到单聊窗口ChatDialog *cd = dynamic_cast<ChatDialog*>(send);//获取窗口里边的好友是谁UserData ud = cd->getTo();//查找好友信息map<UserData,ChatDialog*>::iterator iter = chatFridends.find(ud);//判断是否找到if(iter != d()){ase(iter);//删除}
}
ChatDialog::ChatDialog(UserData data,QWidget *parent) :QDialog(parent),ui(new Ui::ChatDialog),to(data)
{ui->setupUi(this);//设置总布局QGridLayout *g = new QGridLayout;setLayout(g);//把聊天列表添加在总布局chatWidget = new ChatWidget(this);g->addWidget(chatWidget,0,0,1,2);//从第一行第一列开始,占一行两列//设置子布局(水平布局)QHBoxLayout *h = new QHBoxLayout;h->addWidget(ui->pushButton);h->addWidget(ui->pushButton_2);//把子布局添加在总布局g->addLayout(h,1,0,1,2);//从第二行第一列开始,占一行两列//把发送文本部分添加在总布局g->addWidget(ui->textEdit,2,0,1,1);//从第3行第1列开始,占1行1列//把发送按钮添加在总布局g->addWidget(ui->pushButton_send,2,1,1,1);//从第3行第2列开始,占1行1列//设置窗口标题QString str = QString("%1(%2)").arg(data.nickname).arg(data.id);setWindowTitle(str);//获取套接字socket = MySocket::getIns();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OfaPBEsV-1687012526105)(C:Users28558AppDataRoamingTyporatypora-user-imagesimage-20230617130200828.png)]
void ChatDialog::addReceiveMsg(QString msg)
{//显示好友消息chatWidget->addMsg(msg,0,ChatItem::guest);
}
/*每当来消息的时候,就新建一个行对象*/
void ChatWidget::addMsg(QString msg, int headId, ChatItem::ChatRole role)
{//创建自定义控件ChatItem* item = new ChatItem(msg, headId, role, this);//创建行对象QListWidgetItem* listItem = new QListWidgetItem(this);//修改行对象的尺寸listItem->setSizeHint(item->size());//三者进行绑定setItemWidget(listItem, item);
}
/*ChatItem的构造中设置了头像与文本*/
ChatItem::ChatItem(QString msg, int headId, ChatItem::ChatRole role, QWidget *parent):QWidget(parent)
{//头像labelHead = new QLabel(this);QPixmap head = QPixmap(":/heads/head1.jpg");
// switch(headId)
// {
// case 1:
// head = QPixmap(":/heads/head1.jpg");
// break;
// case 2:
// head = QPixmap(":/heads/head2.jpg");
// break;
// }head = head.scaled(QSize(HEAD_WIDTH, HEAD_HEIGHT));labelHead->setPixmap(head);//文本labelMsg = new QLabel(this);labelMsg->setMaximumWidth(ITEM_WIDTH - HEAD_WIDTH);//设置最大宽度,注意自适应大小时可能小于这个宽度就换行labelMsg->setWordWrap(true);//允许自动换行labelMsg->setText(msg);labelMsg->adjustSize();//文本根据内容自适应,这里注意设置的顺序labelMsg->setAlignment(Qt::AlignLeft|Qt::AlignTop);//左上对齐 labelMsg->resize(labelMsg->width(), labelMsg->height()*1.2f);//把文本的高度调大些,因为控件加入QListyWidget中后文本会被缩短labelMsg->setTextInteractionFlags(Qt::TextSelectableByMouse);//设置鼠标可选中文本switch(role){case host:hostSet();break;case guest:guestSet();break;}resize(ITEM_WIDTH, labelMsg->height()>HEAD_HEIGHT?labelMsg->height():HEAD_HEIGHT);
}
/*用户与好友的区别在于头像和文本的位置*/
void ChatItem::hostSet()
{labelHead->setGeometry(QRect(ITEM_WIDTH - HEAD_WIDTH, 0, HEAD_WIDTH, HEAD_HEIGHT));labelMsg->move(ITEM_WIDTH - HEAD_WIDTH-labelMsg->width(), 0);
}void ChatItem::guestSet()
{labelHead->setGeometry(QRect(0, 0, HEAD_WIDTH, HEAD_HEIGHT));labelMsg->move(HEAD_WIDTH, 0);
}
/*查看储存未读消息的容器,读出里面的信息*/
void ChatDialog::showWaitMsg(QStringList list)
{//遍历未读信息的容器for(QStringList::iterator it=list.begin();it!d();it++){//显示未读信息在左侧(因为是好友发送的)chatWidget->addMsg(*it,0,ChatItem::guest);}
}
/*将输入框中的文本打包发给服务器处理,然后清空输入框*/
void ChatDialog::on_pushButton_send_clicked()
{//获取发送内容QString msg = ui->textEdit->toPlainText();//获取自己的信息UserData from = socket->getSelf();//构建jsonQString jsonstr = MsgBuilder::buildSendMsg(from,to,msg);qDebug()<<jsonstr;//发送给服务器socket->Local8Bit());//在聊天列表区域显示发送的信息chatWidget->addMsg(msg,from.headId,ChatItem::host);//发送内容清空ui->textEdit->clear();
}
/*self是所有客户端的界面都共用的socket对象的定义了自己新的结构体的成员,类型未UserData*/
inline UserData getSelf(){return self;}
MainWindow::MainWindow(QWidget *parent):QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);qRegisterMetaType<UserData>("UserData");//注册setIp();//设置ip//为了内存管理传递父类指针sever = new MyServer(this);//监听所有ip的地址以及端口号 QHostAddress::Any:主机所有地址sever->listen(QHostAddress::Any,12345);//连接函数 自定义信号(发送sd标志) 槽函数connect(sever,SIGNAL(sig_sendSd(qintptr)),this,SLOT(onSigSendSd(qintptr)));
}
/*获取主机信息,主机地址并且把他们拼接成字符串显示在ui上*/
void MainWindow::setIp()
{//获取主机名称QString hostName = QHostInfo::localHostName();//获取主机信息QHostInfo info = QHostInfo::fromName(hostName);//获取主机地址QList<QHostAddress> list = info.addresses();//定义拼接的字符串QString ip;//遍历容器for(int i=0;i<list.size();i++){//获取协议类型是ipv4//protocol():获取协议类型 ipv4:QAbstractSocket::IPv4Protocolif(list[i].protocol() == QAbstractSocket::IPv4Protocol){//拼接ip += "*";ip += list[i].toString();}}//显示在ui上ui->label_address->setText(ip);
}
/*MyServer类:返回一个套接字*/
/*重写重写incomingConnection函数*/
void MyServer::incomingConnection(qintptr socketDescriptor)
{//根据sd标志发送自定义信号给主线程emit sig_sendSd(socketDescriptor);//自定义的信号
}
/*当server监听到incomingConnection事件后发送的sig_sendSd信号,根据sd标志创建子线程*/
/*监听线程中返回的各种信号*/
void MainWindow::onSigSendSd(qintptr sd)
{//创建子线程st = new SocketThread(sd);//开启线程st->start();//连接函数 子线程在接收的时候会发送finished信号 调用自带槽函数 删除对象(自动调用析构)connect(st,SIGNAL(finished()),st,SLOT(deleteLater()));//连接函数 子线程发送自定义信号(登陆成功)connect(st,SIGNAL(sigUserLoginSuc(UserData,SocketThread*)),this,SLOT(onUserLoginSuc(UserData,SocketThread*)));//连接函数 子线程发送自定义信号(用户下线)connect(st,SIGNAL(sigUserOffLine(UserData)),this,SLOT(onUserOffline(UserData)));//连接函数 子线程发送自定义信号(发送消息)connect(st,SIGNAL(sigUserSendMsg(UserData,UserData,QString)),this,SLOT(onUserSendMsg(UserData,UserData,QString)));
}
/*统计在线人数,将已经登录的好友添加到好友容器中,将自己上线的信息添加到消息队列中,将自己添加到已经登录的容器中*/
void MainWindow::onUserLoginSuc(UserData user, SocketThread *t)
{//判断信息是否存在unt(user)){return;}//定义好友的容器vector<UserData> friends;for(map<UserData,SocketThread*>::iterator it=users.begin();it!d();it++){friends.push_back(it->first);//添加到好友容器中it->second->addUserOnLine(user);//通知其他好友有新用户上线了}//把vector返回子线程t->addUserLoginSuc(user,friends);//统计在线人数 把当前信息存放进map容器中users[user] = t;
}
/*从存储已登录用户的容器中删除自己,将自己下线的消息添加到消息队列*/
void MainWindow::onUserOffline(UserData user)
{//从map容器中删除下线用户unt(user))ase(user);//通知给其他在线用户user下线了for(map<UserData,SocketThread*>::iterator it=users.begin();it!d();it++){it->second->addUserOffLine(user);//通知其他好友有用户下线了}
}
/*从登录的容器中找到要通讯的好友,将发消息给好友的信息添加到消息队列中*/
void MainWindow::onUserSendMsg(UserData from, UserData to, QString msg)
{//找到接收者的子线程SocketThread *tt = users[to];//接收者的子线程构建jsontt->addUserReceiveMsg(from,to,msg);
}
/*根据sd标志进行初始化,正在工作设定为真,是否来消息设定为假*/
SocketThread::SocketThread(qintptr sd):sd(sd)
{isRun = true;isRead = false;
}
子线程根据sd标志创建套接字后,不断监听取消连接和通信信号,在事件循环中不断判断来的消息并进行处理直到断开连接,断开连接后要给主线程发送下线的自定义信号并且关闭套接字;
void SocketThread::run()
{//根据sd标志 获取套接字socket = new QTcpSocket;socket->setSocketDescriptor(sd);//连接函数connect(socket,SIGNAL(disconnected()),this,SLOT(onDisconnect()));connect(socket,SIGNAL(readyRead()),this,SLOT(onReadyRead()));//事件循环while (isRun) //是否连接状态{if(isRead)//是否来消息{//获取接收消息QByteArray data = socket->readAll();//QByteArray->QStringQString jsonstr = QString::fromLocal8Bit(data);qDebug()<<jsonstr;//添加到消息队列中 为了更好的处理多条信息 让代码更好写mutex.lock();//加锁msgQueue.push(jsonstr);//入队mutex.unlock();//解锁isRead = false;}//判断队列是否为空while(!pty()){//获取队首元素QString jsonstr = msgQueue.front();qDebug()<<jsonstr;mutex.lock();//加锁msgQueue.pop();//出队mutex.unlock();//解锁//获取消息类型int type = MsgBuilder::msgType(jsonstr);//判断是哪种消息switch (type) {case MsgBuilder::registerUser://注册 :type = 0deelUserRegister(jsonstr);//处理注册的函数break;case MsgBuilder::login://登录 :type = 2deelUserLogin(jsonstr);//处理登录的函数break;case MsgBuilder::loginSucReturn://登录成功 :type = 3deelUserLoginSuc(jsonstr);//处理登录成功的函数break;case MsgBuilder::sendMsg://发送消息 :type = 5deelUserSendMsg(jsonstr);//处理发送消息的函数break;case MsgBuilder::receiveMsg://接收消息 :type = 6deelUserReceiveMsg(jsonstr);//处理接收消息的函数break;case MsgBuilder::userOnline://用户上线 :type = 7deelUserOnLine(jsonstr);//处理用户上线的函数break;case MsgBuilder::userOffline://用户下线 :type = 8deelUserOffLine(jsonstr);//处理用户下线的函数break;default:break;}}socket->waitForReadyRead(10);//单位毫秒}//发送用户下线自定义信号给主线程emit sigUserOffLine(user);//关闭套接字socket->close();delete socket;
}
/*对json串进行解析得到其中信息,并且将这些信息存入到数据库中,并且构建一个带有注册id的json串给客户端用于登录*/
void SocketThread::deelUserRegister(QString jsonStr)
{//解析jsonUserData user = MsgBuilder::parseRegisterUserMsg(jsonStr);//创建用于数据库数据传递的结构体类型UserEntity e;e.nickName = user.nickname;e.passwd = user.password;e.headId = user.headId;//调用数据库bool re = UserDao::getIns()->insertTable(e);if(re){qDebug()<<"恭喜 注册成功!";}else{qDebug()<<"注册失败!";}//构建注册返回的json串QString str = MsgBuilder::buildRegisterUserReturnMsg(e.userId);qDebug()<<str;//发送给客户端socket->Local8Bit());
}
/*对json串进行解析并且判断其id与密码是否匹配,登录成功就将这个用户信息保存下来并通知主线程当前用户已登录,登录失败则直接返回登录失败的json串给客户端*/
void SocketThread::deelUserLogin(QString jsonStr)
{//解析jsonUserData user = MsgBuilder::parseLoginMsg(jsonStr);//创建用于数据库数据传递的结构体类型UserEntity e;e.userId = user.id;e.passwd = user.password;//调用数据库bool ok;UserDao::getIns()->selectTable(e,ok);if(ok){qDebug()<<"恭喜 登录成功!";//获取数据user.nickname = e.nickName;user.headId = e.headId;//保存信息this->user = user;//发送登录成功的自定义信号emit sigUserLoginSuc(user,this);}else{qDebug()<<"登录失败!";//构建登录失败返回的json串QString str = MsgBuilder::buildLoginLoseReturnMsg();//发送个客户端socket->Local8Bit());}
}
/*登录成功将登录成功的消息解析后添加到run函数循环处理的消息队列中等待处理*/
void SocketThread::addUserLoginSuc(UserData user, vector<UserData> friends)
{//构建登录成功返回的json串QString str = MsgBuilder::buildLoginSucReturnMsg(user, friends);mutex.lock();//加锁msgQueue.push(str);//入队mutex.unlock();//解锁
}
void SocketThread::deelUserLoginSuc(QString jsonStr)
{//发送个客户端socket->Local8Bit());
}
/*处理方法与登录成功相同*/
void SocketThread::addUserOnLine(UserData user)
{//构建登录上线的json串QString str = MsgBuilder::buildUserOnline(user);mutex.lock();//加锁msgQueue.push(str);//入队mutex.unlock();//解锁
}void SocketThread::deelUserOnLine(QString jsonStr)
{//发送个客户端socket->Local8Bit());
}void SocketThread::addUserOffLine(UserData user)
{//构建登录上线的json串QString str = MsgBuilder::buildUserOffline(user);mutex.lock();//加锁msgQueue.push(str);//入队mutex.unlock();//解锁
}void SocketThread::deelUserOffLine(QString jsonStr)
{//发送个客户端socket->Local8Bit());
}
void SocketThread::addUserReceiveMsg(UserData from, UserData to, QString msg)
{//构建jsonQString str = MsgBuilder::buildReceiveMsg(from,to,msg);mutex.lock();//加锁msgQueue.push(str);//入队mutex.unlock();//解锁
}void SocketThread::deelUserReceiveMsg(QString jsonStr)
{//发送给客户端socket->Local8Bit());
}
登录成功,用户上线,用户下线和接收消息的处理方法逻辑相同 ,都是在主线程中找到对应的子线程,然后将解析好的json串压入子线程中循环处理的消息队列中,再由消息队列根据消息类型的不同分别处理
/*获取发送消息的用户和接收消息的用户,并把他们和要发送的消息一起打包发给主线程*/
void SocketThread::deelUserSendMsg(QString jsonstr)
{//准备好解析要用的结构体UserData from;UserData to;//解析jsonQString msg = MsgBuilder::parseSendMsg(jsonstr,from,to);//发送信号给主线程emit sigUserSendMsg(from,to,msg);
}
客户端的所有界面使用同一个socket对象进行通信
class MySocket : public QTcpSocket
{UserData self;//定义保存自己信息的结构体static MySocket *ins;//私有的静态类指针MySocket();//私有的构造函数MySocket(const MySocket& o);//私有的拷贝构造函数
public:static MySocket* getIns();//公有的静态成员函数返回值是指针//设置自己的个人信息inline void setSelf(UserData self){this->self = self;}//获取自己的个人信息inline UserData getSelf(){return self;}~MySocket();
};
#include "mysocket.h"MySocket* MySocket::ins = NULL;//懒汉模式MySocket::MySocket()
{}MySocket::MySocket(const MySocket &o)
{}MySocket *MySocket::getIns()
{if(ins == NULL)ins = new MySocket;//想什么时候用什么时候给return ins;
}MySocket::~MySocket()
{}
QTcpSocket类:
socket是系统给我们分配的用于tcp发送接收数据的资源。需要包含头文件
#include <QTcpSocket>
本次项目使用自定义的mysocket类继承QTcpSocket类实现相关功能
json是一种流行的网络通信的字符串格式。
本次项目使用的是CJson库,是一个第三方的json库,可以帮助我们构建json字符串和解析json字符串。
//使用CJson库中的函数构建字符串CJsonObject jsonObj;jsonObj.Add("nickname", "小明");jsonObj.Add("passwd", "abc123");jsonObj.Add("headid", 0);
//构建的json串
//{"nickname":"小明", "passwd":"abc123", "headid":0}
{}在json中称为对象容器,容器中的数据以键值对形式存在。
- nickname 小明 是一对 ,passwd abc123 ,是一对headid 0是一对
- key是字符串,value是C++中支持的所有基本数据类型int char double bool,以及字符串。
[] 在json中称为数组容器,数组容器中的数据没有key,是以角标形式访问数据。角标从0开始。
[“name1”, “name2”, “name3”]
- name1的角标是0
- name2的角标是1
- name3的角标是2
本次项目中没有用到[ ]容器
可以看作是一个请求或者一个动作的标志,信号是属于对象的,当对象的状态 改变时发射信号
槽是一个对象对他感兴趣的对象的某个时间做出处理。
其信号槽工作的过程是:当一个对象发射一个信号的时候,则和其连接的对象的槽函数进行处理,等槽函数处理完成之后退出并执行接下来的内容。
槽函数的本质就是类的成员函数,我们可以调用类的成员函数一样来调用槽函数
//槽函数的声明:权限+slots:
public slots:void slot_func();
槽函数可以跟 信号建立起关联,而普通的成员函数不可以
struct UserData//封装用户基本信息
{int id;//账号QString password;//密码int headId;//头像 QString nickname;//昵称bool operator<(const UserData& other)const{return id < other.id;}
};
enum MsgType//消息类型{registerUser, //注册用户registerUserReturn, //注册用户服务器返回login, //登录loginSucReturn, //登录成功服务器返回loginLoseReturn, //登录失败服务器返回sendMsg, //发送聊天消息receiveMsg, //收到聊天信息userOnline, //用户上线userOffline, //用户下线};
//获取消息类型static int msgType(QString jsonStr);//注册用户 static QString buildRegisterUserMsg(QString password, int headId, QString nickname);static UserData parseRegisterUserMsg(QString jsonStr);//注册用户返回static QString buildRegisterUserReturnMsg(int id);static int parseRegisterUserReturnMsg(QString jsonStr);//登录static QString buildLoginMsg(int id, QString password);static UserData parseLoginMsg(QString jsonStr);//登录返回static QString buildLoginSucReturnMsg(UserData hostData, vector<UserData>& friends);static vector<UserData> parseLoginSucReturnMsg(UserData& hostData, QString jsonStr);static QString buildLoginLoseReturnMsg();//发送聊天数据static QString buildSendMsg(UserData from, UserData to, QString msg);static QString parseSendMsg(QString jsonStr, UserData& from, UserData& to);//收到聊天信息static QString buildReceiveMsg(UserData from, UserData to, QString msg);static QString parseReceiveMsg(QString jsonStr, UserData& from, UserData& to);//用户上线static QString buildUserOnline(UserData user);static UserData parseUserOnline(QString jsonStr);//用户下线static QString buildUserOffline(UserData user);static UserData parseUserOffline(QString jsonStr);
/*QString->CJsonobj->QString*/
QString MsgBuilder::buildRegisterUserMsg(QString password, int headId, QString nickname)
{CJsonObject jsonObj;//CJsonObject是给数据加字段的容器jsonObj.Add("type", registerUser);//数据的类型,要注册用户,服务器端首先会看数据的type字段,根据不同的类型执行不同的逻辑jsonObj.Add("password", StdString());//这里之所以把QString转成C++的string类型,因为第三方框架是独立的,不会支持QT的任何类型jsonObj.Add("headId", headId);jsonObj.Add("nickname", StdString());//jsonObj.ToString() 将容器对象转换成JSON字符串//根据这个字符串创建一个QString对象,因为这个字符串要交给QT做处理QString retData(jsonObj.ToString().c_str());return retData;
}
/*QString->CJsonObject->UserData*/
UserData MsgBuilder::parseRegisterUserMsg(QString jsonStr)
{CJsonObject StdString());//根据json字符串创建JSON容器对象UserData data;//用来存在解析后得到的数据std::string stdStr;jsonObj.Get("password", stdStr);//根据数据的字段获得数据data.password = QString(stdStr.c_str());jsonObj.Get("headId", data.headId);jsonObj.Get("nickname", stdStr);data.nickname = QString(stdStr.c_str());return data;
}
本次项目的数据库我们希望它只有共有的一个,所以仍然采用单例模式,为了避免重名,重新定义了有一个储存用户数据的结构体UserEntity
struct UserEntity
{//每个成员对应表中的一个字段,主要用于数据的传递int userId;QString nickName;QString passwd;int headId;
};//单例模式-》数据库
class UserDao
{
private://创建数据驱动类对象QSqlDatabase db;static UserDao *ins;UserDao();//构造 -》创建表UserDao(const UserDao& o);
public:static UserDao* getIns();void createTable();//创建表bool insertTable(UserEntity& e);//插入数据 用于注册bool selectTable(UserEntity& e,bool& ok);//查询数据 用于登录~UserDao();
};
UserDao::UserDao()
{//加载数据库驱动db = QSqlDatabase::addDatabase("QSQLITE");//设置主机名称db.setHostName("testdb");//数据库文件设置名字db.setDatabaseName("testdb.db");//打开数据库db.open();//创建表createTable();
}
void UserDao::createTable()
{//创建操作类对象QSqlQuery query;//创建表bool re = ("create table if not exists user(""userid integer primary key autoincrement,""password varchar(20),""nickname varchar(20),""headid integer)");if(re){qDebug()<<"创建表";}else{qDebug()<<"创建表失败";}//插入一个原始用户因为希望用户的id是用100001开始,所以原始用户的id是("insert into user values(100000,'abc123','admin',0)");
}
bool UserDao::insertTable(UserEntity &e)
{//创建操作类对象QSqlQuery query;//准备sql文query.prepare("insert into user(password,nickname,headid) values(?,?,?)");//绑定数据query.bindValue(0,e.passwd);query.bindValue(1,e.nickName);query.bindValue(2,e.headId);//执行sql文bool re = ();if(re){qDebug()<<"插入表";}else{qDebug()<<"插入表失败";}("select LAST_INSERT_ROWID()");//查询最新插入的())//结果被存入到链表当中 ()获取到下一个有效结点{//d()获取一整条记录 value(0):获取到一个字段的值int id = d().value(0).toInt();qDebug()<<id;//保存到结构体当中e.userId = id;}return re;
}
bool UserDao::selectTable(UserEntity &e, bool &ok)
{//创建操作类对象QSqlQuery query;//准备sql文query.prepare("select * from user where userid = ? and password = ?");//绑定参数query.bindValue(0,e.userId);query.bindValue(1,e.passwd);//执行语句bool re = ();if(re){qDebug()<<"查询表";()){//获取查询的数据e.nickName = d().value(2).toString();e.headId = d().value(3).toInt();qDebug()<<e.nickName;//xmok = true;}else{ok = false;}}else{qDebug()<<"查询表失败";}return re;
}
包含头文件
#include <QDebug>
在Qt终端中显示,可以像cout或者printf一样使用
qDebug()<<"hello world";
qDebug("%s","helloc world");
query.bindValue(2,e.headId);
//执行sql文
bool re = ();
if(re)
{
qDebug()<<“插入表”;
}
else
{
qDebug()<<“插入表失败”;
}
(“select LAST_INSERT_ROWID()”);//查询最新插入的id
())//结果被存入到链表当中 ()获取到下一个有效结点
{
//d()获取一整条记录 value(0):获取到一个字段的值
int id = d().value(0).toInt();
qDebug()<<id;
//保存到结构体当中
e.userId = id;
}
return re;
}
##### 表的查找```c
bool UserDao::selectTable(UserEntity &e, bool &ok)
{//创建操作类对象QSqlQuery query;//准备sql文query.prepare("select * from user where userid = ? and password = ?");//绑定参数query.bindValue(0,e.userId);query.bindValue(1,e.passwd);//执行语句bool re = ();if(re){qDebug()<<"查询表";()){//获取查询的数据e.nickName = d().value(2).toString();e.headId = d().value(3).toInt();qDebug()<<e.nickName;//xmok = true;}else{ok = false;}}else{qDebug()<<"查询表失败";}return re;
}
包含头文件
#include <QDebug>
在Qt终端中显示,可以像cout或者printf一样使用
qDebug()<<"hello world";
qDebug("%s","helloc world");
本文发布于:2024-01-31 21:11:11,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170670667331379.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |