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

【网络编程】之TCP实现客户端远程控制服务器端及断线重连

【网络编程】之TCP实现客户端远程控制服务器端及断线重连

  • TCP网络通信实现客户端简单远程控制主机
    • 基本功能演示
    • 通信过程
    • 代码实现
      • 服务器模块
      • 执行命令模块
        • popen系列函数
      • 客户端模块
      • 服务器主程序
  • windows作为客户端与服务器通信
    • #pragma comment介绍
  • 客户端使用状态机断线重连
    • 代码实现
    • 运行结果

TCP网络通信实现客户端简单远程控制主机

基本功能演示

客户端与服务器端连接后,可以通过Linux中的指令来控制它:

image-20250116162605681

但是为了防止客户端恶意破坏服务器,我们必须创建一个配置文件,只有在这个配置文件里的命令,客户端才能执行。

通信过程

  1. 服务器端创建监听套接字,监听客户端,等待客户端连接。
  2. 客户端发起连接请求。
  3. 连接成功,开始通信。
  4. 客户端发送命令。
  5. 服务器端接收到命令,并创建一个新线程,线程在线程调用的函数中设置为分离状态,主线程不需要对子线程等待。
  6. 新线程执行一系列函数后给客户端返回执行结果。

代码实现

服务器模块

TcpServer.hpp

#pragma once
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<cstdio>
#include<cstdlib>
#include"Log.hpp"
#include<functional>
#include"ThreadPool.hpp"
#include"InetAddr.hpp"// 定义错误码
enum
{SOCKETERROR = 1, // 套接字创建失败BINDERROR,       // 套接字绑定失败USAGEERROR       // 用法错误
};// 定义服务函数的类型别名
using funcservice = function<void(int sockfd, InetAddr addr)>;// 用于多线程任务的数据结构
struct Threaddata
{int _sockfd;            // 客户端连接的套接字InetAddr _addr;         // 客户端的地址信息funcservice _exec;      // 执行的服务函数Threaddata(int sockfd, InetAddr addr, funcservice exec): _sockfd(sockfd), _addr(addr), _exec(exec){}
};// 定义业务逻辑函数类型别名
using funcexec = function<string(const string&)>;// 定义 TcpServer 类
class TcpServer
{
private:int _listensock;         // 监听套接字uint16_t _port;          // 监听端口号bool _is_running;        // 服务器运行状态funcexec _exec;          // 业务逻辑处理函数public:// 构造函数,初始化成员变量TcpServer(uint16_t port, funcexec exec): _listensock(-1), _port(port), _is_running(false), _exec(exec){}// 初始化服务器,包括创建套接字、绑定地址和监听void InitServer(){_listensock = socket(AF_INET, SOCK_STREAM, 0); // 创建 TCP 套接字if (_listensock == -1){LOG(FATAL, "socket error");exit(1); // 套接字创建失败,退出}LOG(INFO, "socket success");struct sockaddr_in addr;addr.sin_family = AF_INET;               // 使用 IPv4addr.sin_port = htons(_port);            // 设置端口号(网络字节序)addr.sin_addr.s_addr = INADDR_ANY;       // 监听本机所有网卡// 绑定地址if (bind(_listensock, (struct sockaddr*)&addr, sizeof(addr)) == -1){LOG(FATAL, "bind error");exit(1); // 绑定失败,退出}LOG(INFO, "bind success");// 开始监听,最大连接数为 5if (listen(_listensock, 5) == -1){LOG(FATAL, "listen error");exit(1); // 监听失败,退出}}// 服务函数,处理客户端请求void Service(int sockfd, InetAddr addr){LOG(INFO, "new connect: %s:%d", inet_ntoa(addr.addr().sin_addr), ntohs(addr.addr().sin_port));while (_is_running){char buffer[1024];                   // 接收缓冲区memset(buffer, 0, sizeof(buffer));   // 清空缓冲区int n = recv(sockfd, buffer, sizeof(buffer), 0); // 接收数据string sender = "[" + addr.ip() + ":" + to_string(addr.port()) + "]#"; // 记录客户端信息if (n == -1){perror("recv");break;}else if (n == 0){LOG(INFO, "%sclient close", sender.c_str());break; // 客户端断开连接}else{buffer[n] = 0; // 确保字符串以 '\0' 结束LOG(INFO, "%s%s", sender.c_str(), buffer);// 执行业务逻辑函数string result = _exec(buffer);// 返回处理结果给客户端string echoserver = "[echo server]#\n" + result;send(sockfd, echoserver.c_str(), echoserver.size(), 0);}}close(sockfd); // 关闭客户端连接}// 线程入口函数static void* ThreadRun(void* arg){pthread_detach(pthread_self()); // 设置线程分离,防止资源泄漏Threaddata* data = (Threaddata*)arg; // 获取任务数据data->_exec(data->_sockfd, data->_addr); // 执行服务函数delete data; // 释放任务数据return nullptr;}// 主循环,接受客户端连接并分配任务void Loop(){_is_running = true;while (_is_running){struct sockaddr_in peer;           // 客户端地址socklen_t len = sizeof(peer);int sockfd = accept(_listensock, (struct sockaddr*)&peer, &len); // 接受连接if (sockfd == -1){perror("accept");break;}InetAddr addr(peer); // 将客户端地址封装为 InetAddr 对象// 多线程版本Threaddata* data = new Threaddata(sockfd, addr, std::bind(&TcpServer::Service, this, placeholders::_1, placeholders::_2));pthread_t tid;pthread_create(&tid, nullptr, ThreadRun, data); // 创建线程处理任务}_is_running = false;}// 析构函数,清理资源~TcpServer(){if (_listensock != -1){close(_listensock); // 关闭监听套接字}}
};
  • 执行命令的函数在外面传入类中,当新线程接收数据后回调这个函数处理任务(创建子进程处理执行命令并返回数据)。

