C++集群聊天服务器 数据模块+业务模块+CMake构建项目 笔记 (上)

跟着施磊老师做C++项目,施磊老师_腾讯课堂 (qq.com)

本文在此篇博客的基础上继续实现数据模块和业务模块代码:

C++集群聊天服务器 网络模块+业务模块+CMake构建项目 笔记 (上)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_41987016/article/details/135991635?spm=1001.2014.3001.5501一、mysql 项目数据库和表的设计

myql 项目数据库和表的设计-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_41987016/article/details/135981407?spm=1001.2014.3001.5501二、mysql数据库代码封装

  • include/public.hpp
#ifndef PUBLIC_H
#define PUBLIC_H
/*server和client的公共文件
*/
enum EnMsgType {LOGIN_MSG = 1, // 登录消息LOGIN_MSG_ACK, // 登录响应消息REG_MSG, // 注册消息REG_MSG_ACK // 注册响应消息
};
#endif // PUBLIC_H
  • include/server/db/db.h
#ifndef DB_H
#define DB_H#include <mysql/mysql.h>
#include <string>
using namespace std;// 数据库操作类
class Mysql {
public:// 初始化数据库连接Mysql();// 释放数据库连接资源~Mysql();// 连接数据库bool connect();// 更新操作bool update(string sql);// 查询操作MYSQL_RES *query(string sql);// 获取连接MYSQL *getConnection();
private:MYSQL *m_conn;
};#endif // DB_H

 src/server/db/db.cpp

