微服务即时通信系统(十二)---入口网关子服务
目录
功能设计
模块划分
业务接口/功能示意图
服务实现流程
网关HTTP接口
网关WebSocket接口
总体流程
服务代码实现
客户端长连接管理封装(connectionManage.hpp)
proto文件的编写
身份鉴权proto
事件通知proto
各项请求的URL的确定
服务端完成入口网关服务类(GatewayServer),进行其中的各项功能函数(请求处理函数)的实现
宏定义个功能请求的URL
GatewayServer类的成员变量和构造函数
WebSocketServer的三个回调函数实现
Speech::SpeechRecognize语音识别请求处理
File::SingleFileUp单文件上传/发送单文件请求处理
File::MultiFileUp多文件上传/发送多文件请求处理
File::SingleFileDonw单文件下载/获取单文件数据请求处理
File::MultiFileDonw多文件下载/获取多文件数据请求处理
User::UserNicknameRegistr用户名注册请求处理
User::UserNicknameLogin用户名登陆请求处理
User::GetPhoneVerificationCode获取手机验证码请求处理
User::UserPhoneRegister手机号注册请求处理
User::UserPhoneLogin手机号登陆请求处理
User::GetOneUserInfo获取用户信息请求处理
User::SetUserAvatar设置用户头像请求处理
User::SetUserNickname设置用户昵称请求处理
User::SetUserDescription设置用户签名请求处理
User::SetUserPhone设置用户手机号请求处理
Transmite::SendNewMessage发送新消息请求处理
Store::GetRecentNMessage获取最近N条消息请求处理
Store::GetTimeRangeMessage获取时间段消息请求处理
Store::GetMessageByKeyword消息关键字搜索请求处理
Friend::GetFriendList获取好友列表请求处理
Friend::UserSearch用户搜索请求处理
Friend::ApplyAddFriend申请添加好友请求处理
Friend::GetFriendApplyList获取好友申请列表请求处理
Friend::FriendApplyEventHandle处理好友申请事件请求处理
Friend::RemoveFriend删除好友请求处理
Friend::GetChatSessionList获取聊天会话列表请求处理
Friend::CreateGroupChatSession创建群聊会话请求处理
Friend::GetChatSessionMember获取会话成员请求处理
完成入口网关服务类的构造者(GatewayServerBuilder)
实例化服务对象,启动服务
工程系统构建配置文件(CMakeLists.txt)
本章节,主要对项目中入口网关子服务模块进行分析、开发与测试。
功能设计
入口网关子服务在设计中,最重要的两个功能:
1、接收客户端的所有请求,并解析,然后进行请求的子服务分发,得到响应后,再响应给客户端。(其中这些请求再细化为具体的各项请求,一共28个)。
2、对客户端进行事件通知:好友申请、申请处理、删除好友、聊天会话的创建、新消息。
基于上述两个功能,入口网关子服务包含两种通信:
1、HTTP通信:进行接收请求,业务处理。
2、WebSocket通信:进行事件通知。
模块划分
参数/配置文件解析模块 | 基于gflags框架直接使用,进行参数/配置文件的解析。 |
日志模块 | 基于spdlog封装的logger 直接进行日志输出。 |
服务发现与调用模块 | 基于etcd框架封装的服务发现与brpc框架封装的服务调用模块。 因为要分发处理所有请求,需要发现所有的子服务模块。 |
redis客户端模块 | 基于redis++封装的客户端进行内存数据库的操作。 根据用户管理子服务添加的登录会话信息,进行用户的身份鉴权和识别。 |
HTTP通信服务器模块 | 基于httplib搭建HTTP服务器,接收HTTP请求,进行业务处理。 |
WebSocket服务器模块 | 基于WebSocketpp,搭建WebSocket服务器,进行事件通知。 |
客户端长连接Connection管理模块 | 通过用户ID 和 长连接句柄 建立映射关系,对长连接进行封装,便于后续管理。 |
业务接口/功能示意图
服务实现流程
网关HTTP接口
HTTP通信,分为首行、头部和正文三部分。
首行中的URL明确了业务请求目标(目的地)。
头部进行正文或连接描述。
正文中包含请求或响应的内容。
因此,之后实现的时候,首先就要确定各项URL。
其次,在HTTP请求正文中,将采用protobuf协议作为正文的序列化方式。不同的请求正文与后台的请求基本上吻合,因此,请求正文结构 将与 后台服务之间复用同一套接口。
网关WebSocket接口
WebSocket通信接口中,包含两个方面:
1、长连接的身份识别。
当用户登陆成功之后,向服务器发送WebSocket长连接请求,建立长连接。
长连接建立成功之后,向服务器发送身份鉴权请求,请求内容为protobuf结构数据,包含内容:请求ID、登录会话ID。
该请求不需要服务端进行回复,鉴权成功则长连接保持,否则断开长连接即可。
2、事件通知的内容。
因为事件通知在长连接中进行,因此,只需要定义出事件通知的消息结构即可。
总体流程
0、完成客户端长连接管理的封装。 |
1、编写服务所需的proto文件,利用protoc工具生成RPC服务器所需的.pb.h 和 .pb.cc 项目文件。 |
2、完成各项功能函数(请求)的URL的编写。 |
3、服务端完成入口网关服务类,进行其中的各项功能函数(请求处理函数)的实现。 |
4、实例化服务器对象,启动服务。 |
服务代码实现
客户端长连接管理封装(connectionManage.hpp)
在该封装中,提供了4个API:
1、新增长连接管理。
2、移除长连接。
3、获取长连接。
4、通过长连接获取对应用户(客户端)信息。
#pragma once
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <iostream>
#include "logger.hpp"namespace yangz
{typedef websocketpp::server<websocketpp::config::asio> websocket_server; // 简单重定义websocket_server类型// 此时, 长连接的类型: websocket_server::connection_ptrclass Connection{public:// 长连接客户端 -> user_id, login_session_idstruct Client{Client(const std::string &uid, const std::string &lssid) : user_id(uid), login_session_id(lssid) {}std::string user_id;std::string login_session_id;};using ptr = std::shared_ptr<Connection>;Connection() {}~Connection() {}public:// 新增长连接管理void append(const websocket_server::connection_ptr &connection, const std::string &user_id, const std::string &login_session_id){// connection_ptr是weak_ptr, 需要get获取shared_ptrstd::unique_lock<std::mutex> lock(_mutex);_userid_connections.insert({user_id, connection});_connection_clients.insert({connection, Client(user_id, login_session_id)});LOG_DEBUG("新增长连接用户信息, connection_ptr: {}, user_id: {}, login_session_id: {}", (size_t)connection.get(), user_id, login_session_id);}// 移除长连接void remove(const websocket_server::connection_ptr &connection){std::unique_lock<std::mutex> lock(_mutex);auto it = _connection_clients.find(connection);if (it == _connection_clients.end()){LOG_ERROR("删除长连接时, 未找到该长连接, connection_ptr: {}", (size_t)connection.get());return;}_userid_connections.erase(it->second.user_id);_connection_clients.erase(it);LOG_DEBUG("移除长连接成功");}// 获取该长连接websocket_server::connection_ptr get_connection(const std::string &user_id){std::unique_lock<std::mutex> lock(_mutex);auto it = _userid_connections.find(user_id);if (it == _userid_connections.end()){LOG_ERROR("获取长连接时, 没有找到该长连接, user_id: {}", user_id);return websocket_server::connection_ptr();}return it->second;}// 判断该用户是否已经建立了长连接, 同时获取user_id 和 login_session_idbool client_is_connect(const websocket_server::connection_ptr &connection, std::string &user_id, std::string &login_session_id){std::unique_lock<std::mutex> lock(_mutex);auto it = _connection_clients.find(connection);if (it == _connection_clients.end()){LOG_ERROR("获取用户信息时, 该用户并没有建立长连接");return false;}user_id = it->second.user_id;login_session_id = it->second.login_session_id;return true;}private:std::mutex _mutex;std::unordered_map<std::string, websocket_server::connection_ptr> _userid_connections; // <user_id, 长连接>std::unordered_map<websocket_server::connection_ptr, Client> _connection_clients; // <长连接, [user_id, login_session_id]>};
}
proto文件的编写
身份鉴权proto
入口网关子服务首先根据用户请求中携带信息,通过长连接获取到客户端信息,然后根据redis数据库中的信息进行审核。
因此,只需要一个简单的protobuf结构的信息:user_id, login_session_id即可:
syntax = "proto3";
package yangz;
option cc_generic_services = true;// 用户身份鉴权请求
message ClientIdentityAuthenticationReq
{string req_id = 1;string login_session_id = 2; // 登录会话ID, 网关根据该字段识别长连接客户端的身份
};
事件通知proto
在该proto文件中,要定义出来需要被通知的事件的protobuf结构数据:申请添加好友、处理申请结构、删除好友、创建群聊、发送新消息。
syntax = "proto3";
package yangz;
import "base.proto";option cc_generic_services = true;enum MessageNotifyType
{APPLY_ADD_FRIEND_NOTIFY = 0; // 申请添加好友FRIEND_APPLY_EVENT_HANDLE_NOTIFY = 1; // 处理好友申请REMOVE_FRIEND_NOTIFY = 2; // 删除好友CREATE_GROUP_CHAT_SESSION_NOTIFY = 3; // 创建群聊NEW_MESSAGE_NOTIFY = 4; // 新消息
}// 申请添加好友
message ApplyAddFriendNotify
{UserInfo user_info = 1; // 申请人信息
};// 好友申请请求处理
message FriendApplyEventHandleNotify
{bool agree = 1;UserInfo user_info = 2; // 处理人信息
};// 删除好友
message RemoveFriendNotify
{string user_id = 1; // 主动删除者的用户ID
};// 创建群聊
message CreateGroupChatSessionNotify
{ChatSessionInfo chat_session_info = 1; // 聊天会话信息
};// 新聊天消息
message NewMessageNotify
{MessageInfo message_info = 1; // 消息元信息
};message NotifyMessage
{optional string notify_event_id = 1; // 通知事件IDMessageNotifyType notify_type = 2;oneof notify_remarks // 事件备注信息{ApplyAddFriendNotify apply_add_friend = 3;FriendApplyEventHandleNotify friend_apply_event_handle = 4;RemoveFriendNotify remove_friend = 5;CreateGroupChatSessionNotify create_group_chat_session = 6;NewMessageNotify new_message = 7;}
};
各项请求的URL的确定
对于客户端的请求,该项目中一共提供28个请求功能,分别对应到各个子服务中,因此,需要提供28个URL,来对应子服务下的对应功能。
并且客户端的请求采用HTTP模式进行通信,通信时采用Post作为请求方法。
SERVICE HHTP PATH:// 语音识别子服务
/service/speech_recognition/speech_recognize 语音识别// 文件管理子服务
/service/file_manage/up_single_file 发送/上传单个文件
/service/file_manage/up_multi_file 发送/上传多个文件
/service/file_manage/donw_single_file 获取/下载单个文件
/service/file_manage/donw_single_file 获取/下载多个文件// 用户管理子服务
/service/user_manage/user_nickname_registr 用户名注册
/service/user_manage/user_nickname_login 用户名登陆
/service/user_manage/get_phone_verification_code 获取手机验证码
/service/user_manage/user_phone_registr 手机号注册
/service/user_manage/user_phone_login 手机号登陆
/service/user_manage/get_one_user_info 获取单个用户信息
/service/user_manage/set_user_avatar 修改头像
/service/user_manage/set_user_nickname 修改昵称
/service/user_manage/set_user_description 修改签名
/service/user_manage/set_user_phone 修改手机号// 消息转发子服务
/service/message_transmite/send_new_message 发送新消息// 消息存储子服务
/service/message_store/get_recent_n_message 获取最近N条消息
/service/message_store/get_time_range_message 获取指定时间段消息
/service/message_store/get_message_by_keyword 消息关键字搜索// 好友管理子服务
/service/friend_manage/get_friend_list 获取好友列表
/service/friend_manage/user_search 用户搜索
/service/friend_manage/apply_add_friend 申请添加好友
/service/friend_manage/get_friend_apply_list 获取好友申请列表
/service/friend_manage/friend_apply_event_handle 处理好友申请
/service/friend_manage/remove_friend 删除好友
/service/friend_manage/get_chat_session_list 获取聊天会话列表
/service/friend_manage/create_group_chat_session 创建群聊会话
/service/friend_manage/get_chat_session_member 获取聊天会话成员
服务端完成入口网关服务类(GatewayServer),进行其中的各项功能函数(请求处理函数)的实现
宏定义个功能请求的URL
// 各功能请求的URL
// speech_recognition_service
#define SPEECH_RECOGNITION "/service/speech_recognition/speech_recognize"// file_manage_service
#define UP_SINGLE_FILE "/service/file_manage/up_single_file"
#define UP_MULTI_FILE "/service/file_manage/up_multi_file"
#define DONW_SINGLE_FILE "/service/file_manage/donw_single_file"
#define DONW_MULTI_FILE "/service/file_manage/donw_multi_file"// user_manage_service
#define USER_NICKNAME_REGISTR "/service/user_manage/user_nickname_registr"
#define USER_NICKNAME_LOGIN "/service/user_manage/user_nickname_login"
#define GET_PHONE_VERIFICATION_CODE "/service/user_manage/get_phone_verification_code"
#define USER_PHONE_REGISTR "/service/user_manage/user_phone_registr"
#define USER_PHONE_LOGIN "/service/user_manage/user_phone_login"
#define GET_ONE_USER_INFO "/service/user_manage/get_one_user_info"
#define SET_USER_AVATAR "/service/user_manage/set_user_avatar"
#define SET_USER_NICKNAME "/service/user_manage/set_user_nickname"
#define SET_USER_DESCRIPTION "/service/user_manage/set_user_description"
#define SET_USER_PHONE "/service/user_manage/set_user_phone"// message_transmite_service
#define SEND_NEW_MESSAGE "/service/message_transmite/send_new_message"// message_store_service
#define GET_RECENT_N_MESSAGE "/service/message_store/get_recent_n_message"
#define GET_TIME_RANGE_MESSAGE "/service/message_store/get_time_range_message"
#define GET_MESSAGE_BY_KEYWORD "/service/message_store/get_message_by_keyword"// friend_manage_service
#define GET_FRIEND_LIST "/service/friend_manage/get_friend_list"
#define USER_SEARCH "/service/friend_manage/user_search"
#define APPLY_ADD_FRIEND "/service/friend_manage/apply_add_friend"
#define GET_FRIEND_APPLY_LIST "/service/friend_manage/get_friend_apply_list"
#define FRIEND_APPLY_EVENT_HANDLE "/service/friend_manage/friend_apply_event_handle"
#define REMOVE_FRIEND "/service/friend_manage/remove_friend"
#define GET_CHAT_SESSION_LIST "/service/friend_manage/get_chat_session_list"
#define CREATE_GROUP_CHAT_SESSION "/service/friend_manage/create_group_chat_session"
#define GET_CHAT_SESSION_MEMBER "/service/friend_manage/get_chat_session_member"
GatewayServer类的成员变量和构造函数
先定义各成员变量,然后进行列表初始化。
随后在构造函数中,对websocket服务器进行初始化:设置建立长连接/关闭长连接/新消息到来的回调处理函数。
再对http服务器进行初始化:设置客户端的Post请求处理方法,一共28个,之后再具体实现28个请求处理方法函数。
class GatewayServer{public:using ptr = std::shared_ptr<GatewayServer>;GatewayServer(const std::shared_ptr<sw::redis::Redis> &redis_client,const std::string &speech_recognition_service_name,const std::string &file_manage_service_name,const std::string &user_manage_service_name,const std::string &message_transmite_service_name,const std::string &message_store_service_name,const std::string &friend_manage_service_name,const ServiceDiscovery::ptr &sd_client,const ServiceChannelManager::ptr &channel_manager,int websocket_port,int http_port): _redis_login_session_client(std::make_shared<LoginSessionManage>(redis_client)),_redis_login_status_client(std::make_shared<LoginStatusManage>(redis_client)),_speech_recognition_service_name(speech_recognition_service_name),_file_manage_service_name(file_manage_service_name),_user_manage_service_name(user_manage_service_name),_message_transmite_service_name(message_transmite_service_name),_message_store_service_name(message_store_service_name),_friend_manage_service_name(friend_manage_service_name),_sd_client(sd_client),_channel_manager(channel_manager),_connections(std::make_shared<Connection>()){// init websocket server_websocket_server.set_access_channels(websocketpp::log::alevel::none); // 关闭日志输出_websocket_server.init_asio(); // 初始化websocket的异步IO_websocket_server.set_open_handler(std::bind(&GatewayServer::open_callback, this, std::placeholders::_1)); // 设置websocket握手成功后的回调函数_websocket_server.set_close_handler(std::bind(&GatewayServer::close_callback, this, std::placeholders::_1)); // 设置websocket连接关闭的回调函数_websocket_server.set_message_handler(std::bind(&GatewayServer::newmessage_callback, this, std::placeholders::_1, std::placeholders::_2)); // 设置websocket新消息的回到处理函数_websocket_server.set_reuse_addr(true); // 启动地址重用_websocket_server.listen(websocket_port); // 开始监听_websocket_server.start_accept(); // 开始接收连接请求// init http server// 即设置各种客户端Post请求的回调处理函数, 即28各功能请求处理函数// Port(URL, (httplib::Server::Handler)call_back)// speech_recognition_service_http_server.Post(SPEECH_RECOGNITION, (httplib::Server::Handler)std::bind(&GatewayServer::SpeechRecognize, this, std::placeholders::_1, std::placeholders::_2));// file_manage_service_http_server.Post(UP_SINGLE_FILE, (httplib::Server::Handler)std::bind(&GatewayServer::SingleFileUp, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(UP_MULTI_FILE, (httplib::Server::Handler)std::bind(&GatewayServer::MultiFileUp, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(DONW_SINGLE_FILE, (httplib::Server::Handler)std::bind(&GatewayServer::SingleFileDown, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(DONW_MULTI_FILE, (httplib::Server::Handler)std::bind(&GatewayServer::MultiFileDown, this, std::placeholders::_1, std::placeholders::_2));// user_manage_service_http_server.Post(USER_NICKNAME_REGISTR, (httplib::Server::Handler)std::bind(&GatewayServer::UserNicknameRegistr, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(USER_NICKNAME_LOGIN, (httplib::Server::Handler)std::bind(&GatewayServer::UserNicknameLogin, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(GET_PHONE_VERIFICATION_CODE, (httplib::Server::Handler)std::bind(&GatewayServer::GetPhoneVerificationCode, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(USER_PHONE_REGISTR, (httplib::Server::Handler)std::bind(&GatewayServer::UserPhoneRegister, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(USER_PHONE_LOGIN, (httplib::Server::Handler)std::bind(&GatewayServer::UserPhoneLogin, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(GET_ONE_USER_INFO, (httplib::Server::Handler)std::bind(&GatewayServer::GetOneUserInfo, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(SET_USER_AVATAR, (httplib::Server::Handler)std::bind(&GatewayServer::SetUserAvatar, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(SET_USER_NICKNAME, (httplib::Server::Handler)std::bind(&GatewayServer::SetUserNickname, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(SET_USER_DESCRIPTION, (httplib::Server::Handler)std::bind(&GatewayServer::SetUserDescription, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(SET_USER_PHONE, (httplib::Server::Handler)std::bind(&GatewayServer::SetUserPhone, this, std::placeholders::_1, std::placeholders::_2));// message_transmite_service_http_server.Post(SEND_NEW_MESSAGE, (httplib::Server::Handler)std::bind(&GatewayServer::SendNewMessage, this, std::placeholders::_1, std::placeholders::_2));// message_store_service_http_server.Post(GET_RECENT_N_MESSAGE, (httplib::Server::Handler)std::bind(&GatewayServer::GetRecentNMessage, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(GET_TIME_RANGE_MESSAGE, (httplib::Server::Handler)std::bind(&GatewayServer::GetTimeRangeMessage, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(GET_MESSAGE_BY_KEYWORD, (httplib::Server::Handler)std::bind(&GatewayServer::GetMessageByKeyword, this, std::placeholders::_1, std::placeholders::_2));// friend_manage_service_http_server.Post(GET_FRIEND_LIST, (httplib::Server::Handler)std::bind(&GatewayServer::GetFriendList, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(USER_SEARCH, (httplib::Server::Handler)std::bind(&GatewayServer::UserSearch, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(APPLY_ADD_FRIEND, (httplib::Server::Handler)std::bind(&GatewayServer::ApplyAddFriend, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(GET_FRIEND_APPLY_LIST, (httplib::Server::Handler)std::bind(&GatewayServer::GetFriendApplyList, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(FRIEND_APPLY_EVENT_HANDLE, (httplib::Server::Handler)std::bind(&GatewayServer::FriendApplyEventHandle, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(REMOVE_FRIEND, (httplib::Server::Handler)std::bind(&GatewayServer::RemoveFriend, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(GET_CHAT_SESSION_LIST, (httplib::Server::Handler)std::bind(&GatewayServer::GetChatSessionList, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(CREATE_GROUP_CHAT_SESSION, (httplib::Server::Handler)std::bind(&GatewayServer::CreateGroupChatSession, this, std::placeholders::_1, std::placeholders::_2));_http_server.Post(GET_CHAT_SESSION_MEMBER, (httplib::Server::Handler)std::bind(&GatewayServer::GetChatSessionMember, this, std::placeholders::_1, std::placeholders::_2));// http 新启线程, 放置阻塞等待消息, 并分离_http_thread = std::thread([this, http_port](){ _http_server.listen("0.0.0.0", http_port); });_http_thread.detach();}// 服务器启动void start(){_websocket_server.run();}private:// redisLoginSessionManage::ptr _redis_login_session_client; // 登录会话操作句柄LoginStatusManage::ptr _redis_login_status_client; // 登陆状态操作句柄// service namestd::string _speech_recognition_service_name;std::string _file_manage_service_name;std::string _user_manage_service_name;std::string _message_transmite_service_name;std::string _message_store_service_name;std::string _friend_manage_service_name;// service discoverServiceDiscovery::ptr _sd_client; // etcd.hpp中服务发现的操作句柄// channel managerServiceChannelManager::ptr _channel_manager; // 通信信道, 用于和子服务建立连接// connectionConnection::ptr _connections;// wevsocket & httpwebsocket_server _websocket_server;httplib::Server _http_server;std::thread _http_thread; // http新线程, 异步执行, 放置阻塞等候};
WebSocketServer的三个回调函数实现
握手成功回调函数:
简单打印日志提示。
// websocket握手成功后的回调函数void open_callback(websocketpp::connection_hdl hdl){LOG_DEBUG("websocket长连接建立成功, connection: {}", (size_t)_websocket_server.get_con_from_hdl(hdl).get());}
连接关闭回调函数:
1、清理对应的connection缓存。
2、日志打印提示。
// websocket连接关闭的回调函数void close_callback(websocketpp::connection_hdl hdl){// 1. 获取到connectionauto connection = _websocket_server.get_con_from_hdl(hdl);// 2. 通过connection获取到客户端信息std::string user_id, login_session_id;bool res = _connections->client_is_connect(connection, user_id, login_session_id);if (res == false){LOG_WARN("长连接断开时, 未找到客户端信息");return;}// 3. 移除相关管理信息_redis_login_session_client->remove(login_session_id);_redis_login_status_client->remove(user_id);_connections->remove(connection);LOG_DEBUG("长连接断开, 清理缓存完成");}
新消息到来回调函数:
1、获取其中的connection。
2、将消息正文反序列化。
3、提取login_session_id。
4、根据login_session_id在redis中进行身份查找。
5、添加长连接管理。
6、长连接进行保活。
// 设置websocket新消息的回到处理函数void newmessage_callback(websocketpp::connection_hdl hdl, websocket_server::message_ptr message){// 1. 获取connectionauto connection = _websocket_server.get_con_from_hdl(hdl);// 2. 将消息中的ClientIdentityAuthenticationReq进行反序列化获取ClientIdentityAuthenticationReq req
相关文章:
微服务即时通信系统(十二)---入口网关子服务
目录 功能设计 模块划分 业务接口/功能示意图 服务实现流程 网关HTTP接口 网关WebSocket接口 总体流程 服务代码实现 客户端长连接管理封装(connectionManage.hpp) proto文件的编写 身份鉴权proto 事件通知proto 各项请求的URL的确定 服务端完成入口网关服务类…...
ES练习册
es索引结构和数据实例 这里提供索引结构和数据实例提供给大家使用练习,希望大家能够一起成长进步~~~~ #添加索引 PUT /ecommerce_products {"settings": {"number_of_shards": 3,"number_of_replicas": 1,"analysis": {&…...
运算符分为哪几类?哪些运算符常用作判断?简述运算符的优先级
运算符主要分为以下几类: 算术运算符:用于执行基本的数学运算,如加、减、乘、除、取模等。例如:、-、*、/、%。赋值运算符:用于将值赋给变量。例如:、、-、*、/、%。比较运算符ÿ…...
shell编程基础知识及脚本示例
文章目录 前言一、shell变量1.命名规则2.定义及使用变量 二、shell传递参数1.位置参数2. 任意参数 三、shell一维数组0.定义方式1.定义并获取数组的单个元素2.定义并获取数组的所有元素3.定义并获取数组的元素个数4.定义并获取数组的元素索引 四、shell条件判断语法五、shell常…...
再学GPIO(一)
GPIO输出模式 STM32的GPIO(General Purpose Input Output 通用输入输出)引脚支持多种输出模式,不同模式决定了引脚的驱动能力和信号特性。STM32的GPIO输出模式主要分为以下4种: 推挽输出(Push-Pull Output)…...
OpenCV彩色图像分割
OpenCV计算机视觉开发实践:基于Qt C - 商品搜索 - 京东 灰度图像大多通过算子寻找边缘和区域生长融合来分割图像。彩色图像增加了色彩信息,可以通过不同的色彩值来分割图像,常用彩色空间HSV/HIS、RGB、LAB等都可以用于分割。本节使用inRange…...
django filter 排除字段
在Django中,当你使用filter查询集(QuerySet)时,通常你会根据模型的字段来过滤数据。但是,有时你可能想要排除某些特定的字段,而不是过滤这些字段。这里有几种方法可以实现这一点: 使用exclude方…...
多模态大语言模型arxiv论文略读(四十五)
CAT: Enhancing Multimodal Large Language Model to Answer Questions in Dynamic Audio-Visual Scenarios ➡️ 论文标题:CAT: Enhancing Multimodal Large Language Model to Answer Questions in Dynamic Audio-Visual Scenarios ➡️ 论文作者:Qil…...
Vue3 通过Vue3-Print-Nb在线工单打印 模板打印 自定义打印 打印下载
介绍 通过在应用中集成打印功能,用户可以直接从页面打印工单,不用导出文件或使用其他外部工具。节省时间,提高效率,特别是当需要大量打印时。同时也可以将文件模板上传到数据库,提供给部门工作自行下载。 开源文档&am…...
视觉“解锁”触觉操控:Franka机器人如何玩转刚柔物体?
集智联机器人(Plug & Play Robotics),简称PNP机器人,是Franka Robotics和思灵机器人金牌合作伙伴,集智联机器人团队成员均来自于国内外机器人行业知名企业,具有较强的学术背景。PNP机器人致力于为客户提…...
FlinkUpsertKafka深度解析
1. 设计目标与工作机制 Upsert-Kafka Connector 核心功能:支持以 Upsert(插入/更新/删除) 模式读写 Kafka 数据,适用于需要动态更新结果的场景(如聚合统计、CDC 数据同步)。数据流类型: 作为 …...
百度Create大会深度解读:AI Agent与多模态模型如何重塑未来?
目录 百度Create大会亮点全解析:从数字人到Agent生态布局 数字人商业化:从"拟人"到"高说服力"的进化 Agent生态:从"心响"App看百度的Agent战略布局 "心响"App的技术架构与创新点 多模态大模型&a…...
新能源汽车运动控制器核心芯片选型与优化:MCU、DCDC与CANFD协同设计
摘要:随着新能源汽车产业的迅猛发展,汽车运动控制器的性能和可靠性面临着更高的要求。本文深入探讨了新能源汽车运动控制器中MCU(微控制单元)、DCDC电源管理芯片和CANFD总线通信芯片的选型要点、优化策略及其协同设计方案。通过综…...
【软件工程】 白盒测试简介
1. 前言 在软件测试过程中,白盒测试(White-box Testing)是一种重要方法,它通过检查程序内部结构来验证软件功能。本文以一道典型的伪代码程序为例,结合白盒测试的基本操作,设计语句覆盖测试用例࿰…...
uniapp自定义一个选择年月日时分的组件。
<template><view><u-popup :show"timePopShow" mode"bottom" close"close" open"open" :closeOnClickOverlay"true"><view class"popup-container"><!-- 自定义时间内容 --><vi…...
Git命令(Gitee)
三板斧: git init //初始化本地仓库 git add . //添加所有文件到缓存区 (或指定文件) git commit -m "备注" //提交,填写备注 git remote add origin <远程仓库链接> git push -u origin ma…...
Node.js 应用部署:镜像体积优化与安全的多阶段构建探索
Node.js 应用部署:镜像体积优化与安全的多阶段构建探索 在开发 Node.js 应用时,部署过程中的镜像体积优化和安全性保障是至关重要的环节。本文将通过两种不同的 Docker 部署方式,深入探讨如何实现高效的镜像体积优化和安全的部署环境。 传统的单阶段构建方式 许多开发者在…...
深度解析:Web Crawling与Web Scraping的区别与联系
在现代互联网数据驱动的时代,Web Crawling(网页爬取)和Web Scraping(网页抓取)成为数据采集领域的两大核心技术。尽管两者常被混用,但它们在技术实现、应用目的和操作流程上存在显著差异。本文将基于权威资料,特别是维基百科的定义࿰…...
C# 利用log4net 工作台打印和保存到文件
目录 log4net简介引言1、添加引用库2、添加引用和构建实例3、添加属性配置4、添加配置文件最后 log4net简介 log4net是一个开源的日志记录组件,专为.NET平台设计。它是Apache log4j框架在Microsoft .NET平台的实现,属于Apache Logging Servic…...
2025 VSCode中如何进行dotnet开发环境配置完整教程
我一直都是用Visual Studio 2019来开发C#项目的,用的比较顺手,也习惯了。看其他技术文章有介绍VS Code更轻量,更方便。 所以就想来研究如何使用VS Code,看看它是如何构建代码、调试代码、如何运行C#应用程序。 本文将详细介绍如何…...
vscode本地化显示远程图形化界面
远程登陆到服务器端 sudo vim /etc/ssh/sshd_config修改文件中的参数,保存。主要参数如下 配置好上述后需要reload一下ssh,安装xclock。 service ssh reload # 安装x11-apps,这样就可以使用xclock apt install x11-apps查看环境变量,在其中并没有DISPL…...
STM32 CAN通信 HAL库实战教程:从零到测试成功
STM32 CAN通信 HAL库实战教程:从零到测试成功 <我打印的是陀螺仪的数据> 目录 简介:为什么学习CAN通信CAN通信基础概念STM32 CAN硬件配置CAN初始化详解CAN数据发送实现CAN数据接收实现测试与验证方法常见问题与解决总结与拓展 1. 简介࿱…...
数据结构强化篇
应用题 排序 插入排序 void InsertSort (ElemType A[], n int) {int i, j;for (i2; i<n; i) //依次将 A[2]~A[n]插入前面已排序序列if (A[i]<A[i-1]) { //若 A[i]关键码小于其前驱,将 A[i]插入有序表A[0]A[i]; //复制为哨兵,A[0]不存放…...
泰迪杯实战案例超深度解析:旅游景点游客流量预测与资源优化
(2025年泰迪杯数据挖掘挑战赛D题特等奖案例) 一、案例背景与目标 1.1 应用场景与痛点 某5A级景区面临以下核心问题: 拥堵严重:节假日热门景点游客密度超过10人/㎡,排队时间长达2小时。 资源浪费:接驳车空载率30%,餐饮点位在非高峰时段闲置率60%。 应急滞后:突发降雨或…...
qt 3d航迹图
一般就是三种方法 1.opengl,vtk这种从零自己画,网上也可能有半成品,大多是付费的。 2.重写qwt3d,07年就停止更新了,画出来类似opengl,需要自己修改参数,参数修改不对很难搞,对于经…...
探索排序算法的奥秘(上):冒泡排序、选择排序、插入排序
在计算机科学中,排序算法是数据处理的基础工具之一。通过对数据进行有序排列,可以极大地提高数据检索和处理的效率。本文将详细介绍三种经典的排序算法:冒泡排序、选择排序和插入排序。我们将从算法思想、原理、代码实现(C语言、P…...
Stable Diffusion 技术全景解析与行业竞争力分析
目录 一、Stable Diffusion 技术概览 1. 核心背景 2. 技术架构 二、行业地位与竞品对比 1. 市场定位 2. 核心竞争优势 三、部署成本与硬件要求 1. 硬件配置方案 2. 优化技巧 四、优势与劣势分析 1. 核心优势 2. 主要劣势 五、开源策略与商业模型 1. 开源协议 2. …...
基于Python的DeepSeek API图形界面开发
基于Python的DeepSeek API图形界面开发 在人工智能技术广泛应用的今天,如何将强大的API能力与用户友好的界面结合,成为开发者关注的重点。本文将以DeepSeek API为例,详解如何通过Python构建兼具功能性与美观度的GUI应用程序。 一、技术架构…...
w~嵌入式C语言~合集4
我自己的原文哦~ https://blog.51cto.com/whaosoft/13870376 一、STM32怎么选型 什么是 STM32 STM32,从字面上来理解,ST是意法半导体,M是Microelectronics的缩写,32表示32位,合起来理解,STM32就是指S…...
自动驾驶(ADAS)领域常用数据集介绍
1. KITTI 数据集 简介:由德国卡尔斯鲁厄理工学院与丰田研究院联合创建,是自动驾驶领域最经典的评测基准,涵盖立体视觉、光流、3D检测等任务。包含市区、乡村和高速公路场景的真实数据,标注对象包括车辆、行人等,支持多…...
【嵌入式八股23】Linux关键指令
系统关机命令 在 Linux 系统中,有多种命令可用于实现系统关机或重启操作,以下为你详细介绍各命令及其特点: 指令 说明 shutdown该命令能够安全地将系统关机。它允许用户指定关机的时间,还可以在关机前向所有登录用户发送警告消息。例如,shutdown -h now 可立即关机,shu…...
机器学习的一百个概念(13)布里尔分数
前言 本文隶属于专栏《机器学习的一百个概念》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见[《机器学习的一百个概念》 ima 知识库 知识库广场搜索: 知识库创建人机器学习@Shockang机器学习数学基础@Shocka…...
AI提示词(Prompt)终极指南:从入门到精通(附实战案例)
在AI重塑工作方式的今天,掌握Prompt工程已成为智能时代的必备技能。本指南系统解析提示词设计精髓,从认知底层逻辑到实战应用技巧,带您解锁AI工具的完整潜能。涵盖主流模型选型策略、三大黄金法则、结构化模板及典型避坑指南,更有…...
鸿蒙版电影app设计开发
鸿蒙电影购票APP项目介绍 一、项目概述及使用方式 (一)项目概述 在智能手机与移动互联网蓬勃发展的当下,移动设备已成为人们生活不可或缺的一部分,而电影购票这一日常娱乐消费行为也日益向移动端迁徙。鸿蒙系统作为新兴力量&…...
如何启动jar包隐藏cmd窗口
要启动一个JAR文件并隐藏命令行窗口(cmd),你可以使用不同的方法,具体取决于你的操作系统。以下是几种常见的方法: 在Windows中使用批处理文件 你可以创建一个批处理文件(.bat文件),…...
【Redis】基础3:一些应用场景
文章目录 1. session管理1.1 手机号登录流程1.2 session的数据结构设计1.2.1 一些概念1.2.2 session数据结构例子 1.3 引入redis来实现共享session1.3.1 流程发送验证码短信的流程验证码登录流程登录检查流程 1.3.2 要考虑的问题 2. 限时抢购代金券2.1 业务流程2.2 代金券表设计…...
【创新实训个人博客】multi-agent调研(2)
下面的部分我们将简单介绍现有的三个multi-agent框架,并对它们进行简单的比较 multi-agent框架分析 MetaGPT 相关资料 代码repo:https://github.com/geekan/MetaGPT论文:https://arxiv.org/pdf/2308.0035 AgentVerse 相关资料 代码repo&…...
对鸿蒙 Next 系统“成熟论”的深度剖析-优雅草卓伊凡
对鸿蒙 Next 系统“成熟论”的深度剖析-优雅草卓伊凡 在科技飞速发展的当下,鸿蒙 Next 系统无疑成为了众多科技爱好者与行业人士关注的焦点。今日,卓伊凡便收到这样一个饶有趣味的问题:鸿蒙 Next 系统究竟需要多长时间才能完全成熟ÿ…...
模方ModelFun瓦片数据操作教程
摘要:本文主要介绍模方ModelFun瓦片数据操作,包含:打开/关闭瓦片数据,备份和恢复,导出 OBJ/ OSGB,导出DOM/DSM。 1.打开/关闭瓦片数据 在“总体模型视图/OSGB”里对 tiles 进行操作:选择&…...
RabbitMQ应用(基于腾讯云)
1.防火墙配置 关于RabbitMQ的远程,主要有4个端口需要开通【入站规则】,分别是: 端口 5672:这是 RabbitMQ 的默认 AMQP(Advanced Message Queuing Protocol)端口,用于客户端与 RabbitMQ 服务器之…...
堆和二叉树的概念和操作
目录 1.树的概念 1.1数的表示 1.2二叉树 1.3特殊的二叉树 1.3.1满二叉树 1.3.2完全二叉树 1.3.3 二叉树存储结构 2.堆 2.1堆的实现 初始化和销毁 堆的插入 堆的向上调整算法编辑 编辑 堆的删除 出堆顶 1.树的概念 树是非线性的数据结构,有限节点具有的层…...
互联网大厂Java面试实录:从Spring Boot到微服务架构的技术问答
互联网大厂Java面试实录:从Spring Boot到微服务架构的技术问答 在某互联网大厂的面试现场,严肃的面试官开始了对求职者谢飞机的技术考核。此次面试围绕Java技术栈的应用与场景展开。 第一轮:基础架构与语言 面试官: 请你用简单…...
Rollup、Webpack、Esbuild 和 Vite 前端打包工具
Rollup、Webpack、Esbuild 和 Vite 都是前端开发中常用的打包工具,它们各有特点,以下是对它们的详细对比: 核心功能 Rollup:专注于 JavaScript 模块打包,擅长将小的 JavaScript 模块打包成一个或多个文件,…...
刀客独家 | 潘胜接管百度移动生态市场部
一、 据刀客doc向独家信源确认,百度移动生态事业群(MEG)市场部日前完成重要人事调整:潘胜已经接任市场负责人。 此前,根据雷锋网3月底的报道,百度云渠道生态总经理陈之若离职,原移动生态事业群…...
如何在Chrome浏览器中启用硬件加速【提升浏览器性能】
在使用谷歌浏览器时,硬件加速是提升性能的一个重要功能。它可以将一些计算任务从CPU转移到GPU,从而加速图像处理、视频播放等任务。如果您希望提升浏览器的运行速度,可以通过以下简单步骤启用硬件加速。 1. 打开Chrome浏览器设置 首先&#…...
部署mongodb三幅本集群
背景:原先使用的mongodb采用的是SSPL协议,此协议客户检测到有bug,故需要替换mongodb的版本,原先采用helm部署的mongodb但是无法找到4.1.4版本的chart包,故手写yaml部署 1、部署mongodb-arbiter服务 这个服务是用来选…...
JavaScript 解构赋值(下):对象解构与高级应用
对象解构赋值 基本语法 const person { name: Alice, age: 30 }; const { name, age } person;console.log(name); // Alice console.log(age); // 30别名 const { name: fullName, age: years } person; console.log(fullName); // Alice console.log(years); // 30…...
批量级负载均衡(Batch-Wise Load Balance)和顺序级负载均衡(Sequence-Wise Load Balance)
本文主要比较了批量级负载均衡(Batch-Wise Load Balance)和顺序级负载均衡(Sequence-Wise Load Balance)在 Mixture-of-Experts(MoE)模型训练中的核心区别 。批量级负载均衡通过对整个训练批次的专家负载进行平衡约束,实现了更灵活的均衡策略,有助于专家在不同领域的专…...
什么是EI期刊?EI目录列表如何看?一文查看最新目录+避坑指南
近期有很多人在后台问小编关于EI期刊的各种问题,且前段时间EI期刊目录刚刚更新,小编正好借此,这期仔细给大家介绍一下。 什么是EI期刊? EI期刊也称为工程索引期刊,是供查阅工程技术领域文献的综合性情报检索刊物。每年…...
[创业之路-341]:华为人力资源管理 - 华为技术专家体系详解
华为技术专家体系详解 一、技术专家等级划分 华为技术专家体系以 “专业能力管理贡献” 为双维度,覆盖 19级(技术专家B)至22级(集团核心领导者),其中 19B-20A级 为核心技术专家层级: 19B级&a…...