利用线程池多线程并发实现TCP两端通信交互,并将服务端设为守护进程

文章目录

  • 实现目标
  • 实现步骤
  • 封装日志类
  • 封装线程池
    • 封装线程
    • 封装锁
    • 封装线程池
  • TCP通信的接口和注意事项
    • accept
  • TCP
    • 封装任务
    • 客户端
      • Client.hpp
      • Client.cc
    • 服务端
      • Server.hpp
    • Server.cc
    • 实现效果
  • 守护进程
    • 服务端守护进程化

实现目标

利用线程池多线程并发实现基于TCP通信的多个客户端与服务端之间的交互,客户端发送数据,服务端接收后处理数据并返回。服务端为守护进程

实现步骤

  1. 封装一个记录日志的类,将程序运行的信息保存到文件
  2. 封装线程类、服务端处理任务类以及将锁进行封装,为方便实现线程池
  3. 实现服务端,使服务端能接收客户端所发来的数据,处理数据后返回。服务端采用多线程并发处理
  4. 封装守护进程方法,使服务端为守护进程
  5. 实现客户端,可以向服务端发送数据,并接收到服务端发送回来的数据

封装日志类

将程序运行的信息保存到指定文件,例如创建套接字成功或者失败等信息。以【状态】【时间】【信息】的格式保存。

状态可分为五种:“DEBUG”,“NORMAL”,“WARNING”,“ERROR”,“FATAL”

日志类保存的信息需带有可变参数

#pragma once#include <iostream>
#include <string>
#include <cstdarg>
#include <ctime>
#include <unistd.h>using namespace std;#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4const char *to_levelstr(int level)
{switch (level){case DEBUG:return "DEBUG";case NORMAL:return "NORMAL";case WARNING:return "WARNING";case ERROR:return "ERROR";case FATAL:return "FATAL";default:return nullptr;}
}void LogMessage(int level, const char *format, ...)
{
#define NUM 1024char logpre[NUM];snprintf(logpre, sizeof(logpre), "[%s][%ld][%d]", to_levelstr(level), (long int)time(nullptr), getpid());char line[NUM];// 可变参数va_list arg;va_start(arg, format);vsnprintf(line, sizeof(line), format, arg);// 保存至文件FILE* log = fopen("log.txt", "a");FILE* err = fopen("log.error", "a");if(log && err){FILE *curr = nullptr;if(level == DEBUG || level == NORMAL || level == WARNING) curr = log;if(level == ERROR || level == FATAL) curr = err;if(curr) fprintf(curr, "%s%s\n", logpre, line);fclose(log);fclose(err);}
}

封装线程池

封装线程

将线程的创建,等待封装成类的成员函数。不再需要单个的条用线程库接口,以对象的方式创建。

需要注意:在类里面的线程回调方法必须设为static类型,而静态的方法是不能访问类内成员的,因此传给回调函数的参数需要将整个对象传过去,通过对象来获取类内成员

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
#include <functional>
#include <pthread.h>typedef std::function<void *(void *)> func_t;class Thread
{
private:// 在类内创建线程,想让线程执行对应的方法,需要将方法设置成为staticstatic void *start_routine(void *args) // 类内成员,有缺省参数!{Thread *_this = static_cast<Thread *>(args);return _this->callback();}public:// 构造函数里直接生成线程名,利用静态变量从1开始Thread(){char namebuffer[1024];snprintf(namebuffer, sizeof namebuffer, "thread-NO.%d", threadnum++);_name = namebuffer;}// 线程启动void start(func_t func, void *args = nullptr){_func = func;_args = args;// 由于静态的方法是不能访问类内成员的,// 因此传给回调函数的参数需要将整个对象传过去,通过对象来获取类内成员// 也就是this指针int n = pthread_create(&_tid, nullptr, start_routine, this);assert(n == 0);(void)n;}// 线程等待void join(){int n = pthread_join(_tid, nullptr);assert(n == 0);(void)n;}~Thread(){}void *callback(){return _func(_args);}private:std::string _name; // 类名func_t _func;      // 线程回调函数void *_args;       // 线程回调函数的参数pthread_t _tid;    // 线程idstatic int threadnum; // 线程的编号,为生成线程名
};
// static的成员需在类外初始化
int Thread::threadnum = 1;

封装锁

同样的为了不再需要一直调用系统接口,可以将整个方法封装成类,通过类的对象实现加锁过程

