当前位置: 首页 > news >正文

WiFiMouseServer手机等作为远程输入

将代码保存为myWifiMouse.cpp, 在MSYS终端编译运行这个服务端,

$ cd   /source_dir_path/

 $ g++ -std=c++14 -o myWifiMouse myWifiMouse.cpp -lws2_32 && ./myWifiMouse.exe

/*
--------------------通信协议--------------------鼠标数据帧格式:
mos+'  '+len+func+' '+data
mos:为固定标识,注意:mos后是两个空格
len:func加data的字节长度,len与func之间是没有空格的
func:有3个分别为R(按键),m(移动),w(滚轮),c(左键点击)
data:有3种,按键:l左键、r右键、m中键,按下为d,释放为u,按键数据举例为:"l d"、"l u"、"m d"、"m u";移动:分别为xy坐标,移动数据举例为:"1 5"、"0 -8"、"-1 0"、"-5 -5";滚轮:只不滚动值,要么正,要么负;下面是鼠标数据帧举例: 左键点击一次:"mos  1c"左键按下:"mos  5R l d"左键释放:"mos  5R l u"右键按下:"mos  5R r d"右键释放:"mos  5R r u"中键按下:"mos  5R m d"中键释放:"mos  5R m u"移动:"mos  5m 0 3"滚轮:"mos  3w 0"滚轮:"mos  3w 1"键盘数据帧格式:
key+'  '+len+data
key:为固定标识,注意:key后是2个空格
len:data的字节长度,len与data之间是没有空格的
data:为键盘操作输入数据
下面是键盘数据帧举例:回车键:"key  3RTN"退格键:"key  3BAS"输入法数据帧格式:
utf8+' '+data
utf8:为固定标识,注意:utf8后是1个空格
data:为字符文字数据
下面是键盘数据帧举例:"utf8 abdsd":接收到字符串"abdsd""utf8 好的,好东西":接收到字符串"好的,好东西"--------------------通信协议--------------------*/
#include <iostream>
#include <thread>
#include <mutex>
#include <unordered_map>
#include <string>
#include <winsock2.h>
#include <windows.h>
#include <ctime>
#include <list>
#pragma comment(lib, "ws2_32.lib")// 配置日志(包含帧计数信息)
// 定义日志宏,用于输出不同级别的日志信息
// LOG_INFO 用于输出信息级别的日志
#define LOG_INFO(msg) std::cout << getCurrentTime()<<" "<< __LINE__ << " - INFO - " << msg << std::endl
// LOG_ERROR 用于输出错误级别的日志
#define LOG_ERROR(msg) std::cerr << getCurrentTime()<<" "<< __LINE__ << " - ERROR - " << msg << std::endl
// LOG_WARNING 用于输出警告级别的日志
#define LOG_WARNING(msg) std::cout << getCurrentTime()<<" "<< __LINE__ << " - WARNING - " << msg << std::endl
// LOG_DEBUG 用于输出调试级别的日志
#define LOG_DEBUG(msg) std::cout << getCurrentTime()<<" "<< __LINE__ << " - DEBUG - " << msg << std::endl// 获取当前时间的函数,返回一个字符串表示当前时间
std::string getCurrentTime() {time_t now = time(0);char* dt = ctime(&now);std::string timeStr(dt);timeStr.pop_back();  // 去掉换行符return timeStr;
}// WiFiMouseServer 类,用于实现一个WiFi鼠标服务器
class WiFiMouseServer {
public:// 构造函数,初始化服务器的主机地址和端口号WiFiMouseServer(const std::string& host = "0.0.0.0", int port = 1978): host(host), port(port), running(false), frame_count(0) {// 初始化Winsock库WSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {LOG_ERROR("WSAStartup failed");return;}// 创建服务器套接字server_socket = socket(AF_INET, SOCK_STREAM, 0);if (server_socket == INVALID_SOCKET) {LOG_ERROR("Failed to create socket");WSACleanup();return;}// 配置服务器地址信息sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = inet_addr(host.c_str());server_addr.sin_port = htons(port);// 将套接字绑定到指定的地址和端口if (bind(server_socket, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {LOG_ERROR("Failed to bind socket");closesocket(server_socket);WSACleanup();return;}// 开始监听客户端连接if (listen(server_socket, 5) == SOCKET_ERROR) {LOG_ERROR("Failed to listen on socket");closesocket(server_socket);WSACleanup();return;}}// 析构函数,停止服务器并清理Winsock库~WiFiMouseServer() {stop();WSACleanup();}// 启动服务器,开启一个新线程来接受客户端连接void start() {running = true;LOG_INFO("Server started, listening on " + host + ":" + std::to_string(port) + " | Waiting for client connections...");std::thread(&WiFiMouseServer::_accept_connections, this).detach();}// 停止服务器,关闭套接字并输出处理的总帧数void stop() {running = false;if (server_socket != INVALID_SOCKET) {closesocket(server_socket);server_socket = INVALID_SOCKET;}LOG_INFO("Server stopped | Total frames processed: " + std::to_string(frame_count));}// 从列表中获取指定索引的元素,如果索引越界则抛出异常static std::string getElement(const std::list<std::string>& myList, int index){if (index < 0 || index >= myList.size()) {throw std::out_of_range("Index out of range");}auto it = myList.begin();std::advance(it, index); // 移动迭代器到目标位置return *it; // 解引用获取元素}// 将字符串按指定分隔符分割成多个子字符串,并存储在列表中返回static std::list<std::string> split(const std::string& _buffer, const std::string& _split ){int pos = 0;int len;std::list<std::string> temp;for (size_t i = 0; i < _buffer.length(); ){pos = _buffer.find( _split, i );if( pos < 0 ){temp.push_back( _buffer.substr( i ) );break;}len = pos - i;temp.push_back( _buffer.substr( i, len ) );i += len+1;}return temp;}private:std::string host;  // 服务器主机地址int port;  // 服务器端口号SOCKET server_socket;  // 服务器套接字bool running;  // 服务器运行状态std::unordered_map<SOCKET, std::string> client_buffers;  // 客户端缓冲区,存储每个客户端接收到的数据int frame_count;  // 处理的总帧数// 接受客户端连接的线程函数,不断循环接受新的客户端连接void _accept_connections() {while (running) {sockaddr_in client_addr;int client_addr_len = sizeof(client_addr);SOCKET client_socket = accept(server_socket, (sockaddr*)&client_addr, &client_addr_len);if (client_socket == INVALID_SOCKET) {if (running) {LOG_ERROR("Failed to accept connection");}continue;}client_buffers[client_socket] = "";LOG_INFO("New client connected: " + std::string(inet_ntoa(client_addr.sin_addr)) + ":" + std::to_string(ntohs(client_addr.sin_port)) + " | Current connections: " + std::to_string(client_buffers.size()));std::thread(&WiFiMouseServer::_handle_client, this, client_socket).detach();}}// 处理客户端连接的线程函数,不断接收客户端发送的数据并处理void _handle_client(SOCKET client_socket) {// try {while (true) {char buffer[1024];int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);if (bytes_received <= 0) {LOG_INFO("Client disconnected | Total frames processed: " + std::to_string(frame_count));break;}client_buffers[client_socket] += std::string(buffer, bytes_received);LOG_DEBUG("Received data, current buffer length: " + std::to_string(client_buffers[client_socket].length()) + " characters");_split_and_process_frames(client_socket);}// } catch (const std::exception& e) {//     LOG_ERROR("Error handling client: " + std::string(e.what()));// }if (client_buffers.find(client_socket) != client_buffers.end()) {client_buffers.erase(client_socket);}closesocket(client_socket);}// 解析帧头信息,返回一个包含长度、功能和帧头长度的元组指针std::unique_ptr<std::tuple<std::string, std::string, int>> _parse_frame_header(const std::string& buffer){std::list<std::string> ds = split( buffer, " " );if ( ds.size() < 2 ){return nullptr;}std::string mark;std::string len_func_part ;std::string func;std::string len_str;int i=0;if ( ds.size()>i ) mark = getElement(ds,i);i++;if ( ds.size()>i ) ;//rev = getElement(ds,i);i++;if ( ds.size()>i ) len_func_part = getElement(ds,i);i++;if (len_func_part.find("1c") != std::string::npos) {func = "c";len_str = "1";} else {func = len_func_part.substr(len_func_part.length() - 1);len_str = len_func_part.substr(0, len_func_part.length() - 1);}if (func != "R" && func != "m" && func != "w" && func != "c") {LOG_WARNING("Invalid func field: " + func);return nullptr;}int frame_header_len = mark.size() + len_str.size() + 2;return std::make_unique<std::tuple<std::string, std::string, int>>(len_str, func, frame_header_len);}// 处理鼠标帧数据,解析并处理鼠标相关的操作void _process_mouse_frame(SOCKET client_socket, std::string& buffer){size_t mos_pos = buffer.find("mos  ");if (mos_pos == std::string::npos) {return;}size_t _pos = mos_pos;buffer = buffer.substr(_pos);auto frame_parts = _parse_frame_header(buffer);if (!frame_parts) {return;}std::string len_str = std::get<0>(*frame_parts);std::string func = std::get<1>(*frame_parts);int frame_header_len = std::get<2>(*frame_parts);int expected_func_data_len;try {expected_func_data_len = std::stoi(len_str);} catch (const std::invalid_argument& e) {LOG_WARNING("Invalid len field: " + len_str + " | Discarding current incomplete frame");buffer = buffer.substr(1);return;}int min_frame_len = frame_header_len + expected_func_data_len ;if (buffer.length() < min_frame_len) {buffer = buffer.substr(1);return;}std::string func_data_str = buffer.substr(frame_header_len);int actual_func_data_len = func_data_str.length();if (actual_func_data_len < expected_func_data_len) {return;}func_data_str = func_data_str.substr(0, expected_func_data_len);int full_frame_len = frame_header_len + func_data_str.length();if (full_frame_len > buffer.length()) {return;}std::string full_frame = buffer.substr(0, full_frame_len);_process_single_frame(full_frame);buffer = buffer.substr(full_frame_len);frame_count++;LOG_DEBUG("Successfully processed 1 frame | Total frames: " + std::to_string(frame_count) + " | Remaining buffer length: " + std::to_string(buffer.length()));}// 处理键盘帧数据,解析并处理键盘相关的操作void _process_keyboard_frame(SOCKET client_socket, std::string& buffer){size_t key_pos = buffer.find("key  ");if (key_pos == std::string::npos) {return;}buffer = buffer.substr(key_pos);size_t space_pos = buffer.find("  ");if (space_pos == std::string::npos) {return;}std::string len_str = buffer.substr(space_pos + 2);size_t data_start = len_str.find_first_not_of("0123456789");if (data_start == std::string::npos) {return;}std::string len_part = len_str.substr(0, data_start);std::string data = len_str.substr(data_start);int expected_len = std::stoi(len_part);int actual_len = data.length();if (actual_len < expected_len) {LOG_WARNING("Length verification failed for key frame: expected " + std::to_string(expected_len) + " bytes, actual " + std::to_string(actual_len) + " bytes");buffer = buffer.substr(1);return;}std::string full_frame = buffer.substr(0, space_pos + 2 + len_part.length() + expected_len );_process_single_frame(full_frame);buffer = buffer.substr(full_frame.length());frame_count++;LOG_DEBUG("Successfully processed 1 key frame | Total frames: " + std::to_string(frame_count) + " | Remaining buffer length: " + std::to_string(buffer.length()));}// 处理输入法帧数据,解析并处理输入法相关的操作void _process_input_method_frame(SOCKET client_socket, std::string& buffer) {size_t utf8_pos = buffer.find("utf8 ");if (utf8_pos == std::string::npos) {return;}buffer = buffer.substr(utf8_pos);size_t space_pos = buffer.find(' ');if (space_pos == std::string::npos) {return;}std::string func = "utf8";std::string data = buffer.substr(space_pos + 1);std::string full_frame = buffer.substr(0, space_pos + 0 + data.length());_process_single_frame(full_frame);buffer = buffer.substr(full_frame.length());frame_count++;LOG_DEBUG("Successfully processed 1 frame | Total frames: " + std::to_string(frame_count) + " | Remaining buffer length: " + std::to_string(buffer.length()));}// 分割并处理接收到的数据帧,根据帧的类型调用相应的处理函数void _split_and_process_frames(SOCKET client_socket) {std::string& buffer = client_buffers[client_socket];while (true){size_t mos_pos = buffer.find("mos  ");size_t utf8_pos = buffer.find("utf8 ");size_t key_pos = buffer.find("key  ");// LOG_DEBUG("---> buffer: " + (buffer) );if (mos_pos == std::string::npos && utf8_pos == std::string::npos && key_pos == std::string::npos) {return;}if (mos_pos != std::string::npos && (utf8_pos == std::string::npos || mos_pos < utf8_pos) && (key_pos == std::string::npos || mos_pos < key_pos)){_process_mouse_frame( client_socket, buffer) ;}else if (utf8_pos != std::string::npos && (key_pos == std::string::npos || utf8_pos < key_pos)){_process_input_method_frame( client_socket, buffer) ;}else{_process_keyboard_frame( client_socket, buffer);}}}// 处理单个数据帧,根据帧的功能调用相应的处理函数void _process_single_frame(const std::string& frame) {try {// std::string func;std::string data;std::string mark;std::string len_func_part ;std::string func;std::string len_str;std::list<std::string> ds = split( frame, " ");if ( ds.size() < 2 ){return  ;}int i=0;if ( ds.size()>i ) mark = getElement(ds,i);i++;if ( ds.size()>i ) ;//rev = getElement(ds,i);i++;if ( ds.size()>i ) len_func_part = getElement(ds,i);i++;if ( mark == "utf8") {func = mark;data = frame.substr(5);}else if ( mark == "key"){func = mark;size_t space_pos = frame.find(' ');std::string len_str = frame.substr(space_pos + 2);size_t data_start = len_str.find_first_not_of("0123456789");data = len_str.substr(data_start);}else{if (len_func_part.find("1c") != std::string::npos) {func = "c";len_str = "1";} else {func = len_func_part.substr(len_func_part.length() - 1);len_str = len_func_part.substr(0, len_func_part.length() - 1);}int _d_pos = frame.find( len_func_part );if( _d_pos + len_func_part.size() + 1 < frame.size() )data = frame.substr( _d_pos + len_func_part.size() + 1 );int expected_len = std::stoi(len_str);int actual_len = (func + " " + data).length();if ( !data.empty() && actual_len != expected_len){return ;//throw std::invalid_argument("Length verification failed: expected " + std::to_string(expected_len) + " bytes, actual " + std::to_string(actual_len) + " bytes");}}if (func == "R") {handle_mouse_key(data);} else if (func == "m") {handle_mouse_move(data);} else if (func == "w") {handle_mouse_scroll(data);} else if (func == "c") {handle_mouse_click(data);} else if (func == "utf8") {handle_keyboard(data);} else if (func == "key") {handle_keyboard_key(data);} else {return ;//throw std::invalid_argument("Unsupported func: " + func);}} catch (const std::exception& e) {LOG_WARNING("Failed to process frame: " + std::string(e.what()) + " | Frame content: " + frame);}}
#if 1// 处理鼠标按键事件,模拟鼠标按键的按下和释放操作void handle_mouse_key(const std::string& data) {size_t pos = data.find(' ');if (pos == std::string::npos) {throw std::invalid_argument("Invalid key data format: " + data);}std::string key_type = data.substr(0, pos);std::string action = data.substr(pos + 1);if ((key_type != "l" && key_type != "r" && key_type != "m") || (action != "d" && action != "u")) {throw std::invalid_argument("Invalid key parameters: " + data);}INPUT input = {0};input.type = INPUT_MOUSE;// 设置按键类型if (key_type == "l") {input.mi.dwFlags = (action == "d") ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;} else if (key_type == "r") {input.mi.dwFlags = (action == "d") ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;} else { // minput.mi.dwFlags = (action == "d") ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;}// 发送输入事件SendInput(1, &input, sizeof(INPUT));}// 处理鼠标移动事件,模拟鼠标的移动操作void handle_mouse_move(const std::string& data) {size_t pos = data.find(' ');if (pos == std::string::npos) {throw std::invalid_argument("Invalid move data format: " + data);}int x = std::stoi(data.substr(0, pos));int y = std::stoi(data.substr(pos + 1));INPUT input = {0};input.type = INPUT_MOUSE;input.mi.dwFlags = MOUSEEVENTF_MOVE;input.mi.dx = x;input.mi.dy = y;SendInput(1, &input, sizeof(INPUT));}// 处理鼠标滚动事件,模拟鼠标的滚动操作void handle_mouse_scroll(const std::string& data) {try {int value = std::stoi(data);if (value == 0) return; // 无滚动,直接返回INPUT input = {0};input.type = INPUT_MOUSE;input.mi.dwFlags = MOUSEEVENTF_WHEEL;input.mi.mouseData = (value > 0) ? WHEEL_DELTA : -WHEEL_DELTA;SendInput(1, &input, sizeof(INPUT));} catch (const std::invalid_argument& e) {throw std::invalid_argument("Invalid scroll value: " + data);}}// 处理鼠标点击事件,模拟鼠标左键的点击操作void handle_mouse_click(const std::string& data) {INPUT inputs[2] = {0};// 左键按下inputs[0].type = INPUT_MOUSE;inputs[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;// 左键释放inputs[1].type = INPUT_MOUSE;inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTUP;// 发送两个输入事件SendInput(2, inputs, sizeof(INPUT));}
#else// 另一种处理鼠标按键事件的实现,使用mouse_event函数void handle_mouse_key(const std::string& data) {size_t pos = data.find(' ');if (pos == std::string::npos) {throw std::invalid_argument("Invalid key data format: " + data);}std::string key_type = data.substr(0, pos);std::string action = data.substr(pos + 1);if ((key_type != "l" && key_type != "r" && key_type != "m") || (action != "d" && action != "u")) {throw std::invalid_argument("Invalid key parameters: " + data);}DWORD button;if (action == "d") {if (key_type == "l") {button = MOUSEEVENTF_LEFTDOWN;} else if (key_type == "r") {button = MOUSEEVENTF_RIGHTDOWN;} else {button = MOUSEEVENTF_MIDDLEDOWN;}mouse_event( button, 0, 0, 0, 0);}else{if (key_type == "l") {button = MOUSEEVENTF_LEFTUP;} else if (key_type == "r") {button = MOUSEEVENTF_RIGHTUP;} else {button = MOUSEEVENTF_MIDDLEUP;}mouse_event( button, 0, 0, 0, 0);}}// 另一种处理鼠标移动事件的实现,使用mouse_event函数void handle_mouse_move(const std::string& data) {size_t pos = data.find(' ');if (pos == std::string::npos) {throw std::invalid_argument("Invalid move data format: " + data);}int x = std::stoi(data.substr(0, pos));int y = std::stoi(data.substr(pos + 1));mouse_event(MOUSEEVENTF_MOVE, x, y, 0, 0);}// 另一种处理鼠标滚动事件的实现,使用mouse_event函数void handle_mouse_scroll(const std::string& data) {try {int value = std::stoi(data);if (value > 0) {mouse_event(MOUSEEVENTF_WHEEL, 0, 0, WHEEL_DELTA, 0);} else if (value < 0) {mouse_event(MOUSEEVENTF_WHEEL, 0, 0, -WHEEL_DELTA, 0);}} catch (const std::invalid_argument& e) {throw std::invalid_argument("Invalid scroll value: " + data);}}// 另一种处理鼠标点击事件的实现,使用mouse_event函数void handle_mouse_click(const std::string& data) {mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);}
#endif// 辅助函数:将UTF-8字符串转换为宽字符字符串wchar_t* utf8_to_wchar(const char* utf8_str) {if (!utf8_str) return nullptr;// 计算宽字符所需长度int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, nullptr, 0);if (wlen == 0) {LOG_WARNING("UTF-8 to wide char conversion failed (length 0)");return nullptr;}// 分配内存并转换wchar_t* wstr = new wchar_t[wlen];if (!wstr) {LOG_ERROR("Memory allocation failed for wide char");return nullptr;}if (MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, wstr, wlen) == 0) {LOG_WARNING("UTF-8 to wide char conversion failed (MultiByteToWideChar)");delete[] wstr;return nullptr;}return wstr;}// 辅助函数:模拟单个宽字符的输入void simulate_unicode_input(wchar_t c) {INPUT input[2] = {0}; // 存储按键按下和释放两个事件// 按键按下事件input[0].type = INPUT_KEYBOARD;input[0].ki.wVk = 0; // 不使用虚拟键码(通过Unicode扫描码输入)input[0].ki.wScan = c; // 宽字符扫描码(直接对应Unicode字符)input[0].ki.dwFlags = KEYEVENTF_UNICODE; // 标记为Unicode输入// 按键释放事件input[1].type = INPUT_KEYBOARD;input[1].ki.wVk = 0;input[1].ki.wScan = c;input[1].ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP; // 释放标记// 发送输入事件(返回值为成功发送的事件数,此处简化处理)SendInput(2, input, sizeof(INPUT));}// 处理键盘输入,将UTF-8字符串转换为宽字符并逐个字符模拟输入void handle_keyboard(const std::string& data) {if (data.empty()) {LOG_DEBUG("No keyboard data to input");return;}// 1. 将UTF-8字符串转换为宽字符(支持中文等)wchar_t* wstr = utf8_to_wchar(data.c_str());if (!wstr) {LOG_WARNING("Failed to process keyboard data (invalid UTF-8)");return;}// 2. 逐个字符模拟输入(间隔短时间避免输入混乱)for (int i = 0; wstr[i] != L'\0'; ++i) {simulate_unicode_input(wstr[i]);Sleep(10); // 轻微延迟,确保系统能正确接收}// 释放内存delete[] wstr;LOG_DEBUG("Keyboard input completed: " + data);}// 处理键盘按键事件,模拟回车键和退格键的按下和释放操作void handle_keyboard_key(const std::string& data) {if (data == "RTN") {// 处理回车键keybd_event(VK_RETURN, 0, 0, 0);keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0);} else if (data == "BAS") {// 处理退格键keybd_event(VK_BACK, 0, 0, 0);keybd_event(VK_BACK, 0, KEYEVENTF_KEYUP, 0);} else {LOG_WARNING("Unsupported keyboard key: " + data);}}};int main() {WiFiMouseServer server;server.start();try {std::cout << "Server is running, press Ctrl+C to stop..." << std::endl;while (true) {std::this_thread::sleep_for(std::chrono::seconds(1));}} catch (const std::exception& e) {server.stop();}return 0;
}

