当前位置: 首页 > news >正文

IM聊天代码

客户端

Headers

inet

inet.h
#pragma once 
#include<Winsock2.h>//#pragma comment(lib,"Ws2_32.lib")class INetMediator;
class INet {
public:INet(){}virtual ~INet(){}//初始化网络virtual bool initNet() = 0;//接收数据virtual void recvData() = 0;//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括virtual bool sendData(char* data, int len, long to) = 0;//关闭网络virtual void unInitNet() = 0;protected:INetMediator* m_pMediator;};
packDef.h
#pragma once#define  _DEF_TCP_PORT           (67890)
#define  _DEF_SERVER_IP          ("192.168.248.1")
#define  _DEF_NAME_MAX           (100)
#define  _DEF_CONTENT_LENGHT     (4096)
#define  _DEF_PROTOCOL_COUNT     (10)//协议头
#define _DFE_PROTOCOL_BASE       (1000)
// 注册请求
#define _DEF_TCP_REGISTER_RQ     (_DFE_PROTOCOL_BASE + 1)
// 注册回复
#define _DEF_TCP_REGISTER_RS     (_DFE_PROTOCOL_BASE + 2)
// 登录请求
#define _DEF_TCP_LOGIN_RQ        (_DFE_PROTOCOL_BASE + 3)
// 登录回复
#define _DEF_TCP_LOGIN_RS        (_DFE_PROTOCOL_BASE + 4)
// 聊天请求
#define _DEF_TCP_CHAT_RQ         (_DFE_PROTOCOL_BASE + 5)
// 聊天回复
#define _DEF_TCP_CHAT_RS         (_DFE_PROTOCOL_BASE + 6)
// 添加好友请求
#define _DEF_TCP_ADD_FRIEND_RQ   (_DFE_PROTOCOL_BASE + 7)
// 添加好友回复
#define _DEF_TCP_ADD_FRIEND_RS   (_DFE_PROTOCOL_BASE + 8)
// 下线请求
#define _DEF_TCP_OFFLINE_RQ      (_DFE_PROTOCOL_BASE + 9)
// 好友信息
#define _DEF_TCP_FRIEND_INFO     (_DFE_PROTOCOL_BASE + 10)// 重定义协议头变量
typedef int packType;//结果定义
//注册结果
#define register_success         (0)
#define register_name_repeat     (1)
#define register_tel_repeat      (2)
//登录结果
#define login_success            (0)
#define login_tel_not_exist      (1)
#define login_password_error     (2)
//聊天结果
#define send_success             (0)
#define send_fail                (1)
//添加好友结果
#define add_friend_success       (0)
#define add_friend_no_this_user  (1)
#define add_friend_user_refuse   (2)
#define add_friend_user_offline  (3)
//用户状态
#define _status_online           (0)
#define _status_offline          (1)//请求结构体
// 注册请求
typedef struct STRU_TCP_REGISTER_RQ{STRU_TCP_REGISTER_RQ():type(_DEF_TCP_REGISTER_RQ){memset(name,0,_DEF_NAME_MAX);memset(tel,0,_DEF_NAME_MAX);memset(password,0,_DEF_NAME_MAX);}//协议头packType type;//昵称char name[_DEF_NAME_MAX];//手机号char tel[_DEF_NAME_MAX];//密码char password[_DEF_NAME_MAX];}STRU_TCP_REGISTER_RQ;// 注册回复
typedef struct STRU_TCP_REGISTER_RS{STRU_TCP_REGISTER_RS():type(_DEF_TCP_REGISTER_RS),result(register_tel_repeat){}//协议头packType type;//注册结果int result;}STRU_TCP_REGISTER_RS;// 登录请求
typedef struct STRU_TCP_LOGIN_RQ{STRU_TCP_LOGIN_RQ():type(_DEF_TCP_LOGIN_RQ){memset(tel,0,_DEF_NAME_MAX);memset(password,0,_DEF_NAME_MAX);}//协议头packType type;//手机号char tel[_DEF_NAME_MAX];//密码char password[_DEF_NAME_MAX];}STRU_TCP_LOGIN_RQ;// 登录回复
typedef struct STRU_TCP_LOGIN_RS{STRU_TCP_LOGIN_RS():type(_DEF_TCP_LOGIN_RS),id(0),result(login_password_error){}//协议头packType type;//自己的idint id;//登录结果int result;}STRU_TCP_LOGIN_RS;// 聊天请求
typedef struct STRU_TCP_CHAT_RQ{STRU_TCP_CHAT_RQ():type(_DEF_TCP_CHAT_RQ),userId(0),friendId(0){memset(content,0,_DEF_CONTENT_LENGHT);}//协议头packType type;//聊天内容char content[_DEF_CONTENT_LENGHT];//自己是谁int userId;//和谁聊天int friendId;}STRU_TCP_CHAT_RQ;// 聊天回复
typedef struct STRU_TCP_CHAT_RS{STRU_TCP_CHAT_RS():type(_DEF_TCP_CHAT_RS),result(send_fail),friendId(0){}//协议头packType type;//聊天结果int result;//好友的idint friendId;}STRU_TCP_CHAT_RS;// 添加好友请求
typedef struct STRU_TCP_ADD_FRIEND_RQ{STRU_TCP_ADD_FRIEND_RQ():type(_DEF_TCP_ADD_FRIEND_RQ),userId(0){memset(friendName,0,_DEF_NAME_MAX);memset(userName,0,_DEF_NAME_MAX);}//协议头packType type;//自己是谁int userId;//对方昵称char friendName[_DEF_NAME_MAX];//我的昵称char userName[_DEF_NAME_MAX];}STRU_TCP_ADD_FRIEND_RQ;// 添加好友回复
typedef struct STRU_TCP_ADD_FRIEND_RS {STRU_TCP_ADD_FRIEND_RS() :type(_DEF_TCP_ADD_FRIEND_RS), result(add_friend_user_offline), userId(0), friendId(0){memset(friendName, 0, _DEF_NAME_MAX);}//协议头packType type;//添加好友结果int result;//好友昵称char friendName[_DEF_NAME_MAX];//自己的idint userId;//好友的idint friendId;}STRU_TCP_ADD_FRIEND_RS;// 下线请求
typedef struct STRU_TCP_OFFLINE_RQ{STRU_TCP_OFFLINE_RQ():type(_DEF_TCP_OFFLINE_RQ),userId(0){}//协议头packType type;//自己是谁int userId;}STRU_TCP_OFFLINE_RQ;//好友信息:协议头、头像id、好友id、昵称、签名、状态
typedef struct STRU_TCP_FRIEND_INFO {STRU_TCP_FRIEND_INFO() :type(_DEF_TCP_FRIEND_INFO),iconId(0),friendId(0),status(_status_offline){memset(name, 0, _DEF_NAME_MAX);memset(feeling, 0, _DEF_NAME_MAX);}//协议头packType type;int iconId;int friendId;char name[_DEF_NAME_MAX];char feeling[_DEF_NAME_MAX];int status;}STRU_TCP_FRIEND_INFO;
TcpClient.h
#pragma once
#include"inet.h"class TcpClient :public INet {
public:TcpClient(INetMediator* pMediator);~TcpClient();//初始化网络bool initNet();//接收数据void recvData();//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括bool sendData(char* data, int len, long to);//关闭网络void unInitNet();
private:static unsigned __stdcall recvThread(void* IpVoid);
private://SOCKETSOCKET m_sock;//句柄HANDLE m_handle;//退出标志位bool m_bStop;
};
UdpNet.h
#pragma once
#include"inet.h"class UdpNet :public INet {
public:UdpNet();~UdpNet();//初始化网络bool initNet();//接收数据void recvData();//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括bool sendData(char* data, int len, long to);//关闭网络void unInitNet();
};

inetmediator

inetMediator.h
#pragma once
#include<QObject>class INet;
class INetMediator:public QObject{Q_OBJECT
public:INetMediator();virtual ~INetMediator();//打开网络virtual bool openNet() = 0;//发送数据virtual bool sendData(char* data,int len,long to) = 0;//转发数据virtual void dealData(char* data, int len, long from) = 0;//关闭网络virtual void closeNet() = 0;
protected:INet* m_pNet;
};
TcpClientMediator.h
#include"inetMediator.h"class TcpClientMediator :public INetMediator {Q_OBJECT
signals:void SIG_dealData(char* data, int len, long from);
public:TcpClientMediator();~TcpClientMediator();//打开网络bool openNet();//发送数据bool sendData(char* data, int len, long to);//转发数据void dealData(char* data, int len, long from);//关闭网络void closeNet();
};
UdpNetTcpMediator.h

chat.h

#ifndef CHAT_H
#define CHAT_H#include <QDialog>namespace Ui {
class Chat;
}class Chat : public QDialog
{Q_OBJECT
signals://把聊天内容发给kernelvoid SIG_chatMessage(QString content,int friendId);
public:explicit Chat(QWidget *parent = 0);~Chat();//设置聊天窗口void setChatInfo(int friendId,QString friendName);//设置聊天内容到窗口void setChatContent(QString content);//设置好友不在线void setFriendOffline();private slots:void on_pb_send_clicked();private:Ui::Chat *ui;QString m_friendName;int m_friendId;
};#endif // CHAT_H

chatdialog.h

#ifndef CHATDIALOG_H
#define CHATDIALOG_H#include <QDialog>namespace Ui {
class ChatDialog;
}class ChatDialog : public QDialog
{Q_OBJECT
signals://把注册信息发给kernelvoid SIG_registerMessage(QString name,QString tel,QString passW);//把登录信息发给kernelvoid SIG_loginMessage(QString tel,QString passW);//发送关闭程序的信号给kernelvoid SIG_closeEvent();public:explicit ChatDialog(QWidget *parent = 0);~ChatDialog();//重写关闭窗口事件void closeEvent(QCloseEvent * event);private slots:void on_pb_clear_clicked();void on_pb_commit_clicked();void on_pb_clear_register_clicked();void on_pb_commit_register_clicked();private:Ui::ChatDialog *ui;
};#endif // CHATDIALOG_H

ckernel.h

#ifndef CKERNEL_H
#define CKERNEL_H#include <QObject>
#include"chatdialog.h"
#include"inetmediator/inetMediator.h"
#include"inet/packDef.h"
#include"friendlist.h"
#include<QMap>
#include"chat.h"//定义函数指针数组
class CKernel;
typedef void (CKernel::* PFun)(char* data, int len, long from);class CKernel : public QObject
{Q_OBJECT
public:explicit CKernel(QObject *parent = 0);~CKernel();QString name() const;signals:public slots://处理所有接收到的数据void slot_dealData(char* data, int len, long from);//处理注册信息,发给服务端void slot_registerMessage(QString name,QString tel,QString passW);//处理登录信息,发给服务端void slot_loginMessage(QString tel,QString passW);//处理聊天内容void slot_chatMessage(QString content,int friendId);//显示与好友的聊天窗口void slot_showChat(int friendId);//处理发送关闭程序的信号void slot_closeEvent();//处理发送下线信号void slot_offline();//通知kernel要添加好友void slot_addFriend();private://QT使用UTF-8,VS使用GB2312void utf8ToGb2312(QString utf8,char* gb,int len);QString gb2312ToUtf8(char* gb);//初始化协议头数组void setProtocolArr();//处理注册回复void dealRegisterRs(char* data, int len, long from);//处理登录回复void dealLoginRs(char* data, int len, long from);//处理好友信息void dealFriendInfo(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);//处理添加好友请求void dealAddFriendRq(char* data, int len, long from);//处理添加好友回复void dealAddFriendRs(char* data, int len, long from);private:int m_id;QString m_name;INetMediator* m_pMediator;//登录&注册界面ChatDialog* m_pChatDlg;//好友列表界面FriendList* m_pFriendList;//协议头数组PFun m_pFun[_DEF_PROTOCOL_COUNT];//保存好友的useritemQMap<int,useritem*>m_mapFriendIdToUseritem;//保存聊天窗口QMap<int,Chat*>m_mapFriendIdToChat;};#endif // CKERNEL_H

friendlist.h

#ifndef FRIENDLIST_H
#define FRIENDLIST_H#include <QDialog>
#include<QVBoxLayout>
#include"useritem.h"
#include<QCloseEvent>
#include<QMenu>namespace Ui {
class FriendList;
}class FriendList : public QDialog
{Q_OBJECT
signals://发送下线信号给kernelvoid SIG_offline();//通知kernel要添加好友void SIG_addFriend();
public:explicit FriendList(QWidget *parent = 0);~FriendList();//设置自己的信息void setInfo(QString name,QString feeling,int iconId);//往界面上添加一个好友void addFriend(useritem* item);//重写关闭窗口事件void closeEvent(QCloseEvent *event);private slots:void on_pb_menu_clicked();//处理点击菜单项的信号void slot_triggered(QAction* action);private:Ui::FriendList *ui;//垂直布局的层QVBoxLayout* m_pLayout;//定义一个菜单栏指针QMenu* m_pMenu;
};#endif // FRIENDLIST_H

useritem.h

#ifndef USERITEM_H
#define USERITEM_H#include <QWidget>namespace Ui {
class useritem;
}class useritem : public QWidget
{Q_OBJECT
signals://显示与好友的聊天窗口void SIG_showChat(int friendId);
public:explicit useritem(QWidget *parent = 0);~useritem();//设置用户信息void setFriendInfo(int iconId,int friendId ,QString name,QString feeling,int status);//设置好友下线void setFriendOffline();const QString &name() const;private slots:void on_pb_icon_clicked();private:Ui::useritem *ui;int m_iconId;int m_friendId;QString m_name;QString m_feeling;int m_status;
};#endif // USERITEM_H

Sources

inet

TcpClient.cpp
#include"TcpClient.h"
#include"packDef.h"
#include<iostream>
#include"../inetmediator/TcpClientMediator.h"
#include<process.h>
using namespace std;TcpClient::TcpClient(INetMediator* pMediator):m_sock(INVALID_SOCKET),m_handle(NULL),m_bStop(false) {m_pMediator = pMediator;}
TcpClient::~TcpClient() {unInitNet();
}
//线程函数(调用recvData)
unsigned __stdcall TcpClient::recvThread(void* IpVoid) {TcpClient* const pThis = (TcpClient* const)IpVoid;pThis->recvData();return false;
}//初始化网络
bool TcpClient::initNet() {//1.加载库WORD wVersion = MAKEWORD(2, 2);WSAData data;int err = WSAStartup(wVersion, &data);if (err != 0){cout << "WSAStartup error" << err << endl;return false;}if (2 != LOBYTE(data.wVersion) || 2 != HIBYTE(data.wVersion)) {cout << "WSAStartup version error" << endl;return false;}else {cout << "WSAStartup success" << endl;}//2.创建套接字m_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (INVALID_SOCKET == m_sock) {cout << "socket error" << WSAGetLastError() << endl;return false;}else {cout << "socket success" << endl;}//3.连接服务端struct sockaddr_in addrServer;addrServer.sin_family = AF_INET;addrServer.sin_port = htons(_DEF_TCP_PORT);  //htons 转换成网络字节序addrServer.sin_addr.S_un.S_addr = inet_addr(_DEF_SERVER_IP);err = connect(m_sock, (sockaddr*)&addrServer, sizeof(addrServer));if (err == SOCKET_ERROR) {cout << "connect error " << WSAGetLastError() << endl;return false;}else {cout << "connect success" << endl;}// 4.创建接收数据的线程// CreateThread和ExitThread是一对,如果在线程里面使用C++运行时库(例如strcpy,在函数中申请空间不释放)// ExitThread也不会释放这个空间,就会造成内存泄漏。// _beginthreadex和_endthreadex是一对,_endthreadex会先回收空间,再调用ExitThread。// 创建线程的时候,操作系统分配: 句柄、线程id、内核对象// 回收线程:1、结束线程工作; 2、关闭句柄m_handle =(HANDLE)_beginthreadex(0,0,&recvThread,(void*)this,0,NULL);return true;
}//接收数据
void TcpClient::recvData() {int packSize = 0;int nRecvNum = 0;//偏移量int nOffset = 0;while (!m_bStop) {nOffset = 0;//先接收包大小nRecvNum = recv(m_sock, (char*)&packSize, sizeof(int), 0);if (nRecvNum > 0) {//new空间char* packBuf = new char[packSize];//再接收包内容while (packSize > 0) {nRecvNum = recv(m_sock, packBuf + nOffset, packSize, 0);if (nRecvNum > 0) {nOffset += nRecvNum;packSize -= nRecvNum;}else {cout << "recv error:" << WSAGetLastError() << endl;break;}}//TODO:接收成功,把数据传给中介者,在处理函数里面回收packBufm_pMediator->dealData(packBuf, nOffset, m_sock);}else {cout<<"recv error:"<< WSAGetLastError() << endl;break;}}
}//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)
//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括
bool TcpClient::sendData(char* data, int len, long to) { //1.判断参数是否有效if (data == NULL || len <= 0) {cout << "paramater error" << endl;return false;}//2.先发包大小if (send(m_sock,(char*)& len,sizeof(int),0) <= 0) {cout << "send error:" << WSAGetLastError() << endl;return false;}//3.再发包内容if (send(m_sock,data,len,0) <= 0) {cout << "send error" << WSAGetLastError() << endl;return false;}return true;
}//关闭网络:回收线程、关闭套接字、卸载库
void TcpClient::unInitNet() {// 回收线程:1、结束线程工作; 2、关闭句柄m_bStop = true;if (m_handle) {if (WAIT_TIMEOUT == WaitForSingleObject(m_handle, 500)) {//如果等待超时,就强制杀死线程TerminateThread(m_handle, -1);}CloseHandle(m_handle);m_handle = NULL;}//关闭套接字、卸载库if (m_sock && INVALID_SOCKET != m_sock) {closesocket(m_sock);}WSACleanup();
}
UdpNet.cpp
#include"UdpNet.h"UdpNet::UdpNet() {}
UdpNet::~UdpNet() {}//初始化网络
bool UdpNet::initNet() {return false;
}//接收数据
void UdpNet::recvData() {}//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)
//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括
bool UdpNet::sendData(char* data, int len, long to) {return false;
}//关闭网络
void UdpNet::unInitNet() {}

inetmediator

inetMediator.cpp
#include"inetMediator.h"INetMediator::INetMediator(){}INetMediator:: ~INetMediator(){}
TcpClientMediator.cpp
#include"TcpClientMediator.h"
#include"../inet/TcpClient.h"TcpClientMediator::TcpClientMediator() {m_pNet = new TcpClient(this);}
TcpClientMediator:: ~TcpClientMediator() {if (m_pNet) {m_pNet->unInitNet();delete m_pNet;m_pNet = NULL;}
}
//打开网络
bool TcpClientMediator::openNet() {return m_pNet->initNet();
}//发送数据
bool TcpClientMediator::sendData(char* data, int len, long to) {return m_pNet->sendData(data, len, to);}//转发数据
void TcpClientMediator::dealData(char* data, int len, long from) {//把接收到的数据传给kernelQ_EMIT SIG_dealData(data,len,from);}//关闭网络
void TcpClientMediator::closeNet() {return m_pNet->unInitNet();
}
UdpNetTcpMediator.cpp

chat.cpp

#include "chat.h"
#include "ui_chat.h"
#include<QTime>Chat::Chat(QWidget *parent) :QDialog(parent),ui(new Ui::Chat)
{ui->setupUi(this);
}Chat::~Chat()
{delete ui;
}//设置聊天窗口
void Chat::setChatInfo(int friendId, QString friendName)
{//保存m_friendId=friendId;m_friendName=friendName;//设置窗口标题setWindowTitle(QString("[%1]").arg(m_friendName));
}//设置聊天内容到窗口
void Chat::setChatContent(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 Chat::setFriendOffline()
{ui->tb_chat->append(QString("好友 [%1] 不在线").arg(m_friendName));
}void Chat::on_pb_send_clicked()
{//获取纯文本的输入内容QString content=ui->te_chat->toPlainText();if(content.isEmpty()||content.remove(" ").isEmpty()){ui->te_chat->setText("");return;}//2.获取带格式的内容content=ui->te_chat->toHtml();//3.清空输入窗口ui->te_chat->setText("");//4.把内容显示到浏览窗口上ui->tb_chat->append(QString("[我] %1").arg(QTime::currentTime().toString("hh:mm:ss")));ui->tb_chat->append(content);//5.发给kernelQ_EMIT SIG_chatMessage(content,m_friendId);
}

chatdialog.cpp

#include "chatdialog.h"
#include "ui_chatdialog.h"
#include<QMessageBox>ChatDialog::ChatDialog(QWidget *parent) :QDialog(parent),ui(new Ui::ChatDialog)
{ui->setupUi(this);
}ChatDialog::~ChatDialog()
{delete ui;
}//重写关闭窗口事件
void ChatDialog::closeEvent(QCloseEvent *event)
{Q_EMIT SIG_closeEvent();
}void ChatDialog::on_pb_clear_clicked()
{ui->le_tel->setText("");ui->le_password->setText("");
}void ChatDialog::on_pb_commit_clicked()
{//1.获取控件中的数据QString tel=ui->le_tel->text();QString passW=ui->le_password->text();QString telTmp=tel;QString passWTmp=passW;//2.校验数据合法性//2.1是否为空或者是全空格(判断全空格就是先移除全部空格,然后判断是否为空)if(tel.isEmpty()||passW.isEmpty()||telTmp.remove(" ").isEmpty()||passWTmp.remove(" ").isEmpty()){QMessageBox::about(this,"title","不能为空或全空格,请重新输入");return;}//2.2长度合法性(电话号长度为11,密码长度不能超过20)if(tel.length()!=11||passW.length()>20){QMessageBox::about(this,"title","长度错误,请重新输入");return;}//2.3内容合法性(电话号必须全是数字,昵称只允许中文、字母、数字、下划线,密码只允许字母、数字、下划线)——正则表达式//3.通过信号把数据发给kernelQ_EMIT SIG_loginMessage(tel,passW);}void ChatDialog::on_pb_clear_register_clicked()
{ui->le_name_register->setText("");ui->le_password_register->setText("");ui->le_tel_register->setText("");
}void ChatDialog::on_pb_commit_register_clicked()
{//1.获取控件中的数据QString name=ui->le_name_register->text();QString tel=ui->le_tel_register->text();QString passW=ui->le_password_register->text();QString nameTmp=name;QString telTmp=tel;QString passWTmp=passW;//2.校验数据合法性//2.1是否为空或者是全空格(判断全空格就是先移除全部空格,然后判断是否为空)if(name.isEmpty()||tel.isEmpty()||passW.isEmpty()||nameTmp.remove(" ").isEmpty()||telTmp.remove(" ").isEmpty()||passWTmp.remove(" ").isEmpty()){QMessageBox::about(this,"title","不能为空或全空格,请重新输入");return;}//2.2长度合法性(电话号长度为11,昵称和密码长度不能超过20)if(tel.length()!=11||name.length()>20||passW.length()>20){QMessageBox::about(this,"title","长度错误,请重新输入");return;}//2.3内容合法性(电话号必须全是数字,昵称只允许中文、字母、数字、下划线,密码只允许字母、数字、下划线)——正则表达式//3.通过信号把数据发给kernelQ_EMIT SIG_registerMessage(name,tel,passW);}

ckernel.cpp

#include "ckernel.h"
#include"inetmediator/TcpClientMediator.h"
#include<QDebug>
#include<QMessageBox>
#include"useritem.h"
#include<QTextCodec>
#include<QInputDialog>CKernel::CKernel(QObject *parent) : QObject(parent)
{qDebug()<<__func__;//初始化协议头数组setProtocolArr();//new好友列表界面m_pFriendList=new FriendList;//绑定下线的信号和槽函数connect(m_pFriendList,SIGNAL(SIG_offline()),this,SLOT(slot_offline()));//绑定添加好友的信号和槽函数connect(m_pFriendList,SIGNAL(SIG_addFriend()),this,SLOT(slot_addFriend()));//new窗口对象m_pChatDlg=new ChatDialog;m_pChatDlg->showNormal();//绑定发送注册信息的信号和槽函数connect(m_pChatDlg,SIGNAL(SIG_registerMessage(QString,QString,QString)),this,SLOT(slot_registerMessage(QString,QString,QString)));//绑定发送登录信息的信号和槽函数connect(m_pChatDlg,SIGNAL(SIG_loginMessage(QString,QString)),this,SLOT(slot_loginMessage(QString,QString)));//绑定关闭程序的信号和槽函数connect(m_pChatDlg,SIGNAL(SIG_closeEvent()),this,SLOT(slot_closeEvent()));//new中介者类的对象m_pMediator=new TcpClientMediator;//打开网络if(!m_pMediator->openNet()){QMessageBox::about(m_pChatDlg,"message","network error");exit(0);//退出程序}connect(m_pMediator,SIGNAL(SIG_dealData(char*, int, long)),this,SLOT(slot_dealData(char*,int,long)));//    //测试:给服务端发送数据
//    m_pMediator->sendData("I am Client",sizeof("I am Client"),123);}CKernel::~CKernel()
{qDebug()<<__func__;if(m_pMediator){m_pMediator->closeNet();delete m_pMediator;m_pMediator=nullptr;}if(m_pChatDlg){m_pChatDlg->hide();delete m_pChatDlg;m_pChatDlg=nullptr;}if(m_pFriendList){m_pFriendList->hide();delete m_pFriendList;m_pFriendList=nullptr;}for(auto ite=m_mapFriendIdToChat.begin();ite!=m_mapFriendIdToChat.end();){Chat* ch=*ite;if(ch){ch->hide();delete ch;ch=nullptr;}ite=m_mapFriendIdToChat.erase(ite);}}//处理所有接收到的数据
void CKernel::slot_dealData(char *data, int len, long from)
{qDebug()<<__func__;//qDebug()<<data;//1.取出协议头packType type = *(packType*)data;//2.计算数组下标int index = type - _DFE_PROTOCOL_BASE - 1;//3.判断协议头是否在有效范围内if (index >= 0 && index < _DEF_PROTOCOL_COUNT) {PFun pf = m_pFun[index];if (pf) {(this->*pf)(data, len, from);}else {//如果type错了,两个原因:1.发送的时候type不对(结构体初始化赋值的不对)2.绑定协议头数组没绑定这个协议qDebug()<< "type2 error:" << type << endl;}}else {//如果type错了,两个原因:1.发送的时候type不对(结构体初始化赋值的不对)2.接收函数里面nOffset没清零qDebug() << "type1 error:" << type << endl;}//4.回收空间delete[] data;}//处理注册信息,发给服务端
void CKernel::slot_registerMessage(QString name, QString tel, QString passW)
{qDebug()<<__func__;//1.装包STRU_TCP_REGISTER_RQ rq;//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,passW.toStdString().c_str());//2.发给服务端m_pMediator->sendData((char*)&rq,sizeof(rq),27);}//处理登录信息,发给服务端
void CKernel::slot_loginMessage(QString tel, QString passW)
{qDebug()<<__func__;//1.装包STRU_TCP_LOGIN_RQ rq;strcpy_s(rq.tel,tel.toStdString().c_str());strcpy_s(rq.password,passW.toStdString().c_str());//2.发给服务端m_pMediator->sendData((char*)&rq,sizeof(rq),27);
}
//处理聊天内容
void CKernel::slot_chatMessage(QString content, int friendId)
{qDebug()<<__func__;//1.打包STRU_TCP_CHAT_RQ rq;strcpy_s(rq.content,content.toStdString().c_str());rq.friendId=friendId;rq.userId=m_id;//2.发给服务端m_pMediator->sendData((char*)&rq,sizeof(rq),213);}
//显示与好友的聊天窗口
void CKernel::slot_showChat(int friendId)
{qDebug()<<__func__;//1.判断是否有该好友的聊天窗口
//    for(auto ite=m_mapFriendIdToChat.begin();ite!=m_mapFriendIdToChat.end();ite++){
//        int id = ite.key();
//            qDebug()<<id;
//    }
//    qDebug()<< "friendId:" << friendId;if(m_mapFriendIdToChat.count(friendId)>0){Chat* ch=m_mapFriendIdToChat[friendId];if(ch){ch->showNormal();}}
}//处理发送关闭程序的信号
void CKernel::slot_closeEvent()
{qDebug()<<__func__;if(m_pMediator){m_pMediator->closeNet();delete m_pMediator;m_pMediator=nullptr;}if(m_pChatDlg){m_pChatDlg->hide();delete m_pChatDlg;m_pChatDlg=nullptr;}if(m_pFriendList){m_pFriendList->hide();delete m_pFriendList;m_pFriendList=nullptr;}for(auto ite=m_mapFriendIdToChat.begin();ite!=m_mapFriendIdToChat.end();){Chat* ch=*ite;if(ch){ch->hide();delete ch;ch=nullptr;}ite=m_mapFriendIdToChat.erase(ite);}}//处理发送下线信号
void CKernel::slot_offline()
{qDebug()<<__func__;//1.给服务端发送下线请求STRU_TCP_OFFLINE_RQ rq;rq.userId=m_id;m_pMediator->sendData((char*)&rq,sizeof(rq),789);//2.回收资源slot_closeEvent();
}void CKernel::slot_addFriend()
{qDebug()<<__func__;//1.弹出输入框,判断好友的昵称QString name=QInputDialog::getText(m_pFriendList,"添加好友","请输入姓名");QString nameTmp=name;//2.校验昵称合法性if(name.isEmpty()||nameTmp.remove(" ").isEmpty()||name.length()>20){QMessageBox::about(m_pFriendList,"title","无效的昵称");return;}//3.判断是不是自己if(m_name==name){QMessageBox::about(m_pFriendList,"title","昵称不能是自己");return;}//4.判断是不是已经是好友了for(auto ite=m_mapFriendIdToUseritem.begin();ite!=m_mapFriendIdToUseritem.end();ite++){useritem* item=*ite;if(item){if(name==item->name()){QMessageBox::about(m_pFriendList,"title",QString("[%1] 已经是你的好友").arg(name));return;}}}//5.打包,发给服务端STRU_TCP_ADD_FRIEND_RQ rq;rq.userId=m_id;strcpy_s(rq.userName,m_name.toStdString().c_str());utf8ToGb2312(name,rq.friendName,sizeof(rq.friendName));//strcpy_s(rq.friendName,name.toStdString().c_str());m_pMediator->sendData((char*)&rq,sizeof(rq),78);
}void CKernel::utf8ToGb2312(QString utf8, char *gb, int len)
{QTextCodec* gb2312=QTextCodec::codecForName("gb2312");QByteArray ba=gb2312->fromUnicode(utf8);strcpy_s(gb,len,ba.data());
}QString CKernel::gb2312ToUtf8(char *gb)
{QTextCodec* gb2312=QTextCodec::codecForName("gb2312");return gb2312->toUnicode(gb);
}//初始化协议头数组
void CKernel::setProtocolArr(){qDebug()<<__func__;//给数组初始化memset(m_pFun, 0, sizeof(m_pFun));//绑定协议头数组m_pFun[_DEF_TCP_REGISTER_RS - _DFE_PROTOCOL_BASE - 1] = &CKernel::dealRegisterRs;m_pFun[_DEF_TCP_LOGIN_RS - _DFE_PROTOCOL_BASE - 1] = &CKernel::dealLoginRs;m_pFun[_DEF_TCP_FRIEND_INFO - _DFE_PROTOCOL_BASE - 1] = &CKernel::dealFriendInfo;m_pFun[_DEF_TCP_CHAT_RQ - _DFE_PROTOCOL_BASE - 1] = &CKernel::dealChatRq;m_pFun[_DEF_TCP_CHAT_RS - _DFE_PROTOCOL_BASE - 1] = &CKernel::dealChatRs;m_pFun[_DEF_TCP_OFFLINE_RQ - _DFE_PROTOCOL_BASE - 1]=&CKernel::dealOfflineRq;m_pFun[_DEF_TCP_ADD_FRIEND_RQ - _DFE_PROTOCOL_BASE - 1]=&CKernel::dealAddFriendRq;m_pFun[_DEF_TCP_ADD_FRIEND_RS - _DFE_PROTOCOL_BASE - 1]=&CKernel::dealAddFriendRs;
}//处理注册回复
void CKernel::dealRegisterRs(char* data, int len, long from){qDebug()<<__func__;//1.拆包STRU_TCP_REGISTER_RS* rs=(STRU_TCP_REGISTER_RS*)data;//2.根据结果提示用户switch(rs->result){case register_success:QMessageBox::about(m_pChatDlg,"title","注册成功");break;case register_name_repeat:QMessageBox::about(m_pChatDlg,"title","昵称重复,注册失败");break;case register_tel_repeat:QMessageBox::about(m_pChatDlg,"title","电话号重复,注册失败");break;}
}//处理登录回复void CKernel::dealLoginRs(char* data, int len, long from){qDebug()<<__func__;//1.拆包STRU_TCP_LOGIN_RS* rs = (STRU_TCP_LOGIN_RS*)data;//2.根据结果显示switch(rs->result){case login_success:{//跳转到好友列表界面(隐藏当前界面,显示另一个界面)m_id=rs->id;m_pChatDlg->hide();m_pFriendList->showNormal();//              //测试代码,添加20个好友
//              for(int i=0;i<20;i++){
//                  useritem* item=new useritem;
//                  m_pFriendList->addFriend(item);
//              }break;}case login_tel_not_exist:QMessageBox::about(m_pChatDlg,"title","登录失败,用户不存在");break;case login_password_error:QMessageBox::about(m_pChatDlg,"title","登录失败,密码错误");break;default:break;}}//处理好友信息void CKernel::dealFriendInfo(char *data, int len, long from){qDebug()<<__func__;//1.拆包STRU_TCP_FRIEND_INFO* info=(STRU_TCP_FRIEND_INFO*)data;QString name=gb2312ToUtf8(info->name);QString feeling=gb2312ToUtf8(info->feeling);//2.判断是不是自己的信息if(m_id==info->friendId){//保存自己的昵称m_name=name;//设置自己的信息m_pFriendList->setInfo(name,feeling,info->iconId);return;}//3.是好友的信息,判断好友是否已经在列表上if(m_mapFriendIdToUseritem.count(info->friendId)>0){//4.如果好友在列表上,更新好友信息useritem* item=m_mapFriendIdToUseritem[info->friendId];item->setFriendInfo(info->iconId,info->friendId,name,feeling,info->status);}else{//5.如果好友不在列表上,new一个新的好友useritem* item=new useritem;//设置好友的信息item->setFriendInfo(info->iconId,info->friendId,name,feeling,info->status);//把好友添加到列表上m_pFriendList->addFriend(item);//保存好友的useritemm_mapFriendIdToUseritem[info->friendId]=item;//绑定显示好友的聊天窗口的信号和槽函数connect(item,SIGNAL(SIG_showChat(int)),this,SLOT(slot_showChat(int)));//创建一个与这个好友的聊天窗口Chat* ch=new Chat;//设置聊天窗口的信息ch->setChatInfo(info->friendId,name);//保存聊天窗口m_mapFriendIdToChat[info->friendId]=ch;//绑定发送聊天内容的信号和槽函数connect(ch,SIGNAL(SIG_chatMessage(QString,int)),this,SLOT(slot_chatMessage(QString,int)));}}//处理聊天请求void CKernel::dealChatRq(char *data, int len, long from){qDebug()<<__func__;//1.拆包STRU_TCP_CHAT_RQ* rq = (STRU_TCP_CHAT_RQ*)data;//2.找到聊天窗口,把聊天内容显示到聊天窗口上,显示窗口if(m_mapFriendIdToChat.count(rq->userId)>0){Chat* ch=m_mapFriendIdToChat[rq->userId];if(ch){ch->setChatContent(rq->content);ch->showNormal();}}}//处理聊天回复void CKernel::dealChatRs(char *data, int len, long from){qDebug()<<__func__;//1.拆包STRU_TCP_CHAT_RS* rs = (STRU_TCP_CHAT_RS*)data;//2.找到聊天窗口,在窗口上显示好友不在线if(m_mapFriendIdToChat.count(rs->friendId)>0){Chat* ch=m_mapFriendIdToChat[rs->friendId];if(ch){ch->setFriendOffline();ch->showNormal();}}}//处理下线请求void CKernel::dealOfflineRq(char *data, int len, long from){qDebug()<<__func__;//1.拆包STRU_TCP_OFFLINE_RQ* rq = (STRU_TCP_OFFLINE_RQ*)data;//2.找到好友的useritem,把头像设置成暗显if(m_mapFriendIdToUseritem.count(rq->userId)>0){useritem* item=m_mapFriendIdToUseritem[rq->userId];if(item){item->setFriendOffline();}}}//处理添加好友请求void CKernel::dealAddFriendRq(char *data, int len, long from){qDebug()<<__func__;//1.拆包STRU_TCP_ADD_FRIEND_RQ* rq=(STRU_TCP_ADD_FRIEND_RQ*)data;//2.弹出询问窗口STRU_TCP_ADD_FRIEND_RS rs;QString str=QString("用户 [%1] 想添加你为好友,是否同意?").arg(rq->userName);if (QMessageBox::Yes == QMessageBox::question(m_pFriendList, "add friend", str)) {rs.result=add_friend_success;}else {rs.result = add_friend_user_refuse;}rs.userId = rq->userId;rs.friendId=m_id;strcpy_s(rs.friendName , m_name.toStdString().c_str()) ;//3、把添加好友结果发给服务端m_pMediator->sendData((char*)&rs, sizeof(rs), 78);}//处理添加好友回复void CKernel::dealAddFriendRs(char *data, int len, long from){qDebug()<<__func__;//1.拆包STRU_TCP_ADD_FRIEND_RS* rs=(STRU_TCP_ADD_FRIEND_RS*)data;QString friendName=gb2312ToUtf8(rs->friendName);//2.根据结果提示用户switch(rs->result){case add_friend_success:QMessageBox::about(m_pFriendList,"title",QString("添加好友 [%1] 成功").arg(rs->friendName));break;case add_friend_no_this_user:QMessageBox::about(m_pFriendList,"title",QString("添加好友 [%1] 失败,用户不存在").arg(friendName));break;case add_friend_user_refuse:QMessageBox::about(m_pFriendList,"title",QString("[%1] 拒绝了你的好友请求").arg(rs->friendName));break;case add_friend_user_offline:QMessageBox::about(m_pFriendList,"title",QString("添加好友 [%1] 失败,好友不在线").arg(friendName));break;}}QString CKernel::name() const{return m_name;}//字符串:char*、std:string、QString//char* 是基础类型,std:string,QString都是封装的类
//char* 可以直接给std:string,QString赋值
//std::string.c_str()=>char*
//QString.toStdString()=>std::string

friendlist.cpp

#include "friendlist.h"
#include "ui_friendlist.h"
#include<QMessageBox>
#include<QDebug>FriendList::FriendList(QWidget *parent) :QDialog(parent),ui(new Ui::FriendList)
{ui->setupUi(this);//new一个垂直布局层的对象m_pLayout=new QVBoxLayout;//每个控件间的距离m_pLayout->setSpacing(3);//层距离外边框的距离m_pLayout->setContentsMargins(0,0,0,0);//把层设置到外面的大控件上ui->wdg_list->setLayout(m_pLayout);//new一个菜单栏的对象m_pMenu=new QMenu;//添加两个菜单项m_pMenu->addAction("添加好友");m_pMenu->addAction("系统设置");//绑定点击菜单项的信号和槽函数(信号是QMenu类发送的,我们只接收信号)connect(m_pMenu,SIGNAL(triggered(QAction*)),this,SLOT(slot_triggered(QAction*)));}FriendList::~FriendList()
{delete ui;
}
//设置自己的信息
void FriendList::setInfo(QString name, QString feeling, int iconId)
{ui->lb_name->setText(name);ui->le_feeling->setText(feeling);//拼接头像文件路径QString path=QString(":/tx/%1.png").arg(iconId);ui->pb_icon->setIcon(QIcon(path));
}//往界面上添加一个好友
void FriendList::addFriend(useritem *item)
{m_pLayout->addWidget(item);
}//重写关闭窗口事件
void FriendList::closeEvent(QCloseEvent *event)
{//忽略事件event->ignore();if(QMessageBox::Yes==QMessageBox::question(this,"title","确定关闭吗")){Q_EMIT SIG_offline();}
}//在鼠标点击位置向上显示菜单栏
void FriendList::on_pb_menu_clicked()
{//获取当前鼠标点击的位置QPoint pos=QCursor::pos();//获取菜单栏的绝对大小QSize size=m_pMenu->sizeHint();//显示菜单栏m_pMenu->exec(QPoint(pos.x(),pos.y()-size.height()));
}//处理点击菜单项的信号
void FriendList::slot_triggered(QAction *action)
{if("添加好友"==action->text()){//通知kernel要添加好友Q_EMIT SIG_addFriend();}else if("系统设置"==action->text()){qDebug()<<"系统设置";}
}

main.cpp

#include "ckernel.h"
#include <QApplication>int main(int argc, char *argv[])
{QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling);QApplication a(argc, argv);//ChatDialog w;//w.show();CKernel kernel;return a.exec();
}

useritem.cpp

#include "useritem.h"
#include "ui_useritem.h"
#include "./inet/packDef.h"
#include <QBitmap>
#include<QDebug>useritem::useritem(QWidget *parent) :QWidget(parent),ui(new Ui::useritem)
{ui->setupUi(this);
}useritem::~useritem()
{delete ui;
}//设置用户信息
void useritem::setFriendInfo(int iconId,int friendId, QString name, QString feeling, int status)
{//保存好友信息m_iconId=iconId;m_friendId=friendId;m_name=name;m_feeling=feeling;m_status=status;//设置到控件显示ui->lb_name->setText(m_name);ui->lb_feeling->setText(m_feeling);//拼接头像文件路径QString path=QString(":/tx/%3.png").arg(iconId);if(_status_online==m_status){//在线,亮显ui->pb_icon->setIcon(QIcon(path));}else{//不在线,暗显QBitmap bit;bit.load(path);ui->pb_icon->setIcon(bit);}//重绘this->repaint();
}//设置好友下线
void useritem::setFriendOffline()
{//设置为下线状态m_status=_status_offline;//设置头像暗显QString path=QString(":/tx/%1.png").arg(m_iconId);QBitmap bit;bit.load(path);ui->pb_icon->setIcon(bit);//重绘this->repaint();qDebug()<<"userItem::setFriendOffline()";
}void useritem::on_pb_icon_clicked()
{Q_EMIT SIG_showChat(m_friendId);
}const QString &useritem::name() const
{return m_name;
}

服务端

inet

inet.h
#pragma once 
#include<Winsock2.h>#pragma comment(lib,"Ws2_32.lib")class INetMediator;
class INet {
public:INet(){}virtual ~INet(){}//初始化网络virtual bool initNet() = 0;//接收数据virtual void recvData() = 0;//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括virtual bool sendData(char* data, int len, long to) = 0;//关闭网络virtual void unInitNet() = 0;
protected:INetMediator* m_pMediator;
};
packDef.h
#pragma once #define  _DEF_TCP_PORT           (67890)
#define  _DEF_SERVER_IP          ("192.168.248.1")
#define  _DEF_NAME_MAX           (100)
#define  _DEF_CONTENT_LENGHT     (4096)
#define  _DEF_PROTOCOL_COUNT     (10)//协议头
#define _DFE_PROTOCOL_BASE       (1000)
// 注册请求
#define _DEF_TCP_REGISTER_RQ     (_DFE_PROTOCOL_BASE + 1)
// 注册回复
#define _DEF_TCP_REGISTER_RS     (_DFE_PROTOCOL_BASE + 2)
// 登录请求
#define _DEF_TCP_LOGIN_RQ        (_DFE_PROTOCOL_BASE + 3)
// 登录回复
#define _DEF_TCP_LOGIN_RS        (_DFE_PROTOCOL_BASE + 4)
// 聊天请求
#define _DEF_TCP_CHAT_RQ         (_DFE_PROTOCOL_BASE + 5)
// 聊天回复
#define _DEF_TCP_CHAT_RS         (_DFE_PROTOCOL_BASE + 6)
// 添加好友请求
#define _DEF_TCP_ADD_FRIEND_RQ   (_DFE_PROTOCOL_BASE + 7)
// 添加好友回复
#define _DEF_TCP_ADD_FRIEND_RS   (_DFE_PROTOCOL_BASE + 8)
// 下线请求
#define _DEF_TCP_OFFLINE_RQ      (_DFE_PROTOCOL_BASE + 9)
// 好友信息
#define _DEF_TCP_FRIEND_INFO     (_DFE_PROTOCOL_BASE + 10)// 重定义协议头变量
typedef int packType;//结果定义
//注册结果
#define register_success         (0)
#define register_name_repeat     (1)
#define register_tel_repeat      (2)
//登录结果
#define login_success            (0)
#define login_tel_not_exist      (1)
#define login_password_error     (2)
//聊天结果
#define send_success             (0)
#define send_fail                (1)
//添加好友结果
#define add_friend_success       (0)
#define add_friend_no_this_user  (1)
#define add_friend_user_refuse   (2)
#define add_friend_user_offline  (3)
//用户状态
#define _status_online           (0)
#define _status_offline          (1)//请求结构体
// 注册请求
typedef struct STRU_TCP_REGISTER_RQ {STRU_TCP_REGISTER_RQ() :type(_DEF_TCP_REGISTER_RQ){memset(name, 0, _DEF_NAME_MAX);memset(tel, 0, _DEF_NAME_MAX);memset(password, 0, _DEF_NAME_MAX);}//协议头packType type;//昵称char name[_DEF_NAME_MAX];//手机号char tel[_DEF_NAME_MAX];//密码char password[_DEF_NAME_MAX];}STRU_TCP_REGISTER_RQ;// 注册回复
typedef struct STRU_TCP_REGISTER_RS {STRU_TCP_REGISTER_RS() :type(_DEF_TCP_REGISTER_RS), result(register_tel_repeat){}//协议头packType type;//注册结果int result;}STRU_TCP_REGISTER_RS;// 登录请求
typedef struct STRU_TCP_LOGIN_RQ {STRU_TCP_LOGIN_RQ() :type(_DEF_TCP_LOGIN_RQ){memset(tel, 0, _DEF_NAME_MAX);memset(password, 0, _DEF_NAME_MAX);}//协议头packType type;//手机号char tel[_DEF_NAME_MAX];//密码char password[_DEF_NAME_MAX];}STRU_TCP_LOGIN_RQ;// 登录回复
typedef struct STRU_TCP_LOGIN_RS {STRU_TCP_LOGIN_RS() :type(_DEF_TCP_LOGIN_RS), id(0), result(login_password_error){}//协议头packType type;//自己的idint id;//登录结果int result;}STRU_TCP_LOGIN_RS;// 聊天请求
typedef struct STRU_TCP_CHAT_RQ {STRU_TCP_CHAT_RQ() :type(_DEF_TCP_CHAT_RQ), userId(0), friendId(0){memset(content, 0, _DEF_CONTENT_LENGHT);}//协议头packType type;//聊天内容char content[_DEF_CONTENT_LENGHT];//自己是谁int userId;//和谁聊天int friendId;}STRU_TCP_CHAT_RQ;// 聊天回复
typedef struct STRU_TCP_CHAT_RS {STRU_TCP_CHAT_RS() :type(_DEF_TCP_CHAT_RS), result(send_fail), friendId(0){}//协议头packType type;//聊天结果int result;//好友的idint friendId;}STRU_TCP_CHAT_RS;// 添加好友请求
typedef struct STRU_TCP_ADD_FRIEND_RQ {STRU_TCP_ADD_FRIEND_RQ() :type(_DEF_TCP_ADD_FRIEND_RQ), userId(0){memset(friendName, 0, _DEF_NAME_MAX);memset(userName, 0, _DEF_NAME_MAX);}//协议头packType type;//自己是谁int userId;//对方昵称char friendName[_DEF_NAME_MAX];//我的昵称char userName[_DEF_NAME_MAX];}STRU_TCP_ADD_FRIEND_RQ;// 添加好友回复
typedef struct STRU_TCP_ADD_FRIEND_RS {STRU_TCP_ADD_FRIEND_RS() :type(_DEF_TCP_ADD_FRIEND_RS), result(add_friend_user_offline), userId(0), friendId(0){memset(friendName, 0, _DEF_NAME_MAX);}//协议头packType type;//添加好友结果int result;//好友昵称char friendName[_DEF_NAME_MAX];//自己的idint userId;//好友的idint friendId;}STRU_TCP_ADD_FRIEND_RS;// 下线请求
typedef struct STRU_TCP_OFFLINE_RQ {STRU_TCP_OFFLINE_RQ() :type(_DEF_TCP_OFFLINE_RQ), userId(0){}//协议头packType type;//自己是谁int userId;}STRU_TCP_OFFLINE_RQ;//好友信息:协议头、头像id、好友id、昵称、签名、状态
typedef struct STRU_TCP_FRIEND_INFO {STRU_TCP_FRIEND_INFO() :type(_DEF_TCP_FRIEND_INFO), iconId(0), friendId(0), status(_status_offline){memset(name, 0, _DEF_NAME_MAX);memset(feeling, 0, _DEF_NAME_MAX);}//协议头packType type;int iconId;int friendId;char name[_DEF_NAME_MAX];char feeling[_DEF_NAME_MAX];int status;}STRU_TCP_FRIEND_INFO;
TcpClient.cpp
#include"TcpClient.h"
#include"packDef.h"
#include<iostream>
#include<process.h>
#include"../inetmediator/TcpClientMediator.h"using namespace std;TcpClient::TcpClient(INetMediator* pMediator):m_sock(INVALID_SOCKET), m_handle(NULL),m_bStop(false){m_pMediator = pMediator;
}
TcpClient::~TcpClient() {unInitNet();
}//线程函数(调用recvData)
unsigned __stdcall TcpClient::recvThread(void* lpVoid)
{TcpClient* const pThis = (TcpClient* const)lpVoid;pThis->recvData();return false;
}//初始化网络
bool TcpClient::initNet() {//1.加载库WORD wVersion = MAKEWORD(2, 2);WSAData data;int err = WSAStartup(wVersion, &data);if (err != 0){cout << "WSAStartup error" << err << endl;return false;}if (2 != LOBYTE(data.wVersion) || 2 != HIBYTE(data.wVersion)) {cout << "WSAStartup version error" << endl;return false;}else {cout << "WSAStartup success" << endl;}//2.创建套接字m_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (INVALID_SOCKET == m_sock) {cout << "socket error" << WSAGetLastError() << endl;return false;}else {cout << "socket success" << endl;}//3.连接服务端struct sockaddr_in addrServer;addrServer.sin_family = AF_INET;addrServer.sin_port = htons(_DEF_TCP_PORT);  //htons 转换成网络字节序addrServer.sin_addr.S_un.S_addr = inet_addr(_DEF_SERVER_IP);err = connect(m_sock, (sockaddr*)&addrServer, sizeof(addrServer));if (err == SOCKET_ERROR) {cout << "connect error" << WSAGetLastError() << endl;return false;}else {cout << "connect success" << endl;}// 4.创建接收数据的线程// CreateThread和ExitThread是一对,如果在线程里面使用C++运行时库(例如strcpy,在函数中申请空间不释放)// ExitThread也不会释放这个空间,就会造成内存泄漏。// _beginthreadex和_endthreadex是一对,_endthreadex会先回收空间,再调用ExitThread。// 创建线程的时候,操作系统分配: 句柄、线程id、内核对象// 回收线程:1、结束线程工作; 2、关闭句柄m_handle =  (HANDLE)_beginthreadex(0, 0, &recvThread, (void*)this, 0, NULL);return true;
}//接收数据
void TcpClient::recvData() {int packSize = 0;int nRecvNum = 0;//偏移量int nOffset = 0;while (!m_bStop) {nOffset = 0;//先接收包大小nRecvNum = recv(m_sock, (char*)&packSize, sizeof(int), 0);if (nRecvNum > 0) {//new空间char* packBuf = new char[packSize];//再接收包内容while (packSize > 0) {nRecvNum = recv(m_sock, packBuf + nOffset, packSize, 0);if (nRecvNum > 0) {nOffset += nRecvNum;packSize -= nRecvNum;}else {cout << "recv error:" << WSAGetLastError() << endl;break;}}//TODO:接收成功,把数据传给中介者,在处理函数里面回收packBufm_pMediator->dealData(packBuf, nOffset, m_sock);}else {cout << "recv error:" << WSAGetLastError() << endl;break;}}
}//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)
//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括
bool TcpClient::sendData(char* data, int len, long to) {//1.判断参数是否有效if (data == NULL || len <= 0) {cout << "paramater error" << endl;return false;}//2.先发包大小if (send(m_sock, (char*)&len, sizeof(int), 0) <= 0) {cout << "send error:" << WSAGetLastError() << endl;return false;}//3.再发包内容if (send(m_sock, data, len, 0) <= 0) {cout << "send error" << WSAGetLastError() << endl;return false;}return true;
}//关闭网络:回收线程,关闭套接字,卸载库
void TcpClient::unInitNet() {// 回收线程:1、结束线程工作; 2、关闭句柄m_bStop = true;if (m_handle) {if (WAIT_TIMEOUT == WaitForSingleObject(m_handle, 500)) {//如果等待超时,就强制杀死线程TerminateThread(m_handle, -1);}CloseHandle(m_handle);m_handle = NULL;}//关闭套接字、卸载库if (m_sock && INVALID_SOCKET != m_sock) {closesocket(m_sock);}WSACleanup();
}
TcpClient.h
#pragma once
#include"inet.h"class TcpClient :public INet {
public:TcpClient(INetMediator* pMediator);~TcpClient();//初始化网络bool initNet();//接收数据void recvData();//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括bool sendData(char* data, int len, long to);//关闭网络void unInitNet();private:/*需要声明成静态,否则_beginthreadex(0, 0, &recvData, (void*)this, 0, NULL) 中 &recvData会报错,原因是类的成员函数通过对象调用,编译时对象不存在,运行时对象才存在,要想让编译的时候就存在,需要变成静态*/static unsigned __stdcall recvThread(void* lpVoid);private://SOCKETSOCKET m_sock;HANDLE m_handle;bool m_bStop;
};
TcpServer.cpp
#include"TcpServer.h"
#include"packDef.h"
#include<process.h>
#include<iostream>
#include"../inetmediator/TcpServerMediator.h"using namespace std;TcpServer::TcpServer(INetMediator* pMediator):m_sock(INVALID_SOCKET),m_bStop(false) {m_pMediator = pMediator;
}
TcpServer::~TcpServer() {unInitNet();
}//初始化网络:加载库、创建套接字、绑定、监听、创建接受连接的线程
bool TcpServer::initNet() {//1.加载库WORD wVersion = MAKEWORD(2, 2);WSAData data;int err = WSAStartup(wVersion, &data);if (err != 0){cout << "WSAStartup error" << err << endl;return false;}if (2 != LOBYTE(data.wVersion) || 2 != HIBYTE(data.wVersion)) {cout << "WSAStartup version error" << endl;return false;}else {cout << "WSAStartup success" << endl;}//2.创建套接字m_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (INVALID_SOCKET == m_sock) {cout << "socket error" << WSAGetLastError() << endl;return false;}else {cout << "socket success" << endl;}//3.绑定struct sockaddr_in addrServer;addrServer.sin_family = AF_INET;addrServer.sin_port = htons(_DEF_TCP_PORT);  //htons 转换成网络字节序addrServer.sin_addr.S_un.S_addr = INADDR_ANY;  //绑定任意网卡err = bind(m_sock, (sockaddr*)&addrServer, sizeof(addrServer));if (err == SOCKET_ERROR) {cout << "bind error" << WSAGetLastError() << endl;return false;}else {cout << "bind success" << endl;}//4.监听err = listen(m_sock, 10);if (SOCKET_ERROR == err) {cout << "listen error" << WSAGetLastError() << endl;return false;}else {cout << "listen success" << endl;}//5.创建接受连接的线程HANDLE handle = (HANDLE)_beginthreadex(0, 0, &acceptThread, (void*)this, 0, NULL);if (handle){m_listHandle.push_back(handle);}return true;
}//接受连接的线程函数
unsigned __stdcall TcpServer::acceptThread(void* lpVoid)
{TcpServer* pThis = (TcpServer*)lpVoid;SOCKET sock = INVALID_SOCKET;struct sockaddr_in addrClient;int addrClientSize = sizeof(addrClient);unsigned int threadId = 0;while (!pThis->m_bStop){//接受连接sock = accept(pThis->m_sock, (sockaddr*)&addrClient, &addrClientSize);//判断是否连接成功if (SOCKET_ERROR != sock){//成功,先打印客户端的IP地址cout << "client ip:" << inet_ntoa(addrClient.sin_addr) << endl;//同时给客户端创建一个接收数据的线程HANDLE handle = (HANDLE)_beginthreadex(0, 0, &recvThread, (void*)pThis, 0, &threadId);//保存线程句柄if (handle){pThis->m_listHandle.push_back(handle);}//保存线程ID和对应的socketpThis->m_mapThreadIdToSocket[threadId] = sock;}else{cout << "accept error:" << WSAGetLastError() << endl;}}return false;
}unsigned __stdcall TcpServer::recvThread(void* lpVoid)
{TcpServer* pThis = (TcpServer*)lpVoid;pThis->recvData();return false;
}//接收数据
void TcpServer::recvData() {Sleep(1);//从map中取出当前线程的socketSOCKET sock = m_mapThreadIdToSocket[GetCurrentThreadId()];if (!sock || INVALID_SOCKET == sock){cout << "sock error" << endl;return;}int packSize = 0;int nRecvNum = 0;//偏移量int nOffset = 0;while (!m_bStop) {nOffset = 0;//先接收包大小nRecvNum = recv(sock, (char*)&packSize, sizeof(int), 0);//!!!这里的socket要用从map中取出来的sock!!!if (nRecvNum > 0) {//new空间char* packBuf = new char[packSize];//再接收包内容while (packSize > 0) {nRecvNum = recv(sock, packBuf + nOffset, packSize, 0);if (nRecvNum > 0) {nOffset += nRecvNum;packSize -= nRecvNum;}else {cout << "recv error:" << WSAGetLastError() << endl;break;}}//TODO:接收成功,把数据传给中介者,在处理函数里面回收packBuf//-----------------------测试代码------------------------m_pMediator->dealData(packBuf, nOffset, sock);}else {cout << "recv error:" << WSAGetLastError() << endl;break;}}
}//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)
//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括
bool TcpServer::sendData(char* data, int len, long to) {//1.判断参数是否有效if (data == NULL || len <= 0) {cout << "paramater error" << endl;return false;}//2.先发包大小if (send(to, (char*)&len, sizeof(int), 0) <= 0) {cout << "send error:" << WSAGetLastError() << endl;return false;}//3.再发包内容if (send(to, data, len, 0) <= 0) {cout << "send error" << WSAGetLastError() << endl;return false;}return true;
}//关闭网络
void TcpServer::unInitNet() {// 回收线程:1、结束线程工作; 2、关闭句柄m_bStop = true;for (auto ite = m_listHandle.begin(); ite != m_listHandle.end();){HANDLE handle = *ite;if (handle) {if (WAIT_TIMEOUT == WaitForSingleObject(handle, 500)) {//如果等待超时,就强制杀死线程TerminateThread(handle, -1);}CloseHandle(handle);handle = NULL;}//移除无效节点ite = m_listHandle.erase(ite);}//关闭套接字、卸载库 !!!这里注意一个是关闭监听用的套接字(m_sock),一个是关闭map中存的sock!!!if (m_sock && INVALID_SOCKET != m_sock) {closesocket(m_sock);}for (auto ite = m_mapThreadIdToSocket.begin(); ite != m_mapThreadIdToSocket.end();){SOCKET sock = ite->second;if (sock && INVALID_SOCKET != sock){closesocket(sock);}//移除无效节点ite = m_mapThreadIdToSocket.erase(ite);}WSACleanup();
}
TcpServer.h
#pragma once
#include"inet.h"
#include<map>
#include<list>
using namespace std;class TcpServer :public INet {
public:TcpServer(INetMediator* pMediator);~TcpServer();//初始化网络bool initNet();//接收数据void recvData();//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括bool sendData(char* data, int len, long to);//关闭网络void unInitNet();
private://接受连接的线程函数static unsigned __stdcall acceptThread(void* lpVoid);//接收数据的线程函数static unsigned __stdcall recvThread(void* lpVoid);private:SOCKET m_sock;//退出线程循环标志位bool m_bStop;//保存线程id和对应socketmap<unsigned int, SOCKET>m_mapThreadIdToSocket;//保存线程句柄list<HANDLE>m_listHandle;};
UdpNet.cpp
#include"UdpNet.h"UdpNet::UdpNet() {}
UdpNet::~UdpNet() {}//初始化网络
bool UdpNet::initNet() {return false;
}//接收数据
void UdpNet::recvData() {}//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)
//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括
bool UdpNet::sendData(char* data, int len, long to) {return false;
}//关闭网络
void UdpNet::unInitNet() {}
UdpNet.h
#pragma once
#include"inet.h"class UdpNet :public INet {
public:UdpNet();~UdpNet();//初始化网络bool initNet();//接收数据void recvData();//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括bool sendData(char* data, int len, long to);//关闭网络void unInitNet();
};

inetMediator

inetMediator.h
#pragma once class INet;
class INetMediator {
public:INetMediator() {}virtual ~INetMediator() {}//打开网络virtual bool openNet() = 0;//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括virtual bool sendData(char* data, int len, long to) = 0;//转发数据virtual void dealData(char* data, int len, long from) = 0;//关闭网络virtual void closeNet() = 0;
protected:INet* m_pNet;
};
TcpClientMediator.cpp
#include"TcpClientMediator.h"
#include"../inet/TcpClient.h"TcpClientMediator::TcpClientMediator()
{m_pNet = new TcpClient(this);
}
TcpClientMediator::~TcpClientMediator()
{if (m_pNet){m_pNet->unInitNet();delete m_pNet;m_pNet = NULL;}
}//打开网络
bool TcpClientMediator::openNet()
{return m_pNet->initNet();
}//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)
//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括
bool TcpClientMediator::sendData(char* data, int len, long to)
{return m_pNet->sendData(data, len, to);
}//转发数据
void TcpClientMediator::dealData(char* data, int len, long from)
{}//关闭网络
void TcpClientMediator::closeNet()
{m_pNet->unInitNet();
}
TcpClientMediator.h
#pragma once 
#include"inetMediator.h"class TcpClientMediator:public INetMediator {
public:TcpClientMediator();~TcpClientMediator();//打开网络bool openNet();//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括bool sendData(char* data, int len, long to);//转发数据void dealData(char* data, int len, long from);//关闭网络void closeNet();};
TcpServerMediator.cpp
#include"TcpServerMediator.h"
#include"../inet/TcpServer.h"
#include"../CKernel.h"TcpServerMediator::TcpServerMediator()
{m_pNet = new TcpServer(this);
}
TcpServerMediator::~TcpServerMediator()
{if (m_pNet){m_pNet->unInitNet();delete m_pNet;m_pNet = NULL;}
}//打开网络
bool TcpServerMediator::openNet()
{return m_pNet->initNet();
}//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)
//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括
bool TcpServerMediator::sendData(char* data, int len, long to)
{return m_pNet->sendData(data,len,to);
}//转发数据
void TcpServerMediator::dealData(char* data, int len, long from)
{//把数据发给KernelCKernel::pKernel->dealData(data, len, from);
}//关闭网络
void TcpServerMediator::closeNet()
{m_pNet->unInitNet();
}
TcpServerMediator.h
#pragma once 
#include"inetMediator.h"class TcpServerMediator :public INetMediator {
public:TcpServerMediator();~TcpServerMediator();//打开网络bool openNet();//发送数据(TCP由socket决定发送给谁,UDP由IP决定发送给谁)//SOCKET是UINT类型,IP是ULONG类型,long类型两个都可以包括bool sendData(char* data, int len, long to);//转发数据void dealData(char* data, int len, long from);//关闭网络void closeNet();};
UdpNetMediator.cpp
UdpNetMediator.h

mysql

CMySql.cpp
//#include "stdafx.h"
#include "CMySql.h"CMySql::CMySql(void)
{/*这个函数用来分配或者初始化一个MYSQL对象,用于连接mysql服务端。如果你传入的参数是NULL指针,它将自动为你分配一个MYSQL对象,如果这个MYSQL对象是它自动分配的,那么在调用mysql_close的时候,会释放这个对象*/m_sock = new MYSQL;mysql_init(m_sock);  mysql_set_character_set(m_sock, "gb2312"); //gb2312 中华人民共和国简体字标准
}CMySql::~CMySql(void)
{if(m_sock) {delete m_sock;m_sock = NULL;}
}void CMySql::DisConnect()
{mysql_close(m_sock);
}bool CMySql::ConnectMySql(char *host, char *user, char *pass, char *db, short nport)
{if (!mysql_real_connect(m_sock, host, user, pass, db, nport, NULL, CLIENT_MULTI_STATEMENTS)) {cout << "连接数据库失败,失败错原因:" << mysql_error(m_sock);//连接错误return false;}return true;
}bool  CMySql::GetTables(char* szSql, list<string>& lstStr){if(mysql_query(m_sock, szSql)) {return false;}m_results = mysql_store_result(m_sock);if(NULL == m_results) {return false;}while (m_record = mysql_fetch_row(m_results)) {lstStr.push_back(m_record[0]);}return true;}
bool CMySql::SelectMySql(char* szSql, int nColumn, list<string>& lstStr)
{//mysql_query() 函数用于向 MySQL 发送并执行 SQL 语句if(mysql_query(m_sock, szSql)) {return false;}/*·mysql_store_result 对于成功检索了数据的每个查询(SELECT、SHOW、DESCRIBE、EXPLAIN、CHECK TABLE等)返回值:. CR_COMMANDS_OUT_OF_SYNC   以不恰当的顺序执行了命令。· CR_OUT_OF_MEMORY   内存溢出。· CR_SERVER_GONE_ERROR   MySQL服务器不可用。· CR_SERVER_LOST   在查询过程中,与服务器的连接丢失。· CR_UNKNOWN_ERROR   出现未知错误。*/m_results = mysql_store_result(m_sock);if(NULL == m_results)return false;//遍历表中的下一行,取出内容放入m_record 结果集while (m_record = mysql_fetch_row(m_results)) {for(int i = 0; i < nColumn; i++) {if(!m_record[i]) {lstStr.push_back("");} else {lstStr.push_back(m_record[i]);}}}return true;
}bool  CMySql::UpdateMySql(char* szSql){if(!szSql) {return false;}if(mysql_query(m_sock, szSql)) {return false;}return true;}
CMySql.h
#pragma once#include <mysql.h>
#include <string>
#include <iostream>#pragma comment(lib,"libmysql.lib")
//
#include <list>
using namespace std;class CMySql
{
public:CMySql(void);~CMySql(void);
public:                    //ip,用户名,密码,数据库,端口号bool  ConnectMySql(char *host,char *user,char *pass,char *db,short nport = 3306);void  DisConnect();bool  SelectMySql(char* szSql,int nColumn,list<string>& lstStr);//获得数据库中的表bool GetTables(char* szSql,list<string>& lstStr);//更新:删除、插入、修改bool  UpdateMySql(char* szSql);private:MYSQL *m_sock;   MYSQL_RES *m_results;   MYSQL_ROW m_record; };

头文件

CKernel.h
#pragma once
#include"inetmediator/inetMediator.h"
#include<iostream>
#include"MySQL/CMySql.h"
#include"inet/packDef.h"
#include<map>using namespace std;//定义函数指针函数组
class CKernel;
typedef void(CKernel::* PFun)(char* data, int len, long from);class CKernel
{
public:CKernel();~CKernel();//打开服务器bool startServer();//关闭服务器void closeServer();//初始化协议头数组void setProtocolArr();//处理所有接收到的数据void dealData(char* data, int len, long from);//处理注册请求void dealRegisterRq(char* data, int len, long from);//处理登录请求void dealLoginRq(char* data, int len, long from);//获取登录用户的好友信息void getFriendInfo(int userId);//根据用户id查询用户信息void getUserInfo(int userId, STRU_TCP_FRIEND_INFO* info);//处理聊天请求void dealChatRq(char* data, int len, long from);//处理下线请求void dealOfflineRq(char* data, int len, long from);//处理添加好友请求void dealAddFriendRq(char* data, int len, long from);//处理添加好友回复void dealAddFriendRs(char* data, int len, long from);public:static CKernel* pKernel;
private:INetMediator* m_pMediator;CMySql m_sql;//协议头数组PFun m_pFun[_DEF_PROTOCOL_COUNT];//保存登录成功客户端的socketmap<int, SOCKET>m_mapIdToSocket;
};

源文件

CKernel.cpp
#include "CKernel.h"
#include"inetmediator/TcpServerMediator.h"CKernel* CKernel::pKernel = nullptr;CKernel::CKernel()
{setProtocolArr();pKernel = this;m_pMediator = new TcpServerMediator;
}
CKernel::~CKernel()
{closeServer();
}//打开服务器
bool CKernel::startServer()
{//连接数据库if (!m_sql.ConnectMySql("127.0.0.1", "root", "123456", "20230320im")){cout << "connect mysql fail" << endl;return false;}//初始化网络if (!m_pMediator->openNet()){return false;}return true;
}
//初始化协议头数组
void CKernel::setProtocolArr()
{//给数组初始化memset(m_pFun, 0, sizeof(m_pFun));//绑定协议头数组m_pFun[_DEF_TCP_REGISTER_RQ   - _DFE_PROTOCOL_BASE - 1] = &CKernel::dealRegisterRq;m_pFun[_DEF_TCP_LOGIN_RQ      - _DFE_PROTOCOL_BASE - 1] = &CKernel::dealLoginRq;m_pFun[_DEF_TCP_CHAT_RQ       - _DFE_PROTOCOL_BASE - 1] = &CKernel::dealChatRq;m_pFun[_DEF_TCP_OFFLINE_RQ    - _DFE_PROTOCOL_BASE - 1] = &CKernel::dealOfflineRq;m_pFun[_DEF_TCP_ADD_FRIEND_RQ - _DFE_PROTOCOL_BASE - 1] = &CKernel::dealAddFriendRq;m_pFun[_DEF_TCP_ADD_FRIEND_RS - _DFE_PROTOCOL_BASE - 1] = &CKernel::dealAddFriendRs;}//处理所有接收到的数据
void CKernel::dealData(char* data, int len, long from)
{cout << "CKernel::dealData" << endl;//1.取出协议头packType type = *(packType*)data;//2.计算数组下标int index = type - _DFE_PROTOCOL_BASE - 1;//3.判断协议头是否在有效范围内if (index >= 0 && index < _DEF_PROTOCOL_COUNT) {PFun pf = m_pFun[index];if (pf) {(this->*pf)(data, len, from);}else {//如果type错了,两个原因:1.发送的时候type不对(结构体初始化赋值的不对)2.绑定协议头数组没绑定这个协议cout << "type2 error:" << type << endl;}}else {//如果type错了,两个原因:1.发送的时候type不对(结构体初始化赋值的不对)2.接收函数里面nOffset没清零cout << "type1 error:" << type << endl;}//4.回收空间delete[] data;
}//处理注册请求
void CKernel::dealRegisterRq(char* data, int len, long from)
{cout << "CKernel::dealRegisterRq" << endl;//1.拆包STRU_TCP_REGISTER_RQ* rq = (STRU_TCP_REGISTER_RQ*)data;//2.校验合法性//3.根据昵称查询list<string>lstRes;char sqlBuf[1024] = "";sprintf_s(sqlBuf,"select name from t_user where name = '%s';",rq->name);if (!m_sql.SelectMySql(sqlBuf, 1, lstRes)){cout << "查询数据库失败" << sqlBuf << endl;return;}//4.判断查询结果STRU_TCP_REGISTER_RS rs;if (lstRes.size() > 0){//5.如果查询结果不为空,说明昵称重复,注册失败rs.result = register_name_repeat;}else{//6.如果查询结果为空,根据电话号查询sprintf_s(sqlBuf, "select tel from t_user where tel = %s;", rq->tel);if (!m_sql.SelectMySql(sqlBuf, 1, lstRes)){cout << "查询数据库失败" << sqlBuf << endl;return;}if (lstRes.size() > 0){//7.如果查询结果不为空,说明电话号码重复,注册失败rs.result = register_tel_repeat;}else{//8.如果查询结果为空,把注册信息写入数据库sprintf_s(sqlBuf, "insert into t_user(name,tel,password,icon,feeling)values('%s','%s','%s',1,'这个人很懒,什么也没留下');",rq->name,rq->tel,rq->password);if (!m_sql.UpdateMySql(sqlBuf)){cout << "插入数据库失败" << sqlBuf << endl;return;}//9.注册成功rs.result = register_success;}}//10.不管成功还是失败,都要给客户端回复注册结果m_pMediator->sendData((char*)&rs,sizeof(rs),from);
}//处理登录请求
void CKernel::dealLoginRq(char* data, int len, long from)
{cout << "CKernel::dealLoginRq" << endl;//1.拆包STRU_TCP_LOGIN_RQ* rq = (STRU_TCP_LOGIN_RQ*)data;//2. 校验合法性//3.根据电话号码查密码list<string>lstRes;char sqlBuf[1024] = "";sprintf_s(sqlBuf, "select password,id from t_user where tel = %s;", rq->tel);if (!m_sql.SelectMySql(sqlBuf, 2, lstRes)){cout << "查询数据库失败" << sqlBuf << endl;return;}//4.判断查询结果STRU_TCP_LOGIN_RS rs;if (0 == lstRes.size()){//5.如果结果为空,登录失败,用户不存在rs.result = login_tel_not_exist;}else{//6.如果结果不为空,取出密码string pass = lstRes.front();lstRes.pop_front();//取出用户idint userId = stoi(lstRes.front());lstRes.pop_front();//7.比较用户输入的密码和查询到的密码if (0 == strcmp(rq->password, pass.c_str())){//8.如果相等,登录成功rs.id = userId;//!!!登录成功保存id 客户端也一样 后续登录成功回发送自己信息和好友信息 到时候我们根据id判断!!!rs.result = login_success;//把登录成功的客户端的socket保存起来m_mapIdToSocket[userId] = from;m_pMediator->sendData((char*)&rs, sizeof(rs), from);//查询登录用户的好友信息getFriendInfo(userId);return;}else{//9.不相等,登录失败,密码错误rs.result = login_password_error;}}//10.不管成功还是失败,都要给客户端回复登录结果m_pMediator->sendData((char*)&rs, sizeof(rs), from);
}//获取登录用户的好友信息
void CKernel::getFriendInfo(int userId)
{cout << "CKernel::getFriendInfo" << endl;//1.查询自己的用户信息STRU_TCP_FRIEND_INFO userInfo;getUserInfo(userId,&userInfo);//2.把自己的信息发给客户端if (m_mapIdToSocket.count(userId) > 0){m_pMediator->sendData((char*)&userInfo, sizeof(userInfo), m_mapIdToSocket[userId]);}//3.查询好友id列表list<string>lstRes;char sqlBuf[1024] = "";sprintf_s(sqlBuf, "select idB from t_friend where idA = '%d';", userId);if (!m_sql.SelectMySql(sqlBuf, 1, lstRes)){cout << "查询数据库失败" << sqlBuf << endl;return;}//遍历好友id列表int friendId = 0;STRU_TCP_FRIEND_INFO friendInfo;while (lstRes.size() > 0){//取出每个好友的idfriendId = stoi(lstRes.front());lstRes.pop_front();//根据好友id查询好友信息getUserInfo(friendId,&friendInfo);//把好友信息发送给登录用户客户端if (m_mapIdToSocket.count(userId) > 0){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::getUserInfo(int userId, STRU_TCP_FRIEND_INFO* info)
{cout << "CKernel::getUserInfo" << endl;info->friendId = userId;if (m_mapIdToSocket.count(userId) > 0){//在线info->status = _status_online;}else{//不在线info->status = _status_offline;}//从数据库中查询用户的昵称、iconId、签名list<string>lstRes;char sqlBuf[1024] = "";sprintf_s(sqlBuf, "select name,icon,feeling from t_user where id = '%d';", userId);if (!m_sql.SelectMySql(sqlBuf, 3, lstRes)){cout << "查询数据库失败" << sqlBuf << endl;return;}if (3 == lstRes.size()){strcpy(info->name,lstRes.front().c_str());lstRes.pop_front();info->iconId = stoi(lstRes.front());lstRes.pop_front();strcpy(info->feeling, lstRes.front().c_str());lstRes.pop_front();}
}//处理聊天请求
void CKernel::dealChatRq(char* data,int len,long from)
{cout << "CKernel::dealChatRq" << endl;//1.拆包STRU_TCP_CHAT_RQ* rq = (STRU_TCP_CHAT_RQ*)data;//2.判断好友是否在线if (m_mapIdToSocket.count(rq->friendId) > 0){//如果好友在线,转发聊天请求m_pMediator->sendData(data, len, m_mapIdToSocket[rq->friendId]);}else{//如果好友不在线,直接回复客户端发送失败STRU_TCP_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 << "CKernel::dealOfflineRq" << endl;//1.拆包STRU_TCP_OFFLINE_RQ* rq = (STRU_TCP_OFFLINE_RQ*)data;//2.获取好友id列表list<string>lstRes;char sqlBuf[1024] = "";sprintf_s(sqlBuf, "select idB from t_friend where idA = '%d';", rq->userId);if (!m_sql.SelectMySql(sqlBuf, 1, lstRes)){cout << "查询数据库失败" << sqlBuf << endl;return;}//3.遍历好友列表int friendId = 0;while (lstRes.size() > 0){//4.取出好友idfriendId = stoi(lstRes.front());lstRes.pop_front();//5.判断好友是否在线,在线就转发下线请求给好友if (m_mapIdToSocket.find(friendId) != m_mapIdToSocket.end()){m_pMediator->sendData(data, len, m_mapIdToSocket[friendId]);}}//6.处理自己的信息:关闭socket,把节点从map中移除auto ite = m_mapIdToSocket.find(rq->userId);if (ite != m_mapIdToSocket.end()){closesocket(from);m_mapIdToSocket.erase(ite);}
}//处理添加好友请求
void CKernel::dealAddFriendRq(char* data, int len, long from)
{cout << "CKernel::dealAddFriendRq" << endl;//1.拆包STRU_TCP_ADD_FRIEND_RQ* rq = (STRU_TCP_ADD_FRIEND_RQ*)data;//2.判断是否有好友这个人list<string>lstRes;char sqlBuf[1024] = "";sprintf_s(sqlBuf, "select id from t_user where name = '%s';", rq->friendName);if (!m_sql.SelectMySql(sqlBuf, 1, lstRes)){cout << "查询数据库失败" << sqlBuf << endl;return;}//3.判断查询结果STRU_TCP_ADD_FRIEND_RS rs;if (0 == lstRes.size()){//4.好友不存在,添加好友失败rs.result = add_friend_no_this_user;rs.userId = rq->userId;//---------------------------------------------------------------------------------strcpy_s(rs.friendName, rq->friendName);//5.发回给客户端m_pMediator->sendData((char*)&rs, sizeof(rs), from);}else{//好友存在,取出好友idint friendId = stoi(lstRes.front());lstRes.pop_front();//7.判断好友在不在线if (m_mapIdToSocket.count(friendId) > 0){//8.好友在线,转发添加好友请求m_pMediator->sendData(data, len, m_mapIdToSocket[friendId]);}else{//9.好友不在线,添加好友失败rs.result = add_friend_user_offline;rs.userId = rq->userId;//----------------------------------------------------------------------strcpy_s(rs.friendName, rq->friendName);//10.发回给客户端m_pMediator->sendData((char*)&rs, sizeof(rs), from);}}
}//处理添加好友回复
void CKernel::dealAddFriendRs(char* data, int len, long from)
{cout << "CKernel::dealAddFriendRs" << endl;//1.拆包STRU_TCP_ADD_FRIEND_RS* rs = (STRU_TCP_ADD_FRIEND_RS*)data;//2.判断添加结果是否成功if (add_friend_success == rs->result){//3.把好友关系写入数据库(好友关系双向存储,需要写入两次,两次必须成功使用事务保证)char sqlBuf[1024] = "";sprintf_s(sqlBuf, "insert into t_friend values (%d,%d);", rs->friendId, rs->userId);if (!m_sql.UpdateMySql(sqlBuf)){cout << "插入数据库失败" << sqlBuf << endl;return;}sprintf_s(sqlBuf, "insert into t_friend values (%d,%d);", rs->userId, rs->friendId);if (!m_sql.UpdateMySql(sqlBuf)){cout << "插入数据库失败" << sqlBuf << endl;return;}//4.更新好友列表getFriendInfo(rs->userId);}//5.把结果发给A客户端m_pMediator->sendData(data, len, m_mapIdToSocket[rs->userId]);
}//关闭服务器
void CKernel::closeServer()
{//回收资源if (m_pMediator){m_pMediator->closeNet();delete m_pMediator;m_pMediator = nullptr;}
}
network_5_01_IMServer.cpp
#include<iostream>
#include<Windows.h>
#include"CKernel.h"using namespace std;int main() {//new一个kernel的对象CKernel kernel;//打开服务器if (!kernel.startServer()){cout << "start server error" << endl;return 1;}while (true){cout << "server is running" << endl;Sleep(5000);}测试连接数据库//CMySql sql;//if (!sql.ConnectMySql("127.0.0.1", "root", "123456", "20230320im"))//{//	cout << "connect mysql error" << endl;//	return 1;//}查询数据库//list<string>lstStr;//char sqlBuf[1024] = "";//sprintf(sqlBuf,"select number,name,age,sex from studentinfo;");//if (!sql.SelectMySql(sqlBuf, 4, lstStr))//{//	cout << "select fail" << endl;//}//else//{//	//打印查询结果//	while (lstStr.size() > 0)//	{//		//取出number//		int num = stoi(lstStr.front());//		lstStr.pop_front();//		//取出name//		string name = lstStr.front();//		lstStr.pop_front();//		//取出age//		int age = stoi(lstStr.front());//		lstStr.pop_front();//		//取出sex//		string sex = lstStr.front();//		lstStr.pop_front();//		cout << "num=" << num << ",name=" << name << ",age=" << age << ",sex=" << sex << endl;//	}//}//sql.DisConnect();return 0;
}

相关文章:

IM聊天代码

客户端 Headers inet inet.h #pragma once #include<Winsock2.h>//#pragma comment(lib,"Ws2_32.lib")class INetMediator; class INet { public:INet(){}virtual ~INet(){}//初始化网络virtual bool initNet() 0;//接收数据virtual void recvData() 0;…...

【Go - context 速览,场景与用法】

作用 context字面意思上下文&#xff0c;用于关联管理上下文&#xff0c;具体有如下几个作用 取消信号传递&#xff1a;可以用来传递取消信号&#xff0c;让一个正在执行的函数知道它应该提前终止。超时控制&#xff1a;可以设定一个超时时间&#xff0c;自动取消超过执行时间…...

Linus: vim编辑器的使用,快捷键及配置等周边知识详解

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 vim的安装创建新用户 adduser 用户名Linus是个多用户的操作系统是否有创建用户的权限查看当前用户身份:whoami** 怎么创建设置密码passwdsudo提权(sudo输入的是用户…...

数仓作业延时告警-基于关键路径预推

简介 作业延时告警&#xff0c;通常来说有两种方式&#xff1a; 其一&#xff0c;当作业到目标时间点还没完成触发告警&#xff1b;这类情况&#xff0c;对于目标作业而言&#xff0c;延时已经触发了&#xff0c;风险相对较大&#xff1b;有的是监控接口延时&#xff08;raw层…...

秋招复习笔记——八股文部分:网络TCP

TCP 三次握手和四次挥手 TCP 基本认识 序列号&#xff1a;在建立连接时由计算机生成的随机数作为其初始值&#xff0c;通过 SYN 包传给接收端主机&#xff0c;每发送一次数据&#xff0c;就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。 确认应答号&#xf…...

麒麟桌面操作系统上配置Samba

原文链接&#xff1a;麒麟桌面操作系统上配置Samba Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在麒麟桌面操作系统上配置Samba的文章。Samba是一种免费的软件&#xff0c;实现了SMB/CIFS网络协议&#xff0c;使得Linux和Windows系统之间可以共享文件和打印机…...

【Go】探索 Go 语言的内建函数 copy

山水间歌声回荡 回荡思念的滚烫 去年的家书两行 读来又热了眼眶 云水边静沐暖阳 烟波里久违的故乡 别来无恙 你在心上 &#x1f3b5; 张靓颖/张杰《燕归巢》 在 Go 语言中&#xff0c;copy 是一个用于在切片之间复制元素的内建函数。它提供了一种简单而高…...

【React】JSX:从基础语法到高级用法的深入解析

文章目录 一、什么是 JSX&#xff1f;1. 基础语法2. 嵌入表达式3. 使用属性4. JSX 是表达式 二、JSX 的注意事项1. 必须包含在单个父元素内2. JSX 中的注释3. 避免注入攻击 三、JSX 的高级用法1. 条件渲染2. 列表渲染3. 内联样式4. 函数作为子组件 四、最佳实践 在 React 开发中…...

JMeter 使用

1.JMeter 是什么&#xff1f; JMeter 是一款广泛使用的开源性能测试工具&#xff0c;由 Apache 软件基金会维护。它主要用于测试 Web 应用程序的负载能力和性能&#xff0c;但也支持其他类型的测试&#xff0c;如数据库、FTP、JMS、LDAP、SOAP web services 等。 2.特点&#x…...

20240724----安装git和配置git的环境变量/如何用命令git项目到本地idea

备注参考博客&#xff1a; 1&#xff09;可以参考博客&#xff0c;用git把项目git到本地 2&#xff09;可以参考博客vcs没有git 3)git版本更新&#xff0c;覆盖安装 &#xff08;一&#xff09;安装git &#xff08;1&#xff09;官网下载的链接 https://git-scm.com/downlo…...

JavaScript实战 - 用Canvas画一个心形

作者&#xff1a;逍遥Sean 简介&#xff1a;一个主修Java的Web网站\游戏服务器后端开发者 主页&#xff1a;https://blog.csdn.net/Ureliable 觉得博主文章不错的话&#xff0c;可以三连支持一下~ 如有疑问或建议&#xff0c;请私信或评论留言&#xff01; 前言&#xff1a; 如…...

vim gcc

vim 使用 vs filename 分屏 ctrl ww 切窗口 shift zz 快速提出vim vim配置 vim启动时自动读取当前用户的家目录的.vimrc文件 vim配置只影响本用户 其他用户观看同一文件不受影响 gcc指令 & c文件编译过程 动态库 静态库 & 链接方式 有相应库才能进行…...

Symfony 表单构建器:创建和管理表单的最佳实践

Symfony 表单构建器&#xff1a;创建和管理表单的最佳实践 Symfony 是一个流行的 PHP 框架&#xff0c;以其强大的功能和灵活性闻名。表单构建器是 Symfony 中一个非常重要的组件&#xff0c;它提供了简单且高效的方式来创建和管理表单。本文将详细介绍 Symfony 表单构建器的最…...

Intel电脑CPU的选择

酷睿 i5/i7/i9 系列至强 Xeon 系列应用场景家用消费级电脑企业服务器工作站PCIe通道数 16X 最多识别到2张显卡&#xff0c;且每张降速为8X 64X 最多支持8张显卡同时使用 内存信道2通道8通道内存容量最大128GB最大6TB工作时长不建议长期不间断连续使用专为365*24不断电使用而设…...

MySQL字段设置的varchar长度小于数据长度自动截取丢弃超出的长度而不是报错?

MySQL字段设置的varchar长度小于数据长度自动截取丢弃超出的长度而不是报错&#xff1f; 事情是这样的&#xff0c;我们一个订单表存放了商品的快照信息其中快照信息存储的是json格式商品信息&#xff0c;当查看订单是报错了&#xff0c;发现我们后端服务查询到订单的快照信息…...

Linux|多线程(三)

线程池 线程池是一种多线程处理形式&#xff0c;处理过程中它将被提交的任务分配给预先创建好的多个线程中的一个去执行。 线程池的实现 #pragma once #include <pthread.h> #include <vector> #include <string> #include <unistd.h> #include <…...

智能合约中如何返回mapping

在 Solidity 中&#xff0c;直接返回一个 mapping 的所有数据是不可能的&#xff0c;因为 mapping 本身不支持直接遍历。但是&#xff0c;可以使用一些技巧来实现这一目标&#xff0c;例如通过维护一个额外的数组来跟踪 mapping 中的键&#xff0c;并通过这个数组来返回所有的键…...

nginx的学习(二):负载均衡和动静分离

简介 nginx的负载均衡和动静分离的简单使用 负载均衡配置 外部访问linux的ip地址:80/edu/a.html地址&#xff0c;会轮询访问Tomcat8080和Tomcat8081服务。 Tomcat的准备 准备两个Tomcat&#xff0c;具体准备步骤在nginx的学习一的反向代理例子2中&#xff0c;在Tomcat8080…...

普中51单片机:DS1302时钟芯片讲解与应用(十)

文章目录 引言基本特性什么是RAM&#xff1f;什么是涓流充电&#xff1f; 电路图和引脚说明通信协议以及工作流程寄存器控制寄存器日历/时钟寄存器 DS1302读写时序代码演示——数码管显示时分秒 引言 DS1302 是一款广泛使用的实时时钟 (RTC) 芯片&#xff0c;具有低功耗、内置…...

Preact:轻量级替代React的选择

Preact是一个轻量级的JavaScript库&#xff0c;它提供了与React相似的API&#xff0c;但体积更小&#xff0c;性能更优。Preact的核心理念是尽可能地保持与React的兼容性&#xff0c;同时去除不必要的部分&#xff0c;使其成为一个理想的替代品&#xff0c;尤其是在对性能和包大…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机

这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机&#xff0c;因为在使用过程中发现 Airsim 对外部监控相机的描述模糊&#xff0c;而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置&#xff0c;最后在源码示例中找到了&#xff0c;所以感…...

基于IDIG-GAN的小样本电机轴承故障诊断

目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) ​梯度归一化(Gradient Normalization)​​ (2) ​判别器梯度间隙正则化(Discriminator Gradient Gap Regularization)​​ (3) ​自注意力机制(Self-Attention)​​ 3. 完整损失函数 二…...

scikit-learn机器学习

# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...

毫米波雷达基础理论(3D+4D)

3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文&#xff1a; 一文入门汽车毫米波雷达基本原理 &#xff1a;https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...