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

【网络编程】实现一个简单多线程版本TCP服务器(附源码)

00

TCP多线程

  • 🌵预备知识
    • 🎄 Accept函数
    • 🌲字节序转换函数
    • 🌳listen函数
  • 🌴代码
    • 🌱Log.hpp
    • 🌿Makefile
    • ☘️TCPClient.cc
    • 🍀TCPServer.cc
    • 🎍 util.hpp

🌵预备知识

🎄 Accept函数

accept 函数是在服务器端用于接受客户端连接请求的函数,它在监听套接字上等待客户端的连接,并在有新的连接请求到来时创建一个新的套接字用于与该客户端通信。

  • 下面是 accept 函数的详细介绍以及各个参数的意义:
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd: 是服务器监听套接字的文件描述符,通常是使用 socket 函数创建的套接字。accept 函数在该套接字上等待连接请求。

addr: 是一个指向 struct sockaddr 类型的指针,用于存储客户端的地址信息。当新连接建立成功后,客户端的地址信息将会被填充到这个结构体中。

addrlen: 是一个指向 socklen_t 类型的指针,它指示 addr 结构体的长度。在调用 accept 函数之前,需要将其初始化为 addr 结构体的大小,函数执行后会更新为实际的客户端地址长度。

返回值:如果连接成功建立,accept 函数将返回一个新的文件描述符,该文件描述符用于与客户端进行通信。如果连接失败,函数将返回 -1,并设置 errno 以指示错误原因。

  • accept 函数的工作原理如下:

当服务器的监听套接字接收到一个新的连接请求时,accept 函数会创建一个新的套接字用于与该客户端通信。
新的套接字会继承监听套接字的监听属性,包括 IP 地址、端口等。
accept 函数会填充 addr 结构体,以便获取客户端的地址信息。
服务器可以使用返回的新套接字与客户端进行通信。

  • 注意事项:

accept 函数在没有连接请求时会阻塞,直到有新的连接请求到来。
如果希望设置非阻塞模式,可以使用 fcntl 函数设置 O_NONBLOCK 属性。
在多线程或多进程环境下,需要注意 accept 函数的线程安全性,可以使用互斥锁等机制来保护。
综上所述,accept 函数在构建服务器程序时非常重要,它使服务器能够接受客户端的连接请求并创建新的套接字与客户端进行通信。

🌲字节序转换函数

在网络编程中,字节序问题很重要,因为不同的计算机体系结构可能使用不同的字节序,这可能导致在通信过程中的数据解释错误。为了在不同体系结构之间正确传递数据,需要进行字节序的转换。

  • 以下是一些常用的字节序转换函数:

ntohl 和 htonl: 这些函数用于 32 位整数的字节序转换。ntohl 用于将网络字节序转换为主机字节序,htonl 则相反,将主机字节序转换为网络字节序。

ntohs 和 htons: 这些函数用于 16 位整数的字节序转换。ntohs 用于将网络字节序转换为主机字节序,htons 则相反,将主机字节序转换为网络字节序。

这些函数通常用于在网络编程中处理套接字通信中的数据转换,以确保在不同平台上的正确数据交换。

  • 示例
#include <arpa/inet.h>int main() {uint32_t networkValue = 0x12345678;uint32_t hostValue = ntohl(networkValue); // 0x78563412 on a little-endian hostuint32_t convertedValue = htonl(hostValue); // 0x12345678 on a little-endian hostuint16_t networkPort = 0x1234;uint16_t hostPort = ntohs(networkPort); // 0x3412 on a little-endian hostuint16_t convertedPort = htons(hostPort); // 0x1234 on a little-endian hostreturn 0;
}

请注意,在使用这些函数时,需要包含 <arpa/inet.h> 头文件。这些函数通常在网络编程中用于正确处理字节序问题,以确保不同平台之间的数据传输正确。

🌳listen函数

在TCP通信中,服务端需要使用 listen 函数来监听连接请求。这是因为TCP是一种面向连接的协议,它采用客户端-服务端模型进行通信,通信双方需要先建立连接,然后进行数据的传输。监听的过程是为了等待客户端发起连接请求。

  • 具体原因如下:

建立连接: 在TCP通信中,通信双方需要通过三次握手建立连接。客户端通过 connect 函数向服务器发起连接请求,而服务端则需要通过 listen 函数来准备接收连接请求。