执行命令模块

#pragma once
#include<string>
#include<set>
#include<fstream>
#include<iostream>
#include"Log.hpp"
using namespace std;const static string seq = " ";
const static string commandpath = "./command.txt";class ExecuteCommand
{
private:set<string> _CommandSet;//安全命令集合string _cond_path;//命令的路径
private:void LoadCommandSet()//加载安全命令集合{ifstream infile(commandpath, ios::in);//打开文件以读取的方式if(!infile.is_open())//判断文件是否打开成功{LOG(FATAL, "open command.txt failed");  //打开文件失败return;}string line;while(getline(infile, line))//读取文件中的安全命令{_CommandSet.insert(line);//插入到安全命令集合中}infile.close();//关闭文件}bool IsSecure(const string& Command)//判断是否是安全命令{if(Command.empty())//判断命令是否为空{return false;}//先把核心命令提取出来(不要后面的选项)int pos = Command.find(seq);//找到空格的位置string core = Command.substr(0, pos);//提取核心命令//判断是否在安全命令集合中if(_CommandSet.find(core) != _CommandSet.end())//在安全命令集合中{return true;}return false;//不在安全命令集合中}
public:ExecuteCommand(const string path = commandpath):_cond_path(path){LoadCommandSet();//加载安全命令集合}string Execute(const string& Command){//1.先fork && pipe//2.再exec 执行命令//3. 执行命令前 dup2//这些都可以通过库函数popen来实现//(这个函数是一个标准库函数,用于创建一个管道,然后调用fork产生一个子进程,然后调用exec执行一个命令) if(!IsSecure(Command))//判断是否是安全命令{LOG(WARNING, "command is not secure");return "command is not secure";}else//是安全的命令{FILE* fp = popen(Command.c_str(), "r");//执行命令if(fp == nullptr)//判断是否执行成功{LOG(WARNING, "command execute failed");return "command execute failed";}string result;char output[1024] = {0};//定义一个缓冲区while(fgets(output, sizeof(output)-1, fp) != nullptr)//读取命令的输出{result += output;}pclose(fp);//关闭文件return result;}}~ExecuteCommand(){}
};
  • 需求:1. 执行安全的命令 2.将命令执行的结果发送给客户端。
  • 实现:
    • 执行命令:不能在子线程中执行命令,因为子线程还要接收来自某个客户端的数据,所有应该子线程创建一个子进程调用exec系列的函数执行命令。
    • 返回数据:进程之间具有独立性,我们选择使用匿名管道进行进程间通信。
    • 安全的命令可以通过创建一个配置文件,然后创建命令对象时,将配置文件加载进集合(文件IO慢),如果命令前缀在集合中就执行,反之直接返回提示信息。

上述两个步骤都不需要我们自己去实现,C语言库中提供了一个这样的函数,可以帮我们完成上述功能,我们来介绍这个函数:

popen系列函数
FILE *popen(const char *command, const char *type)
  • 函数功能popen 是一个 C 库函数,提供了一种简单的方式来创建一个管道(pipe),并启动一个子进程以执行外部命令。popen 允许父进程与子进程之间进行单向通信。

  • 参数

    • const char *command:你需要执行的命令及其选项,例如:

      ls -l
      
    • const char *type

      • "r": 打开管道用于读取子进程的标准输出。

      • "w": 打开管道用于写入子进程的标准输入。

  • 返回值

    • 成功:返回一个指向管道文件的 FILE 指针。

    • 失败:返回 NULL

  • 头文件<stdio.h>

int pclose(FILE *stream);
  • 功能

    • 关闭管道文件。
    • 回收子进程,防止它变成僵尸进程。
  • 参数

    • FILE *stream:由 popen 返回的指向管道的 FILE 指针。
  • 返回值

    • 成功: 返回子进程的退出状态(以 waitpid 的方式返回,可以通过 WEXITSTATUS 宏提取退出码)。

    • 失败: 返回 -1,并设置 errno

  • 头文件<stdio.h>

客户端模块

和echo功能中的客户端模块基本一致。

#include <iostream>
#include <string>
#include <unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>using namespace std;void Usage(char* s)
{cout << "Usage:\n\t" << s << " serverip" << "serverport" << endl;exit(1);
}int main(int argc,char* argv[])
{if(argc != 3){Usage(argv[0]);return 1;}string ip = argv[1];uint16_t port = stoi(argv[2]);int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建套接字if(sockfd == -1){perror("socket creat error");return 1;}//客户端需要bind,但是不需要我们显示的bind//客户端也不需要listen,监听请求是服务器程序的工作struct sockaddr_in addr;//服务器的地址信息addr.sin_family = AF_INET;addr.sin_port = htons(port);inet_pton(AF_INET,ip.c_str(),&addr.sin_addr.s_addr);if(connect(sockfd,(struct sockaddr*)&addr,sizeof(addr)) == -1){perror("connect error");return 1;}while(true){string message;cout << "please input message:";getline(cin,message);//获取用户输入的信息,一行一行的获取send(sockfd,message.c_str(),message.size()+1,0);char buffer[1024];memset(buffer,0,sizeof(buffer));int n = recv(sockfd,buffer,sizeof(buffer),0);if(n == -1){perror("recv error");break;}else if(n == 0){cout << "server close" << endl;break;}else{buffer[n] = 0;cout << buffer;}}close(sockfd);return 0;
}

服务器主程序

服务器主程序将命令处理函数bind并传给服务器类的成员,以供子线程调用该方法。

#include"TcpServer.hpp"  // 引入自定义的头文件,用于创建并管理服务器
#include<memory>  // 引入内存管理的头文件,用于创建智能指针
#include"ExecuteCommand.hpp"  // 引入自定义的头文件,用于执行命令
#include<functional>  // 引入函数式编程的头文件,用于创建函数对象void Usage(char* s)
{cout << "Usage:\n\t" << s << " serverport" << endl;  // 打印程序的命令行使用格式,提示用户输入端口号exit(USAGEERROR);
}int main(int argc,char* argv[])
{// 判断命令行参数是否正确if(argc != 2)  // 程序需要接收一个参数:端口号{Usage(argv[0]);  // 如果参数不为2,则调用Usage函数打印使用说明return 1;  // 退出程序,返回错误码1}// 将命令行参数转为端口号(uint16_t类型),这是服务器监听的端口号uint16_t port = stoi(argv[1]);  // 使用stoi将字符串转化为整数类型的端口号// 创建TcpServer对象,并初始化unique_ptr<TcpServer> server = make_unique<TcpServer>(port,std::bind(&ExecuteCommand::Execute,ExecuteCommand()      , placeholders::_1      ));  // 使用从命令行获得的端口号创建TcpServer实例server->InitServer();  // 初始化服务器,进行绑定等操作server->Loop();  // 启动服务器,开始接收和处理客户端请求return 0;
}

windows作为客户端与服务器通信

我们修改一下客户端的echo代码即可,windows中的网络库与Linux上的有一些差异,在Udp通信的时候已经介绍过了:

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <string.h>
#include<string>
#pragma comment(lib, "ws2_32.lib")  // 自动链接 Winsock 库using namespace std;const string ip = "47.98.179.70";
const uint16_t port = 8080;int main()
{// 初始化 Winsock 库WSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)  // 以指定版本初始化{cerr << "WSAStartup failed" << endl;return 1;}// 创建套接字,使用IPv4地址族和TCP协议(SOCK_STREAM表示流式套接字)int sockfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建TCP套接字if (sockfd == INVALID_SOCKET)  // 如果创建套接字失败,打印错误并返回{cerr << "Socket creation failed: " << WSAGetLastError() << endl;WSACleanup();return 1;}// 设置服务器的地址信息struct sockaddr_in addr;  // sockaddr_in结构体用于存储服务器的网络地址addr.sin_family = AF_INET;  // 使用IPv4地址族addr.sin_port = htons(port);  // 设置服务器的端口号(htons将端口号转换为网络字节序)inet_pton(AF_INET, ip.c_str(), &addr.sin_addr.s_addr);  // 将IP地址字符串转换为网络字节序的二进制格式// 连接到服务器if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)  // 调用connect连接服务器{cerr << "Connect failed: " << WSAGetLastError() << endl;  // 如果连接失败,输出错误信息closesocket(sockfd);WSACleanup();return 1;}// 客户端和服务器之间进行通信while (true){string message;cout << "Please input message: ";  // 提示用户输入消息getline(cin, message);  // 从标准输入获取一行字符串作为消息// 将输入的消息发送到服务器int bytesSent = send(sockfd, message.c_str(), message.size() + 1, 0);  // 发送消息到服务器,+1用于包括消息结尾的'\0'if (bytesSent == SOCKET_ERROR)  // 如果发送失败{cerr << "Send failed: " << WSAGetLastError() << endl;break;}// 接收服务器返回的消息char buffer[1024];  // 定义接收缓冲区,大小为1024字节memset(buffer, 0, sizeof(buffer));  // 将缓冲区初始化为0int n = recv(sockfd, buffer, sizeof(buffer), 0);  // 从服务器接收数据if (n == SOCKET_ERROR)  // 如果接收数据失败{cerr << "Recv failed: " << WSAGetLastError() << endl;  // 输出错误信息break;  // 跳出循环,关闭连接}else if (n == 0)  // 如果服务器关闭了连接{cout << "Server closed the connection." << endl;  // 打印提示信息break;  // 跳出循环,结束通信}else  // 数据接收成功{buffer[n] = 0;  // 确保接收到的数据是一个合法的C字符串(添加终止符'\0')cout << "Server: " << buffer << endl;  // 输出服务器返回的消息}}// 关闭套接字,结束与服务器的通信closesocket(sockfd);  // Windows 关闭套接字时使用 closesocket()WSACleanup();  // 清理 Winsock 库return 0;  // 程序正常结束
}

运行结果:

image-20250117173147867

#pragma comment介绍

这是一条预处理指令,是Microsoft Visual C++ 编译器MSVC的扩展指令,它的功能和gcc/g++中的-l选项类似,可以告诉编译器要链接的库的名称。

MSVC(Microsoft Visual C++)是由微软开发的一款集成开发环境(IDE)和编译器工具套件,用于开发基于 CC++ C++/CLI 的应用程序。

客户端使用状态机断线重连

状态机是一种用于描述系统行为数学模型,它的核心是状态,它通过定义不同的状态让系统执行不同的操作,且执行这些操作时得到的结果会更新状态。

代码实现

#include<iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include<memory>
#include<string>
#include<string.h>// 枚举类型定义退出码
enum Exitcode
{USAGEERR = 1,  // 命令输入错误SOCKETERR,     // 创建 socket 失败INET_PTONERR   // 地址转换失败
};// 枚举类型定义连接状态
enum Status
{NEW,            // 新建连接的状态CONNECTED,      // 已连接状态(连接或重连成功)DISCONNECTED,   // 连接失败的状态CONNECTEDING,   // 正在连接的状态CLOSED          // 经历重连,但是失败了
};// 常量定义
const int defaultsocketfd = -1;  // 默认 socket 文件描述符
const int maxreconnectcount = 5; // 最大重连次数
const int defaultinterval = 2;   // 每次重连的时间间隔(秒)// Clientconnect 类:封装与服务器的连接管理和通信
class Clientconnect
{
public:// 构造函数,初始化连接信息,包括 IP,端口,最大重连次数和重连间隔Clientconnect(int16_t port, std::string ip, int maxcount = maxreconnectcount, int interval = defaultinterval):_port(port), _ip(ip), _maxcount(maxcount), _interval(interval), _socketfd(defaultsocketfd), _status(Status::NEW){}// 连接服务器void Connect(){// 1. 创建 socket_socketfd = socket(AF_INET, SOCK_STREAM, 0);if (_socketfd < 0){std::cerr << "create socket failed" << std::endl;exit(Exitcode::SOCKETERR);}struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(_port); // 设置端口if (inet_pton(AF_INET, _ip.c_str(), &addr.sin_addr.s_addr) <= 0)  // 地址转换{std::cerr << "inet_pton error" << std::endl;exit(Exitcode::INET_PTONERR);}// 尝试连接int n = connect(_socketfd, (struct sockaddr*)&addr, sizeof(addr));if (n < 0){Close();  // 连接失败,关闭文件描述符_status = Status::DISCONNECTED;  // 设置为连接失败状态return;}_status = Status::CONNECTED;  // 连接成功}// 尝试重连void Reconnect(){_status = Status::CONNECTEDING;  // 设置为正在连接状态int cnt = 0;while (true){cnt++;std::cout << "正在重连中... 重连次数: " << cnt << std::endl;Connect();  // 尝试重新连接// 如果连接成功,跳出循环if (_status == Status::CONNECTED){std::cout << "重连成功!" << std::endl;break;}// 如果超过最大重连次数,退出if (cnt >= _maxcount){_status = Status::CLOSED;  // 设置为关闭状态std::cout << "已达最大重连次数..." << std::endl;break;}sleep(_interval);  // 重连间隔}}// 基本的 I/O 处理void Process(){while (true){std::cout << "client#";std::string message = "hello server!!";int n = send(_socketfd, message.c_str(), message.size(), 0);  // 发送数据if (n < 0){std::cerr << "send error" << std::endl;_status = Status::CLOSED;  // 发送失败,关闭连接}if (n > 0){char buffer[1024] = {0};n = recv(_socketfd, buffer, sizeof(buffer), 0);  // 接收数据if (n <= 0){std::cerr << "recv error" << std::endl;Close();_status = Status::DISCONNECTED;  // 接收失败,设置为断开连接break;}buffer[n] = 0;std::cout << "server#" << buffer << std::endl;}sleep(1);}}// 关闭连接void Close(){if (_socketfd > 0){close(_socketfd);  // 关闭 socket_status = Status::CLOSED;  // 设置为关闭状态_socketfd = -1;}}// 获取当前连接的状态Status status(){return _status;}private:std::string _ip;uint16_t _port;int _socketfd;int _maxcount;  // 最大重连次数int _interval;  // 重连间隔Status _status; // 当前连接状态
};// 客户端类:负责管理客户端连接的生命周期
class Client
{
public:// 构造函数Client(int16_t port, std::string ip): _connect(port, ip){}// 执行客户端操作void Excute(){while (true){switch (_connect.status())  // 根据状态执行不同的操作{case Status::NEW:            // 初始状态,尝试连接_connect.Connect();break;case Status::CONNECTED:      // 已连接状态,处理数据_connect.Process();break;case Status::DISCONNECTED:   // 连接失败,尝试重连_connect.Reconnect();break;case Status::CLOSED:         // 已关闭,退出_connect.Close();return;default:break;}}}~Client() {}private:Clientconnect _connect;  // 客户端连接对象
};// 输出程序用法
void Usage(const std::string& process)
{std::cout << process << " serverip serverport" << std::endl;exit(Exitcode::USAGEERR);
}// 程序入口
int main(int argc, char* argv[])
{if (argc != 3){Usage(argv[0]);}int16_t port = std::stoi(argv[2]);std::string ip = argv[1];std::unique_ptr<Client> client = std::make_unique<Client>(port, ip);client->Excute();return 0;
}
  • 状态说明

    • NEW:初始状态,处于这个状态的客户端,还未尝试连接服务器。
    • CONNECTED:连接服务器成功,处于这个状态的客户端,即将开始IO通信。
    • CONNECTING:正在连接服务器的状态,这个状态一般是瞬时状态,发生在开始连接了,但是还未连接成功。
    • DISCONNECTED:连接失败的状态,处于这个状态的服务器将会开始重连逻辑。
    • CLOSED:客户端套接字关闭,一般发生在重连失败后。
  • 状态机执行过程

    image-20250119153335641

运行结果

我们编译客户端程序,并在本地云服务器创建一个具有echo功能的服务器程序,测试如下情况:

  1. 服务器未运行,客户端运行起来了。
    • 连接肯定是失败的,进入重连逻辑。
  2. 服务器运行起来后,关闭服务器。
    • 一开始连接是成功的,服务器和客户端可以正常的通信。但是服务器不再运行后,客户端又开始重连,未达到重连次数时,又运行起服务器就可以重连成功。否则重连失败。

屏幕录制-2025-01-18-183857

相关文章:

【网络编程】之TCP实现客户端远程控制服务器端及断线重连

【网络编程】之TCP实现客户端远程控制服务器端及断线重连 TCP网络通信实现客户端简单远程控制主机基本功能演示通信过程代码实现服务器模块执行命令模块popen系列函数 客户端模块服务器主程序 windows作为客户端与服务器通信#pragma comment介绍 客户端使用状态机断线重连代码实…...

云原生容器编排:构建智能弹性应用的自动化引擎

引言&#xff1a;重构应用部署范式 Google Borg系统管理着超2500万容器实例&#xff0c;每日处理200亿个任务。阿里巴巴双十一使用Kubernetes实现300万Pod秒级弹性&#xff0c;资源利用率达65%。CNCF 2023报告显示全球Kubernetes生产采用率突破92%&#xff0c;CRI-O容器启动速…...

centos虚拟机安装

以下是一个详细的 VMware CentOS 虚拟机安装教程&#xff0c;结合了最新的信息和步骤&#xff1a; 一、准备工作 1. 下载 VMware 软件 访问 VMware 官方网站&#xff1a;VMware Workstation 官网。点击“现在安装”并下载适合您操作系统的 VMware Workstation。 2. 下载 Ce…...

社会力模型:Social force model for pedestrian dynamics

Social Force Model——社会力模型-CSDN博客 简介&#xff1a; 时间&#xff1a;1995 期刊&#xff1a;《Physical Review E》 作者&#xff1a;Dirk Helbing and Peter Molnar 摘要&#xff1a; 提出一种描述行人运动的“社会力模型”。认为行人的运动可看作是受到一系列…...

机器学习数学通关指南

✨ 写在前面 &#x1f4a1; 在代码的世界里沉浸了十余载&#xff0c;我一直自诩逻辑思维敏捷&#xff0c;编程能力不俗。然而&#xff0c;当我初次接触 DeepSeek-R1 并领略其清晰、系统的思考过程时&#xff0c;我不禁为之震撼。那一刻&#xff0c;我深刻意识到&#xff1a;在A…...

【Mac】2025-MacOS系统下常用的开发环境配置

早期版本的一个环境搭建参考 1、brew Mac自带终端运行&#xff1a; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" Installation successful!成功后运行三行命令后更新环境&#xff08;xxx是mac的username&a…...

# C# 中堆(Heap)与栈(Stack)的区别

在 C# 中&#xff0c;堆和栈是两种不同的内存分配机制&#xff0c;它们在存储位置、生命周期、性能和用途上存在显著差异。理解堆和栈的区别对于优化代码性能和内存管理至关重要。 1. 栈&#xff08;Stack&#xff09; 1.1 定义 栈是一种后进先出&#xff08;LIFO&#xff0…...

ubuntu离线安装nvidia-container-runtime

参考文章 ubuntu系统docker20.4版本安装nvidia-container-runtime3.11.0-1版本(离线安装nvidia-docker) - jokerMM - 博客园 https://zhuanlan.zhihu.com/p/15194336245 一、软件地址 Index of /nvidia-docker/libnvidia-container/stable/ 从上述地地址——进入对应系统—…...

用Python+Flask打造可视化武侠人物关系图生成器:从零到一的实战全记录

用PythonFlask打造可视化武侠人物关系图生成器&#xff1a;从零到一的实战全记录 一、缘起&#xff1a;一个程序小白的奇妙探索之旅 作为一个接触Python仅13天的编程萌新&#xff0c;我曾以为开发一个完整的应用是遥不可及的事情。但在DeepSeek的帮助下&#xff0c;我竟用短短…...

学习笔记-DeepSeek在开源第四天发布DualPipe和EPLB两项技术

在AI模型训练的进程中&#xff0c;优化并行策略对于提升训练效率和资源利用率至关重要。DeepSeek在开源周第四天发布的DualPipe和EPLB两项技术&#xff0c;为V3/R1训练场景下的并行优化提供了创新解决方案。 DualPipe&#xff1a;双向管道并行算法 技术原理&#xff1a; Dua…...

C++入门基础知识1

今天&#xff0c;我们正式来学习C&#xff0c;由于C是在C的基础之上&#xff0c;容纳进去了面向对象编程思想&#xff0c;并增加了许多有用的库&#xff0c;以及编程范式等。熟悉C语言之后&#xff0c;对C学习有一定的帮助。 现在我们这篇主要是&#xff1a; 1. 补充C语言语法…...

神经网络AI原理回顾

长期记忆存储在大模型的参数权重中&#xff0c;不经过推理和编码无法读取&#xff0c;且必须依赖输入的提示&#xff0c;因为大模型不会无缘无故的自言自语&#xff0c;毕竟输入层是它唯一 与外界交互的窗口。 目前个性化大模型的局限就是训练成本过高&#xff0c;除非使用RAG&…...

PPT 小黑第38套

对应大猫40 幻灯片母板-最后一页-重命名为奇数页 奇偶页-点中标题-形状格式-形状填充-青色 最后一页页码左对齐 更换幻灯片背景&#xff1a;设计-设置背景格式-图片填充 【开始】-段落居中&#xff0c;对齐文本-中部对齐&#xff0c;排列-对齐-底端&#xff0c;-再水平居中…...

主时钟与虚拟时钟约束

1、主时钟约束 1.1、主时钟约束语法&#xff1a; create_clock -name< clock_name > -period <period> -waveform{ <rise_time> <fall_time> } [get_ports< port_name >] 说明&#xff1a; name 之后的<clock_name> 是clk 的name&a…...

K8S学习之基础六:k8s中pod亲和性

Pod节点亲和性和反亲和性 podaffinity&#xff1a;pod节点亲和性指的是pod会被调度到更趋近与哪个pod或哪类pod。 podunaffinity&#xff1a;pod节点反亲和性指的是pod会被调度到远离哪个pod或哪类pod 1. Pod节点亲和性 requiredDuringSchedulingIgnoredDuringExecution&am…...

如何通过rust实现自己的web登录图片验证码

在进行web系统开发时&#xff0c;为保障系统登录安全&#xff0c;登录页面中的验证码必不可少。在java中&#xff0c;我们可以利用相应的2D图像库快速生成图形验证码&#xff0c;而对于rust&#xff0c;我们没有合适的标准库进行图像验证码的生成。今天&#xff0c;我们通过使用…...

盛京开源社区加入 GitCode,书写东北开源生态新篇章

在数字化转型与开源技术蓬勃发展的浪潮下&#xff0c;开源社区已成为推动技术创新的核心力量。盛京开源社区&#xff08;SJOSC&#xff09;作为沈阳地区的开源交流平台&#xff0c;始终致力于连接开发者、企业及高校&#xff0c;构建区域技术生态圈。 现在&#xff0c;盛京开源…...

QKV 注意力机制在Transformer架构中的作用,和卷积在卷积神经网络中的地位,有哪些相似之处?

QKV 注意力机制在Transformer架构中的作用&#xff0c;和卷积在卷积神经网络中的地位&#xff0c;有哪些相似之处&#xff1f; QKV&#xff08;Query-Key-Value&#xff09;注意力机制在Transformer架构和卷积在卷积神经网络&#xff08;CNN&#xff09;中都起着核心作用&…...

高效与高并发API开发:使用FastAPI与Redis实现请求限制与速率控制

高效与高并发API开发&#xff1a;使用FastAPI与Redis实现请求限制与速率控制 &#x1f4da; 目录 API速率限制的基本概念Redis实现分布式速率限制防止DDoS攻击的常见策略基于IP或用户身份的访问频率控制 1. API速率限制的基本概念 API速率限制&#xff08;Rate Limiting&…...

Centos7源码编译安装Sqlite最新版本

下载源码 https://www.sqlite.org/download.html 复制下载链接&#xff0c;然后用 wget 下载 wget https://www.sqlite.org/2025/sqlite-autoconf-3490100.tar.gz 解压缩编译安装 tar -zxf sqlite-autoconf-3490100.tar.gz cd sqlite-autoconf-3490100 ./configure --prefi…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

HashMap中的put方法执行流程(流程图)

1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中&#xff0c;其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下&#xff1a; 初始判断与哈希计算&#xff1a; 首先&#xff0c;putVal 方法会检查当前的 table&#xff08;也就…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

CSS3相关知识点

CSS3相关知识点 CSS3私有前缀私有前缀私有前缀存在的意义常见浏览器的私有前缀 CSS3基本语法CSS3 新增长度单位CSS3 新增颜色设置方式CSS3 新增选择器CSS3 新增盒模型相关属性box-sizing 怪异盒模型resize调整盒子大小box-shadow 盒子阴影opacity 不透明度 CSS3 新增背景属性ba…...