C++项目——集群聊天服务器项目(十三)客户端登录、注册、退出业务

截止到上节,我们已将服务器端主要代码介绍完毕,由于不可能一直手动输入信息,所以我们还需编写客户端代码,进行双向通信。

客户端不要求高并发,因此我们这里不使用muduo网络库的TcpClient类编写,仅采用C++自带的Thread类实现读写多线程啦。

一、CMakeLists.txt

在src目录下的CMakeLists.txt添加客户端的子目录

add_subdirectory(server)
add_subdirectory(client)

在src/client下创建CMakeLists.txt,指定可执行文件、所用源文件、依赖的库文件

# 定义了一个SRC_LIST变量,包含了该目录下所有的源文件
aux_source_directory(. SRC_LIST)# 指定生成可执行文件
add_executable(ChatClient ${SRC_LIST})
# 指定可执行文件链接时需要依赖的库文件
target_link_libraries(ChatClient pthread)

二、客户端主程序

首先定义全局变量、对象等,分别用于标识当前登录用户信息、好友列表、群组列表信息

// 全局变量、对象
User g_currentUser;                   // 记录当前系统登录的用户信息
vector<User> g_currentUserFriendList; // 记录当前登录用户的好友列表信息
vector<Group> g_currentUserGroupList; // 记录当前登录用户的群组列表信息// 信号量
sem_t rwsem;                         // 用于读写线程之间的通信/*atomic原子变量是一种多线程编程中常用的同步机制,它能够确保对共享变量的操作在执行时不会被其他线程的操作干扰
原子变量它具有类似于普通变量的操作,但是这些操作都是原子级别的,即要么全部完成,要么全部未完成。*/atomic_bool g_isLoginSuccess{false}; // 记录登录状态   

信号量用于主线程与子线程,即读写线程间通信

bool类型的原子变量标识用户登录撞他,是多线程下常用同步机制

2.1 显示登录成功用户的基本信息

将登录成功用户的基本信息、好友信息、群组信息打印显示,以便用户可以与好友、群组聊天通信

// 显示当前登录成功用户的基本信息
void showCurrentUserData()
{cout << "======================login user======================" << endl;cout << "current login user -> id:" << g_currentUser.getId() << " name:" << g_currentUser.getName() << endl;cout << "----------------------friend list---------------------" << endl;if (!g_currentUserFriendList.empty()){for (User &user : g_currentUserFriendList){cout << user.getId() << " " << user.getName() << " " << user.getState() << endl;}}cout << endl;cout << "----------------------group list----------------------" << endl;if (!g_currentUserGroupList.empty()){for (Group &group : g_currentUserGroupList){cout << group.getId() << " " << group.getName() << " " << group.getDesc() << endl;for (GroupUser &user : group.getUsers()){cout << user.getId() << " " << user.getName() << " " << user.getState()<< " " << user.getRole() << endl;}cout << endl;}}cout << "======================================================" << endl;
}

2.2 获取系统时间

用户发送的消息会带有时间信息,因此我们记录发送信息的时间

// 获取系统时间(聊天信息需要添加时间信息)
string getCurrentTime()
{auto tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());struct tm *ptm = localtime(&tt);char date[60] = {0};sprintf(date, "%d-%02d-%02d %02d:%02d:%02d",(int)ptm->tm_year + 1900, (int)ptm->tm_mon + 1, (int)ptm->tm_mday,(int)ptm->tm_hour, (int)ptm->tm_min, (int)ptm->tm_sec);return std::string(date);
}

2.3 主线程main

主线程负责聊天客户端的实现,用作多线程中的发送线程,其中创建子线程用于接收服务端带来的响应

主线程流程如下:

(1)解析命令行参数获取ip和port

(2)创建客户端的socket,填写客户端需要连接的服务器的ip和port

(3)将客户端与服务器进行连接

(4)连接成功后,初始化读写线程信号量,启动接收子线程,分离子线程