处理并发连接: 服务端可能会同时接收多个客户端的连接请求,而每个连接都需要为其分配一个独立的套接字。通过监听连接请求,服务端可以在一个循环中接受多个连接,为每个连接创建对应的套接字,从而实现并发处理多个客户端。

连接队列: listen 函数将连接请求存储在一个队列中,等待服务端逐个接受。这个队列称为“未完成连接队列”(backlog queue)。如果连接请求过多,超出了队列的长度,那么新的连接请求可能会被拒绝或被丢弃。

连接参数: listen 函数还可以指定一个参数,表示在未完成连接队列中可以容纳的连接请求数量。这个参数可以影响服务端处理并发连接的能力。

总之,TCP监听是为了等待客户端发起连接请求,建立连接,然后实现双方的数据传输。这种机制允许服务器处理多个客户端连接,实现高并发的网络服务。

  • 函数原型:
int listen(int sockfd, int backlog);
  • 参数说明:

sockfd:要进行监听的套接字描述符。
backlog:表示在未完成连接队列中可以容纳的连接请求数量。这个参数可以影响服务器处理并发连接的能力。通常情况下,系统会为这个值设置一个默认的最大值,但你也可以根据你的需求进行适当调整。
返回值:
如果函数调用成功,返回 0。
如果出现错误,返回 -1,并设置全局变量 errno 来指示错误类型。

使用步骤:

