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字面意思上下文,用于关联管理上下文,具体有如下几个作用 取消信号传递:可以用来传递取消信号,让一个正在执行的函数知道它应该提前终止。超时控制:可以设定一个超时时间,自动取消超过执行时间…...
Linus: vim编辑器的使用,快捷键及配置等周边知识详解
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 vim的安装创建新用户 adduser 用户名Linus是个多用户的操作系统是否有创建用户的权限查看当前用户身份:whoami** 怎么创建设置密码passwdsudo提权(sudo输入的是用户…...
数仓作业延时告警-基于关键路径预推
简介 作业延时告警,通常来说有两种方式: 其一,当作业到目标时间点还没完成触发告警;这类情况,对于目标作业而言,延时已经触发了,风险相对较大;有的是监控接口延时(raw层…...
秋招复习笔记——八股文部分:网络TCP
TCP 三次握手和四次挥手 TCP 基本认识 序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。 确认应答号…...
麒麟桌面操作系统上配置Samba
原文链接:麒麟桌面操作系统上配置Samba Hello,大家好啊!今天给大家带来一篇关于在麒麟桌面操作系统上配置Samba的文章。Samba是一种免费的软件,实现了SMB/CIFS网络协议,使得Linux和Windows系统之间可以共享文件和打印机…...
【Go】探索 Go 语言的内建函数 copy
山水间歌声回荡 回荡思念的滚烫 去年的家书两行 读来又热了眼眶 云水边静沐暖阳 烟波里久违的故乡 别来无恙 你在心上 🎵 张靓颖/张杰《燕归巢》 在 Go 语言中,copy 是一个用于在切片之间复制元素的内建函数。它提供了一种简单而高…...
【React】JSX:从基础语法到高级用法的深入解析
文章目录 一、什么是 JSX?1. 基础语法2. 嵌入表达式3. 使用属性4. JSX 是表达式 二、JSX 的注意事项1. 必须包含在单个父元素内2. JSX 中的注释3. 避免注入攻击 三、JSX 的高级用法1. 条件渲染2. 列表渲染3. 内联样式4. 函数作为子组件 四、最佳实践 在 React 开发中…...
JMeter 使用
1.JMeter 是什么? JMeter 是一款广泛使用的开源性能测试工具,由 Apache 软件基金会维护。它主要用于测试 Web 应用程序的负载能力和性能,但也支持其他类型的测试,如数据库、FTP、JMS、LDAP、SOAP web services 等。 2.特点&#x…...
20240724----安装git和配置git的环境变量/如何用命令git项目到本地idea
备注参考博客: 1)可以参考博客,用git把项目git到本地 2)可以参考博客vcs没有git 3)git版本更新,覆盖安装 (一)安装git (1)官网下载的链接 https://git-scm.com/downlo…...
JavaScript实战 - 用Canvas画一个心形
作者:逍遥Sean 简介:一个主修Java的Web网站\游戏服务器后端开发者 主页:https://blog.csdn.net/Ureliable 觉得博主文章不错的话,可以三连支持一下~ 如有疑问或建议,请私信或评论留言! 前言: 如…...
vim gcc
vim 使用 vs filename 分屏 ctrl ww 切窗口 shift zz 快速提出vim vim配置 vim启动时自动读取当前用户的家目录的.vimrc文件 vim配置只影响本用户 其他用户观看同一文件不受影响 gcc指令 & c文件编译过程 动态库 静态库 & 链接方式 有相应库才能进行…...
Symfony 表单构建器:创建和管理表单的最佳实践
Symfony 表单构建器:创建和管理表单的最佳实践 Symfony 是一个流行的 PHP 框架,以其强大的功能和灵活性闻名。表单构建器是 Symfony 中一个非常重要的组件,它提供了简单且高效的方式来创建和管理表单。本文将详细介绍 Symfony 表单构建器的最…...
Intel电脑CPU的选择
酷睿 i5/i7/i9 系列至强 Xeon 系列应用场景家用消费级电脑企业服务器工作站PCIe通道数 16X 最多识别到2张显卡,且每张降速为8X 64X 最多支持8张显卡同时使用 内存信道2通道8通道内存容量最大128GB最大6TB工作时长不建议长期不间断连续使用专为365*24不断电使用而设…...
MySQL字段设置的varchar长度小于数据长度自动截取丢弃超出的长度而不是报错?
MySQL字段设置的varchar长度小于数据长度自动截取丢弃超出的长度而不是报错? 事情是这样的,我们一个订单表存放了商品的快照信息其中快照信息存储的是json格式商品信息,当查看订单是报错了,发现我们后端服务查询到订单的快照信息…...
Linux|多线程(三)
线程池 线程池是一种多线程处理形式,处理过程中它将被提交的任务分配给预先创建好的多个线程中的一个去执行。 线程池的实现 #pragma once #include <pthread.h> #include <vector> #include <string> #include <unistd.h> #include <…...
智能合约中如何返回mapping
在 Solidity 中,直接返回一个 mapping 的所有数据是不可能的,因为 mapping 本身不支持直接遍历。但是,可以使用一些技巧来实现这一目标,例如通过维护一个额外的数组来跟踪 mapping 中的键,并通过这个数组来返回所有的键…...
nginx的学习(二):负载均衡和动静分离
简介 nginx的负载均衡和动静分离的简单使用 负载均衡配置 外部访问linux的ip地址:80/edu/a.html地址,会轮询访问Tomcat8080和Tomcat8081服务。 Tomcat的准备 准备两个Tomcat,具体准备步骤在nginx的学习一的反向代理例子2中,在Tomcat8080…...
普中51单片机:DS1302时钟芯片讲解与应用(十)
文章目录 引言基本特性什么是RAM?什么是涓流充电? 电路图和引脚说明通信协议以及工作流程寄存器控制寄存器日历/时钟寄存器 DS1302读写时序代码演示——数码管显示时分秒 引言 DS1302 是一款广泛使用的实时时钟 (RTC) 芯片,具有低功耗、内置…...
Preact:轻量级替代React的选择
Preact是一个轻量级的JavaScript库,它提供了与React相似的API,但体积更小,性能更优。Preact的核心理念是尽可能地保持与React的兼容性,同时去除不必要的部分,使其成为一个理想的替代品,尤其是在对性能和包大…...
全栈嵌入式C++、STM32、Modbus、FreeRTOS和MQTT协议:工业物联网(IIoT)可视化系统设计思路(附部分代码解析)
项目概述 随着工业4.0时代的到来,工业物联网(IIoT)在提高生产效率、降低运营成本和实现智能制造方面得到了广泛应用。本项目旨在开发一个全面的工业物联网监控系统,能够实时监测设备的温度、压力、振动和电流等参数,并…...
Greenplum数据库中的数据倾斜问题及处理方法
一、数据倾斜问题的原因 数据分布不均匀:当数据在表的分区或分片中不均匀分布时,会导致某些分区或分片的数据量较大,从而引发数据倾斜问题。连接键存在热点数据:如果连接操作中使用的键值存在热点数据,即某些键值出现…...
缓存设计理论
缓存设计理论是一个涉及多个方面的复杂主题,主要目标是优化数据访问速度,减少数据访问延迟,提高系统性能,并同时保持数据的一致性和系统的稳定性。以下是从几个关键方面对缓存设计理论的概述: 一、缓存的作用与目的 …...
IDEA-安装插件 驼峰下划线转换
第一步:安装 file-settings-plugins-在marketplace搜索“CamelCase”-点击安装 第二步:设置 file-settings-editor-camel_case 第三步:使用 选中想转换的遍历 使用快捷键 Alt Shift U...
乾坤: 微前端项目切换时样式闪动(从无样式变为正常样式需要等 css chunk 文件加载完成, 加载延时受网速影响)
背景: 点击基座项目页面左侧目录, 进入微前端子项目页面, 会有短暂的样式未加载效果一闪而过, 造成页面闪烁或更严重的其他样式错位问题 定位: 同事查了 qiankun git 项目的 issue: https://github.com/umijs/qiankun/issues/219 , 找到解决方案 解决: 项目 webpack 打包配…...
《电子元器件之固态电容》
固态电容全称是固态铝质电解电容,它与普通液态铝质电解电容的最大差别在于采用了不同的介电材料。液态铝电容介电材料为电解液,而固态电容的介电材料是固态的导电性高分子材料。 固态电容和液态电容,从外观上区分,就是固态电容顶…...
PLC 远程下载网关
一、 产品概述 SSF-BOX-100 是三石峰科技有限公司推出的工业级 PLC 远程下载网关,主 要用于 PLC 远程调试、程序上下载,为用户提供一种简单可靠的远程维护方案。 1.1 SGBOX 软件 SGBOX 软件是 SSF-BOX-100 网关的配套软件,可以查看设备状态…...
【Django】 读取excel文件并在前端以网页形式显示-安装使用Pandas
文章目录 安装pandas写views写urls安装openpyxl重新调试 安装pandas Pandas是一个基于NumPy的Python数据分析库,可以从各种文件格式如CSV、JSON、SQL、Excel等导入数据,并支持多种数据运算操作,如归并、再成形、选择等。 更换pip源 pip co…...
自动控制:带死区的PID控制算法
带死区的PID控制算法 在计算机控制系统中,为了避免控制动作过于频繁,消除因频繁动作所引起的振荡,可采用带死区的PID控制。带死区的PID控制通过引入一个死区,使得在误差较小的范围内不进行控制动作,从而减少控制系统的…...
橙单后端项目下载编译遇到的问题与解决
今天下载orange-admin项目,不过下载下来运行出现一些问题。 1、涉及到XMLStreamException的几个类都出现下面的错误 The package javax.xml.stream is accessible from more than one module: <unnamed>, java.xml ctrl-shift-t 可以找到这个引入是哪些包里…...
EasyExcel 初使用—— Java 实现多种写入 Excel 功能
前言 大家好,我是雪荷。之前有一篇博客(EasyExcel 初使用—— Java 实现读取 Excel 功能_java easyexcel.read-CSDN博客)介绍了 Java 如何读取 Excel 表格,那么此篇博客就和大家介绍下 Java 如何利用 EasyExcel 写入 Excel。 Ea…...
MySQL 和 SQL Server 中的连表更新 UPDATE JOIN 写法比较
MySQL 和 SQL Server 中的连表更新 UPDATE JOIN 写法比较 一、前言1. MySQL 写法1.1 解释 2. SQL Server 写法2.1 解释 二、总结 一、前言 在关系型数据库管理系统(RDBMS)中,使用 UPDATE 语句进行表格更新是非常常见的操作。特别是当需要根据…...
手把手教你FL Studio 24.1.1.4234中文破解安装激活图文激活教程
在数字化音乐制作的浪潮中,FL Studio 24.1.1.4234中文破解版的发布无疑又掀起了一股新的热潮。这款由Image-Line公司开发的数字音频工作站(DAW)软件,以其强大的功能和易用的界面,赢得了全球无数音乐制作人的青睐。本文…...
使用Spring Boot与Spire.Doc实现Word文档的多样化操作
博客主页: 南来_北往 系列专栏:Spring Boot实战 前言 使用Spring Boot与Spire.Doc实现Word文档的多样化操作具有以下优势: 强大的功能组合:Spring Boot提供了快速构建独立和生产级的Spring应用程序的能力,而Spire.Doc则…...
从食堂采购系统源码到成品:打造供应链采购管理平台实战详解
本篇文章,笔者将详细介绍如何从食堂采购系统的源码开始,逐步打造一个完备的供应链采购管理平台,帮助企业实现采购流程的智能化和高效化。 一、需求分析与规划 一般来说,食堂采购系统需要具备以下基本功能: 1.供应商…...
在window将Redis注册为服务
将redis注册为系统服务,开启自启动 安装服务 默认注册完之后会自动启动,在window中的服务看一下,如果启动类型为自动,状态是自动运行则启动完成。如果是手动,需要右键属性调整为自动,在点击启动,…...
PHP商城案例
http://www.e9933.com/...
Linux:bash在被调用时会读取哪些启动文件?
(本文基于5.1-6ubuntu1.1版本的bash) bash在被调用时会读取哪些启动文件?要回答这个问题,首先要弄清楚两个概念:login shell和interactive shell。 login shell login shell是指这样的shell: 第一个命令行参数(进程…...
帆软FineReport之替换函数
在日常帆软FineReport中经常会使用字符串替换函数,记录下来,方便备查。 一、字符串替换 第一种、指定文本替换 使用SUBSTITUTE函数,语法如下所示 SUBSTITUTE(text,old_text,new_text,instance_num) 字段…...
Redis的应用场景及类型
目录 一、Redis的应用场景 1、限流 2、分布式锁 3、点赞 4、消息队列 二、Redis类型的命令及用法 1、String类型 2、Hash类型 3、List类型 4、Set类型 5、Zset类型 6、Redis工具类 Redis使用缓存的目的就是提升读写性能 实际业务场景下,我们就可以把 Mys…...
【图像处理】不智能的目标识别
目录 目标识别的划分 识别入门 概念学习 滤波 模版 阈值化 形态学操作 开运算 闭运算 编程语言 示例 大家有没有想过在没有人工智能或者说没有机器学习的的时候,计算机是怎么做目标识别的? 计算机视觉时至今日也是急需人才的领域&…...
《500 Lines or Less》(5)异步爬虫
https://aosabook.org/en/500L/a-web-crawler-with-asyncio-coroutines.html ——A. Jesse Jiryu Davis and Guido van Rossum 介绍 网络程序消耗的不是计算资源,而是打开许多缓慢的连接,解决此问题的现代方法是异步IO。 本章介绍一个简单的网络爬虫&a…...
Transformer!自注意力机制的高层级理解Attention Is All You Need!
背景 最近在不断深入学习LLM的相关内容,那么transformer就是一个绕不开的话题。然而对于一个NLP门外汉来说,论文看得是真头疼,总览全网,我们似乎缺少一个至高而下的高层级理解。所以本文就来弥补此方面的缺失~ 本文并不讲解有关…...
关于使用Postman在请求https网址没有响应,但是用浏览器有响应的问题解决
一、问题描述 使用postman调用正式环境的公共接口,无需鉴权,但是产生了返回状态码200,但是data中却无数据,如下 {"code": "200","message": "操作成功","data": {"qr_c…...
【React 】开发环境搭建详细指南
文章目录 一、准备工作1. 安装 Node.js 和 npm2. 选择代码编辑器 二、创建 React 项目1. 使用 Create React App2. 手动配置 React 项目 三、集成开发工具1. ESLint 和 Prettier2. 使用 Git 进行版本控制 在现代前端开发中,React 是一个非常流行的框架,用…...
结构体笔记
结构体 C语言中的数据类型: 基本数据类型:char/int/short/double/float/long 构造数据类型:数组,指针,结构体,共用体,枚举 概念: 结构体是用户自定义的一种数据类型,…...
Elasticsearch:Golang ECS 日志记录 - zerolog
ECS 记录器是你最喜欢的日志库的格式化程序/编码器插件。它们可让你轻松地将日志格式化为与 ECS 兼容的 JSON。在本教程中,我将详述如何 编码器以 JSON 格式记录日志,并以 ECS 错误格式处理错误字段的记录。 默认情况下,会添加以下字段&…...
Ip2region - 基于xdb离线库的Java IP查询工具提供给脚本调用
文章目录 Pre效果实现git clone编译测试程序将ip2region.xdb放到指定目录使用改进最终效果 Pre OpenSource - Ip2region 离线IP地址定位库和IP定位数据管理框架 Ip2region - xdb java 查询客户端实现 效果 最终效果 实现 git clone git clone https://github.com/lionsou…...
研发管理革命:探索顶尖的工时系统选择
国内外主流的10款研发工时管理系统对比:PingCode、Worktile、无鱼项目工时系统、Toggl Track、泽众ALM、Asana、Jira、GitHub、Trello、TrackingTime。 在研发团队中,工时管理常常成为效率瓶颈,尤其是在资源分配和项目进度跟踪方面。选择合适…...
微服务-MybatisPlus下
微服务-MybatisPlus下 文章目录 微服务-MybatisPlus下1 MybatisPlus扩展功能1.1 代码生成1.2 静态工具1.3 逻辑删除1.4 枚举处理器1.5 JSON处理器**1.5.1.定义实体****1.5.2.使用类型处理器** **1.6 配置加密(选学)**1.6.1.生成秘钥**1.6.2.修改配置****…...