(5)定义死循环函数,显示首页主菜单,即登录、注册、退出业务,并读取输入客户端输入选项

<1>登录功能

读取客户端输入id和密码,使用json序列化发送给服务器端后等待线程同步信号量,子线程完成登录业务后会通知主线程

子线程接收到服务器端响应信息后,反序列化为json对象,获取msgid,如果为登录响应信息,调用处理登录响应的业务逻辑

登录业务逻辑函数:登录成功后,从服务器端返回的响应中获取当前登录的用户信息、好友列表信息、群组列表信息,并插入全局变量中用于登录页面显示。同时将用户个人聊天、群组聊天离线信息进行显示。登录业务逻辑执行完成后,记录当前登录状态为true。

登录业务逻辑函数如下:

// 处理登录的响应逻辑
void doLoginResponse(json &responsejs)
{if (0 != responsejs["errno"].get<int>()) // 登录失败{cerr << responsejs["errmsg"] << endl;g_isLoginSuccess = false;}else // 登录成功{// 记录当前用户的id和nameg_currentUser.setId(responsejs["id"].get<int>());g_currentUser.setName(responsejs["name"]);// 记录当前用户的好友列表信息if (responsejs.contains("friends")){// 初始化g_currentUserFriendList.clear();vector<string> vec = responsejs["friends"];for (string &str : vec){json js = json::parse(str); // 反序列化User user;user.setId(js["id"].get<int>());user.setName(js["name"]);user.setState(js["state"]);g_currentUserFriendList.push_back(user);}}// 记录当前用户的群组列表信息if (responsejs.contains("groups")){// 初始化g_currentUserGroupList.clear();vector<string> vec1 = responsejs["groups"];for (string &groupstr : vec1){json grpjs = json::parse(groupstr); // 反序列化Group group;group.setId(grpjs["id"].get<int>());group.setName(grpjs["groupname"]);group.setDesc(grpjs["groupdesc"]);vector<string> vec2 = grpjs["users"]; // 群组内成员信息for (string &userstr : vec2){GroupUser user;json js = json::parse(userstr);user.setId(js["id"].get<int>());user.setName(js["name"]);user.setState(js["state"]);user.setRole(js["role"]);group.getUsers().push_back(user);}g_currentUserGroupList.push_back(group);}}// 显示登录用户的基本信息showCurrentUserData();// 显示当前用户的离线消息  个人聊天信息或者群组消息if (responsejs.contains("offlinemsg")){vector<string> vec = responsejs["offlinemsg"];for (string &str : vec){json js = json::parse(str);                 // 反序列化if (ONE_CHAT_MSG == js["msgid"].get<int>()) // 点对点聊天{cout << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()<< " said: " << js["msg"].get<string>() << endl;}else // 群聊{cout << "群消息[" << js["groupid"] << "]:" << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()<< " said: " << js["msg"].get<string>() << endl;}}}g_isLoginSuccess = true;}
}

<2>注册功能

读取客户端输入姓名和密码,使用json序列化发送给服务器端后等待线程同步信号量,子线程完成注册业务后会通知主线程

子线程接收到服务器端响应信息后,反序列化为json对象,获取msgid,如果为注册响应信息,调用处理注册响应的业务逻辑

注册业务逻辑函数:依据服务器返回的errno字段判断是否注册成功,进行相应显示

// 处理注册的响应逻辑
void doRegResponse(json &responsejs)
{if (0 != responsejs["errno"].get<int>()) // 注册失败{cerr << "name is already exist, register error!" << endl;}else // 注册成功{cout << "name register success, userid is " << responsejs["id"]<< ", do not forget it!" << endl;}
}

<3>退出功能

关闭客户端连接套接字、销毁信号量、关闭程序即可

main函数如下:

// 聊天客户端程序实现,main线程用作发送线程,子线程用作接收线程
int main(int argc, char **argv) // argc 参数个数    argv 参数序列或指针
{if (argc < 3){cerr << "command invalid! example: ./ChatClient 127.0.0.1 6000" << endl; // cerr:程序错误信息exit(-1);}// 解析通过命令行参数传递的ip和portchar *ip = argv[1];uint16_t port = atoi(argv[2]);// 创建client端的socketint clientfd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET:IPv4  SOCK_STREAM:可靠的、双向的通信数据流 0:调用者不指定协议if (-1 == clientfd){cerr << "socket create error" << endl;exit(-1);}// 填写client需要连接的server信息ip+portsockaddr_in server;memset(&server, 0, sizeof(sockaddr_in)); // memset清0server.sin_family = AF_INET;            // IPv4server.sin_port = htons(port);          // 端口server.sin_addr.s_addr = inet_addr(ip); // ip地址// client和server进行连接if (-1 == connect(clientfd, (sockaddr *)&server, sizeof(sockaddr_in))){cerr << "connect server error" << endl;close(clientfd);exit(-1);}// 初始化读写线程通信用的信号量sem_init(&rwsem, 0, 0);// 连接服务器成功,启动接收子线程std::thread readTask(readTaskHandler, clientfd); // pthread_createreadTask.detach();                               // pthread_detach// main线程用于接收用户输入,负责发送数据for (;;){// 显示首页面菜单 登录、注册、退出cout << "========================" << endl;cout << "======  1. login  ======" << endl;cout << "======  2. register  ===" << endl;cout << "======  3. quit  =======" << endl;cout << "========================" << endl;cout << "Please input your choice:";int choice = 0;cin >> choice; // 读取功能选项cin.get();     // 读掉缓冲区残留的回车switch (choice){case 1: // 登录业务{int id = 0;char pwd[50] = {0};cout << "user id:";cin >> id;cin.get(); // 读掉缓冲区残留的回车cout << "user password:";cin.getline(pwd, 50);json js;js["msgid"] = LOGIN_MSG;js["id"] = id;js["password"] = pwd;string request = js.dump();g_isLoginSuccess = false;int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0); // 通过网络send给服务器端if (len == -1){cerr << "send login msg error:" << request << endl;}sem_wait(&rwsem); // 等待信号量,由子线程处理完登录的响应消息后,通知这里if (g_isLoginSuccess){// 进入聊天主菜单页面isMainMenuRunning = true;// mainMenu(clientfd);}}break;case 2: // 注册业务{char name[50] = {0};char pwd[50] = {0};cout << "user name:";cin.getline(name, 50);cout << "user password:";cin.getline(pwd, 50);json js;js["msgid"] = REG_MSG;js["name"] = name;js["password"] = pwd;string request = js.dump();int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0); // 通过网络send给服务器端if (len == -1){cerr << "send reg msg error:" << request << endl;}sem_wait(&rwsem); // 等待信号量,子线程处理完注册消息会通知}break;case 3: // 退出业务close(clientfd);sem_destroy(&rwsem);exit(0);default:cerr << "invalid input!" << endl;break;}}return 0;
}

2.4 接收子线程

// 子线程 - 接收线程
void readTaskHandler(int clientfd)
{for (;;){char buffer[1024] = {0};int len = recv(clientfd, buffer, 1024, 0); // 阻塞if (-1 == len || 0 == len){close(clientfd);exit(-1);}// 接收服务器转发的数据,反序列化生成json数据对象json js = json::parse(buffer);int msgtype = js["msgid"].get<int>(); // 获取处理业务msgidif (ONE_CHAT_MSG == msgtype)          // 点对点聊天{cout << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()<< " said: " << js["msg"].get<string>() << endl;continue;}if (GROUP_CHAT_MSG == msgtype) // 群聊{cout << "群消息[" << js["groupid"] << "]:" << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()<< " said: " << js["msg"].get<string>() << endl;continue;}if (LOGIN_MSG_ACK == msgtype) // 登录业务{doLoginResponse(js); // 处理登录响应的业务逻辑sem_post(&rwsem);    // 通知主线程,登录结果处理完成continue;}if (REG_MSG_ACK == msgtype) // 注册业务{doRegResponse(js); // 处理注册响应的业务逻辑sem_post(&rwsem);  // 通知主线程,注册结果处理完成continue;}}
}

2.5 整体代码

// 自定义头文件
#include "json.hpp"
#include "group.hpp"
#include "user.hpp"
#include "public.hpp"
using json = nlohmann::json;// 标准库头文件
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <chrono>
#include <ctime>
#include <unordered_map>
#include <functional>
using namespace std;#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <semaphore.h>
#include <atomic>// 全局变量、对象、函数
User g_currentUser;                   // 记录当前系统登录的用户信息
vector<User> g_currentUserFriendList; // 记录当前登录用户的好友列表信息
vector<Group> g_currentUserGroupList; // 记录当前登录用户的群组列表信息
bool isMainMenuRunning = false;       // 控制主菜单页面程序// 信号量
sem_t rwsem;                         // 用于读写线程之间的通信/*atomic原子变量是一种多线程编程中常用的同步机制,它能够确保对共享变量的操作在执行时不会被其他线程的操作干扰
原子变量它具有类似于普通变量的操作,但是这些操作都是原子级别的,即要么全部完成,要么全部未完成。*/atomic_bool g_isLoginSuccess{false}; // 记录登录状态            // 获取系统时间(聊天信息需要添加时间信息)
string getCurrentTime();// 显示当前登录成功用户的基本信息
void showCurrentUserData();// 主聊天页面程序
// void mainMenu(int);// 接收线程
void readTaskHandler(int clientfd);// 聊天客户端程序实现,main线程用作发送线程,子线程用作接收线程
int main(int argc, char **argv) // argc 参数个数    argv 参数序列或指针
{if (argc < 3){cerr << "command invalid! example: ./ChatClient 127.0.0.1 6000" << endl; // cerr:程序错误信息exit(-1);}// 解析通过命令行参数传递的ip和portchar *ip = argv[1];uint16_t port = atoi(argv[2]);// 创建client端的socketint clientfd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET:IPv4  SOCK_STREAM:可靠的、双向的通信数据流 0:调用者不指定协议if (-1 == clientfd){cerr << "socket create error" << endl;exit(-1);}// 填写client需要连接的server信息ip+portsockaddr_in server;memset(&server, 0, sizeof(sockaddr_in)); // memset清0server.sin_family = AF_INET;            // IPv4server.sin_port = htons(port);          // 端口server.sin_addr.s_addr = inet_addr(ip); // ip地址// client和server进行连接if (-1 == connect(clientfd, (sockaddr *)&server, sizeof(sockaddr_in))){cerr << "connect server error" << endl;close(clientfd);exit(-1);}// 初始化读写线程通信用的信号量sem_init(&rwsem, 0, 0);// 连接服务器成功,启动接收子线程std::thread readTask(readTaskHandler, clientfd); // pthread_createreadTask.detach();                               // pthread_detach// main线程用于接收用户输入,负责发送数据for (;;){// 显示首页面菜单 登录、注册、退出cout << "========================" << endl;cout << "======  1. login  ======" << endl;cout << "======  2. register  ===" << endl;cout << "======  3. quit  =======" << endl;cout << "========================" << endl;cout << "Please input your choice:";int choice = 0;cin >> choice; // 读取功能选项cin.get();     // 读掉缓冲区残留的回车switch (choice){case 1: // 登录业务{int id = 0;char pwd[50] = {0};cout << "user id:";cin >> id;cin.get(); // 读掉缓冲区残留的回车cout << "user password:";cin.getline(pwd, 50);json js;js["msgid"] = LOGIN_MSG;js["id"] = id;js["password"] = pwd;string request = js.dump();g_isLoginSuccess = false;int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0); // 通过网络send给服务器端if (len == -1){cerr << "send login msg error:" << request << endl;}sem_wait(&rwsem); // 等待信号量,由子线程处理完登录的响应消息后,通知这里if (g_isLoginSuccess){// 进入聊天主菜单页面isMainMenuRunning = true;// mainMenu(clientfd);}}break;case 2: // 注册业务{char name[50] = {0};char pwd[50] = {0};cout << "user name:";cin.getline(name, 50);cout << "user password:";cin.getline(pwd, 50);json js;js["msgid"] = REG_MSG;js["name"] = name;js["password"] = pwd;string request = js.dump();int len = send(clientfd, request.c_str(), strlen(request.c_str()) + 1, 0); // 通过网络send给服务器端if (len == -1){cerr << "send reg msg error:" << request << endl;}sem_wait(&rwsem); // 等待信号量,子线程处理完注册消息会通知}break;case 3: // 退出业务close(clientfd);sem_destroy(&rwsem);exit(0);default:cerr << "invalid input!" << endl;break;}}return 0;
}// 显示当前登录成功用户的基本信息
void showCurrentUserData()
{cout << "======================login user======================" << endl;cout << "current login user -> id:" << g_currentUser.getId() << " name:" << g_currentUser.getName() << endl;cout << "----------------------friend list---------------------" << endl;if (!g_currentUserFriendList.empty()){for (User &user : g_currentUserFriendList){cout << user.getId() << " " << user.getName() << " " << user.getState() << endl;}}cout << endl;cout << "----------------------group list----------------------" << endl;if (!g_currentUserGroupList.empty()){for (Group &group : g_currentUserGroupList){cout << group.getId() << " " << group.getName() << " " << group.getDesc() << endl;for (GroupUser &user : group.getUsers()){cout << user.getId() << " " << user.getName() << " " << user.getState()<< " " << user.getRole() << endl;}cout << endl;}}cout << "======================================================" << endl;
}// 获取系统时间(聊天信息需要添加时间信息)
string getCurrentTime()
{auto tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());struct tm *ptm = localtime(&tt);char date[60] = {0};sprintf(date, "%d-%02d-%02d %02d:%02d:%02d",(int)ptm->tm_year + 1900, (int)ptm->tm_mon + 1, (int)ptm->tm_mday,(int)ptm->tm_hour, (int)ptm->tm_min, (int)ptm->tm_sec);return std::string(date);
}// 处理注册的响应逻辑
void doRegResponse(json &responsejs)
{if (0 != responsejs["errno"].get<int>()) // 注册失败{cerr << "name is already exist, register error!" << endl;}else // 注册成功{cout << "name register success, userid is " << responsejs["id"]<< ", do not forget it!" << endl;}
}// 处理登录的响应逻辑
void doLoginResponse(json &responsejs)
{if (0 != responsejs["errno"].get<int>()) // 登录失败{cerr << responsejs["errmsg"] << endl;g_isLoginSuccess = false;}else // 登录成功{// 记录当前用户的id和nameg_currentUser.setId(responsejs["id"].get<int>());g_currentUser.setName(responsejs["name"]);// 记录当前用户的好友列表信息if (responsejs.contains("friends")){// 初始化g_currentUserFriendList.clear();vector<string> vec = responsejs["friends"];for (string &str : vec){json js = json::parse(str); // 反序列化User user;user.setId(js["id"].get<int>());user.setName(js["name"]);user.setState(js["state"]);g_currentUserFriendList.push_back(user);}}// 记录当前用户的群组列表信息if (responsejs.contains("groups")){// 初始化g_currentUserGroupList.clear();vector<string> vec1 = responsejs["groups"];for (string &groupstr : vec1){json grpjs = json::parse(groupstr); // 反序列化Group group;group.setId(grpjs["id"].get<int>());group.setName(grpjs["groupname"]);group.setDesc(grpjs["groupdesc"]);vector<string> vec2 = grpjs["users"]; // 群组内成员信息for (string &userstr : vec2){GroupUser user;json js = json::parse(userstr);user.setId(js["id"].get<int>());user.setName(js["name"]);user.setState(js["state"]);user.setRole(js["role"]);group.getUsers().push_back(user);}g_currentUserGroupList.push_back(group);}}// 显示登录用户的基本信息showCurrentUserData();// 显示当前用户的离线消息  个人聊天信息或者群组消息if (responsejs.contains("offlinemsg")){vector<string> vec = responsejs["offlinemsg"];for (string &str : vec){json js = json::parse(str);                 // 反序列化if (ONE_CHAT_MSG == js["msgid"].get<int>()) // 点对点聊天{cout << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()<< " said: " << js["msg"].get<string>() << endl;}else // 群聊{cout << "群消息[" << js["groupid"] << "]:" << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()<< " said: " << js["msg"].get<string>() << endl;}}}g_isLoginSuccess = true;}
}// 子线程 - 接收线程
void readTaskHandler(int clientfd)
{for (;;){char buffer[1024] = {0};int len = recv(clientfd, buffer, 1024, 0); // 阻塞if (-1 == len || 0 == len){close(clientfd);exit(-1);}// 接收服务器转发的数据,反序列化生成json数据对象json js = json::parse(buffer);int msgtype = js["msgid"].get<int>(); // 获取处理业务msgidif (ONE_CHAT_MSG == msgtype)          // 点对点聊天{cout << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()<< " said: " << js["msg"].get<string>() << endl;continue;}if (GROUP_CHAT_MSG == msgtype) // 群聊{cout << "群消息[" << js["groupid"] << "]:" << js["time"].get<string>() << " [" << js["id"] << "]" << js["name"].get<string>()<< " said: " << js["msg"].get<string>() << endl;continue;}if (LOGIN_MSG_ACK == msgtype) // 登录业务{doLoginResponse(js); // 处理登录响应的业务逻辑sem_post(&rwsem);    // 通知主线程,登录结果处理完成continue;}if (REG_MSG_ACK == msgtype) // 注册业务{doRegResponse(js); // 处理注册响应的业务逻辑sem_post(&rwsem);  // 通知主线程,注册结果处理完成continue;}}
}

三、客户端功能实现

使用CMake编译通过后,执行ChatServer和ChatClient程序

3.1 验证登录功能

我们使用张三的账号登录,即id=13,password = 123456

登录成功,回显张三信息、好友信息、群组信息,由于张三没有离线信息,此处没有显示

查看底层数据库,判断客户端是否显示正确

登录功能验证成功

3.2 验证注册功能

创建新用户Rabbit,密码666666,服务端返回id号为24

数据库插入成功

我们登录一下

注册、登录成功

3.3 验证退出业务

功能验证成功!

客户端主菜单的登录、注册、退出业务编码完成

感兴趣的小伙伴一起来试一下吧~

如果有问题还请及时联系我哦,感谢~

四、项目流程

 1、项目环境搭建 

C++项目——集群聊天服务器项目(一)项目介绍、环境搭建、Boost库安装、Muduo库安装、Linux与vscode配置_c++集群聊天服务器-CSDN博客

2、Json第三方库介绍

C++项目——集群聊天服务器项目(二)Json第三方库-CSDN博客

3、muduo网络库介绍

C++项目——集群聊天服务器项目(三)muduo网络库-CSDN博客

4、MySQL数据库创建

C++项目——集群聊天服务器项目(四)MySQL数据库-CSDN博客

5、网络模块与业务模块代码编写

C++项目——集群聊天服务器项目(五)网络模块与业务模块-CSDN博客

6、MySQL模块编写

C++项目——集群聊天服务器项目(六)MySQL模块-CSDN博客

7、Model层设计、注册业务实现

C++项目——集群聊天服务器项目(七)Model层设计、注册业务实现-CSDN博客

8、用户登录业务

C++项目——集群聊天服务器项目(八)用户登录业务-CSDN博客

9、客户端异常退出业务

C++项目——集群聊天服务器项目(九)客户端异常退出业务-CSDN博客

10、点对点聊天业务

11、服务器异常退出与添加好友业务

12、群组业务

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

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

相关文章

Lumos学习王佩丰Excel第一讲:认识Excel

最近发现自己在操作excel的一些特殊功能时会有些不顺手&#xff0c;所以索性找了一个比较全的教程&#xff08;王佩丰excel24讲&#xff09;拿来学习&#xff0c;刚好形成文档笔记&#xff0c;分享给有需要但没有时间看视频的朋友们。整体笔记以王老师授课的知识点去记录&#…

前端JS商品规格组合

给定一个数组 let data [{name: "颜色",specs: ["白色", "黑色"],},{name: "尺寸",specs: ["14寸","15寸", "16寸"],},{name: "处理器",specs: ["i5", "i7", "i9&…

XenCenter 2024 导入虚拟机

导入虚拟机 虚拟机位置 导入到那一个服务器 导入虚拟机存放存储位置 虚拟机网卡配置 SR修复功能&#xff0c;看自己需求 虚拟机恢复确认最终配置 恢复好的虚拟机 虚拟机模板转换

肿瘤免疫反应瀑布图(源于The Miller Lab)

目录 数据格式 绘图 ①根据剂量 ②根据type ③根据治疗响应度 添加水平线 数据格式 肿瘤免疫响应数据 rm(list ls()) library(tidyverse) library(dplyr) library(knitr)#模拟数据 # We will randomly assign the two doses, 80 mg or 150 mg, to the 56 subjects Me…

2024年第八届材料科学与纳米材料国际会议(ICMSN 2024)即将召开!

2024年第八届材料科学与纳米材料国际会议&#xff08;ICMSN 2024&#xff09;将于2024年7月9日至12日在英国爱丁堡召开。在过去的十年中&#xff0c;纳米材料一直是人们极大关注的主题。这些材料以其极小的特征尺寸而著称&#xff0c;具有广泛的工业、生物医学和电子应用潜力。…

深度学习之详解常见梯度算法(概念、公式、原理、算法实现过程)

目录 前言 一、如何实现梯度下降&#xff1f; 二、梯度计算 三、常见的梯度公式及梯度算法 常见的梯度公式&#xff1a; 1.标量对向量的梯度&#xff1a; 2. 标量对矩阵的梯度&#xff1a; 3. 向量对标量的梯度&#xff1a; 常见梯度算法&#xff1a; 四、常见梯度算法实现 1、…

OWASP API 安全风险,有哪些安全措施

随着互联网的快速发展&#xff0c;Web应用已成为人们日常生活和工作中不可或缺的一部分。然而&#xff0c;Web应用的安全问题也日益凸显&#xff0c;给企业和个人带来了极大的风险。 对于一些安全行业的用户来说&#xff0c;不少都听过关于OWASP这个词&#xff0c;很多用户想要…

ssm024家政服务网站设计+jsp

家政服务管理系统 摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 家政服务网站&#xff0c;主要的模块包括查看管理员&#xff1b;个人中心、用户管理、服务类型管理、家政类型管理、家政评…

学代码是理解就行,还是全部背?

在我没接触编程以前&#xff0c;看到程序&#xff0c;觉得这玩意到底怎么写出来的&#xff0c;写出这些代码的人&#xff0c;也太厉害了吧&#xff1f; 不会很多都要背下来吧&#xff1f; 我小学背课本都费劲&#xff0c;背不出来&#xff0c;中午不准回家吃饭&#xff0c;我就…

【零基础入门芯片课】半导体是什么?

大家好&#xff0c;我是砖一。 今天给大家分享一下半导体的基础知识&#xff0c;普及一下何为半导体&#xff0c;有从事IC&#xff0c;功率元器件&#xff0c;开关电源的朋友可以了解一下&#xff0c;希望对你有用~ 近几年&#xff0c;我们或多或少听过半导体&#xff0c;芯片…

02 Python进阶:CGI编程

什么是CGI CGI是通用网关接口&#xff08;Common Gateway Interface&#xff09;的缩写&#xff0c;它是一种标准协议&#xff0c;用于Web服务器执行外部程序或脚本与Web浏览器进行交互。通过CGI&#xff0c;Web服务器能够动态生成网页内容&#xff0c;处理用户提交的表单数据…

33-5 XXE漏洞 - xxe外部实体注入相关工具

1)xxe.sh || OOB XXE tool:一个方便的网站,可以输入您的域名并生成OOB XXE PoC。适用于盲注Out-of-band攻击。(主要) 2)staaldraad/xxeserv :简化FTP服务器的设置,以通过FTP接收OOB XXE攻击 以下是 ./xxeserv 命令的使用说明: Usage of ./xxeserv:-o string记录日志…

js的Proxy和Reflect

目录 Proxy对象的基本操作常见捕获器getset函数相关拦截器 ReflectReflect与基本语法的区别Reflect常见方法 Proxy和Reflect 在 ES6之前&#xff0c;如果我们对对象进行 新增或者删除属性时&#xff0c;仅靠原有的 Object.defineProperty是无法 监听的&#xff0c;基于此&a…

QT4-QT5-VS2019-编译无法打开文件 QtCore/qchar.h

1.系统环境变量 添加QTDIR 添加PATH 2.项目配置 2.1vc目录&#xff1a;包含目录库目录 2.2C/C 常规&#xff1a;附加包含目录QT5的头文件必须在附加包含目录里配置。 2.3链接器&#xff1a;附加库目录 2.4QTProject Settings

镜舟科技荣获第十三届中国智能制造高峰论坛两项大奖

2024年3月29日&#xff0c;由e-works数字化企业网和湖北省中小企业服务中心联合主办&#xff0c;中国中小企业发展促进中心指导的“第十三届中国智能制造高峰论坛暨第二十一届中国智能制造岁末盘点颁奖典礼”在北京圆满落幕。本次论坛汇聚了国内外智能制造领域的专家学者、企业…

有效的职场沟通及其重要性

欢迎来到职场沟通的世界&#xff0c;在这里&#xff0c;每一个词都至关重要&#xff0c;沉默则能传达出无尽的信息。一个有效沟通能力不仅仅是在线办公套件中的可选配件&#xff0c;而是一项绝对必需的能力。今天我们与您分享有效的职场沟通及其重要性。有效的职场沟通可以增强…

Redis高可用及持久化

文章目录 一、Redis高可用1、Redis高可用概述2、Redis高可用策略 二、Redis持久化1、Redis持久化的功能2、Redis持久化的两种方式2.1 RDB持久化2.2 AOF持久化&#xff08;append only file&#xff09; 3、RDB持久化3.1 触发条件3.1.1 手动触发3.1.2 自动触发3.1.2.1 配置方式3…

桶排序---

1、算法概念 桶排序&#xff1a;一种非比较的排序算法。桶排序采用了一些分类和分治的思想&#xff0c;把元素的值域分成若干段&#xff0c;每一段对应一个桶。在排序的时候&#xff0c;首先把每一个元素放到其对应的桶中&#xff0c;再对每一个桶中的元素分别排序&#xff0c…

EF数据持久化(三层架构,公司查,改)

效果图 Model设置具体流程在下面链接中 https://blog.csdn.net/Mr_wangzu/article/details/136805824?spm1001.2014.3001.5501 DAL using System; using System.Collections.Generic; using System.Linq; using System.Web; using WebApplication2.Models; namespace WebAppli…

结构体,联合体,枚举( 2 )

目录 2.联合体 2.1联合体类型的声明 2.2联合体的特点 2.3联合体的内存大小 3.枚举 3.1枚举类型的声明 3.2枚举类型的优点 3.3枚举类型的使用 2.联合体 联合体&#xff08;Union&#xff09;是另一种复合数据类型&#xff0c;它允许我们在同一内存位置存储不同的数据类型…