#include "db.h"
#include <muduo/base/Logging.h>
// 数据库配置信息
static string server = "127.0.0.1";
static string user = "root";
static string password = "123456";
static string dbname = "chat";// 初始化数据库连接
Mysql::Mysql() {m_conn = mysql_init(nullptr);// 这里相当于只是给它开辟了一块存储连接数据的资源空间
}// 释放数据库连接资源
Mysql::~Mysql() {if(m_conn != nullptr) {mysql_close(m_conn);}// 析构的时候把这块资源空间用mysql_close掉
}// 连接数据库
bool Mysql::connect() {MYSQL *p = mysql_real_connect(m_conn,server.c_str(),user.c_str(),password.c_str(),dbname.c_str(),3306,nullptr,0);if(p!=nullptr) {// C和C++代码默认的编码字符是ASCII,如果不设置,// 从MYSQL上拉下来的中文显示?mysql_query(m_conn, "set names gbk");LOG_INFO << "connect mysql success!!!";} else{LOG_INFO << "connect mysql failed!!!";}return p;
}// 更新操作
bool Mysql::update(string sql) {if(mysql_query(m_conn, sql.c_str())) {LOG_INFO << __FILE__ << ":" << __LINE__ << ":" << sql <<"更新失败!";return false;}return true;
}// 查询操作
MYSQL_RES* Mysql::query(string sql) {if(mysql_query(m_conn, sql.c_str())) {LOG_INFO << __FILE__ << ":" << __LINE__ << ":"<< sql <<"查询失败!";   return nullptr;}return mysql_use_result(m_conn);
}// 获取连接
MYSQL* Mysql::getConnection() {return m_conn;
}

三、Model数据层代码框架设计

  • include/server/user.hpp
#ifndef USER_H
#define USER_H#include <string>
using namespace std;// 匹配User表的ORM类
class User {
public:User(int id=-1, string name="", string password="", string state="offline") {m_id = id;m_name = name;m_password = password;m_state = state;}void setId(int id) { m_id = id; }void setName(string name) { m_name = name; }void setPwd(string pwd) { m_password = pwd; }   void setState(string state) { m_state = state; }int getId() const { return m_id; }string getName() const { return m_name; }string getPwd() const { return m_password; }string getState() const { return m_state; }
private:int m_id;string m_name;string m_password;string m_state;
};
#endif // USER_H
  • include/server/usermodel.hpp
#ifndef USERMODEL_H
#define USERMODEL_H
#include "user.hpp"
// User表的数据操作类
class UserModel {
public:// user表的增加方法bool insert(User& user); // 根据用户号码查询用户信息User query(int id);// 更新用户的状态信息bool updateState(User user);
};#endif // USERMODEL_H
  • src/server/usermodel.cpp
#include "usermodel.hpp"
#include "db.h"
#include <iostream>
// User表的增加方法
bool UserModel::insert(User &user) {// 1.组装sql语句char sql[1024] = {0};std::sprintf(sql,"insert into user(name,password,state) values('%s','%s', '%s')",user.getName().c_str(), user.getPwd().c_str(), user.getState().c_str());// 2.执行sql语句Mysql mysql;if(mysql.connect()) {if(mysql.update(sql)) {// 获取插入成功的用户数据生成的主键iduser.setId(mysql_insert_id(mysql.getConnection()));return true;}}return false;
}// 根据用户号码查询用户信息
User UserModel::query(int id) {// 1.组装sql语句char sql[1024] = {0};sprintf(sql,"select * from user where id = %d", id);// 2.执行sql语句Mysql mysql;if(mysql.connect()) {MYSQL_RES* res = mysql.query(sql);if(res != nullptr) {MYSQL_ROW row = mysql_fetch_row(res);if(row != nullptr) {User user;user.setId(atoi(row[0]));user.setName(row[1]);user.setPwd(row[2]);user.setState(row[3]);// 释放资源mysql_free_result(res);return user;}}}return User();
}// 更新用户的状态信息
bool UserModel::updateState(User user) {// 1.组装sql语句char sql[1024] = {0};sprintf(sql,"update user set state = '%s' where id = %d",user.getState().c_str(), user.getId());// 2.执行sql语句Mysql mysql;if(mysql.connect()) {if(mysql.update(sql)) {return true;}}return false;
}

四、CMake 构建项目 

  • src/server/CMakeLists.txt
# 定义了一个SRC_LIST变量 包含了该目录下所有的源文件
aux_source_directory(. SRC_LIST)
aux_source_directory(./db DB_LIST)# 指定生成可执行文件
add_executable(ChatServer ${SRC_LIST} ${DB_LIST})# 指定可执行文件链接时需要依赖的库文件
target_link_libraries(ChatServer muduo_net muduo_base mysqlclient pthread)
  • src/CMakeLists.txt
add_subdirectory(server)
  • 和src,include,thirdparty同级目录的CMakeLists.txt
cmake_minimum_required(VERSION 3.28.0)
project(chat)# 配置编译选项
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g)# 配置可执行文件生成路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)# 配置头文件搜索路径
include_directories(${PROJECT_SOURCE_DIR}/include)
include_directories(${PROJECT_SOURCE_DIR}/include/server)
include_directories(${PROJECT_SOURCE_DIR}/include/server/db)
include_directories(${PROJECT_SOURCE_DIR}/thirdparty)# 加载子目录
add_subdirectory(src)

cmake -B build
cmake --build build

1.测试注册:

telnet 127.0.0.1 6000
{"msgid":3,"name":"heheda","password":"1024"} // 注册

{"msgid":3,"name":"Tom","password":"520"} // 注册

{"msgid":3,"name":"Jerry","password":"1314"} // 注册

2.测试登录:

(1)未登录

(2) 已经登录

telnet 127.0.0.1 6000
{"msgid":1,"id":4,"password":"1024"}

telnet 127.0.0.1 6000
{"msgid":1,"id":4,"password":"1024"}

(3)登录失败

3.gdb排错练习

比如输入以下这句,其实"id":5才对,但是如果误输入的会引起核心中断,如何排查错误呢?

{"msgid":1,"id":"5","password":"520"}

>>gdb调试,比如我们怀疑可能是chatservice.cpp的20行出错了

heheda@linux:~/Linux/Server$ gdb ./bin/ChatServer
(gdb) break chatservice.cpp 20
(gdb) run
telnet 127.0.0.1 6000

输入:

{"msgid":1,"id":"5","password":"520"}

检查出错误了:

reason: [json.exception.type_error.302] type must be number, but is string

故我们把

{"msgid":1,"id":"5","password":"520"}修改为以下:
​
{"msgid":1,"id":5,"password":"520"}

