科林网络_IM通信

一、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设置每条好友信息,创建与该好友的聊天窗口

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
neokylin-live-desktop-7.0-x86_64-b052-20170727是新操作系统的一个版本。新是中国自主开发的一个Linux操作系统,旨在为中国市场提供高度定制化的解决方案。该版本为x86_64架构,并于2017年7月27日发布。 新操作系统以其稳定性、安全性和易用性而闻名。它基于Linux内核,并加入了许多国内外技术专利,以便更好地满足中国用户的需求。该操作系统支持多种语言,并提供大量的本土化软件和应用程序,以便用户根据自己的喜好和需求进行定制。 neokylin-live-desktop-7.0-x86_64-b052-20170727是一个可以直接从USB或光盘启动的镜像文件,用户可以通过这个镜像文件以“live”模式运行新操作系统,而无需将其安装到计算机硬盘上。这种方式对于用户来说非常方便,因为他们可以在不更改原有操作系统的情况下尝试新,并决定是否要安装它。 这个版本提供了一个友好的图形用户界面,使用户可以轻松地浏览、访问和管理他们的文件和应用程序。同时,新操作系统也支持多媒体功能和网络连接,用户可以在系统中轻松播放音乐、观看视频,并与互联网进行交互。 总之,neokylin-live-desktop-7.0-x86_64-b052-20170727是新操作系统的一个特定版本,通过这个版本,用户可以方便地尝试新操作系统,并决定是否安装它以替代之前的操作系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值