施磊老师基于muduo网络库的集群聊天服务器(六)
文章目录
- 客户端开发开始
- 客户端首页面功能
- 为何不逐行开发?
- 客户端CMake
- 代码搭配知识补充--有很多漏的
- 客户端main-登录,注册,退出
- 群组有问题
- 测试
- 客户端好友添加与聊天功能
- 表驱动设计:
- commandMap
- commandHandlerMap
- 为什么都是int, string
- 添加好友和聊天功能
- 测试
- 错误解决
- friend表问题
- mysql注入--(额外辉)
- 数据表问题
客户端开发开始
客户端
客户端首页面功能
为何不逐行开发?
-
客户端不需要高并发(仅需处理用户输入和服务器响应),无需像服务端那样处理多线程竞争、IO复用等复杂问题。
-
协议驱动开发:客户端只需按服务端定义的JSON协议收发数据(服务端要什么,客户端就发什么;服务端返回什么,客户端就解析什么),业务逻辑简单。
-
模块化直接讲解:因后续需重点讲解集群改造(如Redis发布订阅、跨服务器通信),客户端代码采用模块化展示,核心分为:
- 网络通信层(TCP连接、数据收发)
- 协议解析层(JSON序列化/反序列化)
- 业务逻辑层(登录/注册/退出等)
客户端CMake
src/CMakeLists.txt
# 加载子目录 src 既然进去, 就有 CMakeLists.txt
add_subdirectory(server)# 加载子目录
add_subdirectory(client)
src/client/CMakeLists.txt
# 所有源文件
aux_source_directory(. SRC_LIST)# 生成可执行
add_executable(Chatserver ${SRC_LIST})# 链接库 -- 仅有两个线程, 读取和写入
target_link_libraries(Chatserver pthread )
代码搭配知识补充–有很多漏的
客户端main-登录,注册,退出
- 无高并发需求:客户端只需处理单一用户的输入输出,无需考虑服务端级别的并发问题(如EPOLL、线程池)。
- 从业务角度: 客户端 需要做的 就是 登录, 注册, 退出
因此, 直接使用 一个简单的 网络连接即可------基于linux的tcp
c++11版本
提高效率:
用户登录成功后,服务器会将该用户的相关信息一次性返回,包括:
- 当前登录用户的信息(如用户名、用户ID);
- 用户的好友列表;
- 用户的群组列表;
- 显示登录用户信息;
- 离线消息记录。
客户端在接收到这些信息后,会将其本地保存,方便用户随时查看,无需再次向服务器请求,从而提高整体交互效率。
从后往前写业务, 退出最简单, 注册次之, 登录及登录后最麻烦
#include <iostream>
#include <thread>
#include <string>
#include <chrono>
#include <ctime>
using namespace std;#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "json.hpp"
using json = nlohmann::json;#include "user.hpp"
#include "public.hpp"
#include "group.hpp"// 记录当前系统登录的用户信息
User g_currentUser;
// 记录当前登录用户的好友列表
vector<User> g_currentUserFriendsList;
// 记录当前登录用户的群组列表
vector<Group> g_currentUserGroupsList;
// 显示当前登录用户的基本信息
void showCurrentUserInfo();// 接收线程----一共两个线程, 接收和发送, 是并行的 --- main主线程用于发送
void readTaskHandler(int clientfd);
// 获取系统时间(聊天信息添加时间信息)
string getCurrentTime();
// 主聊天页面程序
void mainMenu();// 聊天客户端程序实现, main线程用作发送线程, 子线程用作接受线程int main(int argc, char **argv)
{if (argc < 3){cerr << "command invalid example ./bin/chatserver 127.0.0.1 6000" << endl;exit(-1);}// 解析通过命令行参数传递的ip和portchar *ip = argv[1];uint16_t port = atoi(argv[2]);// 创建socketint clientfd = socket(AF_INET, SOCK_STREAM, 0);if (clientfd < 0){cerr << "socket error" << endl;exit(-1);}// 设置服务器地址结构// sockaddr_in serverAddr;sockaddr_in server;memset(&server, 0, sizeof(sockaddr_in)); // 清空结构体, 确保没有脏数据server.sin_family = AF_INET;server.sin_port = htons(port);server.sin_addr.s_addr = inet_addr(ip);// 连接服务器if (connect(clientfd, (struct sockaddr *)&server, sizeof(server)) < 0){cerr << "connect error" << endl;close(clientfd);exit(-1);}// main线程用于发送数据for (;;){// 显示登录首页面 登录, 注册, 退出cout << "========================================" << endl;cout << "1. login" << endl;cout << "2. register" << endl;cout << "3. exit" << endl;cout << "========================================" << endl;cout << "please input your choice: ";int choice;cin >> choice;cin.get(); // 清空输入缓冲区switch (choice){// # 3case 1: // 登录 根据业务, 需要id与密码{int id = 0;char password[50] = {0};cout << "please input your id: ";cin >> id;cin.get(); // 清空输入缓冲区cout << "please input your password: ";cin.getline(password, 50); // 读取一行, 包括空格 cin和scanf不能读空格// 组装json数据json js;js["msgid"] = LOGIN_MSG; // 登录消息js["id"] = id;js["password"] = password;// 发送登录请求string request = js.dump(); // json转字符串 序列化int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0); // 发送数据if (len < 0){cerr << "send login msg error: " << request << endl;cerr << "connect error" << endl;}else{char buffer[1024] = {0}; // 接收服务器返回的数据len = recv(clientfd, buffer, 1024, 0); // 接收数据if (len < 0){cerr << "recv error" << endl;}else{// 解析json数据json response = json::parse(buffer); // 反序列化 字符串转jsonstring tmp = response.dump(); // json转字符串 序列化if (response["errno"] == 0){ // 根据业务代码处理 1.登录成功返回 2.好友列表 3.群组列表 4.离线消息cout << "login success" << endl;// 客户端记录登录用户信息g_currentUser.setId(response["id"]);g_currentUser.setName(response["name"]);// if(response["friends"].is_null())// 处理好友列表// if (response["friends"].contains("friends")) // 判断是否包含字段, 跟好点, 而不是看 是不是空if(response.contains("friends")){vector<string> friends = response["friends"]; // 类型是vector<string>, 不是vector<User>, 根据服务器业务,存的是js.dump() 字符串g_currentUserFriendsList.clear();for (auto &friendUser : friends){json js = json::parse(friendUser); // 反序列化User user;user.setId(js["id"]);user.setName(js["name"]);user.setState(js["state"]);g_currentUserFriendsList.push_back(user);}for (auto &friendUser : g_currentUserFriendsList){cout << "friendid: " << friendUser.getId() << " name: " << friendUser.getName() << " state: " << friendUser.getState() << endl;}}else{cout << "friends list is empty" << endl;}// 处理群组列表if (response.contains("groups")) // 判断是否包含字段, 跟好点, 而不是看 是不是空{vector<string> groups = response["groups"]; // 类型是vector<string>, 不是vector<User>, 根据服务器业务,存的是js.dump() 字符串g_currentUserGroupsList.clear();for (auto &groupl : groups){json js = json::parse(groupl); // 反序列化Group group;group.setId(js["id"]);group.setName(js["groupname"]);group.setDesc(js["groupdesc"]);// 处理群组成员列表vector<string> users = js["users"];for (auto &userl : users){json js = json::parse(userl); // 反序列化GroupUser user;user.setId(js["id"]);user.setName(js["name"]);user.setState(js["state"]);user.setRole(js["role"]);group.getUsers().push_back(user);}g_currentUserGroupsList.push_back(group);}for(auto &group : g_currentUserGroupsList){cout << "groupid: " << group.getId() << " name: " << group.getName() << " desc: " << group.getDesc() << endl;for (auto &groupUser : group.getUsers()){cout << "group user id: " << groupUser.getId() << " name: " << groupUser.getName() << " state: " << groupUser.getState() << " role: " << groupUser.getRole() << endl;}}}else{cout << "groups list is empty" << endl;}// 显示当前登录用户的基本信息---包含好友列表和群组列表showCurrentUserInfo();// 处理离线消息if (response["offlinemsg"].contains("offlinemsg")) // 判断是否包含字段, 跟好点, 而不是看 是不是空{vector<string> offlinemsg = response["offlinemsg"]; // 类型是vector<string>, 不是vector<User>, 根据服务器业务,存的是js.dump() 字符串for (auto &msg : offlinemsg){json js = json::parse(msg); // 反序列化// 时间+fromid+fromname+msg-----详看笔记 一对一聊天发送的格式cout << js["time"] << "[" << js["id"] << "] " << js["name"] << "said : " << js["msg"] << endl;}}else{cout << "offlinemsg list is empty" << endl;}// 登录成功, 启动接收线程std::thread readTask(readTaskHandler, clientfd); // thread 支持跨平台readTask.detach(); // 分离线程, 让其独立运行, 不阻塞主线程// 主线程继续执行, 进入聊天菜单页面mainMenu(clientfd);}// else if(response["errno"] == 1)// {// // 用户不存在// cout << "login failed, error: " << response["errmsg"] << endl;// }// else if(response["errno"] == 2)// {// // 重复登录// cout << "login failed, error: " << response["errmsg"] << endl;// }// else if(response["errno"] == 3)// {// // 密码错误// cout << "login failed, error: " << response["errmsg"] << endl;// }else // 不分那么细, 服务器已经确定错误信息了{// 登录失败cout << "login failed, error: " << response["errmsg"] << endl;break;}}}}break;// # 2case 2: // 注册{char name[50]; // 比string更好, 因为string会有内存分配, 还可以限制长度char password[50];cout << "please input your name: ";cin.getline(name, 50); // 读取一行, 包括空格 cin和scanf不能读空格cout << "please input your password: ";cin.getline(password, 50);// 组装json数据json js;js["msgid"] = REG_MSG; // 注册消息js["name"] = name;js["password"] = password;// 发送注册请求string request = js.dump(); // json转字符串 序列化// int len = send(clientfd, request.c_str(), request.size(), 0); // 发送数据// 第二个参数必须这么写, 因为规定是const void*类型, 不能直接传入string类型int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0); // 发送数据 这样的+1, 是加了 \0, strlen不算这个if (len < 0){cerr << "send error" << request << endl;}else{char buffer[1024] = {0}; // 接收服务器返回的数据len = recv(clientfd, buffer, 1024, 0); // 接收数据if (len < 0){cerr << "recv error" << endl;}else{// 解析json数据json response = json::parse(buffer); // 反序列化 字符串转jsonif (response["errno"] == 0){ // 根据业务代码处理cout << "register success, userid: " << response["id"] << " do not forget it!" << endl;}else{// 注册失败cout << "register failed, error: " << name << " is already exit!" << endl;}}}}break;// # 1case 3: // 这个最简单 quit业务{cout << "exit system" << endl;close(clientfd);exit(0);}default:{cout << "input error" << endl;break;}}}return 0;
}void readTaskHandler(int clientfd)
{}void mainMenu()
{}
一定要根据之前的 服务器业务, 写这个首页面功能
群组有问题
在服务器登录业务不分, 没有返回 群组字段信息
测试
每种情况都测试一下, 各种错误也试试, 不然会漏掉错误
客户端好友添加与聊天功能
表驱动设计:
-
表驱动(commandMap + commandHandlerMap):
- 解耦:将命令的定义、帮助文本、处理逻辑分离,新增命令只需扩展映射表。
- 用户友好:
help
命令动态展示所有功能,降低学习成本。
-
commandMap
:存储命令名称和帮助文本(如"chat" : "一对一聊天,格式:chat:friendID:message"
)。 -
commandHandlerMap
:关联命令名称和处理函数(如"chat"
绑定到chatHandler
函数)。 -
优势:符合开闭原则,新增命令只需扩展映射表,无需修改主逻辑。
commandMap
commandMap
是一个 命令-格式说明 的映射表,用于 向用户清晰展示所有可用命令及其正确输入格式,帮助用户快速上手并减少输入错误。
// 系统支持的客户端命令列表
unordered_map<string, string> commandList = {{"help", "显示所有支持的命令, 格式help"},{"chat", "一对一聊天, 格式chat:friend:msg"},{"addfriend", "添加好友, 格式addfriend:friendid"},{"creategroup", "创建群组, 格式creategroup:groupname:groupdesc"},{"addgroup", "添加群组, 格式addgroup:groupid"},{"groupchat", "群组聊天, 格式groupchat:groupid:msg"},{"loginout/quit", "退出系统/注销, 格式quit"}
};
commandHandlerMap
commandHandlerMap
是一个 命令-处理函数 的映射表,用于 将用户输入的命令动态绑定到对应的业务逻辑。它的核心价值在于:
- 解耦输入解析与业务逻辑:分离“用户输入的是什么”和“该输入如何被执行”。
- 统一管理所有命令的执行入口:避免冗长的
if-else
或switch-case
分支判断。
// 注册系统支持的客户端命令处理
unordered_map<string, function<void(int, string)>> commandHandlerMap = {{"help", help},{"chat",chat}, // 一对一聊天{"addfriend", addfriend}, // 添加朋友{"creategroup", creategroup}, // 创建群组{"addgroup", addgroup}, // 添加群组{"groupchat", groupchat}, // 群组聊天{"quit", quit}};
为什么都是int, string
int
(clientFd
):- 代表 Socket 文件描述符,所有网络操作(如发送聊天消息、添加好友)必须通过这个
int
标识符与服务器通信。它是 客户端与服务器交互的唯一通道,因此必须传递给每个命令处理函数。
- 代表 Socket 文件描述符,所有网络操作(如发送聊天消息、添加好友)必须通过这个
string
(用户输入):- 用户输入的命令参数–commandMap格式(如好友ID、消息内容)都是 字符串形式,方便解析和序列化成 JSON 发送。
- 统一接口:
- 所有命令处理函数保持 相同参数类型(
int, string
),便于映射到commandHandlerMap
,实现 标准化调用。
- 所有命令处理函数保持 相同参数类型(
添加好友和聊天功能
// 接收线程---实时就收 服务器返回的数据--包括别人发来的聊天消息
void readTaskHandler(int clientfd)
{for (;;){char buffer[1024] = {0}; // 接收服务器返回的数据int len = recv(clientfd, buffer, 1024, 0); // 接收数据if (len < 0) // ==-1{cerr << "recv error" << endl;close(clientfd);exit(-1);}else if (len == 0) // 服务器关闭连接{cout << "server close" << endl;close(clientfd);exit(-1);}// 解析json数据json response = json::parse(buffer); // 反序列化 字符串转jsonif (response["msgid"] == ONE_CHAT_MSG) // 一对一聊天消息{cout << response["time"].get<string>() << "[" << response["id"] << "] " << response["name"].get<string>() << " said: " << response["msg"].get<string>() << endl;}// else if (response["msgid"] == GROUP_CHAT_MSG) // 群组聊天消息// {// cout<<response["time"].get<string>()<<"["<<response["id"]<<"] "<<response["name"].get<string>()<<" said: "<<response["msg"].get<string>()<<endl;// }}
}// handler合集
// 显示帮助信息
void help(int clientfd, string msg);// 一对一聊天
void chat(int clientfd, string msg);// 添加好友
void addfriend(int clientfd, string msg);// 创建群组
void creategroup(int clientfd, string msg);// 添加群组
void addgroup(int clientfd, string msg);// 群组聊天
void groupchat(int clientfd, string msg);
// 退出系统
void quit(int clientfd, string msg);// 系统支持的客户端命令列表
unordered_map<string, string> commandList = {{"help", "显示所有支持的命令, 格式help"},{"chat", "一对一聊天, 格式chat:friend:msg"},{"addfriend", "添加好友, 格式addfriend:friendid"},{"creategroup", "创建群组, 格式creategroup:groupname:groupdesc"},{"addgroup", "添加群组, 格式addgroup:groupid"},{"groupchat", "群组聊天, 格式groupchat:groupid:msg"},{"loginout/quit", "退出系统/注销, 格式quit"}
};// 注册系统支持的客户端命令处理
unordered_map<string, function<void(int, string)>> commandHandlerMap = {{"help", help},{"chat",chat}, // 一对一聊天{"addfriend", addfriend}, // 添加朋友{"creategroup", creategroup}, // 创建群组{"addgroup", addgroup}, // 添加群组{"groupchat", groupchat}, // 群组聊天{"loginout/quit", quit}};// 主页面聊天程序
void mainMenu(int clientfd)
{help();for(;;){// 截取输入的 格式char buffer[1024] = {0}; // 用户输入的命令cin.getline(buffer, 1024); // 读取一行, 包括空格 cin和scanf不能读空格string commandbuf(buffer); // 转成string类型string command; // 存储实际命令int idx = commandbuf.find(":"); // 查找第一个:的位置if(idx == string::npos) // 没有找到 ==-1->不建议用{command = commandbuf; // 直接赋值}else{command = commandbuf.substr(0, idx); // 截取业务命令---"chat"}auto it = commandHandlerMap.find(command); // 查找命令if(it != commandHandlerMap.end()) // 找到命令{// 调用相应命令的事件处理函数, mainMenu 对修改封闭, 添加功能不需要修改函数it->second(clientfd, commandbuf.substr(idx+1, commandbuf.size()-idx-1)); // 调用对应的处理函数 出入剩下的字符串// 调用对应的处理函数 出入剩下的字符串}else{cout << "command invalid" << endl;}}}// help函数
void help(int clientfd=0, string msg="")
{cout << "====================command list====================" << endl;for (auto &command : commandList){cout << command.first << " : " << command.second << endl;}cout << "=====================================================" << endl;
}// addfriend函数
void addfriend(int clientfd, string msg)
{int friendid = atoi(msg.c_str()); // 转成整型json js;js["msgid"] = ADD_FRIEND_MSG; // 添加好友消息js["id"] = g_currentUser.getId(); // 当前登录用户idjs["friendid"] = friendid;// 发送添加好友请求string request = js.dump(); // json转字符串 序列化int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0); // 发送数据if (len < 0){cerr << "send friendid is error ===> " << request << endl;}
}// chat函数
void chat(int clientfd, string msg)
{int idx = msg.find(":"); // 查找第一个:的位置if(idx == string::npos) // 没有找到 ==-1->不建议用{cout << "chat command: friend id is invalid!" << endl;return;}int friendid = atoi(msg.substr(0, idx).c_str()); // 截取好友idstring message = msg.substr(idx + 1, msg.size() - idx); // 截取聊天信息json js;js["msgid"] = ONE_CHAT_MSG; // 一对一聊天消息js["id"] = g_currentUser.getId(); // 当前登录用户idjs["name"] = g_currentUser.getName(); // 当前登录用户姓名js["to"] = friendid; // 好友id -- 字段要对应服务器那边的js["msg"] = message; // 聊天信息js["time"] = getCurrentTime(); // 时间// 发送聊天请求string request = js.dump(); // json转字符串 序列化int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0); // 发送数据if (len < 0){cerr << "send chat msg error: "<< request << endl;}
}
测试
自行测试
没有限制必须是 好友才能聊
错误解决
-
commandHandlerMap 里面的string 必须 是和 commandMap 里面 给的 命令格式一致
我的错误:
// commandHandlerMap "loginout/quit"// commandMap 给的 quit
-
json 包含函数
if(response.contains("friends"))// 错误写法如下 if(response["friends"].contains("friends"))
friend表问题
这个表好像不是 联合主键, 出现了 重复, 修改成联合主键即可
mysql注入–(额外辉)
数据表问题
每个表的主键, 联合主键, 无主键, 设置一定要正确, 什么允许重复, 什么不允许重复, 要搞明白, 不然业务 会出错或者不完整
相关文章:
施磊老师基于muduo网络库的集群聊天服务器(六)
文章目录 客户端开发开始客户端首页面功能为何不逐行开发?客户端CMake代码搭配知识补充--有很多漏的客户端main-登录,注册,退出群组有问题测试 客户端好友添加与聊天功能表驱动设计:commandMapcommandHandlerMap为什么都是int, string添加好友和聊天功能…...
有关字体,语言,字符编码相关的基础知识,询问chatgpt所得
学习这个知识点的背景是,我需要做一个 在canvas 上书写矢量文本的功能, 使用opentype来加载字体文件,并将内容转换为 svg,导入画布。 但是有些字体文件 是不包含 一些其他语言的字符的。就可能出现 “无效字符”。 花了点时间研究…...
Obsidian和Ollama大语言模型的交互过程
之前的文章中介绍了Obsidian配合Ollama的使用案例,那么它们是如何配合起来的呢?其实这个问题并不准确,问题的准确描述应该是Obsidian的Copilot插件是如何与Ollama大语言模型交互的。因为Obsidian在这里只是一个载体,核心功能还是C…...
架构-数据库系统
数据库系统 一、数据库系统概述 (一)课程核心模块 覆盖数据库设计、关系代数、规范化理论、数据控制四大核心模块,旨在构建从理论到实践的完整知识体系至。 (二)典型应用场景 数据管理:学生信息管理&a…...
【C到Java的深度跃迁:从指针到对象,从过程到生态】第三模块·面向对象深度进化 —— 第十二章 接口:比C函数指针更强大的契约
一、从函数指针到接口契约 1.1 C函数指针的本质限制 C语言通过函数指针实现回调机制,但存在根本性缺陷: 回调函数示例: typedef void (*Logger)(const char*); void process_data(int data, Logger logger) { // ... logger("Pro…...
【HFP】蓝牙语音通话控制深度解析:来电拒接与通话终止协议
目录 一、来电拒接的核心流程与信令交互 1.1 拒接场景的分类与触发条件 1.2 HF 端拒接流程 1.3 AG 端拒接流程 二、通话终止流程:主动断开与异常中断 2.1 终止场景的界定 2.2 HF 端终止流程 2.3 AG 端终止流程 三、信令协议的核心要素:AT 命令与…...
linux 中断子系统 层级中断编程
虚拟中断控制器代码: #include<linux/kernel.h> #include<linux/module.h> #include<linux/clk.h> #include<linux/err.h> #include<linux/init.h> #include<linux/interrupt.h> #include<linux/io.h> #include<linu…...
continue插件实现IDEA接入本地离线部署的deepseek等大模型
文章目录 前言一、IDEA安装continue二、continue部署本地大模型三、continue聊天窗口使用deepseek R1四、continue批量接入硅基流动的模型API 前言 亲爱的家人们,创作很不容易,若对您有帮助的话,请点赞收藏加关注哦,您的关注是我…...
Redis-缓存应用 本地缓存与分布式缓存的深度解析
Redis缓存场景与策略:本地缓存与分布式缓存的深度解析 在当今高并发、低延迟的互联网架构中,缓存技术是优化系统性能的核心手段之一。Redis作为分布式缓存的标杆,与本地缓存共同构成了缓存体系的两大支柱。然而,两者的适用场景与…...
关于按键映射软件的探索(其一)
那么先说结论——重构了一次,我还是失败了,失败于拓展调整个性化的设计,不过我还是实现了按键监测然后显示的功能。只不过是说我对于WPF软件等的封装和软窗口的功能还是不怎么熟悉。 引言 在许多游戏玩家中,高难度操作(…...
测试基础笔记第十一天
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、查询连接1.内连接2.左连接3.右连接4.左右连接的必要性5.自关联6.普通查询7.子查询8.子查询充当数据源 二、数据库高级扩展内容1.外键2.索引3.验证索引效果案例实…...
优选算法第十讲:字符串
优选算法第十讲:字符串 1.最长公共前缀2.最长回文子串3.二进制求和4.字符串相乘 1.最长公共前缀 2.最长回文子串 3.二进制求和 4.字符串相乘...
RK3588芯片NPU的使用:官方rknn_yolov5_android_apk_demo运行与解读
一、本文的目标 本文将完成两项任务: 官方的调用摄像头动态目标识别例子运行在rk3588的开发板上。解读源码以增加对rknn开发的认识。二、开发环境说明 主机系统:Windows 11目标设备:搭载RK3588芯片的安卓开发板核心工具:Android Studio Koala | 2024.1.1 Patch 2,NDK 27.…...
面试篇:Spring Boot
基础概念 Spring Boot的核心优势是什么? Spring Boot 的核心优势如下: 自动配置:根据项目中的依赖自动进行配置,减少了大量的手动配置工作。 内嵌服务器:内置 Tomcat、Jetty 等容器,应用可以直接运行为一…...
开源漏洞扫描器:OpenVAS
一、OpenVAS介绍 OpenVAS (Open Vulnerability Assessment System) 是一款功能强大的开源漏洞扫描器。它由 Greenbone Networks 开发和维护,是 Greenbone 安全管理器 (GSM) 产品的基础,同时也有免费的社区版本(Greenbone Community Edition&…...
PCB封装主要组成元素
PCB(Printed Circuit Board,印刷电路板)封装是指将电子元件固定在 PCB 上,并实现电气连接的方式。主要包括以下几类。 1. 焊盘(Pad) 作用:焊盘是 PCB 封装中最重要的元素之一,它是…...
STC8H DMA 串口1全双工中断方式收发通讯C语言
/************* 功能说明 ************** 本例程基于STC8H8K64U为主控芯片的实验箱9进行编写测试,STC8H系列带DMA模块的芯片可通用参考. 串口1全双工中断方式收发通讯程序。 通过PC向MCU发送数据, MCU将收到的数据自动存入DMA空间. 当DMA空间存满设置大小的…...
考研英一学习笔记
2024 年全国硕士研究生招生考试 英语(一)试题 (科目代码:201) Section Ⅰ Use of English Directions: Read the following text. Choose the best word(s) for each numbered blank and mark A, B, C or D on the ANS…...
深度理解spring——BeanFactory的实现
BeanFactory Spring之BeanFactory什么是BeanFactoryApplicationContext相对BeanFactory实现的功能性扩展1. MessageSource2. ResourcePatternResolver3. ApplicationEventPublisher4. EnvironmentCapable通用ApplicationContext实践实现BeanFactoryBeanFactory后处理器排序让谁…...
半导体---检测和量测
目录 1.简介2.AOI(检测) 1.简介 半导体晶圆制造前道量测和检测设备。 公司产品涵盖光学薄膜量测、光学关键尺寸量测、光学衍射套刻量测、光学集成量测、X射线薄膜量测、X射线材料性能量测、X射线成分及表面污染量测等系列产品及解决方案。 半导体领域的量测和AOI如同半导体制造…...
CentOS 7 磁盘分区详细教程
CentOS 7 磁盘分区详细教程 在服务器管理和运维过程中,磁盘分区是一项基础且重要的操作。合理的磁盘分区可以提高数据存储的安全性、高效性,方便系统管理与维护。本文将详细介绍在 CentOS 7 系统中进行磁盘分区的具体步骤和方法。 一、准备工作 1.1 确…...
EasyRTC音视频实时通话在线教育解决方案:打造沉浸式互动教学新体验
一、方案概述 EasyRTC是一款基于WebRTC技术的实时音视频通信平台,为在线教育行业提供了高效、稳定、低延迟的互动教学解决方案。本方案将EasyRTC技术深度整合到在线教育场景中,实现师生间的实时音视频互动等核心功能,打造沉浸式的远程学习体…...
栈(Stack)和队列(Queue)
栈 栈(stack)是一种特殊的线性表,只允许在固定的一端进行插入和删除操作。 我们可以将栈近似看作一个桶,要取出桶底的元素,就要将桶顶的元素先取出,再将底部元素取出,也可以叫做后进先出。 这…...
1、AI及LLM基础:Python语法入门教程
Python语法入门教程 这是一份全面的Python语法入门教程,涵盖了注释、变量类型与操作符、逻辑运算、list和字符串、变量与集合、控制流和迭代、模块、类、继承、进阶等内容,通过详细的代码示例和解释,帮助大家快速熟悉Python语法。 文章目录 Python语法入门教程一、注释二…...
跨境电商关键词分类打标
你是一名顶级的亚马逊关键词分析专家,你将用你的亚马逊运营专业的经验帮助我做精准的关键词打标。 首先你会学习以下的知识内容,以便于你后续的关键词分析。 人群词是什么? 是指直接描述或定位特定用户群体的关键词或标签,用于精准识别目标受众的身份、需求或行为特征。 …...
C# 结构(Struct)
原文:C# 结构(Struct)_w3cschool 在 C# 中,结构是值类型数据结构。它使得一个单一变量可以存储各种数据类型的相关数据。struct 关键字用于创建结构。 结构是用来代表一个记录。假设您想跟踪图书馆中书的动态。您可能想跟踪每本…...
Java Collections工具类指南
一、Collections工具类概述 java.util.Collections是Java集合框架中提供的工具类,包含大量静态方法用于操作和返回集合。这些方法主要分为以下几类: 排序操作查找和替换同步控制不可变集合特殊集合视图其他实用方法 二、排序操作 1. 自然排序 List&…...
BBRv2,v3 吞吐为什么不如 BBRv1
为什么 BBRv2/3 测试下来吞吐远不如 2016 年底的 BBRv1,这个事曾经提到过很多次,今天分析一下原理。注意三个事实: BBR 是一种拥塞控制算法;BBR 已经迭代到了 v3 版本;BBRv3 的 “性能” 远不如 BBRv1. 第二点有点不…...
Java集成【邮箱验证找回密码】功能
目录 1.添加依赖 2.选择一个自己的邮箱,作为发件人角色。 3.编写邮箱配置【配置发件人邮箱】 4.编写邮箱配置类 5.编写controller业务代码 6.演示效果 7.总结流程 8.注意 结语 1.添加依赖 <!--导入邮箱依赖--> <dependency><groupId>or…...
Java微服务架构设计与实践 - 面试实战
Java微服务架构设计与实践 - 面试实战 在互联网大厂的Java求职者面试中,微服务架构设计是一个常见的考察点。本文通过严肃的面试官和资深Java架构师马架构之间的对话,详细展示了如何回答SpringCloud相关的核心技术问题。 第一轮提问 面试官࿱…...
Java后端开发面试题(含答案)
在广州一个小公司(BOSS标注是20-99人,薪资2-3k左右),直接面试没有笔试,按流程自我介绍,然后直接拿着简历问项目场景,问题是结合场景题和八股文。废话不多说,直接分享面试题目个大家做参考。 1、…...
Sharding-JDBC 系列专题 - 第九篇:高可用性与集群管理
Sharding-JDBC 系列专题 - 第九篇:高可用性与集群管理 本系列专题旨在帮助开发者全面掌握 Sharding-JDBC,一个轻量级的分布式数据库中间件。本篇作为系列的第九篇文章,将重点探讨 高可用性(High Availability, HA) 和 集群管理,包括数据库高可用方案、Sharding-JDBC 的故…...
Node.js 学习入门指南
Node.js 学习入门指南 Node.js 是一种流行的开源、跨平台的 JavaScript 运行时环境,它使开发者能够在服务器端运行JavaScript代码。本篇文章旨在帮助初学者快速入门并掌握Node.js的基础知识和常用技巧。 一、什么是Node.js? 定义 Node.js 是一个基于…...
数智视融合驱动未来,Al+数字孪生重塑价值|2025袋鼠云春季数智发布会回顾
4月16日,袋鼠云成功举办了“做DataAI的长期主义者——2025年袋鼠云春季数智发布会”,从智能分析决策平台到AI大模型应用,从数字孪生中枢到AI增强型数字世界,勾勒出企业数智化转型的进化图谱,真正实现AI赋能企业业务&am…...
nfs服务原理、搭建手册、安全配置建议及异常定位手段
一、NFS服务原理 NFS(Network File System)是一种基于TCP/IP协议的网络文件共享系统,允许客户端像访问本地文件一样访问远程服务器上的共享目录。其核心原理依赖于RPC(Remote Procedure Call)机制,具体流程…...
第十三届蓝桥杯 2022 C/C++组 修剪灌木
目录 题目: 题目描述: 题目链接: 思路: 核心思路: 思路详解: 代码: 代码详解: 题目: 题目描述: 题目链接: P8781 [蓝桥杯 2022 省 B] 修…...
MySQL:数据库设计
目录 一、范式 二、第一范式 二、第二范式 三、第三范式 四、设计 (1)一对一关系 (2)一对多关系 (3)多对多关系 一、范式 数据库的范式是一种规则(规范),如果我们…...
OpenManus与OWL部署及体验报告
OpenManus与OWL对任务的执行结果均不及Manus;二者比较,Owl达成率更高;二者使用过程中均会消耗大量tokens,单个问题成本高。 一、部署 OpenManus:https://github.com/mannaandpoem/OpenManus.git OWL:https…...
【Pandas】pandas DataFrame pow
Pandas2.2 DataFrame Binary operator functions 方法描述DataFrame.add(other)用于执行 DataFrame 与另一个对象(如 DataFrame、Series 或标量)的逐元素加法操作DataFrame.add(other[, axis, level, fill_value])用于执行 DataFrame 与另一个对象&…...
Red:1靶场环境部署及其渗透测试笔记(Vulnhub )
环境介绍: 靶机下载: https://download.vulnhub.com/red/Red.ova 本次实验的环境需要用到VirtualBox(桥接网卡),VMware(桥接网卡)两台虚拟机(网段都在192.168.152.0/24࿰…...
【多源01BFS】Codeforce:Three States
题干 翻译 给定一个 nm 的网格地图,包含以下元素: .:表示荒地(可以修建道路)。 #:表示岩石(不可通行)。 数字 1、2、3:分别表示三个国家的位置。 目标:将…...
在深度学习中FLOPs和GFLOPs的含义及区别
在深度学习中,FLOPs和GFLOPs是衡量计算性能的关键指标,但两者的定义和应用场景不同: 1. 定义与区别 • FLOPs(Floating-point Operations) 表示模型或算法执行时所需的浮点运算总次数,用于衡量模型的计算复…...
SpringBoot入门实战(项目搭建、配置、功能接口实现等一篇通关)
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 SpringBoot入门实战(项目搭建、配…...
昆仑万维开源SkyReels-V2,解锁无限时长电影级创作,总分83.9%登顶V-Bench榜单
昆仑万维开源了全球首个基于扩散强迫框架(Diffusion-forcing)的无限时长视频生成模型——SkyReels-V2。这一模型以总分83.9%的优异成绩登顶权威评测V-Bench1.0榜单,并在质量维度获得84.7%的评分,刷新了开源视频生成技术的天花板。…...
精选面试题
1、js中set和map的作用和区别? 在 JavaScript 中,Set 和 Map 是两种非常重要的集合类型 1、Set 是一种集合数据结构,用于存储唯一值。它类似于数组,但成员的值都是唯一的,没有重复的值。Set 中的值只能是唯一的,任何…...
【技术派后端篇】技术派中 Session/Cookie 与 JWT 身份验证技术的应用及实现解析
在现代Web应用开发中,身份验证是保障系统安全的重要环节。技术派在身份验证领域采用了多种技术方案,其中Session/Cookie和JWT(JSON Web Token)是两种常用的实现方式。本文将详细介绍这两种身份验证技术在技术派中的应用及具体实现…...
【CAPL实战:以太网】对IPv4报文的Payload部分进行分片并创建分片包
As we know,TCP/IP协议栈网络层接收到来自上层的数据时,并不是简单地添加报头发送出去,而是会受到来自网卡配置参数MTU的约束。也就是从网卡上发送出去的以太网帧报文的数据部分的长度不能大于MTU值。即网络层头部 + 有效载荷不能大于MTU。 所以网络层在发送上层的数据时,…...
Spring Security认证流程
认证是Spring Security的核心功能之一,Spring Security所提供的认证可以更好地保护系统的隐私数据与资源,只有当用户的身份合法后方可访问该系统的资源。Spring Security提供了默认的认证相关配置,开发者也可以根据自己实际的环境进行自定义身…...
Spring Security基础入门
本入门案例主要演示Spring Security在Spring Boot中的安全管理效果。为了更好地使用Spring Boot整合实现Spring Security安全管理功能,体现案例中Authentication(认证)和Authorization(授权)功能的实现,本案…...
Axure疑难杂症:母版菜单设置打开链接后菜单选中效果
亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:菜单打开链接后子菜单选中效果 主要内容:母版设计、选中效果 应用场景:页面赋值 案例展示: 案例视频:...