04.基于C++实现多线程TCP服务器与客户端通信

基于C++实现多线程TCP服务器与客户端通信

目录

  • 一、项目背景与目标
  • 二、从零开始理解网络通信
  • 三、相关技术背景知识
    • 1. 守护进程(Daemon Process)
    • 2. 线程池(Thread Pool)
    • 3. RAII设计模式
  • 四、项目整体结构与逻辑
  • 五、核心模块详细分析
    • 1. TCP服务器模块
    • 2. 线程池模块
    • 3. 任务处理模块
    • 4. 日志模块
    • 5. 守护进程模块
    • 6. 锁管理模块
  • 六、从实践到理论:关键设计模式与技术
  • 七、进阶主题与扩展思考
  • 八、总结与展望

一、项目背景与目标

在网络编程中,TCP协议因其可靠性和稳定性被广泛应用于各类网络服务。本项目使用C++语言,基于Linux平台实现了一个完整的TCP服务器与客户端通信程序,服务器端采用了线程池技术实现高效并发处理,支持守护进程运行,并实现了完整的日志系统。

本项目的目标是:

  • 掌握TCP协议的基本编程方法
  • 掌握线程池的设计与实现
  • 学习守护进程的创建与管理
  • 掌握日志系统的设计与实现
  • 理解RAII设计模式在资源管理中的应用

二、从零开始理解网络通信

网络通信的本质

想象一下,当你给朋友发送一条短信时,这条信息是如何从你的手机传递到朋友的手机的?这个过程涉及:

  1. 你的手机将信息编码
  2. 通过无线信号发送到基站
  3. 基站将信息路由到目标手机
  4. 目标手机接收并解码信息

计算机网络通信也遵循类似的原理,只是更加复杂和规范化。TCP/IP协议就像是计算机之间沟通的"语言规则",确保信息能够正确传递。

套接字(Socket):网络通信的基础

套接字可以理解为网络通信的"插座",就像家里的电源插座连接电器一样,套接字连接网络中的应用程序。

应用程序 <---> 套接字 <---> 网络 <---> 套接字 <---> 应用程序

在我们的项目中:

// 创建套接字
_sock = socket(AF_INET, SOCK_STREAM, 0);

这行代码就像是安装了一个"网络插座",其中:

  • AF_INET表示使用IPv4地址
  • SOCK_STREAM表示使用TCP协议
  • 0表示使用默认协议

三、相关技术背景知识

1. 守护进程(Daemon Process):服务器的"隐形模式"

想象一下,如果你的手机应用必须保持前台运行才能接收消息,那将是多么不便!守护进程就像是手机的"后台应用",即使你关闭了终端窗口,它仍然在默默工作。

守护进程的创建过程可以类比为一个员工的"独立":

  1. 创建子进程并退出父进程:就像员工从公司分离出来成立自己的工作室
  2. 创建新会话:员工不再接受原公司的直接管理
  3. 重定向输入输出:员工建立了自己的沟通渠道
  4. 更改工作目录:员工搬到了新的办公地点
// 创建守护进程的关键步骤
if (fork() > 0) exit(0);  // 父进程退出
pid_t n = setsid();       // 创建新会话

2. 线程池(Thread Pool):高效的"工作团队"

想象一家餐厅:

  • 如果每来一位客人就雇佣一名新服务员,成本会非常高
  • 如果只有一名服务员,客人可能需要长时间等待
  • 最佳方案是维持一个固定数量的服务员团队,随时准备服务新客人

线程池就是这样的"服务员团队":

  • 预先创建多个线程,等待任务分配
  • 当新任务到来时,从线程池中分配一个空闲线程处理
  • 任务完成后,线程返回池中等待下一个任务