#pragma once#include <iostream>
#include <pthread.h>// 加锁 解锁
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);}~Mutex(){}private:pthread_mutex_t *_lock_p;
};// 锁的类
class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex) : _mutex(mutex){_mutex.lock(); // 在构造函数中进行加锁}~LockGuard(){_mutex.unlock(); // 在析构函数中进行解锁}private:Mutex _mutex;
};

封装线程池

在类里面的线程回调方法必须设为static类型,而静态的方法是不能访问类内成员的,因此传给回调函数的参数需要将整个对象传过去,通过对象来获取类内成员。

线程池需要实现为单例模式:

  1. 第一步就是把构造函数私有,再把拷贝构造和赋值运算符重载delete
  2. 在设置获取单例对象的函数的时候,注意要设置成静态成员函数,因为在获取对象前根本没有对象,无法调用非静态成员函数
  3. 可能会出现多个线程同时申请资源的场景,所以还需要一把锁来保护这块资源,而这把锁也得设置成静态,因为单例模式的函数是静态的
#pragma once#include "Thread.hpp"
#include "log.hpp"
#include "Lock.hpp"
#include <vector>
#include <queue>
#include <mutex>
#include <pthread.h>
#include <unistd.h>using namespace std;// 线程池类定义位于下面,因此属性类想要获取到
// 就必须在前面声明
template <class T>
class ThreadPool;template <class T>
class ThreadData
{
public:ThreadPool<T> *threadpool; // 线程所在的线程池,获取到线程的this指针std::string _name;         // 线程的名字public:ThreadData(ThreadPool<T> *tp, const std::string &name) : threadpool(tp), _name(name){}
};template <class T>
class ThreadPool
{
private:// 线程最终实现的方法static void *handlerTask(void *args){ThreadData<T> *td = (ThreadData<T> *)args;while (true){T t;{LockGuard lockguard(td->threadpool->mutex());while (td->threadpool->isQueueEmpty()){td->threadpool->threadWait();}t = td->threadpool->pop();}t();}delete td;return nullptr;}ThreadPool(const int &num = 10) : _num(num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);for (int i = 0; i < _num; i++){_threads.push_back(new Thread());}}void operator=(const ThreadPool &) = delete;ThreadPool(const ThreadPool &) = delete;public:// 将加锁 解锁 判断任务队列是否为空 和条件变量等待全部封装成类内方法// 方便在线程的回调方法中通过对象直接调用void lockQueue() { pthread_mutex_lock(&_mutex); }void unlockQueue() { pthread_mutex_unlock(&_mutex); }bool isQueueEmpty() { return _task_queue.empty(); }void threadWait() { pthread_cond_wait(&_cond, &_mutex); }// 任务队列删除队头,并返回队头的任务T pop(){T t = _task_queue.front();_task_queue.pop();return t;}pthread_mutex_t *mutex(){return &_mutex;}public:// 让每个线程对象调用其启动函数,并将线程辅助类和最终执行的任务方法传入函数中// 线程的辅助类对象里包含了线程当前线程池对象,也就是可以// 通过辅助类对象可以调用到线程池对象里的成员void run(){for (const auto &t : _threads){ThreadData<T> *td = new ThreadData<T>(this, t->threadname());t->start(handlerTask, td);// 创建成功后打印日志LogMessage(DEBUG, "%s start ...", t->threadname().c_str());}}// 往任务队列里插入一个任务void push(const T &in){LockGuard lockguard(&_mutex);_task_queue.push(in);pthread_cond_signal(&_cond);}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);for (const auto &t : _threads)delete t;}// 实现单例模式static ThreadPool<T> *getInstance(){if (nullptr == tp){_singlock.lock();if (nullptr == tp){tp = new ThreadPool<T>();}_singlock.unlock();}return tp;}private:int _num;//线程的数量std::vector<Thread *> _threads;//线程组std::queue<T> _task_queue;//任务队列pthread_mutex_t _mutex;//锁pthread_cond_t _cond;//条件变量static ThreadPool<T> *tp;static std::mutex _singlock;
};template <class T>
ThreadPool<T> *ThreadPool<T>::tp = nullptr;template <class T>
std::mutex ThreadPool<T>::_singlock;

TCP通信的接口和注意事项