创建套接字并绑定地址。
调用 listen 函数将套接字标记为被动套接字,开始监听连接请求。
使用 accept 函数接受客户端连接请求,建立实际的连接。

  • 示例用法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>int main() {int listen_sock = socket(AF_INET, SOCK_STREAM, 0);if (listen_sock == -1) {perror("socket");exit(EXIT_FAILURE);}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = INADDR_ANY;if (bind(listen_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {perror("bind");exit(EXIT_FAILURE);}if (listen(listen_sock, 5) == -1) { // 开始监听,最多允许5个未完成连接perror("listen");exit(EXIT_FAILURE);}// 现在可以使用 accept 函数接受连接请求并建立连接close(listen_sock);return 0;
}

注意:listen 后的套接字仅能用于接受连接请求,不能用于读写数据。接收到的连接请求将在一个队列中等待,直到使用 accept 函数从队列中取出并建立连接。

🌴代码

🌱Log.hpp

#pragma once#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3const char *log_level[]={"DEBUG", "NOTICE", "WARINING", "FATAL"};// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...)
{assert(level >= DEBUG);assert(level <= FATAL);char *name = getenv("USER");char logInfo[1024];va_list ap; // ap -> char*va_start(ap, format);vsnprintf(logInfo, sizeof(logInfo)-1, format, ap);va_end(ap); // ap = NULLFILE *out = (level == FATAL) ? stderr:stdout;fprintf(out, "%s | %u | %s | %s\n", \log_level[level], \(unsigned int)time(nullptr),\name == nullptr ? "unknow":name,\logInfo);// char *s = format;// while(s){//     case '%'://         if(*(s+1) == 'd')  int x = va_arg(ap, int);//     break;// }
}

🌿Makefile

.PHONY:all
all:TCPClient TCPServerTCPClient: TCPClient.ccg++ -o $@ $^ -std=c++11 -lpthread
TCPServer:TCPServer.ccg++ -o $@ $^ -std=c++11 -lpthread.PHONY:clean
clean:rm -f TCPClient TCPServer

☘️TCPClient.cc

#include"util.hpp"
volatile bool quit=false;
static void Usage(std::string proc)
{std::cerr<<"Usage:\n\t"<<proc<<"serverip serverport "<<std::endl;std::cerr<<"Example:\n\t"<<proc<<"127.0.0.1 8080\n"<<std::endl;
}int main(int argc,char *argv[])
{if(argc!=3){Usage(argv[0]);exit(USAGE_ERR);}std::string serverip=argv[1];uint16_t serverport=atoi(argv[2]);//1.创建socket SOCK_STREAMint sock=socket(AF_INET,SOCK_STREAM,0);if(sock<0){std::cerr<<"socket :"<<strerror(errno)<<std::endl;exit(SOCKET_ERR);}//2.链接 //向服务器发起链接请求struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(server.sin_port);inet_aton(serverip.c_str(),&server.sin_addr);//2.2发起请求 connect自动会进行bindif(connect(sock,(const struct sockaddr*)&server,sizeof(server))!=0){//链接失败std::cerr<<"connect :"<<strerror(errno)<<std::endl;exit(CONN_ERR);}//链接成功std::cout<<" info :connect success :"<<sock<<std::endl;std::string message;while(!quit){message.clear();std::cout<<"请输入您的消息>>>>"<<std::endl;std::getline(std::cin,message);if(strcasecmp(message.c_str(),"quit")==0){//如果输入的是quit 直接退出程序quit=true; //设置成true 会把当前信息先执行发送到服务器 再进入while循环时条件不满直接退出}//从服务器接收到的消息ssize_t s=write(sock,message.c_str(),message.size());if(s>0){message.resize(1024);ssize_t s=read(sock,(char *)(message.c_str()),1024);if(s>0)message[s]=0;std::cout<<"Server Echo>>>"<<"message"<<std::endl;}else if (s <= 0){break;}}close(sock);return 0;
}

🍀TCPServer.cc

#include "util.hpp"
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
class ServerTcp;//先声明class ThreadData
{public:uint16_t clientPort_;//客户端端口号std::string clientip_;//客户端ipint sock_;ServerTcp *this_;ThreadData(uint16_t port, std::string ip, int sock,  ServerTcp *ts): clientPort_(port), clientip_(ip), sock_(sock),this_(ts){}};class ServerTcp
{public://构造和和析构函数ServerTcp(uint16_t port,const std::string &ip=""):port_(port),ip_(ip),listenSock_(-1){}~ServerTcp(){}public://初始化函数void init(){//第一步:创建套接字listenSock_=socket(PF_INET,SOCK_STREAM,0);if(listenSock_<0){//创建失败logMessage(FATAL,"socket:%s",strerror(errno)); //用日志打印错误信息exit(SOCKET_ERR);}//创建成功logMessage(DEBUG,"sockt:%s,%d",strerror(errno),listenSock_);//第二步 bind绑定//2.1填充服务器信息struct sockaddr_in local;memset(&local,0,sizeof(local));//设置0?/*可以确保将所有这些字段初始化为零,以避免在实际使用过程中出现未定义行为或不可预测的结果。*/local.sin_family=AF_INET;   /*如果 ip_ 为空,服务器将绑定到任意可用的本地IP地址。如果 ip_ 不为空,服务器将绑定到 ip_ 所代表的具体IP地址。*/ip_.empty()?(local.sin_addr.s_addr)=htons(INADDR_ANY):(inet_aton(ip_.c_str(),&local.sin_addr));//2.2if(bind(listenSock_,(const struct sockaddr*)&local,sizeof local)<0)//{//bind绑定失败logMessage(FATAL,"bind:%s",strerror(errno));exit(BIND_ERR);}//绑定成功logMessage(DEBUG,"bind:%S,%d",strerror(errno),listenSock_);//3.监听socketif(listen(listenSock_,5)<0){logMessage(FATAL,"listen:%s",strerror(errno));exit(LISTEN_ERR);}//监听成功logMessage(DEBUG,"listen:%S,%d",strerror(errno),listenSock_);//到这一步就等待运行 等待客户端链接}static void *threadRoutine(void *args){pthread_detach(pthread_self()); //设置线程分离ThreadData *td = static_cast<ThreadData*>(args);td->this_->tranService(td->sock_, td->clientip_, td->clientPort_);delete td;return nullptr;}//加载void loop(){while(true){struct sockaddr_in peer;socklen_t len=sizeof(peer);//获取链接 accept返回值??int serviceSock=accept(listenSock_,(struct sockaddr*)&peer,&len);if(serviceSock<0){//获取连接失败logMessage(WARINING,"Accept :%S[%d]",strerror(errno),serviceSock);continue;//获取失败 继续接收....}//获取客户端的基本信息 存储起来uint16_t peerPort=ntohs(peer.sin_port);std::string peerip=inet_ntoa(peer.sin_addr);//打印一下获取的客户端信息logMessage(DEBUG,"Aceept :%s|%s[%d],socket fd :%d",strerror(errno),peerip.c_str(),peerPort,serviceSock);// 5 提供服务, echo -> 小写 -> 大写// 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept// transService(serviceSock, peerIp, peerPort);// 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的// pid_t id = fork();// assert(id != -1);// if(id == 0)// {//     close(listenSock_); //建议//     //子进程//     transService(serviceSock, peerIp, peerPort);//     exit(0); // 进入僵尸// }// // 父进程// close(serviceSock); //这一步是一定要做的!// 5.1 v1.1 版本 -- 多进程版本  -- 也是可以的// 爷爷进程// pid_t id = fork();// if(id == 0)// {//     // 爸爸进程//     close(listenSock_);//建议//     // 又进行了一次fork,让 爸爸进程//     if(fork() > 0) exit(0);//     // 孙子进程 -- 就没有爸爸 -- 孤儿进程 -- 被系统领养 -- 回收问题就交给了系统来回收//     transService(serviceSock, peerIp, peerPort);//     exit(0);// }// // 父进程// close(serviceSock); //这一步是一定要做的!// // 爸爸进程直接终止,立马得到退出码,释放僵尸进程状态// pid_t ret = waitpid(id, nullptr, 0); //就用阻塞式// assert(ret > 0);// (void)ret;// 5.2 v2 版本 -- 多线程// 这里不需要进行关闭文件描述符吗??不需要啦// 多线程是会共享文件描述符表的!ThreadData *td = new ThreadData(peerPort, peerip, serviceSock, this);pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)td);// waitpid(); 默认是阻塞等待!WNOHANG// 方案1// logMessage(DEBUG, "server 提供 service start ...");// sleep(1);}}//提供服务函数 -----> 大小写转换void tranService(int sock,const std::string &clientip,uint16_t clientPort){assert(sock>=0);assert(!clientip.empty());assert(clientPort>=1024); //1~~1024端口为系统端口 不可轻易更改char inbuffer[BUFFER_SIZE];while(true){ssize_t s=read(sock,inbuffer,sizeof(inbuffer)-1); //-1是给\0留出一个位置if(s>0){inbuffer[s]='0';if(strcasecmp(inbuffer,"quit")==0){logMessage(DEBUG,"client quit----------%s[%d]",clientip.c_str(),clientPort);break;}logMessage(DEBUG,"Treans Before:%s[%d]>>>%s",clientip.c_str(),clientPort,inbuffer);//进行大小写转换for(int i=0;i<s;i++){if(isalpha(inbuffer[i])&&islower(inbuffer[i])){inbuffer[i]=toupper(inbuffer[i]);}}logMessage(DEBUG,"Trans after:%s[%d]>>>>%s",clientip.c_str(),clientPort,inbuffer);write(sock,inbuffer,strlen(inbuffer));//给客户端发送回去}else if(s==0){// pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭// s == 0: 代表对方关闭,client 退出logMessage(DEBUG, "client quit -- %s[%d]", clientip.c_str(), clientPort);break;}else{logMessage(DEBUG, "%s[%d] - read: %s", clientip.c_str(), clientPort, strerror(errno));break;}}// 只要走到这里,一定是client退出了,服务到此结束close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!logMessage(DEBUG, "server close %d done", sock);}private:// sockint listenSock_;// portuint16_t port_;// ipstd::string ip_;
};static void Usage(std::string proc)
{std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl;std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n" << std::endl;}// ./ServerTcp local_port local_ip
int main(int argc, char *argv[])
{if(argc != 2 && argc != 3 ){Usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);std::string ip;if(argc == 3) ip = argv[2];ServerTcp svr(port, ip);svr.init();svr.loop();return 0;
}

🎍 util.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"#define SOCKET_ERR 1
#define BIND_ERR   2
#define LISTEN_ERR 3
#define USAGE_ERR  4
#define CONN_ERR   5#define BUFFER_SIZE 1024

大家可以拉下来自行测试…

🎋 🍃 🍂 🍁 🍄 🐚 💐 🌷 🌹 🥀 🌺 🌸 🌼 🌻

相关文章:

【网络编程】实现一个简单多线程版本TCP服务器(附源码)

TCP多线程 &#x1f335;预备知识&#x1f384; Accept函数&#x1f332;字节序转换函数&#x1f333;listen函数 &#x1f334;代码&#x1f331;Log.hpp&#x1f33f;Makefile☘️TCPClient.cc&#x1f340;TCPServer.cc&#x1f38d; util.hpp &#x1f335;预备知识 &…...

centos离线部署docker

有些内部环境需要离线部署&#xff0c;以下做一些备忘。 环境&#xff1a;centos7.9 准备文件&#xff1a; docker-20.10.9.tgz&#xff0c;下载地址 https://download.docker.com/linux/static/stable/x86_64/docker.service&#xff0c;内容见下文daemon.json&#xff0c;内…...

ffmpeg使用滤镜对视频进行处理播放

一、前言 在现代的多媒体处理中,视频和音频滤镜起着至关重要的作用。可以帮助开发者对视频和音频进行各种处理,如色彩校正、尺寸调整、去噪、特效添加等。而FFmpeg作为一个功能强大的开源多媒体框架,提供了丰富的滤镜库,使我们能够轻松地对多媒体文件进行处理和转换。 本…...

Ansible Handlers模块详解,深入理解Ansible Handlers 自动化中的关键组件

深入理解Ansible Handlers 自动化中的关键组件 在现代的IT环境中&#xff0c;自动化已经成为提高效率和减少错误的关键。Ansible作为一款流行的自动化工具&#xff0c;通过使用Playbooks来定义和执行任务。而Handlers作为Ansible的组件之一&#xff0c;在自动化过程中发挥着重要…...

threejs点击模型实现模型边缘高亮的选中效果--更改后提高帧率

先来个效果图 之前写的那个稍微有点问题&#xff0c;帧率只有30&#xff0c;参照官方代码修改后&#xff0c;帧率可以达到50了&#xff0c;在不全屏的状态下&#xff0c;帧率60 1.首先需要导入库 // 用于模型边缘高亮 import { EffectComposer } from "three/examples/js…...

RocketMQ 主备自动切换模式部署

目录 主备自动切换模式部署 Controller 部署​ Controller 嵌入 NameServer 部署​ Controller 独立部署​ Broker 部署​ 兼容性​ 升级注意事项​ 主备自动切换模式部署 该文档主要介绍如何部署支持自动主从切换的 RocketMQ 集群&#xff0c;其架构如上图所示&#xff…...

【MySQL】select相关

文章目录 迭代器distinct 关键字limit offset 关键字order by 列名 asc\descselect语句的执行顺序几点注意 迭代器 指向第一个元素 使用hasNext()进行判断后才进行取元素 resultSet&#xff1a;指向第一个元素前一个 distinct 关键字 去除一列中的重复元素 可以进行多行的去重…...

在Python中应用RSA算法实现图像加密:基于Jupyter环境的详细步骤和示例代码

一、引言 在当今的数字化社会中,信息安全问题备受关注。随着数字图像在生活中的应用越来越广泛,图像的安全性和隐私性也成为人们关心的焦点。如何在网络上安全地传输和存储图像已经成为一项重要的挑战。RSA(Rivest-Shamir-Adleman)算法作为一种被广泛应用的公钥密码体系,…...

Prometheus Blackbox Exporter 的 HTTP 探测指标中各个阶段的时间统计信息

在 Prometheus Blackbox Exporter 的 HTTP 探测指标中&#xff0c;probe_http_duration_seconds 指标包含各个阶段的时间统计信息。这些阶段代表了 HTTP 探测的不同阶段和指标。以下是各个阶段的含义&#xff1a; phase"dns_lookup"&#xff1a;这是指进行 DNS 查找…...

数据结构之时间复杂度-空间复杂度

大家好&#xff0c;我是深鱼~ 目录 1.数据结构前言 1.1什么是数据结构 1.2什么是算法 1.3数据结构和算法的重要性 1.4如何学好数据结构和算法 2.算法的效率 3.时间复杂度 3.1时间复杂度的概念 3.2大O的渐进表示法 【实例1】&#xff1a;双重循环的时间复杂度&#xf…...

新一代构建工具 maven-mvnd

新一代构建工具 maven-mvnd mvnd的前世今生下载安装 mvndIDEA集成 mvnd的前世今生 maven 作为一代经典的构建工具&#xff0c;流行了很多年&#xff0c;知道现在依然是大部分Java项目的构建工具的首选&#xff1b;但随着项目复杂度提高&#xff0c;代码量及依赖库的增多使得ma…...

构建Docker容器监控系统(2)(Cadvisor +Prometheus+Grafana)

Cadvisor产品简介 Cadvisor是Google开源的一款用于展示和分析容器运行状态的可视化工具。通过在主机上运行Cadvisor用户可以轻松的获取到当前主机上容器的运行统计信息&#xff0c;并以图表的形式向用户展示。 接着上一篇来继续 部署Cadvisor 被监控主机上部署Cadvisor容器…...

Leetcode.995 K 连续位的最小翻转次数

题目链接 Leetcode.995 K 连续位的最小翻转次数 rating : 1835 题目描述 给定一个二进制数组 n u m s nums nums 和一个整数 k k k 。 k k k位翻转 就是从 n u m s nums nums 中选择一个长度为 k k k 的 子数组 &#xff0c;同时把子数组中的每一个 0 0 0 都改成 1 1 1 …...

PHP8的跳转语句-PHP8知识详解

如果循环条件满足的时候&#xff0c;则程序会一直执行下去。如果需要强制跳出循环&#xff0c;则需要使用跳转语句来完成。PHP8的跳转语句包括break语句、continue语句和goto语句。 1、break语句 break语句的作用是完全终止循环&#xff0c;包括while、do…while、for、switch…...

Idea中maven无法下载源码

今天在解决问题的时候想要下载源码&#xff0c;突然发现idea无法下载&#xff0c;这是真的蛋疼&#xff0c;没办法查看原因&#xff0c;最后发现问题的原因居然是因为Maven&#xff0c;由于我使用的idea的内置的Bundle3的Maven&#xff0c;之前没有研究过本地安装和内置的区别&…...

【linux-keepalive】keepalive避免单点故障,高可用配置

keepalive: [rootproxy ~]# yum install -y keepalived [rootproxy ~]# vim /etc/keepalived/keepalived.conf global_defs {router_id proxy1 //设置路由ID号vrrp_iptables //不添加任何防火墙规则 } vrrp_instance V…...

测试网络模型的FLOPs和params

概念 FLOPS&#xff1a;注意全大写&#xff0c;是floating point operations per second的缩写&#xff0c;意指每秒浮点运算次数&#xff0c;理解为计算速度。是一个衡量硬件性能的指标。 FLOPs&#xff1a;注意s小写&#xff0c;是floating point operations的缩写&#xf…...

《树莓派项目实战》第十五节 使用L298N驱动板模块驱动双极42步进电机

目录 15.1 双极步进电机引脚介绍 15.2 连接到树莓派 15.3 编写代码驱动步进电机 在本节,我们将学习如何使用L298N驱动板驱动一个双极42步进电机。该项目涉及到的材料有: 树莓派...

基于短信宝API零代码实现短信自动化业务

场景描述&#xff1a; 基于短信宝开放的API能力&#xff0c;实现在特定事件&#xff08;如天气预警&#xff09;或定时自动发送短信&#xff08;本文以定时群发短信为例&#xff09;。通过Aboter平台如何实现呢&#xff1f; 使用方法&#xff1a; 首先创建一个IPaaS流程&…...

Qt应用开发(基础篇)——信号槽 Signals and Slots

一、前言 Qt成为我们今天拥有的灵活而舒适的工具&#xff0c;除了友好和能够快速开发设计师界面&#xff0c;信号槽机制是最大的核心特征&#xff0c;也是区别于其他开发框架最大的优势。 Qt的信号槽作用于两个对象之间的通信。当一个对象发生了改变&#xff0c;它希望其他关心…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

1.3 VSCode安装与环境配置

进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件&#xff0c;然后打开终端&#xff0c;进入下载文件夹&#xff0c;键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

IT供电系统绝缘监测及故障定位解决方案

随着新能源的快速发展&#xff0c;光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域&#xff0c;IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选&#xff0c;但在长期运行中&#xff0c;例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

无人机侦测与反制技术的进展与应用

国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机&#xff08;无人驾驶飞行器&#xff0c;UAV&#xff09;技术的快速发展&#xff0c;其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统&#xff0c;无人机的“黑飞”&…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...