// 线程池的核心:等待并处理任务
while (true) {T t;{LockGuard lockguard(td->threadpool->mutex());while (td->threadpool->isQueueEmpty()) {td->threadpool->threadWait();  // 等待新任务}t = td->threadpool->pop();  // 获取任务}t();  // 执行任务
}

3. RAII(Resource Acquisition Is Initialization):智能资源管理

RAII就像是一个自动化的"资源管家"。想象你去图书馆:

  • 进门时,你借了一本书(获取资源)
  • 离开时,你必须归还这本书(释放资源)
  • 如果你忘记归还,图书馆会有麻烦

RAII确保:

  • 当你"进门"(创建对象)时,自动借书(获取资源)
  • 当你"离开"(对象销毁)时,自动还书(释放资源)
  • 即使发生意外(如异常),也能确保书被归还
// RAII的典型应用:自动管理锁
{LockGuard lockguard(&_mutex);  // 构造时自动加锁_task_queue.push(in);pthread_cond_signal(&_cond);
}  // 离开作用域时自动解锁

四、项目整体结构与逻辑

项目模块关系图

                  +-------------+| tcpServer.cc|+------+------+|v
+----------+      +------+-------+      +-----------+
| daemon.hpp|<-----| tcpServer.hpp|----->|  Task.hpp |
+----------+      +------+-------+      +-----+-----+|                    |v                    v+------+-------+     +------+------+|ThreadPool.hpp|<----|serviceIO()  |+------+-------+     +-------------+|v+------+-------+|  Thread.hpp  |+------+-------+|v+------+-------+| LockGuard.hpp|+-------------+

项目整体运行流程

想象一个餐厅的运作流程:

  1. 餐厅开业(服务器启动):

    • 准备场地(创建套接字)
    • 挂出营业牌(绑定端口)
    • 组建服务团队(初始化线程池)
    • 开始迎接客人(监听连接)
  2. 客人到来(客户端连接):

    • 服务员引导入座(accept接受连接)
    • 分配一名服务员(从线程池分配线程)
    • 开始点餐服务(处理客户端请求)
  3. 服务过程(数据交换):

    • 客人点餐(客户端发送数据)
    • 服务员记录并确认(服务器处理并回应)
    • 上菜(服务器返回结果)
  4. 就餐结束(连接关闭):

    • 客人离开(客户端断开连接)
    • 服务员清理桌面(关闭socket)
    • 准备服务下一位客人(线程返回池中)

五、核心模块详细分析

1. TCP服务器模块 (tcpServer.hpptcpServer.cc)

设计思路:建立通信的"桥梁"

TCP服务器就像是一个电话总机,负责接听来电并将其转接给合适的接线员。其主要职责是:

  • 创建通信渠道(套接字)
  • 公布联系方式(绑定地址和端口)
  • 等待来电(监听连接)
  • 接听并转接(接受连接并提交给线程池)
关键代码解析
void initServer() {// 1. 创建通信渠道_listensock = socket(AF_INET, SOCK_STREAM, 0);// 2. 绑定地址和端口(公布联系方式)struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;bind(_listensock, (struct sockaddr *)&local, sizeof(local));// 3. 开始监听(等待来电)listen(_listensock, gbacklog);
}void start() {// 4. 准备接线员团队(初始化线程池)ThreadPool<Task>::getInstance()->run();for (;;) {// 5. 接听来电struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(_listensock, (struct sockaddr *)&peer, &len);// 6. 转接给接线员(提交任务到线程池)ThreadPool<Task>::getInstance()->push(Task(sock, serviceIO));}
}
实现要点与技巧
  • 错误处理的重要性:网络编程中,各种意外情况都可能发生(端口被占用、连接突然断开等)。良好的错误处理能让程序更加健壮。

  • 为什么使用INADDR_ANY:使用INADDR_ANY(值为0.0.0.0)允许服务器监听所有网络接口,无论客户端从哪个网卡连接都能接受。

  • backlog参数的意义listen(_listensock, gbacklog)中的gbacklog表示等待连接队列的最大长度。当连接请求过多时,超过这个值的连接会被拒绝。

2. 线程池模块 (ThreadPool.hppThread.hpp)

设计思路:高效的"工作团队"

线程池就像一个高效的工作团队:

  • 预先组建团队(创建线程)
  • 统一分配任务(任务队列)
  • 团队成员互相协作(线程同步)
  • 避免重复招聘(线程复用)
关键代码解析
// 线程的工作循环
static void *handlerTask(void *args) {ThreadData<T> *td = (ThreadData<T> *)args;while (true) {T t;{// 1. 等待任务分配LockGuard lockguard(td->threadpool->mutex());while (td->threadpool->isQueueEmpty()) {td->threadpool->threadWait();  // 没有任务时等待}// 2. 领取任务t = td->threadpool->pop();}// 3. 执行任务t();}return nullptr;
}// 添加新任务
void push(const T &in) {// 1. 锁定任务队列LockGuard lockguard(&_mutex);// 2. 添加任务_task_queue.push(in);// 3. 通知等待的线程pthread_cond_signal(&_cond);
}
实现要点与技巧
  • 为什么使用条件变量:条件变量允许线程在特定条件满足前进入睡眠状态,避免了忙等待(不断检查条件)带来的CPU资源浪费。

  • 单例模式的优势:整个程序只需要一个线程池实例,单例模式确保了资源的统一管理,避免了重复创建带来的开销。

  • 双重检查锁定:在getInstance()方法中使用双重检查锁定,既保证了线程安全,又避免了每次获取实例都加锁带来的性能损失。

  • 模板设计的灵活性:使用模板设计线程池,使其能够处理不同类型的任务,提高了代码的复用性。

3. 任务处理模块 (Task.hpp)

设计思路:统一的任务接口

任务处理模块就像是一个标准化的"工作指南":

  • 定义了工作内容(处理客户端连接)
  • 提供了统一的执行方式(operator())
  • 封装了具体实现细节(回调函数)
关键代码解析
// 具体的任务处理函数
void serviceIO(int sock) {char buffer[1024];while (true) {// 1. 接收客户端数据ssize_t n = read(sock, buffer, sizeof(buffer) - 1);if (n > 0) {// 2. 处理数据buffer[n] = 0;std::cout << "recv message: " << buffer << std::endl;// 3. 准备响应std::string outbuffer = buffer;outbuffer += " server[echo]";// 4. 发送响应write(sock, outbuffer.c_str(), outbuffer.size());}else if (n == 0) {// 5. 客户端断开连接logMessage(NORMAL, "client quit, me too!");break;}}// 6. 关闭连接close(sock);
}// 任务封装类
class Task {using func_t = std::function<void(int)>;public:Task(int sock, func_t func): _sock(sock), _callback(func) {}// 统一的任务执行接口void operator()() {_callback(_sock);}private:int _sock;func_t _callback;
};
实现要点与技巧
  • 为什么使用std::functionstd::function提供了一种类型安全的函数封装,可以存储、复制和调用任何可调用目标(函数、lambda表达式、函数对象等)。

  • 为什么重载operator():重载operator()使Task对象可以像函数一样被调用,符合线程池对任务的要求,同时提供了更清晰的接口。

  • read/write vs recv/send:本项目使用read/write而非recv/send,因为前者更符合Unix “一切皆文件” 的哲学,可以统一处理文件、管道、套接字等I/O操作。

  • 为什么接收用char[]而发送用string

    • 接收数据时使用固定大小的char[]缓冲区,可以直接与系统调用配合,避免动态内存分配
    • 发送数据时使用string,便于字符串操作(如拼接)
    • 最后通过c_str()转换回C风格字符串进行发送

4. 日志模块 (log.hpp)

设计思路:系统的"黑匣子"

日志系统就像飞机的黑匣子,记录系统运行的各种状态和事件:

  • 不同级别的日志(从调试信息到致命错误)
  • 详细的时间和上下文信息
  • 持久化存储,便于后期分析
关键代码解析
void logMessage(int level, const char *format, ...) {// 1. 构建日志前缀char logprefix[NUM];snprintf(logprefix, sizeof(logprefix), "[%s][%ld][pid: %d]",to_levelstr(level), (long int)time(nullptr), getpid());// 2. 处理可变参数char logcontent[NUM];va_list arg;va_start(arg, format);vsnprintf(logcontent, sizeof(logcontent), format, arg);va_end(arg);// 3. 选择日志文件FILE *log = fopen(LOG_NORMAL, "a");FILE *err = fopen(LOG_ERR, "a");if(log != nullptr && err != nullptr) {FILE *curr = nullptr;if(level <= WARNING) curr = log;else curr = err;// 4. 写入日志if(curr) fprintf(curr, "%s%s\n", logprefix, logcontent);fclose(log);fclose(err);}
}
实现要点与技巧
  • 可变参数的处理:使用va_listva_startva_endvsnprintf处理可变参数,实现了类似printf的灵活接口。

  • 日志分级的意义

    • DEBUG:详细的调试信息,帮助开发者理解程序流程
    • NORMAL:正常操作信息,记录系统的正常活动
    • WARNING:警告信息,表示可能的问题但不影响主要功能
    • ERROR:错误信息,表示功能受到影响但系统仍能运行
    • FATAL:致命错误,表示系统无法继续运行
  • 为什么分文件存储:将普通日志和错误日志分开存储,便于快速定位问题,同时避免重要的错误信息被大量普通日志淹没。

  • 时间戳和进程ID:记录时间戳和进程ID,便于在多进程环境下追踪问题,确定事件发生的顺序。

5. 守护进程模块 (daemon.hpp)

设计思路:服务器的"隐形模式"

守护进程就像是系统的"隐形服务员":

  • 脱离用户控制(不依赖终端)
  • 在后台默默工作(不显示输出)
  • 长期稳定运行(不受用户登录状态影响)
关键代码解析
void daemonSelf(const char *currPath = nullptr) {// 1. 忽略管道破裂信号signal(SIGPIPE, SIG_IGN);// 2. 创建子进程,父进程退出if (fork() > 0)exit(0);// 3. 创建新会话,脱离控制终端pid_t n = setsid();assert(n != -1);// 4. 重定向标准输入输出int fd = open(DEV, O_RDWR);if(fd >= 0) {dup2(fd, 0);  // 标准输入dup2(fd, 1);  // 标准输出dup2(fd, 2);  // 标准错误close(fd);}// 5. 更改工作目录if(currPath) chdir(currPath);
}
实现要点与技巧
  • 为什么忽略SIGPIPE信号:当写入一个已关闭的管道或套接字时,系统会发送SIGPIPE信号,默认处理是终止进程。忽略此信号可以防止服务器因客户端异常断开而崩溃。

  • 为什么使用fork():使用fork()创建子进程,然后父进程退出,使子进程成为孤儿进程,被init进程收养,从而脱离原来的控制终端。

  • setsid()的作用setsid()创建一个新的会话,使进程成为会话首进程,没有控制终端,不会接收终端相关的信号。

  • 为什么重定向到/dev/null:重定向标准输入输出到/dev/null,确保进程不会因为读写终端而阻塞,同时避免输出信息干扰系统运行。

6. 锁管理模块 (LockGuard.hpp)

设计思路:自动化的"资源管家"

锁管理模块就像是一个自动化的门禁系统:

  • 进入区域时自动上锁(构造函数中加锁)
  • 离开区域时自动解锁(析构函数中解锁)
  • 确保资源安全,避免冲突(线程安全)
关键代码解析
class Mutex {
public:Mutex(pthread_mutex_t *lock_p = nullptr): lock_p_(lock_p) {}void lock() {if(lock_p_) pthread_mutex_lock(lock_p_);}void unlock() {if(lock_p_) pthread_mutex_unlock(lock_p_);}private:pthread_mutex_t *lock_p_;
};class LockGuard {
public:LockGuard(pthread_mutex_t *mutex): mutex_(mutex) {mutex_.lock();  // 构造时自动加锁}~LockGuard() {mutex_.unlock();  // 析构时自动解锁}private:Mutex mutex_;
};
实现要点与技巧
  • RAII的优势:使用RAII模式管理锁资源,无需手动解锁,即使发生异常也能确保锁被释放,避免死锁。

  • 分离Mutex和LockGuard:将Mutex和LockGuard分开实现,提高了代码的复用性和灵活性。Mutex封装了底层锁操作,LockGuard提供了RAII风格的接口。

  • 空指针检查:在lock()和unlock()方法中检查指针是否为空,提高了代码的健壮性,避免空指针异常。

  • 使用示例

    {LockGuard guard(&mutex);  // 进入作用域,自动加锁// 临界区代码...
    }  // 离开作用域,自动解锁
    

六、从实践到理论:关键设计模式与技术

1. 单例模式(Singleton Pattern)

定义:确保一个类只有一个实例,并提供一个全局访问点。

应用:线程池使用单例模式,确保整个程序只有一个线程池实例。

优势

  • 节约系统资源,避免重复创建
  • 提供全局访问点,方便使用
  • 确保所有组件使用同一个实例

实现

static ThreadPool<T> *getInstance() {if (nullptr == tp) {_singlock.lock();if (nullptr == tp) {tp = new ThreadPool<T>();}_singlock.unlock();}return tp;
}

2. 观察者模式(Observer Pattern)的变体

定义:定义对象间的一种一对多依赖关系,使得当一个对象状态改变时,所有依赖于它的对象都会得到通知。

应用:线程池中的条件变量机制实际上是观察者模式的一种变体。

优势

  • 解耦了任务生产者和消费者
  • 支持一对多的通知机制
  • 提高了系统的灵活性

实现

// 生产者(通知者)
void push(const T &in) {LockGuard lockguard(&_mutex);_task_queue.push(in);pthread_cond_signal(&_cond);  // 通知等待的线程
}// 消费者(观察者)
while (td->threadpool->isQueueEmpty()) {td->threadpool->threadWait();  // 等待通知
}

3. 工厂方法模式(Factory Method Pattern)

定义:定义一个创建对象的接口,但由子类决定要实例化的类是哪一个。

应用:Task类使用了工厂方法模式的思想,通过回调函数创建不同的任务处理逻辑。

优势

  • 将对象的创建与使用分离
  • 支持扩展,可以轻松添加新的任务类型
  • 提高了代码的可维护性

实现

Task(int sock, func_t func): _sock(sock), _callback(func) {}void operator()() {_callback(_sock);  // 调用工厂方法创建的处理逻辑
}

七、进阶主题与扩展思考

1. 性能优化

连接池:除了线程池,还可以实现连接池,预先建立和维护一组数据库连接,避免频繁创建和销毁连接的开销。

零拷贝技术:使用sendfile()等系统调用,减少数据在内核空间和用户空间之间的拷贝,提高文件传输效率。

事件驱动模型:使用epollkqueue等I/O多路复用技术,实现高效的事件驱动模型,支持更多并发连接。

2. 安全性考虑

输入验证:对客户端输入进行严格验证,防止缓冲区溢出、SQL注入等攻击。

加密通信:实现SSL/TLS加密,保护数据传输安全。

资源限制:对连接数、请求频率等进行限制,防止DoS攻击。

3. 可扩展性设计

插件系统:设计插件接口,支持动态加载功能模块。

配置中心:实现集中式配置管理,支持动态配置更新。

服务发现:集成服务发现机制,支持分布式部署。

八、总结与展望

本项目实现了一个完整的TCP服务器与客户端通信系统,涵盖了网络编程、多线程编程、线程池、守护进程、日志系统等多个核心知识点。通过模块化设计和面向对象编程,我们构建了一个结构清晰、功能完善的网络服务框架。

从这个项目出发,你可以进一步探索:

  • 实现HTTP/WebSocket等应用层协议
  • 集成数据库访问功能
  • 实现负载均衡和高可用设计
  • 探索异步I/O和协程技术

网络编程是现代软件开发的基础技能,希望这个项目能够帮助你打开网络编程的大门,为你的技术成长提供坚实的基础。

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

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

相关文章

从0到1入门Linux

一、常用命令 ls 列出目录内容 cd切换目录mkdir创建新目录rm删除文件或目录cp复制文件或目录mv移动或重命名文件和目录cat查看文件内容grep在文件中查找指定字符串ps查看当前进程状态top查看内存kill终止进程df -h查看磁盘空间存储情况iotop -o直接查看比较高的磁盘读写程序up…

Nginx负载均衡配置详解:轻松实现高可用与高性能

在现代Web应用中&#xff0c;负载均衡是确保系统高可用性和高性能的关键技术之一。Nginx作为一款高性能的HTTP服务器和反向代理服务器&#xff0c;其负载均衡功能被广泛应用于各种场景。本文将详细介绍如何使用Nginx实现负载均衡配置&#xff0c;帮助开发者轻松应对高并发和大流…

使用chroot预安装软件到ubuntu22中

1、安装依赖 # 安装依赖工具 sudo apt update && sudo apt install -y \ squashfs-tools \ genisoimage \ xorriso \ isolinux \ syslinux-utils \ p7zip-full sudo apt update sudo apt install grub-pc-bin grub-efi-amd64-bin -y # 创建工作目录 mkdir -p ./custom-…

php代码审计工具-rips

代码审计 代码审计就是检查所写的代码中是否有漏洞&#xff0c;检查程序的源代码是否有权限从而被黑客攻击&#xff0c;同时也检查了书写的代码是否规范。通过自动化的审查和人工审查的方式&#xff0c;逐行检查源代码&#xff0c;发现源代码中安全缺陷所造成的漏洞&#xff0…

Docker参数,以及仓库搭建

一。Docker的构建参数 注释&#xff1a; 1.对于CMD&#xff0c;如果不想显示&#xff0c;而是使用交互界面&#xff1a;docker run -ti --rm --name test2 busybox:v5 sh 2.对于CMD&#xff0c;一个交互界面只可以使用一个&#xff0c;如果想多次使用CMD&#xff0c;则用ENTR…

基于Python Django的人脸识别上课考勤系统(附源码,部署)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

基于python实现的疫情数据可视化分析系统

基于python实现的疫情数据可视化分析系统 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;Django框架工具&#xff1a;pycharm、Navicat 系统功能实现 总体设计 系统实现 系统功能模块 系统首页可以查看首页、疫情信息、核酸检测、新闻资讯、个人中心、后…

(十 八)趣学设计模式 之 观察者模式!

目录 一、 啥是观察者模式&#xff1f;二、 为什么要用观察者模式&#xff1f;三、 观察者模式的实现方式四、 观察者模式的优缺点五、 观察者模式的应用场景六、 总结 &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢博主的讲解方式&#xff0c;…

Spring Boot 缓存最佳实践:从基础到生产的完整指南

Spring Boot 缓存最佳实践&#xff1a;从基础到生产的完整指南 引言 在现代分布式系统中&#xff0c;缓存是提升系统性能的银弹。Spring Boot 通过 spring-boot-starter-cache​ 模块提供了开箱即用的缓存抽象&#xff0c;但如何根据业务需求实现灵活、可靠的缓存方案&#xf…

苹果Siri升级遇阻,国行iPhone或将引入阿里、百度AI自救

AI整合进展缓慢 苹果正加速将生成式AI技术整合至Siri&#xff0c;但内部消息称其底层技术研发落后于竞争对手&#xff0c;进展未达预期。 国行iPhone将引入双AI模型 苹果计划在2025年中期为国行iPhone引入AI功能&#xff0c;目前已敲定与 阿里巴巴、百度 合作&#xff0c;用户…

阿里推出全新推理模型(因果语言模型),仅1/20参数媲美DeepSeek R1

阿里Qwen 团队正式发布了他们最新的研究成果——QwQ-32B大语言模型&#xff01;这款模型不仅名字萌萌哒(QwQ)&#xff0c;实力更是不容小觑&#xff01;&#x1f60e; QwQ-32B 已在 Hugging Face 和 ModelScope 开源&#xff0c;采用了 Apache 2.0 开源协议。大家可通过 Qwen C…

TomcatServlet

https://www.bilibili.com/video/BV1UN411x7xe tomcat tomcat 架构图&#xff0c;与 jre&#xff0c;应用程序之前的关系 安装使用 tomcat 10 开始&#xff0c;api 从 javax.* 转为使用 jakarta.*&#xff0c;需要至少使用 jdk 11 cmd 中默认 gbk 编码&#xff0c;解决控制…

JDK ZOOKEEPER KAFKA安装

JDK17下载安装 mkdir -p /usr/local/develop cd /usr/local/develop 将下载的包上传服务器指定路径 解压文件 tar -zxvf jdk-17.0.14_linux-x64_bin.tar.gz -C /usr/local/develop/ 修改文件夹名 mv /usr/local/develop/jdk-17.0.14 /usr/local/develop/java17 配置环境变量…

高考數學。。。

2024上 具体来说&#xff0c;直线的参数方程可以写为&#xff1a; x1t y−t z1t 二、简答题(本大题共5小题&#xff0c;每小题7分&#xff0c;共35分。) 12.数学学习评价不仅要关注结果评价&#xff0c;也要关注过程评价。简要说明过程评价应关注哪几个方面。…

C# 实现鼠标轨迹录制与回放自动化功能(附源码)

在软件自动化测试或者重复性办公任务中&#xff0c;鼠标操作的自动化可以大大减少人工干预&#xff0c;提高工作效率。这里将详细介绍如何使用 C# 实现鼠标轨迹的录制与回放功能&#xff0c;代码结构清晰&#xff0c;具有较强的扩展性。 引用 NuGet 包 在开发这个功能时&…

Nacos 核心功能实战笔记(超详细)

Nacos 核心功能实战笔记 一、Nacos 简介 1. 是什么&#xff1f; 全称&#xff1a;Nacos Naming and Configuration Service定位&#xff1a;阿里巴巴开源的 动态服务发现、配置管理、服务管理平台核心功能&#xff1a;服务注册与发现 统一配置管理 服务健康监测适用场景&…

安装remixd,在VScode创建hardhat

在终端&#xff0c;以管理员身份&#xff0c;cmd 需要科学上网 npm install -g remix-project/remixd 在vscode插件中&#xff0c;安装solidity插件&#xff0c;是暗灰色那款 1.将nodeJs的版本升级至18以上 2.在vscode打开一个新的文件&#xff0c;在终端输入 npx hardhat 3.…

unity pico开发 四 物体交互 抓取 交互层级

文章目录 手部设置物体交互物体抓取添加抓取抓取三种类型抓取点偏移抓取事件抓取时不让物体吸附到手部 射线抓取交互层级 手部设置 为手部&#xff08;LeftHandController&#xff09;添加XRDirInteractor脚本 并添加一个球形碰撞盒&#xff0c;勾选isTrigger,调整大小为0.1 …

CyberRT(apollo) 定时器模块简述及bug分析

timer 模块 timer的定义&#xff0c;cyberrt中timer模块用于设置定时器任务&#xff0c;字面意思&#xff0c;设置设置定时周期及出发频次&#xff08;周期 or oneshot)&#xff0c;到达指定时间时间触发callback time wheel 时钟节拍轮&#xff0c;常见的定时器设计&#x…

java八股文之消息中间件

1.RabbitMQ如何保证消息不丢失 开启生产者确认机制&#xff0c;确保生产者的消息能到达队列开启持久化功能&#xff0c;确保消息未消费前在队列中不会丢失&#xff08;交换机&#xff0c;队列&#xff0c;消息都需要开启持久化功能&#xff09;开启消费者确认机制为auto,由spr…