为了实现TCP版的通信,首先来了解一下相关接口和注意事项

  1. TCP需要在通信前先创建链接,因此在TCP没有链接之前其创建的套接字并不是用来通信的,而是用来监听的。一旦创建链接成功后,才会返回一个用来通信的套接字
  2. TCP时面向字节流的,因此其通信就是往文件上IO,因此不用指定的调用某接口去完成,直接用文件接口读写就可以完成

accept

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

这就是用来创建链接的接口

参数一为负责监听的套接字

参数二就是socket的结构体

参数三为结构体的大小

返回值,成功创建链接之后会返回一个值,这个值就是负责通信的套接字,也就是后面利用文件通信的文件描述符

TCP

封装任务

因为上述说到TCP是可以直接使用文件操作来完成通信的,那么也就是说其通信根本就用不到其他的成员了,只需要知道一个套接字即可。那么这个方法就可以不放在类内,因为这就是线程最后的执行目的,因此可以将这个任务单独放到一个头文件中。因为线程池是一个模板类,则可以封装一个任务类。

#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <functional>
#include "log.hpp"// TCP的通信
// 线程的最终执行方法
void ServerIO(int sock)
{char buffer[1024];while (true){ssize_t n = read(sock, buffer, sizeof(buffer) - 1);if (n > 0){// readbuffer[n] = 0;std::cout << "recv message: " << buffer << std::endl;// writestd::string outbuffer = buffer;outbuffer += " server[echo]";write(sock, outbuffer.c_str(), outbuffer.size());}else if (n == 0){// 代表client退出LogMessage(NORMAL, "client quit, me too!");break;}}close(sock);
}// 任务类
// 为了最终执行的方法而服务
class Task
{using func_t = std::function<void(int)>;public:Task(){}Task(int sock, func_t func): _sock(sock), _callback(func){}void operator()(){_callback(_sock);}private:int _sock; // 通信套接字func_t _callback;
};

客户端

客户端不需要显示的绑定端口号,而是由操作系统随机去绑定。TCP的客户端也不需要监听,因为并没有去主动链接客户端,所以不需要accept。TCP的客户端只需要向服务端发起链接请求

Client.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "log.hpp"using namespace std;class Client
{
public:Client(const string &serverip, const uint16_t &port): _serverip(serverip), _port(port), _sock(-1){}void Init(){// 创建套接字_sock = socket(AF_INET, SOCK_STREAM, 0);if (_sock < 0){LogMessage(FATAL, "create socket error");exit(1);}// TCP的客户端也不需要显示绑定端口,让操作系统随机绑定// TCP的客户端也不需要监听,因为并没有去主动链接客户端,所以不需要accept// TCP的客户端只需要向服务端发起链接请求}void start(){// 向服务端发起链接请求struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_serverip.c_str());if (connect(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)LogMessage(ERROR, "connect socket error");// 和服务端通信else{string line;while (1){cout << "Please cin: " << endl;getline(cin, line);// 向服务端写write(_sock, line.c_str(), line.size());// 读服务端返回来的数据char buff[1024];int n = read(_sock, buff, sizeof(buff) - 1);if (n > 0){buff[n] = 0;cout << "接收到的消息为:" << buff << endl;}elsebreak;}}}~Client(){if(_sock >= 0)close(_sock);}private:int _sock;string _serverip;uint16_t _port;
};

Client.cc

#include "Client.hpp"
#include <memory>// 输出命令错误函数
void Usage(string proc)
{cout << "Usage:\n\t" << proc << " local_ip local_port\n\n";
}int main(int argc, char* argv[])
{// 再运行客户端时,输入的指令需要包括主机ip和端口号if(argc != 3){Usage(argv[0]);exit(1);}string serverip = argv[1];uint16_t port = atoi(argv[2]);unique_ptr<Client> client(new Client(serverip, port));client->Init();client->start();return 0;
}

服务端

那么对于服务端而言,必须要显式的去绑定端口号。则创建的套接字并不是负责通信的。创建好套接字和绑定完网络信息后,需要设置创建的套接字为监听状态。和UDP一样,服务端是不能指定IP的.

还需要注意的是:因为封装的线程池是单例模式,所以不需要创建对象,直接调用静态对象去调用类方法即可

步骤可分为:

  1. 创建监听套接字
  2. 绑定网络信息
  3. 设置套接字为监听状态
  4. 获取链接,得到通信的套接字
  5. 通信
  6. 关闭不需要的套接字

Server.hpp

#pragma once#include "Task.hpp"
#include "ThreadPool.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>class Server
{
public:Server(const uint16_t &port = 8000): _port(port){}void Init(){// 创建负责监听的套接字 面向字节流_listenSock = socket(AF_INET, SOCK_STREAM, 0);if (_listenSock < 0){LogMessage(FATAL, "create socket error!");exit(1);}LogMessage(NORMAL, "create socket %d success!", _listenSock);// 绑定网络信息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;if (bind(_listenSock, (struct sockaddr *)&local, sizeof(local)) < 0){LogMessage(FATAL, "bind socket error!");exit(3);}LogMessage(NORMAL, "bind socket success!");// 设置socket为监听状态if (listen(_listenSock, 5) < 0){LogMessage(FATAL, "listen socket error!");exit(4);}LogMessage(NORMAL, "listen socket success!");}void start(){while (1){// 因为线程池时单例模式,所以直接调用初始化ThreadPool<Task>::getInstance()->run();LogMessage(NORMAL, "Thread init success");// server获取建立新连接struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);// 创建通信的套接字// accept的返回值才是真正用于通信的套接字_sock = accept(_listenSock, (struct sockaddr *)&peer, &len);if (_sock < 0){// 获取通信的套接字失败并不影响未来的操作,只是当前的链接失败而已LogMessage(ERROR, "accept socket error, next");continue;}LogMessage(NORMAL, "accept socket %d success", _sock);cout << "sock: " << _sock << endl;// 往线程池的任务队列里插入任务ThreadPool<Task>::getInstance()->push(Task(_sock, ServerIO));}}private:int _listenSock; // 负责监听的套接字int _sock;       // 通信的套接字uint16_t _port;  // 端口号
};

Server.cc

#include "Server.hpp"
#include "daemon.hpp"
#include <memory>// 输出命令错误函数
void Usage(string proc)
{cout << "Usage:\n\t" << proc << " local_ip local_port\n\n";
}int main(int argc, char* argv[])
{// 启动服务端不需要指定IPif(argc != 2){Usage(argv[0]);exit(1);}uint16_t port = atoi(argv[1]);unique_ptr<Server> server(new Server(port));server->Init();server->start();return 0;
}

实现效果

image-20230804003006475

可以看到多个客户端同时访问也没有问题,并且所对应的套接字也就是文件描述符也不一样。

守护进程

守护进程是一种特殊的孤儿进程,其运行于后台,生存期较长并且独立与终端周期性的执行任务或者等待处理任务

进程分为前台运行和后台运行,每一个进程都会属于一个会话组里。每一个会话组都有且只有能一个前台进程。像上述的服务端,当运行服务端时,操作系统会将其分到含有bash的会话组内,并且将服务端置为前台任务进程,因此服务端运行时bash把放置后台这也就是为什么用户不能再bash继续输入命令的原因。

每一个会话组都会有一个组长,一般而言在bash中输入命令执行的进程都会分到bash的会话组内,这个会话组的组长即为bash。可以通过查看进程的SID确认进程的会话组

image-20230805132436861

可以看到上述图片中运行了三个进程并置于后台,他们的SID也就是会话组都是一样的。那么如果将他们置于前台运行会发生什么呢

image-20230805133033613

可以看到,置于前台运行后,命令行输入什么都没有反应了。也就是说,此时的bash被自动的放到了后台运行,证实了一个会话组只能有一个前台进程

image-20230805133230899

输入ctr + Z 之后前台的进程就会把切回后台,但是切回后台后进程是阻塞状态的,因此输入bg + 作业号就可让进程启动。

服务端守护进程化

那么很显然,在业务逻辑上服务端肯定是需要守护进程化的。因为服务端没有特殊情况是不会关闭的,需要一直运行。如果服务端是前台进程的话,那服务端运行时bash都不能用了,显然不符合。

这里要介绍一个接口:

#include <unistd.h>
pid_t setsid(void);

这个接口的作用是使调用的进程独立成为一个会话组并且为该组的组长。但是调用这个接口是有前置条件的:调用这个接口的进程不能为某个会话组的组长

守护进程化的步骤:

  1. 让调用进程忽略掉异常信号,因为其不受终端控制的
  2. 让调用进程不为组长
  3. 关闭或者重定向之前默认打开的文件,如0 1 2文件描述符
#pragma once#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define DEV "/dev/null"void daemonSelf(const char *currPath = nullptr)
{// 1. 让调用进程忽略掉异常的信号signal(SIGPIPE, SIG_IGN);// 2. 让自己不是组长,setsidif (fork() > 0)exit(0);// 子进程 -- 守护进程,精灵进程,本质就是孤儿进程的一种!pid_t n = setsid();assert(n != -1);// 3. 守护进程是脱离终端的,关闭或者重定向以前进程默认打开的文件int fd = open(DEV, O_RDWR);if(fd >= 0){dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);}else{close(0);close(1);close(2);}
}

接着只需要服务端在初始化完成后调用这个函数,将自己设为守护进程化即可

image-20230805134141141

一起来看看效果:

image-20230805134304036

可以看到服务端启动后并不会影响bash,仍然可以在bash上输入指令去执行。客户端也能够很好的接收到数据,这就符合现实中服务端的逻辑。

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

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

相关文章

JavaSE【继承和多态】(1)(重点:初始化、pretected封装、组合)

一、继承 继承 (inheritance) 机制 &#xff1a;是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特 性 的基础上进行扩展&#xff0c;增加新功能 &#xff0c;这样产生新的类&#xff0c;称 派生类 。 继承呈现了面向对象程序设计的层次结…

微波光子的参数:动态范围

微波光子的参数&#xff1a;无杂散动态范围 无杂散动态范围的定义 微波光子链路中的非线性失真主要由电光调制器的非线性调制产生&#xff0c;这些非线性失真可以分为谐波失真和交调失真两类。图1.2&#xff08;a&#xff09;给出了光信号在调制器内被一个频率为10 GHz的射频…

阿里云负载均衡SLB网络型NLB负载均衡架构性能详解

阿里云网络型负载均衡NLB是阿里云推出的新一代四层负载均衡&#xff0c;支持超高性能和自动弹性能力&#xff0c;单实例可以达到1亿并发连接&#xff0c;帮您轻松应对高并发业务。网络型负载均衡NLB具有超强性能、自动弹性伸缩、高可用、TCPSSL卸载、多场景流量分发和丰富的高级…

HCIP中期实验

1、该拓扑为公司网络&#xff0c;其中包括公司总部、公司分部以及公司骨干网&#xff0c;不包含运营商公网部分。 2、设备名称均使用拓扑上名称改名&#xff0c;并且区分大小写。 3、整张拓扑均使用私网地址进行配置。 4、整张网络中&#xff0c;运行OSPF协议或者BGP协议的设备…

devops-发布vue前端项目

回到目录 将使用jenkinsk8s发布前端项目 1 环境准备 node环境 在部署jenkins的服务器上搭建node环境 node版本 # 1.拉取 https://nodejs.org/download/release/v20.4.0/node-v20.4.0-linux-x64.tar.gz# 2.解压到/usr/local目录下 sudo tar xf v20.4.0.tar.gz -C /usr/loc…

面试热题(最长回文子串)

给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串 输入&#xff1a;s "babad" 输出&#xff1a;"bab" 最长回文子串以前的博客已经讲过KMP算法以及比较不常见的Manacher算法…

uni-app、H5实现瀑布流效果封装,列可以自定义

文章目录 前言一、效果二、使用代码三、核心代码总结 前言 最近做项目需要实现uni-app、H5实现瀑布流效果封装&#xff0c;网上搜索有很多的例子&#xff0c;但是代码都是不够完整的&#xff0c;下面来封装一个uni-app、H5都能用的代码。在小程序中&#xff0c;一个个item渲染…

【ASP.NET MVC】使用动软(二)(10)

一、添加动软生成工程 按前文添加动态到工程 双击动软 完成新建数据库服务器后 &#xff0c;需要关闭重新打开 选择简单三层&#xff0c;注意保存位置 注意切换数据库&#xff1a; 生成后拷贝五个文件夹到工程目录 注意目录结构&#xff1a; 添加四个项目到原来的工程&…

React 在 html 中 CDN 引入(包含 antd、axios ....)

一、简介 cdn 获取推荐 https://unpkg.com&#xff0c;unpkg 是一个快速的全球内容交付网络&#xff0c;适用于 npm 上所有内容。 【必备】react 相关 cdn。附&#xff1a;github 官方文档获取、现阶段官方文档 CDN 网址。 <script crossorigin src"https://unpkg.com…

Vue3和TypeScript项目-移动端兼容

1 全局安装typescript 2 检测安装成功 3 写的是ts代码&#xff0c;但是最后一定要变成js代码&#xff0c;才能在浏览器使用 这样就会多一个js文件 3 ts语法 数组语法 对象语法 安装vue3项目 成功后进入app。安装依赖。因为我们用的是脚手架&#xff0c;要引入东西的时候不需要…

I.MX6ULL_Linux_驱动篇(44)linux MISC驱动

MISC 驱动也叫做杂项驱动&#xff0c;也就是当我们板子上的某些外设无法进行分类的时候就可以使用 MISC 驱动。 MISC 驱动其实就是最简单的字符设备驱动&#xff0c;通常嵌套在 platform 总线驱动中&#xff0c;实现复杂的驱动&#xff0c;本章我们就来学习一下 MISC 驱动的编写…

Homer:一个简单的静态主页

什么是 Homer ? Homer 是一个完全静态的 html/js 仪表板&#xff0c;基于一个简单的 yaml 配置文件。它旨在由 HTTP 服务器提供服务&#xff0c;如果您直接通过 file:// 协议打开 index.html&#xff0c;它将无法工作。 安装 在群晖上以 Docker 方式安装。 在注册表中搜索 h…

蓝桥杯上岸每日N题 第八期 (全球变暖)!!!

蓝桥杯上岸每日N题第八期(全球变暖)&#xff01;&#xff01;&#xff01; 同步收录 &#x1f447; 蓝桥杯上岸必背&#xff01;&#xff01;&#xff01;(第五期BFS) 大家好 我是寸铁&#x1f4aa; 冲刺蓝桥杯省一模板大全来啦 &#x1f525; 蓝桥杯4月8号就要开始了 &am…

原型链污染攻击

原型链污染攻击 prototype 和 _proto_是什么 JavaScript中的类的简历 在JavaScript中&#xff0c;我们如果要定义一个类&#xff0c;需要以定义“构造函数”的方式来定义&#xff1a; function Foo() {this.bar 1 }new Foo() 解析&#xff1a; Foo函数的内容&#xff0c;就…

2023年华数杯数学建模A题思路代码分析 - 隔热材料的结构优化控制研究

# 1 赛题 A 题 隔热材料的结构优化控制研究 新型隔热材料 A 具有优良的隔热特性&#xff0c;在航天、军工、石化、建筑、交通等 高科技领域中有着广泛的应用。 目前&#xff0c;由单根隔热材料 A 纤维编织成的织物&#xff0c;其热导率可以直接测出&#xff1b;但是 单根隔热…

山西电力市场日前价格预测【2023-08-06】

日前价格预测 预测明日&#xff08;2023-08-06&#xff09;山西电力市场全天平均日前电价为411.77元/MWh。其中&#xff0c;最高日前电价为457.52元/MWh&#xff0c;预计出现在19: 30。最低日前电价为370.37元/MWh&#xff0c;预计出现在13: 15。 价差方向预测 1&#xff1a; 实…

机器学习---概述(一)

文章目录 1.人工智能、机器学习、深度学习2.机器学习的工作流程2.1 获取数据集2.2 数据基本处理2.3 特征工程2.3.1 特征提取2.3.2 特征预处理2.3.3 特征降维 2.4 机器学习2.5 模型评估 3.机器学习的算法分类3.1 监督学习3.1.1 回归问题3.1.2 分类问题 3.2 无监督学习3.3 半监督…

前端(十一)——Vue vs. React:两大前端框架的深度对比与分析

&#x1f60a;博主&#xff1a;小猫娃来啦 &#x1f60a;文章核心&#xff1a;Vue vs. React&#xff1a;两大前端框架的深度对比与分析 文章目录 前言概述原理与设计思想算法生态系统与社区支持API与语法性能与优化开发体验与工程化对比总结结语 前言 在当今快速发展的前端领…

软件设计原则

文章目录 一、软件设计原则1. 开闭原则2. 里氏代换原则3. 依赖倒转原则4. 接口隔离原则5. 迪米特法则6. 合成复用原则 一、软件设计原则 在软件开发中&#xff0c;为了提高软件系统的可维护性和可复用性&#xff0c;增加软件的可扩展性和灵活性&#xff0c;程序员要尽量根据软件…

django使用mysql数据库

Django开 发操作数据库比使用pymysql操作更简单&#xff0c;内部提供了ORM框架。 下面是pymysql 和orm操作数据库的示意图&#xff0c;pymysql就是mysql的驱动&#xff0c;代码直接操作pymysql ,需要自己写增删改查的语句 django 就是也可以使用pymysql、mysqlclient作为驱动&a…