http://www.lryc.cn/news/594082.html

相关文章:

  • 进阶向:基于Python的局域网文件传输工具
  • LeetCode|Day20|9. 回文数|Python刷题笔记
  • 多任务学习AITM算法简介
  • Kafka MQ 控制器 broker
  • 数据结构第二章:线性表之顺序表
  • 【新手向】PyTorch常用Tensor shape变换方法
  • C++ STL中迭代器学习笔记
  • Python爬虫实战:研究Genius库相关技术
  • TVLT:无文本视觉-语言Transformer
  • 【设计模式C#】享元模式(用于解决多次创建对象而导致的性能问题)
  • 第十四讲 | AVL树实现
  • [simdjson] `error_code` | .get() | 异常 | is_fatal() | current_location() | 链式处理
  • 苍穹外卖|项目日记(完工总结)
  • 【JS逆向基础】数据库之mysql
  • pip关于缓存的用法
  • Ubuntu挂载和取消挂载
  • 开源安全大模型Foundation-Sec 8B的安全实践
  • PPT科研画图插件
  • 如何使用Python将任意PPT变为“智能模板”(解决 python-pptx 替换元素无法保留格式的问题,阴影、填充等属性保留!)
  • 深度学习篇---矩阵
  • 深度学习图像分类数据集—百种病虫害分类
  • linux + 宝塔面板 部署 django网站 启动方式:uwsgi 和gunicorn如何选择 ?
  • k8s:离线部署存在的相关问题
  • day 30 打卡
  • Redis 详解:从入门到进阶
  • MySQL 配置性能优化实操指南:分版本5.7和8.0适配方案
  • 【Anaconda】Conda 虚拟环境打包迁移教程
  • Redis通用常见命令(含面试题)
  • 28.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--币种服务(二)
  • 零基础学习性能测试第二章-linux/jvm/mysql等数据收集环境搭建