总结:客户端发送过来一个注册的业务,先从最开始的网络,再通过事件的分发,到业务层的相关的handler处理注册,接着访问底层的model。其中在业务类设计,这里看到的都是对象,方便你把底层的数据模块改成你想要的,例如mysql,sql,oracle,mongoDB等都行。实现了网络模块,业务模块以及数据模块的低耦合。

五、记录用户的连接信息以及线程安全问题

  • 在ChatService.hpp文件中,private处添加
private:// 存储在线用户的通信连接unordered_map<int,TcpConnectionPtr> m_userConnMap;// 定义互斥锁,保证m_userConnMap的线程安全mutex m_connMutex;
  • 修改ChatService.cpp中的login函数,在登录成功,记录用户连接信息,将id和conn数据信息插入m_userConnMap,使用lock_guard使得线程安全
// 处理登录业务  user表:id password
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time) {int id = js["id"].get<int>();string pwd = js["password"];User user = m_userModel.query(id);if(user.getId() == id && user.getPwd() == pwd) {if(user.getState() == "online") {//该用户已经登录,不允许重复登录json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 2;response["errmsg"] = "该账号已经登录,请重新输入新账号";conn->send(response.dump());}else{// 登录成功,记录用户连接信息{lock_guard<mutex> lock(m_connMutex);m_userConnMap.insert({id, conn});}// 登录成功,更新用户状态信息 state: offline => onlineuser.setState("online");m_userModel.updateState(user);json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 0;response["id"] = user.getId();response["name"] = user.getName();conn->send(response.dump());}}else {// 该用户不存在/用户存在但是密码错误,登录失败json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 1;response["errmsg"] = "该用户不存在,您输入用户名或者密码可能错误!";conn->send(response.dump());}
}

六、客户端异常退出业务代码和测试

  • 在ChatService.hpp中添加处理客户端异常退出的函数声明
public:// 处理客户端异常退出void clientCloseException(const TcpConnectionPtr& conn);
  • 在ChatService.cpp中编写处理客户端异常退出的函数
// 处理客户端异常退出
void ChatService::clientCloseException(const TcpConnectionPtr &conn) {User user;{lock_guard<mutex> lock(m_connMutex);   for(auto it = m_userConnMap.begin();it!=m_userConnMap.end();++it) {if(it->second == conn) {// 从map表删除用户的链接信息user.setId(it->first);m_userConnMap.erase(it);break;}}}// 更新用户的状态信息if(user.getId() != -1) {user.setState("offline");m_userModel.updateState(user);}}

 ​​​

表里原先有Tom登录用户的信息,然后我们登录了该账号,就从offline状态更新为online状态 

按下ctrl+],切换到telnet>,输入quit,此时客户端异常退出,也就执行了从online更新为offline 

七、离线消息业务代码实现和测试

  • 如果用户登录成功的话,查询该用户是否有离线消息,desc offlinemessage

  • offlinemessagemodel.hpp
#ifndef OFFLINEMESSAGEMODEL_H
#define OFFLINEMESSAGEMODEL_H
#include <string>
#include <vector>
using namespace std;// 提供离线消息表的操作接口方法
class OfflineMsgModel {
public:// 存储用户的离线消息void insert(int userid, string msg);// 删除用户的离线消息void remove(int userid);// 查询用户的离线消息vector<string> query(int userid);
};#endif // OFFLINEMESSAGEMODEL_H
  • offlinemessagemodel.cpp
#include "offlinemessagemodel.hpp"
#include "db.h"
// 存储用户的离线消息
void OfflineMsgModel::insert(int userid, string msg) {// 1.组装sql语句char sql[1024] = {0};sprintf(sql, "insert into offlinemessage values(%d, '%s')", userid, msg.c_str());// 2.执行sql语句Mysql mysql;if(mysql.connect()) {mysql.update(sql);}
}// 删除用户的离线消息
void OfflineMsgModel::remove(int userid) {// 1.组装sql语句char sql[1024] = {0};sprintf(sql, "delete from offlinemessage where userid = %d", userid);// 2.执行sql语句Mysql mysql;if(mysql.connect()) {mysql.update(sql);}
}// 查询用户的离线消息
vector<string> OfflineMsgModel::query(int userid) {// 1.组装sql语句char sql[1024] = {0};sprintf(sql, "select message from offlinemessage where userid = %d", userid);// 2.执行sql语句Mysql mysql;vector<string> vec;if(mysql.connect()) {MYSQL_RES *res = mysql.query(sql);if(res != nullptr) {// 把userid用户的所有离线消息放入vec中返回MYSQL_ROW row;while((row = mysql_fetch_row(res)) != nullptr) {vec.push_back(row[0]);}mysql_free_result(res);return vec;}}return vec;
}
  • 在chatservice.hpp中添加 
#include "offlinemessagemodel.hpp"// 聊天服务器业务类
class ChatService {
private:   OfflineMsgModel m_offlineMsgModel;
}
  • chatservice.cpp
// 处理登录业务  user表:id password
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time) {int id = js["id"].get<int>();string pwd = js["password"];User user = m_userModel.query(id);if(user.getId() == id && user.getPwd() == pwd) {if(user.getState() == "online") {//该用户已经登录,不允许重复登录json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 2;response["errmsg"] = "该账号已经登录,请重新输入新账号";conn->send(response.dump());}else{// 登录成功,记录用户连接信息{lock_guard<mutex> lock(m_connMutex);m_userConnMap.insert({id, conn});}// 登录成功,更新用户状态信息 state: offline => onlineuser.setState("online");m_userModel.updateState(user);json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 0;response["id"] = user.getId();response["name"] = user.getName();// 查询该用户是否有离线消息vector<string> vec = m_offlineMsgModel.query(id);if(!vec.empty()) {response["offlinemsg"] = vec;// 读取该用户的离线消息后,把该用户的所有离线消息删除掉m_offlineMsgModel.remove(id);}conn->send(response.dump());}}else {// 该用户不存在/用户存在但是密码错误,登录失败json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 1;response["errmsg"] = "该用户不存在,您输入用户名或者密码可能错误!";conn->send(response.dump());}
}// 一对一聊天业务
void ChatService::oneChat(const TcpConnectionPtr &conn, json &js, Timestamp time) {int toid = js["to"].get<int>();{lock_guard<mutex> lock(m_connMutex);auto it = m_userConnMap.find(toid);if(it != m_userConnMap.end()) {// toid在线,转发消息  服务器主动推送消息给toid用户it->second->send(js.dump());return;}}// toid不在线,存储离线消息m_offlineMsgModel.insert(toid, js.dump());
}

 

八、服务器异常退出处理代码和测试

  • main.cpp
#include "chatserver.hpp"
#include "chatservice.hpp"
#include <iostream>
#include <signal.h>
using namespace std;// 处理服务器ctrl+c结束后,重置user的状态信息
void resetHandler(int) {ChatService::getInstance()->reset();exit(0);
}int main() {signal(SIGINT,resetHandler);...
}
  • 在chatservice.hpp添加reset()方法声明,服务器异常,业务重置方法
// 服务器异常,业务重置方法
void reset();
  •  在chatservice.cpp中编写reset()方法 
// 服务器异常,业务重置方法
void ChatService::reset() {// 把online状态的用户,设置成offlinem_userModel.resetState();
}
  •  在usermodel.hpp中添加重置用户的状态信息resetState方法声明
// 重置用户的状态信息
void resetState();
  •  在usermodel.cpp中编写resetState()方法
// 重置用户的状态信息
void UserModel::resetState() {// 1.组装sql语句char sql[1024] = "update user set state = 'offline' where state = 'online'";// 2.执行sql语句Mysql mysql;if(mysql.connect()) {mysql.update(sql);}
}

 

  •  ​​​​ctrl+c终止服务

九、添加好友业务代码和测试

  • public.hpp
#ifndef PUBLIC_H
#define PUBLIC_H
/*server和client的公共文件
*/
enum EnMsgType {LOGIN_MSG = 1, // 登录消息LOGIN_MSG_ACK, // 登录响应消息REG_MSG, // 注册消息REG_MSG_ACK, // 注册响应消息ONE_CHAT_MSG, // 聊天消息ADD_FRIEND_MSG, // 添加好友消息
};
#endif // PUBLIC_H
  • friendmodel.hpp
#ifndef FRIENDMODEL_H
#define FRIENDMODEL_H#include "user.hpp"
#include <vector>
using namespace std;// 维护好友信息的操作接口方法
class FriendModel {
public:// 添加好友关系void insert(int userid, int friendid);// 返回用户好友列表 friendid vector<User> query(int userid);
};#endif // FRIENDMODEL_H
  • friendmodel.cpp
#include "friendmodel.hpp"
#include "db.h"
// 添加好友关系
void FriendModel::insert(int userid, int friendid) {// 1.组装sql语句char sql[1024] = {0};sprintf(sql, "insert into friend values (%d, %d)", userid, friendid);// 2.执行sql语句Mysql mysql;if(mysql.connect()) {mysql.update(sql);}
}
// 返回用户好友列表 friendid 
vector<User> FriendModel::query(int userid) {// 1.组装sql语句char sql[1024] = {0};sprintf(sql, "select a.id, a.name, a.state from user a inner join friend b on b.friendid = a.id where b.userid = %d", userid);      vector<User> vec;Mysql mysql;if(mysql.connect()) {MYSQL_RES * res = mysql.query(sql);if(res != nullptr) {// 把userid用户的所有离线消息放入vec中返回MYSQL_ROW row;while((row = mysql_fetch_row(res)) != nullptr) {User user;user.setId(atoi(row[0])); // iduser.setName(row[1]);     // nameuser.setState(row[2]);    // statevec.push_back(user);}mysql_free_result(res);       // 释放资源return vec;}}return vec;
}// select a.id,a.name,a.state from user a inner join 
// friend b on b.friendid = a.id 
// where b.userid = %d
  • chatservice.hpp
// 聊天服务器业务类
class ChatService {
public:// 添加好友业务void addFriend(const TcpConnectionPtr& conn,json& js,Timestamp time);
private:FriendModel m_friendModel;
}
  • chatservice.cpp 
// 注册消息以及对应的Handler回调操作
ChatService::ChatService() {m_msgHandlerMap.insert({LOGIN_MSG,std::bind(&ChatService::login, this, _1, _2, _3)});  m_msgHandlerMap.insert({REG_MSG,std::bind(&ChatService::reg, this, _1, _2, _3)});  m_msgHandlerMap.insert({ONE_CHAT_MSG,std::bind(&ChatService::oneChat, this, _1, _2, _3)});m_msgHandlerMap.insert({ADD_FRIEND_MSG,std::bind(&ChatService::addFriend, this, _1, _2, _3)});  
}// 处理登录业务  user表:id password
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time) {int id = js["id"].get<int>();string pwd = js["password"];User user = m_userModel.query(id);if(user.getId() == id && user.getPwd() == pwd) {if(user.getState() == "online") {//该用户已经登录,不允许重复登录json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 2;response["errmsg"] = "该账号已经登录,请重新输入新账号";conn->send(response.dump());}else{// 登录成功,记录用户连接信息{lock_guard<mutex> lock(m_connMutex);m_userConnMap.insert({id, conn});}// 登录成功,更新用户状态信息 state: offline => onlineuser.setState("online");m_userModel.updateState(user);json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 0;response["id"] = user.getId();response["name"] = user.getName();// 查询该用户是否有离线消息vector<string> vec = m_offlineMsgModel.query(id);if(!vec.empty()) {response["offlinemsg"] = vec;// 读取该用户的离线消息后,把该用户的所有离线消息删除掉m_offlineMsgModel.remove(id);}// 查询该用户的好友信息并返回vector<User>userVec = m_friendModel.query(id);if(!userVec.empty()) {vector<string> vec2;for(User &user : userVec) {json js;js["id"] = user.getId();js["name"] = user.getName();js["state"] = user.getState();vec2.push_back(js.dump());}response["friends"] = vec2;}conn->send(response.dump());}}else {// 该用户不存在/用户存在但是密码错误,登录失败json response;response["msgid"] = LOGIN_MSG_ACK;response["errno"] = 1;response["errmsg"] = "该用户不存在,您输入用户名或者密码可能错误!";conn->send(response.dump());}
}// 添加好友业务 msgid id friendid
void ChatService::addFriend(const TcpConnectionPtr &conn, json &js, Timestamp time) {int userid = js["id"].get<int>();int friendid = js["friendid"].get<int>();// 存储好友信息m_friendModel.insert(userid, friendid);
}

十、模拟QQ好友添加(呵呵哒改造)

  • public.hpp
#ifndef PUBLIC_H
#define PUBLIC_H
/*server和client的公共文件
*/
enum EnMsgType {LOGIN_MSG = 1, // 登录消息LOGIN_MSG_ACK, // 登录响应消息REG_MSG, // 注册消息REG_MSG_ACK, // 注册响应消息ONE_CHAT_MSG, // 聊天消息ADD_FRIEND_REQ_MSG, // 添加好友请求消息ADD_FRIEND_MSG_ACK, // 添加好友响应消息
};
#endif // PUBLIC_H
  •  chatservice.hpp
// 聊天服务器业务类
class ChatService {
public:// 添加好友业务请求void addFriendRequest(const TcpConnectionPtr& conn,json& js,Timestamp time);// 添加好友业务响应void addFriendResponse(const TcpConnectionPtr& conn,json& js,Timestamp time);
}
  • chatservice.cpp
// 注册消息以及对应的Handler回调操作
ChatService::ChatService() {m_msgHandlerMap.insert({LOGIN_MSG,std::bind(&ChatService::login, this, _1, _2, _3)});  m_msgHandlerMap.insert({REG_MSG,std::bind(&ChatService::reg, this, _1, _2, _3)});  m_msgHandlerMap.insert({ONE_CHAT_MSG,std::bind(&ChatService::oneChat, this, _1, _2, _3)});m_msgHandlerMap.insert({ADD_FRIEND_REQ_MSG,std::bind(&ChatService::addFriendRequest, this, _1, _2, _3)});  m_msgHandlerMap.insert({ADD_FRIEND_MSG_ACK,std::bind(&ChatService::addFriendResponse, this, _1, _2, _3)});
}// 添加好友业务请求
void ChatService::addFriendRequest(const TcpConnectionPtr &conn, json &js, Timestamp time) {int userid = js["id"].get<int>();int friendid = js["friendid"].get<int>();json response;response["msgid"] = ADD_FRIEND_REQ_MSG;response["msg"] = "Please add me as a friend, thank you!";response["from"] = userid;response["to"] = friendid;// std::cout<<"来到这里了"<<std::endl;oneChat(conn,response,time);
}// 添加好友业务 msgid id friendid
void ChatService::addFriendResponse(const TcpConnectionPtr &conn, json &js, Timestamp time) {int userid = js["id"].get<int>();int friendid = js["friendid"].get<int>();bool flag = js["flag"].get<bool>();json response;response["msgid"] = ADD_FRIEND_MSG_ACK;response["from"] = userid;response["to"] = friendid;if(flag) {response["msg"] = "I very happy to make friends with you!!!";m_friendModel.insert(userid, friendid);}else{response["msg"] = "I am very sorry, you are not my friend!!!";}oneChat(conn,response,time);
}

情景一:id=1的用户 想要和 id=2的在线用户交个朋友,向其发送好友请求,id=2的在线用户响应同意互为好友

 先登录两个账号:

{"msgid":1,"id":1,"password":"1024"}  // 登录
{"msgid":1,"id":2,"password":"520"}  // 登录

(1)id=1的用户,发送好友请求

{"msgid":6,"id":1,"friendid":2}                // 发送好友请求

 

(2)响应好友请求(允许)

{"msgid":7,"id":2,"friendid":1,"flag":true}    // 响应好友请求(允许)

 

情景二:id=1的用户 想要和 id=3的离线用户交个朋友,向其发送好友请求,id=2的离线用户登录后,在线时看到离线消息,响应不同意互为好友

(1)发送好友请求

 先登录一个账号,id=1的用户:

{"msgid":1,"id":1,"password":"1024"}  // 登录

(1)发送好友请求

{"msgid":6,"id":1,"friendid":3}                // 发送好友请求

 (2)响应好友请求(拒绝)

再登录id=3的用户账号,查看到来自id=1用户发来的离线消息

{"msgid":1,"id":3,"password":"1314"}  // 登录

id=3的用户,响应好友请求(拒绝) 

{"msgid":7,"id":3,"friendid":1,"flag":false}   // 响应好友请求(拒绝)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/672056.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

显示器校准软件:BetterDisplay Pro for Mac v2.0.11激活版下载

BetterDisplay Pro是一款由waydabber开发的Mac平台上的显示器校准软件&#xff0c;可以帮助用户调整显示器的颜色和亮度&#xff0c;以获得更加真实、清晰和舒适的视觉体验。 软件下载&#xff1a; BetterDisplay Pro for Mac v2.0.11激活版下载 以下是BetterDisplay Pro的主要…

【Linux取经路】探寻shell的实现原理

文章目录 一、打印命令行提示符二、读取键盘输入的指令三、指令切割四、普通命令的执行五、内建指令执行5.1 cd指令5.2 export指令5.3 echo指令 六、结语 一、打印命令行提示符 const char* getusername() // 获取用户名 {return getenv("USER"); }const char* geth…

LeetCode-第171题-Excel表的序列号

1.题目描述 给你一个字符串 columnTitle &#xff0c;表示 Excel 表格中的列名称。返回 该列名称对应的列序号 。 例如&#xff1a; A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ... 2.样例描述 3.思路描述 遍历时将每个字母与 A 做减法&…

抖音创作服务平台的自适应,从2560到1024,做的真是棒呀。

不得不服&#xff0c;抖音创作服务平台的电脑页面&#xff0c;自适应效果做的非常好&#xff0c;从2560到1024这个跨度的分辨率都做了很好地处理。 2560 4K屏 1920 2K屏幕 1600 宽屏 1440 中宽屏 1366 笔记本屏 1280 窄屏 1024 超窄屏 768 pad端 这个出现横向滚动条了。 算是用…

虚拟飞控计算机:飞行控制系统验证与优化的利器

01.背景介绍 随着航空技术的飞速发展&#xff0c;飞行控制系统作为飞机的心脏&#xff0c;全面负责监测、调整和维持飞行器的姿态、航向、高度等参数&#xff0c;用以确保飞行的安全和稳定。为了满足这些要求&#xff0c;现代飞控系统通常采用先进的处理器和外设来确保其高效、…

一文简介Maven初级使用

一.概述 Maven是专门用于管理和构建Java项目的工具&#xff0c;它的主要功能有&#xff1a; 提供了一套标准化的项目结构提供了一套标准化的项目构建流程&#xff08;编译&#xff0c;测试&#xff0c;打包&#xff0c;发布&#xff09;提供了一套依赖管理机制 一方面&…

【Java EE】----Bean的作用域和生命周期

1.Bean的作用域 定义&#xff1a;Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式&#xff0c;⽐如 singleton 单例作⽤域&#xff0c;就 表示 Bean 在整个 Spring 中只有⼀份 &#xff08;产生的原因&#xff0c;Bean 默认情况下是单例状态&#xff08;singleton&…

隧道穿透:端口转发、socket隧道代理

目录 端口转发 lcx工具 Lcx工具正向连接 Lcx工具反向连接 SOCKET隧道代理 socks常见利用场景 Proxifier SocksCap64 Proxychains 端口转发 本篇会和搭建介绍一下端口转发和socket隧道代理的概念和简单演示 lcx工具 lcx工具是一个红队人员在内网渗透测试中最典型的端…

俩种方法解决 VScode中 NPM 脚本消失,NPM 脚本未显示在资源管理器侧栏中

npm脚本是npm包管理器的一个功能&#xff0c;允许开发者在package.json文件中定义一系列命令脚本&#xff0c;用于执行各种开发任务。 今天打开准备运行的时候发现找不到NPM脚本了&#xff0c;左侧的一栏完全没有显示&#xff0c;在网上查阅了很多资料后总结出俩个方法可以用来…

存算一体:架构创新,打破算力极限

1 需求背景 在全球数据量呈指数级暴涨&#xff0c;算力相对于AI运算供不应求的现状下&#xff0c;存算一体技术主要解决了高算力带来的高能耗成本矛盾问题&#xff0c;有望实现降低一个数量级的单位算力能耗&#xff0c;在功耗敏感的百亿级AIoT设备上、高能耗的数据中心、自动驾…

2024.02.06

TCP提供面向有连接的&#xff0c;可靠的数据传输服务&#xff0c;传输过程中&#xff0c;数据无误、数据无丢失、数据无失序、数据无重复 UDP面向无连接的&#xff0c;不保证数据可靠的&#xff0c;尽最大努力传输的协议&#xff0c;数据传输过程中&#xff0c;可能出现数据丢…

大路灯护眼灯有必要吗安全吗?五款优秀品质大路灯推荐

随着人们对健康生活的追求和对眼睛健康的关注&#xff0c;落地护眼灯逐渐成为一种热门的家居用品。落地护眼灯主要通过提供适宜的光照条件来保护眼睛&#xff0c;减少眼睛疲劳和视力损害的发生。那么&#xff0c;落地护眼灯是否有必要&#xff1f;它的使用是否安全呢&#xff1…

外包干了10个月,技术退步明显.......

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

三网码支付系统源码,三网免挂有PC软件,有云端源码,附带系统搭建教程

搭建教程 1.先上传云端源码 然后配置Core/Config.php文件里面数据库信息注改&#xff1b;数据库帐号密码 2.云端源码里面Core/Api_Class/Instant_Url_List.php文件配置终端地址注改&#xff1b;第4 http://终端地址/ 3.导入云端数据库 账号admin 密码123456注改&#xff1…

【C语言】三子棋游戏实现代码

目录 1.三子棋代码功能介绍 2.三子棋游戏实现步骤 ①打印菜单栏 ②判断是否进入三子棋游戏 ③三子棋游戏基本函数实现 &#xff08;1&#xff09;清空&#xff08;初始化&#xff09;棋盘函数实现 &#xff08;2&#xff09;打印棋盘函数实现 &#xff08;3&#xff0…

【如何学习CAN总线测试】——Vector VH6501干扰仪测试BusOff

系列文章目录 【如何学习CAN总线测试】系列文章目录汇总 文章目录 系列文章目录前言一、环境搭建1.硬件环境2.软件环境3.原理 二、测试方法1.打开Disturbance(CAN)工程2.使能 VH65013.MainConfigPanel面板4.TriggerConfiguration配置5.Sequence Configuration配置6.干扰结果 前…

leetcode9. 回文数|详细深入讲解算法

前往题目有 反转一半数字 思路 映入脑海的第一个想法是将数字转换为字符串&#xff0c;并检查字符串是否为回文。但是&#xff0c;这需要额外的非常量空间来创建问题描述中所不允许的字符串。 第二个想法是将数字本身反转&#xff0c;然后将反转后的数字与原始数字进行比较&…

通信图(Communication Diagram)

UML2.0之前叫协作图(Collaboration Diagram) 一、定义 显示在某种情形下对象之间发送的消息。 协作图显示了一系列的对象和在这些对象之间的联系以及对象间发送和接收的消息 二、元素 通信图的组成元素: 对象、链、消息 1、对象 通信图中的对象与顺序图中对象的概念相同&…

js中的事件模型详解

文章目录 一、事件与事件流二、事件模型原始事件模型标准事件模型IE事件模型 一、事件与事件流 javascript中的事件&#xff0c;可以理解就是在HTML文档或者浏览器中发生的一种交互操作&#xff0c;使得网页具备互动性&#xff0c; 常见的有加载事件、鼠标事件、自定义事件等 …

工业以太网交换机引领现代工厂自动化新潮流

随着科技的飞速发展&#xff0c;现代工厂正迎来一场前所未有的自动化变革&#xff0c;而工业以太网交换机的崭新角色正是这场变革的关键组成部分。本文将深入探讨工业以太网交换机与现代工厂自动化的紧密集成&#xff0c;探讨这一集成如何推动工业生产的智能化、效率提升以及未…