一、TCP通信
1.1 父类文件
//def.h
#pragma once
//加括号防止计算时出现计算顺序问题
#define _DEF_TCP_PORT (67890)
#define _DEF_TCP_SERVER_IP ("127.0.0.1")
//定义协议头
#define _DEF_PROTOCOL_BASE (1000)
#define _DEF_LEGNTH (100)
#define _DEF_CONTENT_LENGTH (4096)
#define _DEF_PROEOCOL_COUNT (10)
//注册请求
#define _DEF_REGISTER_RQ (_DEF_PROTOCOL_BASE + 1)
//注册回复
#define _DEF_REGISTER_RS (_DEF_PROTOCOL_BASE + 2)
//登录请求
#define _DEF_LOGIN_RQ (_DEF_PROTOCOL_BASE + 3)
//登录回复
#define _DEF_LOGIN_RS (_DEF_PROTOCOL_BASE + 4)
//获取好友列表
#define _DEF_FRIEND_INFO (_DEF_PROTOCOL_BASE + 5)
//聊天请求
#define _DEF_CHAT_RQ (_DEF_PROTOCOL_BASE + 6)
//聊天回复
#define _DEF_CHAT_RS (_DEF_PROTOCOL_BASE + 7)
//添加好友请求
#define _DEF_ADD_FRIEND_RQ (_DEF_PROTOCOL_BASE + 8)
//添加好友回复
#define _DEF_ADD_FRIEND_RS (_DEF_PROTOCOL_BASE + 9)
//下线请求
#define _DEF_OFFLINE_RQ (_DEF_PROTOCOL_BASE + 10)
//声明结果
//注册结果
#define _register_success (0)
#define _register_tel_exists (1)
#define _register_name_exists (2)
//登录结果
#define _login_success (0)
#define _login_tel_not_exists (1)
#define _login_password_error (2)
//用户状态
#define _status_online (0)
#define _status_offline (1)
//发送结果
#define _send_success (0)
#define _send_fail (1)
//添加好友结果
#define _add_friend_success (0)
#define _add_friend_not_exists (1)
#define _add_friend_offline (2)
#define _add_friend_refuse (3)
//声明协议头类型
typedef int PackType;
//请求结构体
//注册请求:协议头、电话号、昵称、密码
typedef struct _STRU_REGISTER_RQ {
_STRU_REGISTER_RQ() :type(_DEF_REGISTER_RQ) {
memset(tel, 0, _DEF_LEGNTH);
memset(name, 0, _DEF_LEGNTH);
memset(password, 0, _DEF_LEGNTH);
}
PackType type;
char tel[_DEF_LEGNTH];
char name[_DEF_LEGNTH];
char password[_DEF_LEGNTH];
}_STRU_REGISTER_RQ;
//注册回复:协议头、注册结果(注册成功、电话已被注册、昵称已被注册)
typedef struct _STRU_REGISTER_RS {
_STRU_REGISTER_RS() :type(_DEF_REGISTER_RS), result(_register_name_exists) {}
PackType type;
int result;
}_STRU_REGISTER_RS;
//登录请求:协议头、电话号码、密码
typedef struct _STRU_LOGIN_RQ {
_STRU_LOGIN_RQ() :type(_DEF_LOGIN_RQ) {
memset(tel, 0, _DEF_LEGNTH);
memset(password, 0, _DEF_LEGNTH);
}
PackType type;
char tel[_DEF_LEGNTH];
char password[_DEF_LEGNTH];
}_STRU_LOGIN_RQ;
//登录回复:协议头、登录结果(登录成功、密码错误、电话不存在)
typedef struct _STRU_LOGIN_RS {
_STRU_LOGIN_RS() :type(_DEF_LOGIN_RS), result(_login_password_error) {}
PackType type;
int userID;
int result;
}_STRU_LOGIN_RS;
//获取好友列表:协议头、用户id、昵称、签名、头像id、状态
typedef struct _STRU_FRIEND_INFO {
_STRU_FRIEND_INFO() :type(_DEF_FRIEND_INFO), status(_status_offline), iconID(0), userID(0) {
memset(name, 0, _DEF_LEGNTH);
memset(feeling, 0, _DEF_LEGNTH);
}
PackType type;
int userID;
char name[_DEF_LEGNTH];
char feeling[_DEF_LEGNTH];
int iconID;
int status;
}_STRU_FRIEND_INFO;
//聊天请求:协议头、自己的id、聊天内容、好友id
typedef struct _STRU_CHAT_RQ {
_STRU_CHAT_RQ() :type(_DEF_CHAT_RQ), userID(0), friendID(0) {
memset(content, 0, _DEF_CONTENT_LENGTH);
}
PackType type;
int userID;
int friendID;
char content[_DEF_CONTENT_LENGTH];
}_STRU_CHAT_RQ;
//聊天回复:协议头、发送结果(成功、用户不在线)
typedef struct _STRU_CHAT_RS {
_STRU_CHAT_RS() :type(_DEF_CHAT_RS), result(_send_fail),friendId(0) {}
PackType type;
int friendId;
int result;
}_STRU_CHAT_RS;
//添加好友请求(根据昵称添加好友):协议头、好友的昵称、自己的id、自己的昵称
typedef struct _STRU_ADD_FRIEND_RQ {
_STRU_ADD_FRIEND_RQ() :type(_DEF_ADD_FRIEND_RQ), userID(0) {
memset(userName, 0, _DEF_LEGNTH);
memset(friendName, 0, _DEF_LEGNTH);
}
PackType type;
int userID;
char userName[_DEF_LEGNTH];
char friendName[_DEF_LEGNTH];
}_STRU_ADD_FRIEND_RQ;
//添加好友回复
typedef struct _STRU_ADD_FRIEND_RS {
_STRU_ADD_FRIEND_RS() :type(_DEF_ADD_FRIEND_RS), result(_add_friend_refuse) {}
PackType type;
int result;
}_STRU_ADD_FRIEND_RS;
//下线请求:协议头、自己的id
typedef struct _STRU_OFFLINE_RQ {
_STRU_OFFLINE_RQ() :type(_DEF_OFFLINE_RQ), userID(0) {}
PackType type;
int userID;
}_STRU_OFFLINE_RQ;
//net.h
#pragma once
#include <WinSock2.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"Ws2_32.lib")
//先声明后使用
//解决头文件互相包含的问题
//先声明有mediator这个类,直接使用,一会就编译到了
class mediator;
class net {
public:
net():m_sock(INVALID_SOCKET) {}
virtual ~net() {}
//初始化网络
virtual bool initNet() = 0;
//发送数据(UDP里由IP地址决定数据发给谁,TCP由SOCKET决定数据发送给谁)
//IP是ulong类型,SCOKET是uint
virtual bool sendData(char* data,int len,long to) = 0;
//接收数据
virtual void recvData() = 0;
//关闭网络
virtual void unInitNet() = 0;
protected:
SOCKET m_sock;
mediator* m_pMediator;
};
1.2 TCPserver
#pragma once
#include "net.h"
#include <list>
#include <map>
class TcpServer :public net {
public:
TcpServer(mediator* pMediator);
~TcpServer();
//初始化网络
bool initNet();
//发送数据(UDP里由IP地址决定数据发给谁,TCP由SOCKET决定数据发送给谁)
//IP是ulong类型,SCOKET是uint
bool sendData(char* data, int len, long to);
//接收数据
void recvData();
//关闭网络
void unInitNet();
protected:
static unsigned __stdcall acceptThread(void* lpVoid);
static unsigned __stdcall recvThread(void* lpVoid);
private:
//HANDLE m_handle;
bool m_bRunning;
list<HANDLE> m_listHandle;
map<unsigned int, SOCKET> m_mapThreadIdToSocket;
};
#include "TcpServer.h"
#include "def.h"
#include <process.h>
#include "../mediator/TcpServerMediator.h"
TcpServer::TcpServer(mediator* pMediator):m_bRunning(1) {
//m_pMediator = new TcpServerMediator; //ERROR:stack overflow
//在main中已经有TcpMediator的对象了,让内部指针指向外部的对象
m_pMediator = pMediator;
}
TcpServer::~TcpServer() {
}
//初始化网络:加载库、创建套接字、绑定、监听、创建接受连接的线程
bool TcpServer::initNet() {
//1.加载库
WORD versions = MAKEWORD(2, 2);
WSADATA data;
int err = WSAStartup(versions, &data);
if (err != 0) {
cout << "加载库失败" << endl;
return 0;
}
if (HIBYTE(data.wVersion) != 2 || LOBYTE(data.wVersion) != 2) {
cout << "版本号错误" << endl;
return 0;
}
else {
cout << "加载库成功" << endl;
}
//2.创建套接字
m_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_sock == INVALID_SOCKET) {
cout << "创建套接字失败:" << WSAGetLastError() << endl;
return 0;
}
else {
cout << "创建套接字成功" << endl;
}
//3.绑定
sockaddr_in addrServer;
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(_DEF_TCP_PORT);
addrServer.sin_addr.S_un.S_addr = INADDR_ANY;
err = bind(m_sock, (sockaddr*)&addrServer, sizeof(addrServer));
if (err == SOCKET_ERROR) {
cout << "绑定失败:" << WSAGetLastError() << endl;
return 0;
}
else {
cout << "绑定成功" << endl;
}
//4.监听
err = listen(m_sock, 10);
if (err == SOCKET_ERROR) {
cout << "监听失败:" << WSAGetLastError() << endl;
return 0;
}
else {
cout << "监听成功" << endl;
}
//5.创建接收连接的线程
HANDLE handle = (HANDLE)_beginthreadex(nullptr, 0, &acceptThread, this, 0, nullptr);
if (handle) {
m_listHandle.push_back(handle);
}
return 1;
}
//接收连接的线程
unsigned __stdcall TcpServer::acceptThread(void* lpVoid) {
TcpServer* pthis = (TcpServer*)lpVoid;
sockaddr_in addrClient;
int addrClientSize = sizeof(addrClient);
unsigned int threadId = 0;
while (pthis->m_bRunning) {
SOCKET sockTalk = accept(pthis->m_sock, (sockaddr*)&addrClient, &addrClientSize);
if (sockTalk == INVALID_SOCKET) {
cout << "accept error:" << WSAGetLastError() << endl;
}
else {
//连接成功
cout << "客户端ip:" << inet_ntoa(addrClient.sin_addr) << endl;
//创建接收数据的线程
HANDLE handle = (HANDLE)_beginthreadex(nullptr, 0, &recvThread, pthis, 0, &threadId);
if (handle) {
pthis->m_listHandle.push_back(handle);
}
//保存socket
pthis->m_mapThreadIdToSocket[threadId] = sockTalk;
}
}
return 1;
}
//接收数据线程
unsigned __stdcall TcpServer::recvThread(void* lpVoid) {
TcpServer* pthis = (TcpServer*)lpVoid;
pthis->recvData();
return 1;
}
//发送数据
bool TcpServer::sendData(char* data, int len, long to) {
//1.校验参数
if (!data || len <= 0) {
return 0;
}
//2.先发包大小(避免粘包问题)
if (send(to, (char*)&len, sizeof(int), 0) <= 0) {
cout << "sendData error1:" << WSAGetLastError() << endl;
return 0;
}
//3.再发包内容
if (send(to, data, len, 0) <= 0) {
cout << "sendData error2:" << WSAGetLastError() << endl;
return 0;
}
return 1;
}
//接收数据
void TcpServer::recvData() {
//休眠一会,以防map中还没有保存socket
Sleep(10);
//从map中取出当前线程对应的socket
SOCKET sock = m_mapThreadIdToSocket[GetCurrentThreadId()];
if (!sock || sock == INVALID_SOCKET) {
cout << "recvData socket error" << endl;
return;
}
int nRecvNum = 0;
int packSize = 0;
int nOffset = 0; //偏移量,记录一个包累计接收到多少数据
while (m_bRunning) {
nRecvNum = recv(sock, (char*)&packSize, sizeof(int), 0);
if (nRecvNum > 0) {
//接收数据成功,知道包大小了,按照包大小准备空间
char* recvBuf = new char[packSize];
//接收包内容
while (packSize > 0) {
nRecvNum = recv(sock, recvBuf + nOffset/*开始写入的地址*/, packSize/*最大能写入的数据*/, 0);
if (nRecvNum > 0) {
nOffset += nRecvNum;
packSize -= nRecvNum;
}
else {
cout << "recvData error2:" << WSAGetLastError() << endl;
break;
}
}
//TODO:接收包内容成功,把数据传给中介者
m_pMediator->transmitData(recvBuf, nOffset, sock);
//偏移量清零
nOffset = 0;
}
else {
cout << "recvData error1:" << WSAGetLastError() << endl;
break;
}
}
}
//关闭网络
void TcpServer::unInitNet() {
//1.结束线程
m_bRunning = 0;
for (auto ite = m_listHandle.begin();ite!=m_listHandle.end(); ){
HANDLE handle=*ite;
if (WaitForSingleObject(handle, 1000) == WAIT_TIMEOUT) {
TerminateThread(handle, -1);
}
CloseHandle(handle);
handle = nullptr;
//从list中移除无用的空间
ite = m_listHandle.erase(ite);
}
//2.关闭套接字
if (!m_sock && m_sock != INVALID_SOCKET) {
closesocket(m_sock);
}
for (auto ite = m_mapThreadIdToSocket.begin(); ite != m_mapThreadIdToSocket.end();) {
SOCKET sock = ite->second;
if (!sock && sock != INVALID_SOCKET) {
closesocket(sock);
}
//从map中移除无效节点
ite = m_mapThreadIdToSocket.erase(ite);
}
//3.卸载库
WSACleanup();
}
1.3 TCPclient
#pragma once
#include "net.h"
class TcpClient:public net {
public:
TcpClient(mediator* pMediator);
~TcpClient();
//初始化网络
bool initNet() ;
//发送数据(UDP里由IP地址决定数据发给谁,TCP由SOCKET决定数据发送给谁)
//IP是ulong类型,SCOKET是uint
bool sendData(char* data, int len, long to);
//接收数据
void recvData();
//关闭网络
void unInitNet();
protected:
static unsigned __stdcall recvThread(void* lpVoid);
private:
HANDLE m_handle;
bool m_bRunning;
};
#include "TcpClient.h"
#include <process.h>
#include "def.h"
#include "../mediator/TcpClientMediator.h"
TcpClient::TcpClient(mediator* pMediator):m_handle(nullptr),m_bRunning(1) {
m_pMediator = pMediator;
}
TcpClient::~TcpClient(){
}
//初始化网络:加载库、创建套接字、连接服务端、创建接收数据的线程(创建即运行)
bool TcpClient::initNet() {
//1.加载库
WORD versions = MAKEWORD(2, 2);
WSADATA data;
int err = WSAStartup(versions, &data);
if (err != 0) {
cout << "WSAStartup fail" << endl;
return 0;
}
if (HIBYTE(data.wVersion) != 2 || LOBYTE(data.wVersion) != 2) {
cout << "version error" << endl;
WSACleanup();
return 1;
}
else {
cout << "WSAStartup success" << endl;
}
//2.创建套接字
m_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_sock == INVALID_SOCKET) {
cout << "socket eror:" << WSAGetLastError() << endl;
return 0;
}
else {
cout << "socket success" << endl;
}
//3.连接服务器
sockaddr_in addrServer;
addrServer.sin_family = AF_INET;
// 魔鬼数字
addrServer.sin_port = htons(_DEF_TCP_PORT);
addrServer.sin_addr.S_un.S_addr = inet_addr(_DEF_TCP_SERVER_IP);
err = connect(m_sock, (sockaddr*)&addrServer, sizeof(addrServer));
if (err == SOCKET_ERROR) {
cout << "connect error:" << WSAGetLastError() << endl;
return 0;
}
else {
cout << "connect success" << endl;
}
//4.创建接收数据的线程(创建即运行)
//CreateThread和ExitThread是一对,如果在线程使用了C++运行时库的函数(例如strcpy,申请了空间但没有释放)ExitThread也不会释放空间,这样子就会造成内存泄露
//使用_beginthreadex和_endthreadex,_endthreadex会先回收空间,再调用ExitThread
m_handle = (HANDLE)_beginthreadex(nullptr, 0/*使用线程默认堆栈大小1M*/, &recvThread/*需要有对象才能&,所以采用静态*/, this/*静态不能调用非静态,所以把this指针传进去*/, 0/*创建即运行*/, nullptr);
return 1;
}
//线程函数(接收数据)
unsigned __stdcall TcpClient::recvThread(void* lpVoid){
TcpClient* pthis = (TcpClient*)lpVoid;/*注意把void*强转*/
pthis->recvData();
return 1;
}
//发送数据
bool TcpClient::sendData(char* data, int len, long to) {
//1.校验参数
if (!data || len <= 0) {
return 0;
}
//2.先发包大小(避免粘包问题)
if (send(m_sock,(char*)&len,sizeof(int),0) <= 0) {
cout << "sendData error1" << WSAGetLastError() << endl;
return 0;
}
//3.再发包内容
if (send(m_sock,data,len,0) <= 0) {
cout << "sendData error1" << WSAGetLastError() << endl;
return 0;
}
return 1;
}
//接收数据:循环接收数据
void TcpClient::recvData() {
int nRecvNum = 0;
int packSize = 0;
int nOffset = 0; //偏移量,记录一个包累计接收到多少数据
while (m_bRunning) {
nRecvNum = recv(m_sock, (char*)&packSize, sizeof(int), 0);
if (nRecvNum > 0) {
//接收数据成功,知道包大小了,按照包大小准备空间
char* recvBuf = new char[packSize];
//接收包内容
while (packSize>0) {
nRecvNum = recv(m_sock, recvBuf + nOffset/*开始写入的地址*/, packSize/*最大能写入的数据*/, 0);
if (nRecvNum > 0) {
nOffset += nRecvNum;
packSize -= nRecvNum;
}
else {
cout << "recvData error2:" << WSAGetLastError() << endl;
break;
}
}
//TODO:接收包内容成功,把数据传给中介者
m_pMediator->transmitData(recvBuf, nOffset, m_sock);
//偏移量清零
nOffset = 0;
}
else {
cout << "recvData error1:" << WSAGetLastError() << endl;
break;
}
}
}
//关闭网络:结束线程、关闭套接字、卸载库
void TcpClient::unInitNet() {
/*
创建线程时,操作系统会给每个线程分配:内核对象、句柄(操作线程)、线程id(标志),引用计数+2
结束进程就是让引用技术变成0:结束线程工作,引用计数-1;关闭句柄,引用计数-1
不允许强制杀死线程,会导致死锁等问题
*/
//1.结束线程
m_bRunning = 0;
if (WaitForSingleObject(m_handle, 1000)==WAIT_TIMEOUT) {
TerminateThread(m_handle,-1/*退出码*/);
}
CloseHandle(m_handle);
m_handle = nullptr;
//2.关闭套接字
if (!m_sock && m_sock != INVALID_SOCKET) {
closesocket(m_sock);
}
//3.卸载库
WSACleanup();
}
二、中介者类
2.1 父类
#pragma once
//先声明后使用
class net;
class mediator {
public:
mediator(){}
virtual ~mediator(){}
//打开网络
virtual bool openNet() = 0;
//发送数据
virtual bool sendData(char* data, int len, long to) = 0;
//转发数据(把接收到的数据转发给kernel处理)
virtual void transmitData(char* data, int len, long from) = 0;
//关闭网络
virtual void closeNet() = 0;
protected:
net* m_pNet;
};
2.2 TCPserverMediator
#pragma once
#include "mediator.h"
class TcpServerMediator :public mediator {
public:
TcpServerMediator();
~TcpServerMediator();
//打开网络
bool openNet();
//发送数据
bool sendData(char* data, int len, long to);
//转发数据(把接收到的数据转发给kernel处理)
void transmitData(char* data, int len, long from);
//关闭网络
void closeNet();
};
#include "TcpServerMediator.h"
#include "../net/TcpServer.h"
#include "../CKernel.h"
TcpServerMediator::TcpServerMediator(){
m_pNet = new TcpServer(this/*把main的外部指针传到内部*/);
}
TcpServerMediator::~TcpServerMediator(){
if (m_pNet) {
m_pNet->unInitNet();
delete m_pNet;
m_pNet = nullptr;
}
}
//打开网络
bool TcpServerMediator::openNet() {
//调用TcpServer的initNet
return m_pNet->initNet();
}
//发送数据
bool TcpServerMediator::sendData(char* data, int len, long to) {
return m_pNet->sendData(data,len,to);
}
//转发数据(把接收到的数据转发给kernel处理)
void TcpServerMediator::transmitData(char* data, int len, long from) {
CKernel::pKernel->dealData(data, len, from);
}
//关闭网络
void TcpServerMediator::closeNet() {
m_pNet->unInitNet();
}
2.3 TCPclientMediator
#pragma once
#include "mediator.h"
class TcpClientMediator :public mediator {
public:
TcpClientMediator();
~TcpClientMediator();
//打开网络
bool openNet();
//发送数据
bool sendData(char* data, int len, long to);
//转发数据(把接收到的数据转发给kernel处理)
void transmitData(char* data, int len, long from);
//关闭网络
void closeNet();
};
#include "TcpClientMediator.h"
#include "../net/TcpClient.h"
TcpClientMediator::TcpClientMediator() {
m_pNet = new TcpClient(this);
}
TcpClientMediator::~TcpClientMediator() {
if (m_pNet) {
m_pNet->unInitNet();
delete m_pNet;
m_pNet = nullptr;
}
}
//打开网络
bool TcpClientMediator::openNet() {
//调用TcpClient的initNet
return m_pNet->initNet();
}
//发送数据
bool TcpClientMediator::sendData(char* data, int len, long to) {
return m_pNet->sendData(data,len,to);
}
//转发数据(把接收到的数据转发给kernel处理)
void TcpClientMediator::transmitData(char* data, int len, long from) {
Q_EMIT sig_transmitData(data,len,from);
}
//关闭网络
void TcpClientMediator::closeNet() {
m_pNet->unInitNet();
}
/*
QT的信号和槽的使用(两个之间通知和数据传递)
1.两个都要继承自QObject,并且要有Q_OBJECT的宏
2.发送通知或者数据的类中,在头文件使用signals声明信号(返回值是void,参数需要传输的数据),信号不是函数不需要实现
在需要发通知或者数据的地方使用Q_EMIT 信号名(参数列表)发送信号
3.在接收数据的类中,使用slots声明一个槽函数(返回值和参数要和信号保持一致),在cpp中实现槽函数
4.绑定信号和槽函数,在接收数据的类中,发送信号的对象new出来以后
*/
三、kernel类
3.1 服务端
#pragma once
#include "mediator/mediator.h"
#include "net/def.h"
#include "MySQL/CMySql.h"
#include <map>
//声明函数指针
//先声明,后定义
class CKernel;
typedef void(CKernel::* PFUN)(char* data, int len, long from);
class CKernel
{
public:
//静态指针
static CKernel* pKernel;
CKernel();
~CKernel();
//打开服务器
bool startServer();
//关闭服务器
void closeServer();
//处理所有接收到的数据
void dealData(char* data, int len, long from);
//绑定协议头数组
void setProrocolMap();
//处理注册请求
void dealRegisterRq(char* data, int len, long from);
//处理登录请求
void dealLoginRq(char* data, int len, long from);
//处理聊天请求
void dealChatRq(char* data, int len, long from);
//处理下线请求
void dealOfflineRq(char* data, int len, long from);
//获取用户的好友信息(包括自己)
void getUserInfo(int userID);
//根据用户id查询用户信息
void getInfoById(int id, _STRU_FRIEND_INFO* info);
private:
mediator* m_pMediator;
//函数指针数组
PFUN m_mapProrocol[_DEF_PROEOCOL_COUNT];
//数据库对象
CMySql m_mysql;
std::map<int,SOCKET> m_mapIdToSocket;
};
#include <iostream>
using namespace std;
#include "CKernel.h"
#include "mediator/TcpServerMediator.h"
CKernel* CKernel::pKernel = nullptr;
CKernel::CKernel() {
pKernel = this;
m_pMediator = new TcpServerMediator;
setProrocolMap();
}
CKernel::~CKernel() {
if (m_pMediator) {
m_pMediator->closeNet();
delete m_pMediator;
m_pMediator = nullptr;
}
}
//打开服务器
bool CKernel::startServer() {
//1.打开网络
if (!m_pMediator->openNet()) {
cout << "openNet error" << endl;
return 0;
}
//2.连接数据库
if (!m_mysql.ConnectMySql((char*)"127.0.0.1", (char*)"root", (char*)"123456", (char*)"kelin_ism")) {
cout << "连接数据库失败" << endl;
return 0;
}
return 1;
}
//关闭服务器
void CKernel::closeServer() {
//关闭网络
if (m_pMediator) {
m_pMediator->closeNet();
delete m_pMediator;
m_pMediator = nullptr;
}
//TODO:关闭数据库
}
//处理所有接收到的数据
void CKernel::dealData(char* data, int len, long from) {
cout << __func__ << endl;
//取出协议头
PackType type = *(PackType*)data;
//计算出数组下标
int index = type - _DEF_PROTOCOL_BASE - 1;
//判断数组下标是否在有效范围内
if (index >= 0 && index < _DEF_PROEOCOL_COUNT) {
//取出函数指针
PFUN pf = m_mapProrocol[index];
if (pf) {
(this->*pf)(data, len, from);
}
else {
//1.没有绑定协议头和数组 2.结构体协议头赋值错误
cout << "type2:" << type << endl;
}
}
else {
//1.结构体协议头赋值错误 2.接收数据有问题,可能offset没有清零
cout << "type1:" << type << endl;
}
}
//绑定协议头数组
void CKernel::setProrocolMap() {
cout << __func__ << endl;
//初始化数组
memset(m_mapProrocol, 0, sizeof(m_mapProrocol));
//给数组赋值
m_mapProrocol[_DEF_REGISTER_RQ - _DEF_PROTOCOL_BASE - 1] = &CKernel::dealRegisterRq;
m_mapProrocol[_DEF_LOGIN_RQ - _DEF_PROTOCOL_BASE - 1] = &CKernel::dealLoginRq;
m_mapProrocol[_DEF_CHAT_RQ - _DEF_PROTOCOL_BASE - 1] = &CKernel::dealChatRq;
m_mapProrocol[_DEF_OFFLINE_RQ - _DEF_PROTOCOL_BASE - 1] = &CKernel::dealOfflineRq;
m_mapProrocol[_DEF_ADD_FRIEND_RQ - _DEF_PROTOCOL_BASE - 1] = &CKernel::dealAddFriendRq;
m_mapProrocol[_DEF_ADD_FRIEND_RS - _DEF_PROTOCOL_BASE - 1] = &CKernel::dealAddFriendRs;
}
//处理注册请求
void CKernel::dealRegisterRq(char* data, int len, long from) {
cout << __func__ << endl;
//1.拆包
_STRU_REGISTER_RQ* rq = (_STRU_REGISTER_RQ*)data;
//2.校验数据合法性(规则和客户端一致)
//3.根据电话号码从数据库查询电话号
list<string> lstRes;
char szSql[1024] = "";
sprintf(szSql, "select tel from t_user where tel = '%s';", rq->tel);
if (!m_mysql.SelectMySql(szSql, 1, lstRes)) {
cout << "查询数据库失败:" << szSql << endl;
return;
}
//4.判断结果查询结果是否为空
_STRU_REGISTER_RS rs;
if (lstRes.size() != 0) {
//查询到了,说明电话号已经被注册过了,注册失败
rs.result = _register_tel_exists;
}
else {
//5.根据昵称查询昵称
sprintf(szSql, "select name from t_user where name = '%s';", rq->name);
if (!m_mysql.SelectMySql(szSql, 1, lstRes)) {
cout << "查询数据库失败:" << szSql << endl;
return;
}
//6.判断结果查询是否为空
if (lstRes.size() != 0) {
//查询到了,说明昵称已经被注册过了,注册失败
rs.result = _register_name_exists;
}
else {
//7.没有查询到,把注册信息写入数据库
sprintf(szSql, "insert into t_user (name,tel,password,iconid,feeling) values ('%s','%s','%s',6,'这个人太懒了,什么也没有留下');", rq->name, rq->tel, rq->password);
if (!m_mysql.UpdateMySql(szSql)) {
cout << "更新数据库失败:" << szSql << endl;
return;
}
//8.注册成功
rs.result = _register_success;
}
}
//9.不论是否成功,都要把结果发回给客户端
m_pMediator->sendData((char*)&rs, sizeof(rs), from);
}
//处理登录请求
void CKernel::dealLoginRq(char* data, int len, long from) {
cout << __func__ << endl;
//1.拆包
_STRU_LOGIN_RQ* rq = (_STRU_LOGIN_RQ*)data;
//2.根据电话号码从数据库查询密码
list<string> lstRes;
char szSql[1024] = "";
sprintf(szSql, "select password, id from t_user where tel = '%s';", rq->tel);
if (!m_mysql.SelectMySql(szSql, 2, lstRes)) { //写入两列信息
cout << "查询数据库失败:" << szSql << endl;
return;
}
//3.判断结果是否为空
_STRU_LOGIN_RS rs;
if (lstRes.size() == 0) {
//如果结果为空,说明电话号码没有注册,登录失败
rs.result = _login_tel_not_exists;
}
else {
//取出查询的密码(取出的数据要从列表中删除)
string pass = lstRes.front();
lstRes.pop_front();
//取出用户id
int userID = stoi(lstRes.front()); //string to int
lstRes.pop_front();
//4.结果不为空,比较查询到的密码和用户输入的密码
if (strcmp(rq->password, pass.c_str()) != 0) {
//密码不相等,登录失败
rs.result = _login_password_error;
}
else {
//5.密码相等,登录成功
rs.result = _login_success;
rs.userID = userID;
//保存当前登录用户的socket
m_mapIdToSocket[userID] = from;
//6.无论登录是否成功,都要给客户端回复登录结果
m_pMediator->sendData((char*)&rs, sizeof(rs), from);
//获取当前好友列表
getUserInfo(userID);
return;
}
}
//6.无论登录是否成功,都要给客户端回复登录结果
m_pMediator->sendData((char*)&rs, sizeof(rs), from);
}
//获取用户的好友信息(包括自己)
void CKernel::getUserInfo(int userID) {
cout << __func__ << endl;
//根据自己的id从数据库查询自己的信息
_STRU_FRIEND_INFO userinfo;
getInfoById(userID, &userinfo);
//把自己的信息发给客户端。采取查一个发一个,解决数据庞大的问题,查找和发送效率都高一些
if (m_mapIdToSocket.find(userID) != m_mapIdToSocket.end()) {
m_pMediator->sendData((char*)&userinfo, sizeof(userinfo), m_mapIdToSocket[userID]);
}
//从好友关系表中查询好友的id列表
list<string> lstRes;
char szSql[1024] = "";
sprintf(szSql, "select idB from t_friend where idA= '%d';",userID);
if (!m_mysql.SelectMySql(szSql, 1, lstRes)) {
cout << "查询数据库失败:" << szSql << endl;
return;
}
//遍历好友id列表
int friendId = 0;
_STRU_FRIEND_INFO friendinfo;
while (lstRes.size() > 0) {
//取出好友id
friendId = stoi(lstRes.front());
lstRes.pop_front();
//根据好友id查询好友信息
getInfoById(friendId, &friendinfo);
//把好友的信息发给客户端
if (m_mapIdToSocket.find(userID) != m_mapIdToSocket.end()) {
m_pMediator->sendData((char*)&friendinfo, sizeof(friendinfo), m_mapIdToSocket[userID]);
}
//通知所有在线好友,自己上线了
if (m_mapIdToSocket.count(friendId) > 0) {
m_pMediator->sendData((char*)&userinfo, sizeof(userinfo), m_mapIdToSocket[friendId]);
}
}
}
//根据用户id查询用户信息
void CKernel::getInfoById(int id, _STRU_FRIEND_INFO* info) {
cout << __func__ << endl;
info->userID = id;
if (m_mapIdToSocket.count(id) > 0) {
//用户在线
info->status = _status_online;
}
else {
//用户不在线
info->status = _status_offline;
}
//根据id从数据库查询用户的昵称、签名、头像id
list<string> lstRes;
char szSql[1024] = "";
sprintf(szSql, "select name, feeling,iconId from t_user where id= '%d';", id);
if (!m_mysql.SelectMySql(szSql, 3, lstRes)) {
cout << "查询数据库失败:" << szSql << endl;
return;
}
if (3 == lstRes.size()) {
//取出昵称
strcpy(info->name, lstRes.front().c_str());
lstRes.pop_front();
//取出签名
strcpy(info->feeling, lstRes.front().c_str());
lstRes.pop_front();
//取出头像id
info->iconID = stoi(lstRes.front());
lstRes.pop_front();
}
cout << info->name << " " << info->feeling << " " << info->iconID << endl;
}
//处理聊天请求
void CKernel::dealChatRq(char* data, int len, long from) {
cout << __func__ << endl;
//1.拆包
_STRU_CHAT_RQ* rq = (_STRU_CHAT_RQ*)data;
//2.判断好友是否在线
if (m_mapIdToSocket.count(rq->friendID) > 0) {
m_pMediator->sendData(data, len, m_mapIdToSocket[rq->friendID]);
}
else {
//好友不在线,把聊天内容存在数据库里,等好友上线再发给他
//好友不在线,发送失败
_STRU_CHAT_RS rs;
rs.result = _send_fail;
rs.friendId = rq->friendID;
m_pMediator->sendData((char*)&rs,sizeof(rs),from);
}
}
//处理下线请求
void CKernel::dealOfflineRq(char* data, int len, long from) {
cout << __func__ << endl;
//1.拆包
_STRU_OFFLINE_RQ* rq = (_STRU_OFFLINE_RQ*)data;
//2.根据用户id查询所有好友的id列表
list<string> lstRes;
char szSql[1024] = "";
sprintf(szSql, "select idB from t_friend where idA= '%d';", rq->userID);
if (!m_mysql.SelectMySql(szSql, 1, lstRes)) {
cout << "查询数据库失败:" << szSql << endl;
return;
}
//3.遍历查询结果
int friendId = 0;
while (lstRes.size() > 0) {
//4.取出每个好友的id
friendId = stoi(lstRes.front());
lstRes.pop_front();
//5.判断好友是否在线
if (m_mapIdToSocket.count(friendId) > 0) {
//6.如果好友在线,给好友转发下线请求
m_pMediator->sendData(data, len, m_mapIdToSocket[friendId]);
}
}
//7.找到下线用户的socket
if (m_mapIdToSocket.count(rq->userID) > 0) {
//关闭socket
closesocket(m_mapIdToSocket[rq->userID]);
//从map中移除无效节点
m_mapIdToSocket.erase(rq->userID);
}
}
//处理添加好友请求
void CKernel::dealAddFriendRq(char* data, int len, long from) {
cout << __func__ << endl;
//1.拆包
_STRU_ADD_FRIEND_RQ* rq = (_STRU_ADD_FRIEND_RQ*)data;
//2.根据昵称查询用户id
list<string> lstRes;
char szSql[1024] = "";
sprintf(szSql, "select id from t_user where name='%s';", rq->friendName);
if (!m_mysql.SelectMySql(szSql, 1, lstRes)) {
cout << "查询数据库失败:" << szSql << endl;
return;
}
_STRU_ADD_FRIEND_RS rs;
strcpy_s(rs.friendName, rq->friendName);
//3.判断结果是否为空
if (lstRes.size() == 0) {
//4.结果为空,说明用户不存在,添加失败
rs.result = _add_friend_not_exists;
}
else {
//取出用户id
int friendId = stoi(lstRes.front());
lstRes.pop_front();
//5.结果不为空,判断用户是否在线
if (m_mapIdToSocket.count(friendId) > 0) {
//6.用户在线,给用户转发添加好友请求
m_pMediator->sendData(data, len, m_mapIdToSocket[friendId]);
return;
}
else {
//7.用户不在线,添加好友失败
rs.result = _add_friend_offline;
}
}
//把失败结果发回给客户端
m_pMediator->sendData((char*)&rs, sizeof(rs), from);
}
//处理添加好友回复
void CKernel::dealAddFriendRs(char* data, int len, long from) {
cout << __func__ << endl;
//1.拆包
_STRU_ADD_FRIEND_RS* rs = (_STRU_ADD_FRIEND_RS*)data;
//2.判断添加结果是否同意
if (rs->result == _add_friend_success) {
//3.把好友关系写入数据库
char szSql[1024] = "";
sprintf(szSql, "insert into t_friend values('%d','%d');", rs->aId, rs->bId); //实现两条插入都成功,可以用事务或者触发器
if (!m_mysql.UpdateMySql(szSql)) {
cout << "插入数据库失败:" << szSql << endl;
return;
}
sprintf(szSql, "insert into t_friend values('%d','%d');", rs->bId, rs->aId);
if (!m_mysql.UpdateMySql(szSql)) {
cout << "插入数据库失败:" << szSql << endl;
return;
}
//4.更新好友列表
getUserInfo(rs->aId);
}
//5.不管结果如何,都要给A客户端转发添加结果
if (m_mapIdToSocket.count(rs->aId) > 0) {
m_pMediator->sendData(data, len, m_mapIdToSocket[rs->aId]);
}
}
3.2 客户端
#ifndef QKERNEL_H
#define QKERNEL_H
#include <QObject>
#include "imdialog.h"
#include "mediator/mediator.h"
#include "net/def.h"
#include "friendlist.h"
#include <QMap>
#include "chatdialog.h"
//声明函数指针
class QKernel;
typedef void(QKernel::* PFUN)(char* data, int len, long from);
class QKernel : public QObject
{
Q_OBJECT
public:
explicit QKernel(QObject *parent = nullptr);
~QKernel();
//QT使用utf-8,vs使用gb3212,在客户端做字符转换
//utf-8转GB2312
void utf8Togb2312(QString src,char* dest ,int len);
//GB2312转utf-8
QString gb2312Toutf8(char* src);
//绑定协议头数组
void setProrocolMap();
//处理注册回复
void dealRegisterRs(char* data, int len, long from);
//处理登录回复
void dealLoginRs(char* data, int len, long from);
//处理好友信息
void dealFriendInfoRs(char* data,int len,long from);
//处理聊天请求
void dealChatRq(char* data,int len,long from);
//处理聊天回复
void dealChatRs(char* data,int len,long from);
//处理下线请求
void dealOfflineRq(char* data,int len,long from);
signals:
public slots:
//把接收到的数据传给kernel
void slot_transmitData(char* data,int len,long from);
//处理注册信息
void slot_commitRegister(QString name,QString tel,QString pass);
//处理登录信息
void slot_commitLogin(QString tel,QString pass);
//处理显示聊天窗口
void slot_showChatDialog(int id);
//处理聊天内容
void slot_sendChatMessage(QString content,int id);
//处理关闭程序的信号
void slot_closeApp();
//处理下线信号
void slot_offline();
//处理添加好友的信号
void slot_addFriend();
private:
int m_id;
QString m_mname;
IMDialog* m_pImDialog;
mediator* m_pMediator;
FriendList* m_pFriendList;
//函数指针数组
PFUN m_mapProtocol[_DEF_PROTOCOL_COUNT];
//保存好友
QMap<int,useritem*> m_mapIdToUseritem;
//保存与好友的聊天窗口
QMap<int,chatDialog*> m_mapIdToChatdlg;
};
#endif // QKERNEL_H
#include "qkernel.h"
#include "mediator/TcpClientMediator.h"
#include <QDebug>
#include <QMessageBox>
#include "useritem.h"
#include <QTextCodec>
#include <QInputDialog>
QKernel::QKernel(QObject *parent) : QObject(parent)
{
qDebug()<<__func__;
//给登录&注册窗口new对象,并显示
m_pImDialog=new IMDialog;
m_pImDialog->show();
//绑定关闭程序的信号和槽函数
connect(m_pImDialog,&IMDialog::sig_closeApp,this,&QKernel::slot_closeApp);
//绑定处理注册信息的信号和槽函数
connect(m_pImDialog,&IMDialog::sig_commitRegister,this,&QKernel::slot_commitRegister);
//绑定处理登录信息的信号和槽函数
connect(m_pImDialog,&IMDialog::sig_commitLogin,this,&QKernel::slot_commitLogin);
//给好友列表界面new对象
m_pFriendList=new FriendList;
//绑定添加好友的信号和槽函数
connect(m_pFriendList,&FriendList::sig_addFriend,this,&QKernel::slot_addFriend);
//绑定下显得信号和槽函数
connect(m_pFriendList,&FriendList::sig_offline,this,&QKernel::slot_offline);
//打开网络
m_pMediator=new TcpClientMediator;
if(!m_pMediator->openNet()){
QMessageBox::about(m_pImDialog,"info","net error");
exit(0);
}
//绑定处理所有数据的信号和槽信号
connect(m_pMediator,&TcpClientMediator::sig_transmitData,this,&QKernel::slot_transmitData);
setProrocolMap();
}
QKernel::~QKernel()
{
qDebug()<<__func__;
if(m_pImDialog){
m_pImDialog->hide();
delete m_pImDialog;
m_pImDialog=nullptr;
}
if(m_pMediator){
m_pMediator->closeNet();
delete m_pMediator;
m_pMediator=nullptr;
}
if(m_pFriendList){
m_pFriendList->hide();
delete m_pFriendList;
m_pFriendList=nullptr;
}
for(auto ite=m_mapIdToChatdlg.begin();ite!=m_mapIdToChatdlg.end();){
chatDialog* chat=ite.value();
if(chat){
chat->hide();
delete chat;
chat=nullptr;
}
//把无效节点从map中移除
ite=m_mapIdToChatdlg.erase(ite);
}
}
//utf-8转GB2312
void QKernel::utf8Togb2312(QString src, char *dest, int len/*目标空间大小*/)
{
QTextCodec* dc = QTextCodec::codecForName("gb2312");
QByteArray ba=dc->fromUnicode(src);
strcpy_s(dest,len,ba.data());
}
//GB2312转utf-8
QString QKernel::gb2312Toutf8(char *src)
{
QTextCodec* dc = QTextCodec::codecForName("gb2312");
return dc->toUnicode(src);
}
void QKernel::slot_transmitData(char* data,int len,long from){
qDebug()<<__func__;
//取出协议头
PackType type=*(PackType*)data;
int index=type-_DEF_PROTOCOL_BASE-1;
if(index>=0 && index<_DEF_PROTOCOL_COUNT){
PFUN pf=m_mapProtocol[index];
if(pf){
(this->*pf)(data,len,from);
}
else{
qDebug()<<"type2:"<<type;
}
}
else{
qDebug()<<"type1:"<<type;
}
}
//处理注册信息
void QKernel::slot_commitRegister(QString name, QString tel, QString pass)
{
qDebug()<<__func__;
//1.打包
_STRU_REGISTER_RQ rq;
//strcpy()复制字符串内容,直到'\0'结束
//memcpt()复制寄存器内容,按长度结束
//strcpy_s(rq.name,name.toStdString().c_str());
utf8Togb2312(name,rq.name,sizeof(rq.name));
strcpy_s(rq.tel,tel.toStdString().c_str());
strcpy_s(rq.password,pass.toStdString().c_str());
//字符串转换:char*/char[],std::string,QString
//char*/char[]是基础类型,std::string、QString是类,基础类型可以直接给类对象赋值
//std::string调用c_str()转换成char*
//QString调用toStdString()转换成std::string
//2.发给服务器
m_pMediator->sendData((char*)&rq,sizeof(rq),0);
}
void QKernel::slot_commitLogin(QString tel, QString pass)
{
qDebug()<<__func__;
//1.打包
_STRU_LOGIN_RQ rq;
strcpy_s(rq.tel,tel.toStdString().c_str());
strcpy_s(rq.password,pass.toStdString().c_str());
//2.发给服务器
m_pMediator->sendData((char*)&rq,sizeof(rq),0);
}
//处理显示聊天窗口
void QKernel::slot_showChatDialog(int id)
{
qDebug()<<__func__<<endl;
//判断map中是否有这个窗口
if(m_mapIdToChatdlg.count(id)>0){
//找到窗口并显示
chatDialog* chat=m_mapIdToChatdlg[id];
chat->showNormal();
}
}
//绑定协议头数组
void QKernel::setProrocolMap(){
//初始化数组
memset(m_mapProtocol,0,sizeof(m_mapProtocol));
//给数组赋值
m_mapProtocol[_DEF_REGISTER_RS-_DEF_PROTOCOL_BASE-1]=&QKernel::dealRegisterRs;
m_mapProtocol[_DEF_LOGIN_RS-_DEF_PROTOCOL_BASE-1]=&QKernel::dealLoginRs;
m_mapProtocol[_DEF_FRIEND_INFO-_DEF_PROTOCOL_BASE-1]=&QKernel::dealFriendInfoRs;
m_mapProtocol[_DEF_CHAT_RQ-_DEF_PROTOCOL_BASE-1]=&QKernel::dealChatRq;
m_mapProtocol[_DEF_CHAT_RS-_DEF_PROTOCOL_BASE-1]=&QKernel::dealChatRs;
m_mapProtocol[_DEF_OFFLINE_RQ-_DEF_PROTOCOL_BASE-1]=&QKernel::dealOfflineRq;
m_mapProtocol[_DEF_ADD_FRIEND_RQ-_DEF_PROTOCOL_BASE-1]=&QKernel::dealAddFriendRq;
m_mapProtocol[_DEF_ADD_FRIEND_RS-_DEF_PROTOCOL_BASE-1]=&QKernel::dealAddFriendRs;
}
//处理注册回复
void QKernel::dealRegisterRs(char* data, int len, long from){
qDebug()<<__func__;
//1.拆包
_STRU_REGISTER_RS* rs=(_STRU_REGISTER_RS*)data;
//2.根据注册结果提示用户
switch (rs->result) {
case _register_success:
QMessageBox::about(m_pImDialog,"提示","注册成功");
break;
case _register_tel_exists:
QMessageBox::about(m_pImDialog,"提示","注册失败,电话号码已被注册");
break;
case _register_name_exists:
QMessageBox::about(m_pImDialog,"提示","注册失败,昵称已被占用");
break;
default:
break;
}
}
//处理登录回复
void QKernel::dealLoginRs(char* data, int len, long from){
qDebug()<<__func__;
//1.拆包
_STRU_LOGIN_RS* rs=(_STRU_LOGIN_RS*)data;
//2.根据注册结果提示用户
switch (rs->result) {
case _login_success:{
//进行页面跳转,隐藏登录界面,显示好友界面
m_pImDialog->hide();
m_pFriendList->show();
//保存登录用户的id
m_id=rs->userID;
}
break;
case _login_tel_not_exists:
QMessageBox::about(m_pImDialog,"提示","登录失败,电话号码未注册");
break;
case _login_password_error:
QMessageBox::about(m_pImDialog,"提示","登录失败,密码错误");
break;
default:
break;
}
}
//处理好友信息
void QKernel::dealFriendInfoRs(char *data, int len, long from)
{
qDebug()<<__func__;
//1.拆包
_STRU_FRIEND_INFO* rq=(_STRU_FRIEND_INFO*)data;
QString name=gb2312Toutf8(rq->name);
QString feeling=gb2312Toutf8(rq->feeling);
//2.判断是不是自己的信息
if(rq->userID==m_id){
//保存自己的昵称
m_mname=name;
//把自己的信息设置到界面上
m_pFriendList->setUserInfo(name,feeling,rq->iconID);
}
//好友的信息,判断列表中是否已经存在这个好友了
if(m_mapIdToUseritem.count(rq->userID)>0){
//有好友,更新好友信息(好友状态变化——上线和下线)
useritem* item=m_mapIdToUseritem[rq->userID];
item->setFriendInfo(rq->userID,rq->status,rq->iconID,name,feeling);
}
else{
//没有好友,new一个好友
useritem* item=new useritem;
//设置好友信息
item->setFriendInfo(rq->userID,rq->status,rq->iconID,name,feeling);
//把好友添加到列表上
m_pFriendList->addFriend(item);
//保存usetitem
m_mapIdToUseritem[rq->userID]=item;
//绑定显示聊天窗口的信号和槽函数
connect(item,&useritem::sig_showChatDialog,this,&QKernel::slot_showChatDialog);
//new一个跟这个好友的聊天窗口
chatDialog* chat=new chatDialog;
//设置窗口信息
chat->setChatInfo(rq->userID,name);
//保存聊天窗口
m_mapIdToChatdlg[rq->userID]=chat;
//绑定发送聊天内容的信号和槽函数
connect(chat,&chatDialog::sig_sendChatMessage,this,&QKernel::slot_sendChatMessage);
}
}
//处理聊天请求
void QKernel::dealChatRq(char *data, int len, long from)
{
qDebug()<<__func__;
//1.拆包
_STRU_CHAT_RQ* rq=(_STRU_CHAT_RQ*)data;
//当前是B客户端,聊天内容是A客户端发来的
//2.找到与A客户端的聊天窗口
if(m_mapIdToChatdlg.count(rq->userID)>0){
//设置聊天内容到窗口上,并显示聊天窗口
chatDialog* chat=m_mapIdToChatdlg[rq->userID];
chat->setChatCotent(rq->content);
chat->showNormal();
}
}
//处理聊天回复
void QKernel::dealChatRs(char *data, int len, long from)
{
qDebug()<<__func__;
//1.拆包
_STRU_CHAT_RS* rs=(_STRU_CHAT_RS*)data;
//我是A客户端,B客户端不在线
//2.找到与B客户端的聊天窗口
if(m_mapIdToChatdlg.count(rs->friendId)>0){
//显示B客户端不在线,并显示聊天窗口
chatDialog* chat=m_mapIdToChatdlg[rs->friendId];
chat->setFriendOffline();
chat->showNormal();
}
}
//处理添加好友请求
void QKernel::dealAddFriendRq(char *data, int len, long from)
{
qDebug()<<__func__;
//1.拆包
_STRU_ADD_FRIEND_RQ* rq=(_STRU_ADD_FRIEND_RQ*)data;
//2.提示用户有人添加好友,询问是否同意
_STRU_ADD_FRIEND_RS rs;
if(QMessageBox::question(m_pFriendList,"添加好友",QString("【%1】申请添加你为好友,是否同意").arg(rq->userName))==QMessageBox::Yes){
rs.result=_add_friend_success;
}
else{
rs.result=_add_friend_refuse;
}
strcpy_s(rs.friendName,m_mname.toStdString().c_str());
rs.aId=rq->userID;
rs.bId=m_id;
//把结果发给服务器端
m_pMediator->sendData((char*)&rs,sizeof(rs),0);
}
//处理添加好友回复
void QKernel::dealAddFriendRs(char *data, int len, long from)
{
qDebug()<<__func__;
//1.拆包
_STRU_ADD_FRIEND_RS* rs=(_STRU_ADD_FRIEND_RS*)data;
QString friendName=gb2312Toutf8(rs->friendName);
//2.根据结果提示用户
switch (rs->result) {
case _add_friend_success:
QMessageBox::about(m_pFriendList,"标题",QString("添加【%1】为好友成功").arg(rs->friendName));
break;
case _add_friend_not_exists:
QMessageBox::about(m_pFriendList,"标题",QString("添加【%1】为好友失败,好友不存在").arg(friendName));
break;
case _add_friend_offline:
QMessageBox::about(m_pFriendList,"标题",QString("添加【%1】为好友失败,好友不在线").arg(friendName));
break;
case _add_friend_refuse:
QMessageBox::about(m_pFriendList,"标题",QString("【%1】拒绝添加你为好友").arg(rs->friendName));
break;
default:
break;
}
}
void QKernel::dealOfflineRq(char *data, int len, long from)
{
qDebug()<<__func__;
//1.拆包
_STRU_OFFLINE_RQ* rq=(_STRU_OFFLINE_RQ*)data;
//2.找到好友的useritem
if(m_mapIdToUseritem.count(rq->userID)>0){
useritem* item=m_mapIdToUseritem[rq->userID];
//设置好友的状态为下线
item->setFriendOffline();
}
}
void QKernel::slot_sendChatMessage(QString content, int id)
{
qDebug()<<__func__;
//1.打包
_STRU_CHAT_RQ rq;
rq.userID=m_id;
rq.friendID=id;
strcpy_s(rq.content,content.toStdString().c_str());
//2.发给服务端
m_pMediator->sendData((char*)&rq,sizeof(rq),0);
}
//处理关闭程序的信号
void QKernel::slot_closeApp()
{
qDebug()<<__func__;
//1.回收资源
if(m_pImDialog){
m_pImDialog->hide();
delete m_pImDialog;
m_pImDialog=nullptr;
}
if(m_pMediator){
m_pMediator->closeNet();
delete m_pMediator;
m_pMediator=nullptr;
}
if(m_pFriendList){
m_pFriendList->hide();
delete m_pFriendList;
m_pFriendList=nullptr;
}
for(auto ite=m_mapIdToChatdlg.begin();ite!=m_mapIdToChatdlg.end();){
chatDialog* chat=ite.value();
if(chat){
chat->hide();
delete chat;
chat=nullptr;
}
//把无效节点从map中移除
ite=m_mapIdToChatdlg.erase(ite);
}
//2.退出进程
exit(0);
}
void QKernel::slot_offline()
{
qDebug()<<__func__;
//1.给服务器端发送下线通知
_STRU_OFFLINE_RQ rq;
rq.userID=m_id;
m_pMediator->sendData((char*)&rq,sizeof(rq),0);
//2.回收资源,退出进程
slot_closeApp();
}
void QKernel::slot_addFriend()
{
//1.弹出输入窗口,让用户输入昵称
QString friendName=QInputDialog::getText(m_pFriendList,"添加好友","请输入好友的昵称:");
QString friendNameTmp=friendName;
//2.判断用户输入是否合法
if(friendName.isEmpty() || friendNameTmp.remove(" ").isEmpty()){
QMessageBox::about(m_pFriendList,"提示","用户昵称不能为空");
return;
}
// //3.判断输入的昵称是否是自己的昵称
// if(friendName==m_mname){
// QMessageBox::about(m_pFriendList,"提示","不能添加自己为好友");
// return;
// }
//4.判断昵称是否已经是好友
for(useritem* item:m_mapIdToUseritem){
if(item->name() == friendName){
QMessageBox::about(m_pFriendList,"提示","已经是好友了");
return;
}
}
//5.给服务器发送添加好友请求
_STRU_ADD_FRIEND_RQ rq;
rq.userID=m_id;
utf8Togb2312(friendName,rq.friendName,sizeof(rq.friendName));
//strcpy_s(rq.friendName,friendName.toStdString().c_str());
strcpy_s(rq.userName,m_mname.toStdString().c_str());
m_pMediator->sendData((char*)&rq,sizeof(rq),0);
}
四、界面
4.1 登录、注册界面
#ifndef IMDIALOG_H
#define IMDIALOG_H
#include <QDialog>
QT_BEGIN_NAMESPACE
namespace Ui { class IMDialog; }
QT_END_NAMESPACE
class IMDialog : public QDialog
{
Q_OBJECT
public:
IMDialog(QWidget *parent = nullptr);
~IMDialog();
//重写关闭事件
void closeEvent(QCloseEvent* event);
signals:
//把注册信息发给kernel
void sig_commitRegister(QString name,QString tel,QString pass);
//把登录信息发给kernel
void sig_commitLogin(QString tel,QString pass);
//通知kernel关闭程序
void sig_closeApp();
private slots:
void on_pb_clear_register_clicked();
void on_pb_commit_register_clicked();
void on_pb_clear_clicked();
void on_pb_commit_clicked();
private:
Ui::IMDialog *ui;
};
#endif // IMDIALOG_H
#include "imdialog.h"
#include "ui_imdialog.h"
#include <QDebug>
#include <QMessageBox>
IMDialog::IMDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::IMDialog)
{
ui->setupUi(this);
}
IMDialog::~IMDialog()
{
delete ui;
}
void IMDialog::closeEvent(QCloseEvent *event)
{
Q_EMIT sig_closeApp();
}
void IMDialog::on_pb_clear_register_clicked()
{
qDebug()<<__func__;
ui->le_name_register->setText("");
ui->le_password_register->setText("");
ui->le_tel_register->setText("");
}
void IMDialog::on_pb_commit_register_clicked()
{
qDebug()<<__func__;
//1.从控件上获取用户输入的数据
QString name=ui->le_name_register->text();
QString tel=ui->le_tel_register->text();
QString pass=ui->le_password_register->text();
QString nameTmp=name;
QString telTmp=tel;
QString passTmp=pass;
//2.校验用户数据合法性
//2.1校验是否为空或全空格(移除所有空格后是否为空)
if(name.isEmpty() || tel.isEmpty() || pass.isEmpty() || nameTmp.remove(" ").isEmpty() || telTmp.remove(" ").isEmpty() || passTmp.remove(" ").isEmpty()){
QMessageBox::about(this,"提示","输入的内容不能为空或者全空格");
return ;
}
//2.2校验长度(昵称不能超过15,密码不超过20,电话号码必须为11位)
if(name.length()>15 || pass.length()>20 || tel.length()!=11){
QMessageBox::about(this,"提示","昵称不能超过15,密码不超过20,电话号码必须为11位");
return ;
}
//2.3 校验内容(电话号码必须是数字,满足电话号码的规律;昵称不允许有特殊字符;密码只能是字母、数字、下划线)——TODO:正则表达式
//3.把数据传给kernel
Q_EMIT sig_commitRegister(name,tel,pass);
}
void IMDialog::on_pb_clear_clicked()
{
qDebug()<<__func__;
ui->le_password->setText("");
ui->le_tel->setText("");
}
void IMDialog::on_pb_commit_clicked()
{
qDebug()<<__func__;
//1.从控件上获取用户输入的数据
QString tel=ui->le_tel->text();
QString pass=ui->le_password->text();
QString telTmp=tel;
QString passTmp=pass;
//2.校验用户数据合法性
//2.1校验是否为空或全空格(移除所有空格后是否为空)
if(tel.isEmpty() || pass.isEmpty() || telTmp.remove(" ").isEmpty() || passTmp.remove(" ").isEmpty()){
QMessageBox::about(this,"提示","输入的内容不能为空或者全空格");
return ;
}
//2.2校验长度(密码不超过20,电话号码必须为11位)
if(pass.length()>20 || tel.length()!=11){
QMessageBox::about(this,"提示","密码不超过20,电话号码必须为11位");
return ;
}
//2.3 校验内容(电话号码必须是数字,满足电话号码的规律;密码只能是字母、数字、下划线)——TODO:正则表达式
//3.把登录信息传给kernel
Q_EMIT sig_commitLogin(tel,pass);
}
4.2 朋友列表
#ifndef FRIENDLIST_H
#define FRIENDLIST_H
#include <QDialog>
#include "useritem.h"
#include <QVBoxLayout>
#include <QMenu>
namespace Ui {
class FriendList;
}
class FriendList : public QDialog
{
Q_OBJECT
signals:
//通知kernel下线
void sig_offline();
//通知kernel添加好友
void sig_addFriend();
public:
explicit FriendList(QWidget *parent = nullptr);
~FriendList();
//把好友添加到列表上
void addFriend(useritem* item);
//设置自己的信息
void setUserInfo(QString name,QString feeling,int iconID);
//重写关闭事件
void closeEvent(QCloseEvent* event);
private slots:
void on_pb_menu_clicked();
//处理菜单项点击的信号
void slot_menuClicked(QAction* action);
private:
Ui::FriendList *ui;
//把小控件添加到大控件上,需要有一个层layout
QVBoxLayout* m_layout;
//定义一个菜单的对象
QMenu* m_pMenu;
};
#endif // FRIENDLIST_H
#include "friendlist.h"
#include "ui_friendlist.h"
#include <QCloseEvent>
#include <QMessageBox>
FriendList::FriendList(QWidget *parent) :
QDialog(parent),
ui(new Ui::FriendList)
{
ui->setupUi(this);
//new一个垂直布局的层
m_layout=new QVBoxLayout;
//设置小控件间的距离
m_layout->setSpacing(3);
//设置小控件和大控件间的距离
m_layout->setContentsMargins(0,0,0,0);
//把层添加到大控件上
ui->wdg_list_friend->setLayout(m_layout);
//new一个菜单的对象
m_pMenu=new QMenu(this);
//添加菜单项
m_pMenu->addAction("添加好友");
m_pMenu->addAction("系统设置");
//绑定菜单项点击的信号和槽函数
connect(m_pMenu,&QMenu::triggered,this,&FriendList::slot_menuClicked);
}
FriendList::~FriendList()
{
delete ui;
}
void FriendList::addFriend(useritem *item)
{
m_layout->addWidget(item);
}
void FriendList::setUserInfo(QString name, QString feeling, int iconID)
{
ui->lb_name->setText(name);
ui->le_feeling->setText(feeling);
ui->pb_icon->setIcon(QIcon(QString(":/tx/%1.png").arg(iconID)));
}
//重写关闭事件
void FriendList::closeEvent(QCloseEvent *event)
{
//忽略当前时间(否则会继续走父类的关闭函数,还能是关闭窗口)
event->ignore();
//询问用户是否确认关闭程序
if(QMessageBox::Yes == QMessageBox::question(this,"提示","是否确认退出程序?")){
Q_EMIT sig_offline();
}
}
void FriendList::on_pb_menu_clicked()
{
//在鼠标点击位置上向上弹出菜单栏
//获取鼠标点击的坐标
QPoint point=QCursor::pos();
//获取菜单栏的绝对大小
QSize size=m_pMenu->sizeHint();
//显示菜单
m_pMenu->exec(QPoint(point.x(),point.y()-size.height()));
}
//处理菜单项点击的信号
void FriendList::slot_menuClicked(QAction *action)
{
if("添加好友"==action->text()){
Q_EMIT sig_addFriend();
}
else{
}
}
4.3 每条好友信息
#ifndef USERITEM_H
#define USERITEM_H
#include <QWidget>
namespace Ui {
class useritem;
}
class useritem : public QWidget
{
Q_OBJECT
signals:
//通知kernel显示聊天内容
void sig_showChatDialog(int id);
public:
explicit useritem(QWidget *parent = nullptr);
~useritem();
//保存并设置好友信息
void setFriendInfo(int friendID,int status,int iconID,QString name,QString feeling);
//设置好友的状态为下线
void setFriendOffline();
const QString &name() const;
private slots:
void on_pb_icon_clicked();
private:
Ui::useritem *ui;
int m_friendID;
int m_status;
int m_iconID;
QString m_name;
QString m_feeling;
};
#endif // USERITEM_H
#include "useritem.h"
#include "ui_useritem.h"
#include "net/def.h"
#include <QBitmap>
useritem::useritem(QWidget *parent) :
QWidget(parent),
ui(new Ui::useritem)
{
ui->setupUi(this);
}
useritem::~useritem()
{
delete ui;
}
//保存并设置好友信息
void useritem::setFriendInfo(int friendID, int status, int iconID, QString name, QString feeling)
{
//保存到成员变量中
m_friendID=friendID;
m_status=status;
m_iconID=iconID;
m_name=name;
m_feeling=feeling;
//设置到控件上
ui->lb_name->setText(m_name);
ui->lb_feeling->setText(m_feeling);
//拼接图片路径
QString path=QString(":/tx/%1.png").arg(m_iconID);
if(_status_online == m_status){
//在线,亮显头像
ui->pb_icon->setIcon(QIcon(path));
}
else{
//不在线,暗显头像
QBitmap bt;
bt.load(path);
ui->pb_icon->setIcon(bt);
}
}
//设置好友的状态为下线
void useritem::setFriendOffline()
{
//改变状态
m_status=_status_offline;
//设置头像暗显
QString path=QString(":/tx/%1.png").arg(m_iconID);
QBitmap bt;
bt.load(path);
ui->pb_icon->setIcon(bt);
}
void useritem::on_pb_icon_clicked()
{
//通知kernel显示聊天窗口
Q_EMIT sig_showChatDialog(m_friendID);
}
const QString &useritem::name() const
{
return m_name;
}
4.4 聊天窗口
#ifndef CHATDIALOG_H
#define CHATDIALOG_H
#include <QDialog>
namespace Ui {
class chatDialog;
}
class chatDialog : public QDialog
{
Q_OBJECT
signals:
//把聊天内容发给kernel
void sig_sendChatMessage(QString content,int id);
public:
explicit chatDialog(QWidget *parent = nullptr);
~chatDialog();
//设置聊天窗口信息
void setChatInfo(int id,QString name);
//设置聊天请求内容到窗口上
void setChatCotent(QString content);
//显示聊天对象不在线
void setFriendOffline();
private slots:
void on_pb_send_clicked();
private:
Ui::chatDialog *ui;
int m_friendId;
QString m_friendName;
};
#endif // CHATDIALOG_H
#include "chatdialog.h"
#include "ui_chatdialog.h"
#include <QTime>
chatDialog::chatDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::chatDialog)
{
ui->setupUi(this);
}
chatDialog::~chatDialog()
{
delete ui;
}
//设置聊天窗口信息
void chatDialog::setChatInfo(int id, QString name)
{
//保存信息
m_friendId=id;
m_friendName=name;
//设置到控件上
setWindowTitle(QString("与【%1】的聊天窗口").arg(m_friendName));
}
void chatDialog::setChatCotent(QString content)
{
ui->tb_chat->append(QString("【%1】 %2").arg(m_friendName).arg(QTime::currentTime().toString("hh:mm:ss")));
ui->tb_chat->append(content);
}
void chatDialog::setFriendOffline()
{
ui->tb_chat->append(QString("【%1】 %2 不在线").arg(m_friendName).arg(QTime::currentTime().toString("hh:mm:ss")));
}
void chatDialog::on_pb_send_clicked()
{
//1.从控件上获取纯文本数据
QString content=ui->te_chat->toPlainText();
//2.校验用户输入的数据是否合法
if(content.isEmpty() || content.remove(" ").isEmpty()){
ui->te_chat->setText("");
return;
}
//3.获取带格式的数据
content=ui->te_chat->toHtml();
//4.清空编辑窗口
ui->te_chat->setText("");
//5.把数据显示到浏览窗口上
ui->tb_chat->append(QString("【我】 %1").arg(QTime::currentTime().toString("hh:mm:ss")));
ui->tb_chat->append(content);
//6.把数据发给kernel
Q_EMIT sig_sendChatMessage(content,m_friendId);
}
五、主函数(分析)
#include <iostream>
#include <Windows.h>
#include "CKernel.h"
using namespace std;
int main() {
//测试:先开启服务端,再开启客户端
CKernel kernel;
if (!kernel.startServer()) {
cout << "打开服务端失败" << endl;
return 1;
}
/*
* mediator* pServer = new TcpServerMediator;
mediator* pClient = new TcpClientMediator;
if (!pClient->openNet()) {
cout << "打开客户端失败" << endl;
return 1;
}
//客户端给服务端发消息
char buf[] = "hello world";
pClient->sendData(buf, sizeof(buf), 0);
*/
//让程序一直运行
while (1) {
Sleep(5000);
cout << "server is running" << endl;
}
return 0;
}
/*
项目分析:
IM聊天系统
通信协议:TCP协议
模型:C/S模型
功能:
注册
登录
获取好友列表
聊天
添加好友
下线
面相对象编程:类
客户端QT:
net类(收发数据、初始化网络、关闭网络)
中介者类(为了以后扩展使用)
kernel类(处理接收到的数据、组织要发送的数据
ui类(注册&登录界面、好友列表界面、聊天窗口)
服务端VS:
net类(收发数据、初始化网络、关闭网络)
中介者类(为了以后扩展使用)
kernel类(处理接收到的数据、组织要发送的数据)
MySq1类
net类(继承和多态)
父类(收发数据、初始化网络、关闭网络)
TCP子类(TCP服务端、TCP客户端)(收发数据、初始化网络、关闭网络)
UDP子类(收发数据、初始化网络、关闭网络)
*/
六、功能详解
QT的信号和槽的使用(两个之间通知和数据传递)1.两个都要继承自QObject,并且要有Q_OBJECT的宏2.发送通知或者数据的类中,在头文件使用signals声明信号(返回值是void,参数需要传输的数据),信号不是函数不需要实现。在需要发通知或者数据的地方使用Q_EMIT 信号名(参数列表)发送信号3.在接收数据的类中,使用slots声明一个槽函数(返回值和参数要和信号保持一致),在cpp中实现槽函数4.绑定信号和槽函数,在接收数据的类中,发送信号的对象new出来以后
6.1 注册
1. 对IMdialog的注册确认按完善槽函数,从le中获取注册信息,校验合法性后,通过信号转发给kernel。(sig信号要在头文件中定义,但是不需要实现)
void IMDialog::on_pb_commit_register_clicked()
{
qDebug()<<__func__;
//1.从控件上获取用户输入的数据
QString name=ui->le_name_register->text();
QString tel=ui->le_tel_register->text();
QString pass=ui->le_password_register->text();
QString nameTmp=name;
QString telTmp=tel;
QString passTmp=pass;
//2.校验用户数据合法性
//2.1校验是否为空或全空格(移除所有空格后是否为空)
if(name.isEmpty() || tel.isEmpty() || pass.isEmpty() || nameTmp.remove(" ").isEmpty() || telTmp.remove(" ").isEmpty() || passTmp.remove(" ").isEmpty()){
QMessageBox::about(this,"提示","输入的内容不能为空或者全空格");
return ;
}
//2.2校验长度(昵称不能超过15,密码不超过20,电话号码必须为11位)
if(name.length()>15 || pass.length()>20 || tel.length()!=11){
QMessageBox::about(this,"提示","昵称不能超过15,密码不超过20,电话号码必须为11位");
return ;
}
//2.3 校验内容(电话号码必须是数字,满足电话号码的规律;昵称不允许有特殊字符;密码只能是字母、数字、下划线)——TODO:正则表达式
//3.把数据传给kernel
Q_EMIT sig_commitRegister(name,tel,pass);
}
2. 在kernel头文件中定义处理sig信号的槽函数,并在IMdialog对象后,绑定发出信号和槽函数
//给登录&注册窗口new对象,并显示
m_pImDialog=new IMDialog;
m_pImDialog->show();
//绑定处理注册信息的信号和槽函数
connect(m_pImDialog,&IMDialog::sig_commitRegister,this,&QKernel::slot_commitRegister);
绑定连接(静态的)
connect ( 1:信号的发出者 , 2:使用带参数的宏SIGNAL(信号的函数名(参数列表)),注:若有形参名应当删去 , 3:信号的传输者 , 4:使用带参数的宏SLOT(槽的函数名(参数列表)),注:若有形参名应当删去 )
3. 在cpp文件中实现slot,通过中介者类对象的指针发送给服务器
//处理注册信息
void QKernel::slot_commitRegister(QString name, QString tel, QString pass)
{
qDebug()<<__func__;
//1.打包
_STRU_REGISTER_RQ rq;
//strcpy()复制字符串内容,直到'\0'结束
//memcpt()复制寄存器内容,按长度结束
//strcpy_s(rq.name,name.toStdString().c_str());
utf8Togb2312(name,rq.name,sizeof(rq.name));
strcpy_s(rq.tel,tel.toStdString().c_str());
strcpy_s(rq.password,pass.toStdString().c_str());
//字符串转换:char*/char[],std::string,QString
//char*/char[]是基础类型,std::string、QString是类,基础类型可以直接给类对象赋值
//std::string调用c_str()转换成char*
//QString调用toStdString()转换成std::string
//2.发给服务器
m_pMediator->sendData((char*)&rq,sizeof(rq),0);
}
3. 服务器端想要接收注册请求的rq需要先绑定协议头
//绑定协议头数组
void CKernel::setProrocolMap() {
cout << __func__ << endl;
//初始化数组
memset(m_mapProrocol, 0, sizeof(m_mapProrocol));
//给数组赋值
m_mapProrocol[_DEF_REGISTER_RQ - _DEF_PROTOCOL_BASE - 1] = &CKernel::dealRegisterRq;
}
4. 再通过TCPServer接收数据的线程函数,将接收到数据传给中介者类transmitData()函数,再交由kernel的dealdata()函数处理信息,找到对应信息的处理函数
//声明函数指针
//先声明,后定义
class CKernel;
typedef void(CKernel::* PFUN)(char* data, int len, long from);
//处理所有接收到的数据
void CKernel::dealData(char* data, int len, long from) {
cout << __func__ << endl;
//取出协议头
PackType type = *(PackType*)data;
//计算出数组下标
int index = type - _DEF_PROTOCOL_BASE - 1;
//判断数组下标是否在有效范围内
if (index >= 0 && index < _DEF_PROEOCOL_COUNT) {
//取出函数指针
PFUN pf = m_mapProrocol[index];
if (pf) {
(this->*pf)(data, len, from);
}
else {
//1.没有绑定协议头和数组 2.结构体协议头赋值错误
cout << "type2:" << type << endl;
}
}
else {
//1.结构体协议头赋值错误 2.接收数据有问题,可能offset没有清零
cout << "type1:" << type << endl;
}
}
5. 处理函数将接收到的数据rq解包,从数据库中查找、写入信息,并将结果rs重新发回给客户端
//处理注册请求
void CKernel::dealRegisterRq(char* data, int len, long from) {
cout << __func__ << endl;
//1.拆包
_STRU_REGISTER_RQ* rq = (_STRU_REGISTER_RQ*)data;
//2.校验数据合法性(规则和客户端一致)
//3.根据电话号码从数据库查询电话号
list<string> lstRes;
char szSql[1024] = "";
sprintf(szSql, "select tel from t_user where tel = '%s';", rq->tel);
if (!m_mysql.SelectMySql(szSql, 1, lstRes)) {
cout << "查询数据库失败:" << szSql << endl;
return;
}
//4.判断结果查询结果是否为空
_STRU_REGISTER_RS rs;
if (lstRes.size() != 0) {
//查询到了,说明电话号已经被注册过了,注册失败
rs.result = _register_tel_exists;
}
else {
//5.根据昵称查询昵称
sprintf(szSql, "select name from t_user where name = '%s';", rq->name);
if (!m_mysql.SelectMySql(szSql, 1, lstRes)) {
cout << "查询数据库失败:" << szSql << endl;
return;
}
//6.判断结果查询是否为空
if (lstRes.size() != 0) {
//查询到了,说明昵称已经被注册过了,注册失败
rs.result = _register_name_exists;
}
else {
//7.没有查询到,把注册信息写入数据库
sprintf(szSql, "insert into t_user (name,tel,password,iconid,feeling) values ('%s','%s','%s',6,'这个人太懒了,什么也没有留下');", rq->name, rq->tel, rq->password);
if (!m_mysql.UpdateMySql(szSql)) {
cout << "更新数据库失败:" << szSql << endl;
return;
}
//8.注册成功
rs.result = _register_success;
}
}
//9.不论是否成功,都要把结果发回给客户端
m_pMediator->sendData((char*)&rs, sizeof(rs), from);
}
6. 客户端接收信息一样要绑定协议头,TCPclient接收到的数据交给中介者类的transmitData()函数,发出信号传给kernel,再由kernel类的slot_transmitData()槽函数转到对应的处理函数
//打开网络
m_pMediator=new TcpClientMediator;
if(!m_pMediator->openNet()){
QMessageBox::about(m_pImDialog,"info","net error");
exit(0);
}
//绑定处理所有数据的信号和槽信号
connect(m_pMediator,&TcpClientMediator::sig_transmitData,this,&QKernel::slot_transmitData);
setProrocolMap();
//绑定协议头数组
void QKernel::setProrocolMap(){
//初始化数组
memset(m_mapProtocol,0,sizeof(m_mapProtocol));
//给数组赋值
m_mapProtocol[_DEF_REGISTER_RS-_DEF_PROTOCOL_BASE-1]=&QKernel::dealRegisterRs;
}
//处理注册回复
void QKernel::dealRegisterRs(char* data, int len, long from){
qDebug()<<__func__;
//1.拆包
_STRU_REGISTER_RS* rs=(_STRU_REGISTER_RS*)data;
//2.根据注册结果提示用户
switch (rs->result) {
case _register_success:
QMessageBox::about(m_pImDialog,"提示","注册成功");
break;
case _register_tel_exists:
QMessageBox::about(m_pImDialog,"提示","注册失败,电话号码已被注册");
break;
case _register_name_exists:
QMessageBox::about(m_pImDialog,"提示","注册失败,昵称已被占用");
break;
default:
break;
}
}
6.2 登录
1. IMdialog点击槽函数,发出信号
void IMDialog::on_pb_commit_clicked()
{
qDebug()<<__func__;
//1.从控件上获取用户输入的数据
QString tel=ui->le_tel->text();
QString pass=ui->le_password->text();
QString telTmp=tel;
QString passTmp=pass;
//2.校验用户数据合法性
//2.1校验是否为空或全空格(移除所有空格后是否为空)
if(tel.isEmpty() || pass.isEmpty() || telTmp.remove(" ").isEmpty() || passTmp.remove(" ").isEmpty()){
QMessageBox::about(this,"提示","输入的内容不能为空或者全空格");
return ;
}
//2.2校验长度(密码不超过20,电话号码必须为11位)
if(pass.length()>20 || tel.length()!=11){
QMessageBox::about(this,"提示","密码不超过20,电话号码必须为11位");
return ;
}
//2.3 校验内容(电话号码必须是数字,满足电话号码的规律;密码只能是字母、数字、下划线)——TODO:正则表达式
//3.把登录信息传给kernel
Q_EMIT sig_commitLogin(tel,pass);
}
2. kernel类(接收数据的类)中声明定义槽函数,并与IMdialog的对象绑定
void QKernel::slot_commitLogin(QString tel, QString pass)
{
qDebug()<<__func__;
//1.打包
_STRU_LOGIN_RQ rq;
strcpy_s(rq.tel,tel.toStdString().c_str());
strcpy_s(rq.password,pass.toStdString().c_str());
//2.发给服务器
m_pMediator->sendData((char*)&rq,sizeof(rq),0);
}
//绑定处理登录信息的信号和槽函数
connect(m_pImDialog,&IMDialog::sig_commitLogin,this,&QKernel::slot_commitLogin);
3. 服务器端绑定协议头,由dealData()转到处理函数。接收客户端发来的rq包,查询数据库后将结果打包rs发回给客户端
m_mapProrocol[_DEF_LOGIN_RQ - _DEF_PROTOCOL_BASE - 1] = &CKernel::dealLoginRq;
//处理登录请求
void CKernel::dealLoginRq(char* data, int len, long from) {
cout << __func__ << endl;
//1.拆包
_STRU_LOGIN_RQ* rq = (_STRU_LOGIN_RQ*)data;
//2.根据电话号码从数据库查询密码
list<string> lstRes;
char szSql[1024] = "";
sprintf(szSql, "select password, id from t_user where tel = '%s';", rq->tel);
if (!m_mysql.SelectMySql(szSql, 2, lstRes)) { //写入两列信息
cout << "查询数据库失败:" << szSql << endl;
return;
}
//3.判断结果是否为空
_STRU_LOGIN_RS rs;
if (lstRes.size() == 0) {
//如果结果为空,说明电话号码没有注册,登录失败
rs.result = _login_tel_not_exists;
}
else {
//取出查询的密码(取出的数据要从列表中删除)
string pass = lstRes.front();
lstRes.pop_front();
//取出用户id
int userID = stoi(lstRes.front()); //string to int
lstRes.pop_front();
//4.结果不为空,比较查询到的密码和用户输入的密码
if (strcmp(rq->password, pass.c_str()) != 0) {
//密码不相等,登录失败
rs.result = _login_password_error;
}
else {
//5.密码相等,登录成功
rs.result = _login_success;
rs.userID = userID;
//保存当前登录用户的socket
m_mapIdToSocket[userID] = from;
//6.无论登录是否成功,都要给客户端回复登录结果
m_pMediator->sendData((char*)&rs, sizeof(rs), from);
//获取当前好友列表
getUserInfo(userID);
return;
}
}
//6.无论登录是否成功,都要给客户端回复登录结果
m_pMediator->sendData((char*)&rs, sizeof(rs), from);
}
4. 客户端绑定协议头,由slot_transmitData()槽函数,转到对应的处理函数
m_mapProtocol[_DEF_LOGIN_RS-_DEF_PROTOCOL_BASE-1]=&QKernel::dealLoginRs;
//处理登录回复
void QKernel::dealLoginRs(char* data, int len, long from){
qDebug()<<__func__;
//1.拆包
_STRU_LOGIN_RS* rs=(_STRU_LOGIN_RS*)data;
//2.根据注册结果提示用户
switch (rs->result) {
case _login_success:{
//进行页面跳转,隐藏登录界面,显示好友界面
m_pImDialog->hide();
m_pFriendList->show();
//保存登录用户的id
m_id=rs->userID;
}
break;
case _login_tel_not_exists:
QMessageBox::about(m_pImDialog,"提示","登录失败,电话号码未注册");
break;
case _login_password_error:
QMessageBox::about(m_pImDialog,"提示","登录失败,密码错误");
break;
default:
break;
}
}
6.3 好友列表
1.服务端在接收到登录请求,验证通过后,给客户端回复rs,并继续根据用户登录信息,在数据库查找好友信息。
//获取用户的好友信息(包括自己)
void CKernel::getUserInfo(int userID) {
cout << __func__ << endl;
//根据自己的id从数据库查询自己的信息
_STRU_FRIEND_INFO userinfo;
getInfoById(userID, &userinfo);
//把自己的信息发给客户端。采取查一个发一个,解决数据庞大的问题,查找和发送效率都高一些
if (m_mapIdToSocket.find(userID) != m_mapIdToSocket.end()) {
m_pMediator->sendData((char*)&userinfo, sizeof(userinfo), m_mapIdToSocket[userID]);
}
//从好友关系表中查询好友的id列表
list<string> lstRes;
char szSql[1024] = "";
sprintf(szSql, "select idB from t_friend where idA= '%d';",userID);
if (!m_mysql.SelectMySql(szSql, 1, lstRes)) {
cout << "查询数据库失败:" << szSql << endl;
return;
}
//遍历好友id列表
int friendId = 0;
_STRU_FRIEND_INFO friendinfo;
while (lstRes.size() > 0) {
//取出好友id
friendId = stoi(lstRes.front());
lstRes.pop_front();
//根据好友id查询好友信息
getInfoById(friendId, &friendinfo);
//把好友的信息发给客户端
if (m_mapIdToSocket.find(userID) != m_mapIdToSocket.end()) {
m_pMediator->sendData((char*)&friendinfo, sizeof(friendinfo), m_mapIdToSocket[userID]);
}
//通知所有在线好友,自己上线了
if (m_mapIdToSocket.count(friendId) > 0) {
m_pMediator->sendData((char*)&userinfo, sizeof(userinfo), m_mapIdToSocket[friendId]);
}
}
}
//根据用户id查询用户信息
void CKernel::getInfoById(int id, _STRU_FRIEND_INFO* info) {
cout << __func__ << endl;
info->userID = id;
if (m_mapIdToSocket.count(id) > 0) {
//用户在线
info->status = _status_online;
}
else {
//用户不在线
info->status = _status_offline;
}
//根据id从数据库查询用户的昵称、签名、头像id
list<string> lstRes;
char szSql[1024] = "";
sprintf(szSql, "select name, feeling,iconId from t_user where id= '%d';", id);
if (!m_mysql.SelectMySql(szSql, 3, lstRes)) {
cout << "查询数据库失败:" << szSql << endl;
return;
}
if (3 == lstRes.size()) {
//取出昵称
strcpy(info->name, lstRes.front().c_str());
lstRes.pop_front();
//取出签名
strcpy(info->feeling, lstRes.front().c_str());
lstRes.pop_front();
//取出头像id
info->iconID = stoi(lstRes.front());
lstRes.pop_front();
}
cout << info->name << " " << info->feeling << " " << info->iconID << endl;
}
2. 将超找到的用户信息逐条回复客户端,客户端绑定协议头,接收friend_info
//处理好友信息
void QKernel::dealFriendInfoRs(char *data, int len, long from)
{
qDebug()<<__func__;
//1.拆包
_STRU_FRIEND_INFO* rq=(_STRU_FRIEND_INFO*)data;
QString name=gb2312Toutf8(rq->name);
QString feeling=gb2312Toutf8(rq->feeling);
//2.判断是不是自己的信息
if(rq->userID==m_id){
//保存自己的昵称
m_mname=name;
//把自己的信息设置到界面上
m_pFriendList->setUserInfo(name,feeling,rq->iconID);
}
//好友的信息,判断列表中是否已经存在这个好友了
if(m_mapIdToUseritem.count(rq->userID)>0){
//有好友,更新好友信息(好友状态变化——上线和下线)
useritem* item=m_mapIdToUseritem[rq->userID];
item->setFriendInfo(rq->userID,rq->status,rq->iconID,name,feeling);
}
else{
//没有好友,new一个好友
useritem* item=new useritem;
//设置好友信息
item->setFriendInfo(rq->userID,rq->status,rq->iconID,name,feeling);
//把好友添加到列表上
m_pFriendList->addFriend(item);
//保存usetitem
m_mapIdToUseritem[rq->userID]=item;
//绑定显示聊天窗口的信号和槽函数
connect(item,&useritem::sig_showChatDialog,this,&QKernel::slot_showChatDialog);
//new一个跟这个好友的聊天窗口
chatDialog* chat=new chatDialog;
//设置窗口信息
chat->setChatInfo(rq->userID,name);
//保存聊天窗口
m_mapIdToChatdlg[rq->userID]=chat;
//绑定发送聊天内容的信号和槽函数
connect(chat,&chatDialog::sig_sendChatMessage,this,&QKernel::slot_sendChatMessage);
}
}
3. 转到friendlist类设置用户信息,useritem设置每条好友信息,创建与该好友的聊天窗口