Linux笔记---TCP套接字编程

1. 核心接口

1.1 监听连接:listen()

使 TCP 套接字进入被动监听状态,准备接受客户端连接(仅服务器端使用)。

#include <sys/socket.h>int listen(int sockfd, int backlog);
  • 参数
    • sockfd:已绑定的 TCP 套接字描述符。
    • backlog:未完成连接队列的最大长度(超过则新连接会被拒绝)。
  • 返回值:成功返回0,失败返回-1。

1.2 接受连接:accept()

从监听队列中取出一个已完成的连接,返回一个新的套接字描述符用于与该客户端通信(仅服务器端使用,会阻塞等待连接)。

#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 参数
    • sockfd:监听状态的套接字描述符(监听套接字)。
    • addr:输出参数,用于存储客户端的 IP 和端口(可设为NULL)。
    • addrlen:输入输出参数,传入addr的长度,输出实际存储的长度(可设为NULL)。
  • 返回值:成功返回新的通信套接字描述符,失败返回-1。

1.3 发起连接:connect()

客户端使用该函数向服务器发起 TCP 连接。

#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 参数
    • sockfd:客户端套接字描述符(socket()创建)。
    • addr:服务器的地址结构(包含 IP 和端口)。
    • addrlen:addr结构的长度。
  • 返回值:成功返回0(连接建立),失败返回-1。

1.4 TCP 发送:send()、write()

#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • 功能:向已连接的 TCP 套接字发送数据。
  • 参数:sockfd(通信套接字)、buf(数据缓冲区)、len(数据长度)、flags(通常为 0)。
  • 返回值:成功返回发送的字节数,失败返回-1。

除此之外,TCP建立连接之后,可以将sockfd当作一个文件描述符,使用write函数进行发送。

1.5 TCP 接收:recv()、read()

#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • 功能:从已连接的 TCP 套接字接收数据。
  • 参数:sockfd(通信套接字)、buf(接收缓冲区)、len(缓冲区大小)、flags(通常为 0)。
  • 返回值:成功返回接收的字节数(0表示对方关闭连接),失败返回-1。

同样地,可以将套接字当作文件,使用sockfd以及read函数读取消息。

1.6 辅助函数(地址转换)

在Linux笔记---UDP套接字编程-CSDN博客中,我们介绍的用于将32位整数转换为点分十进制的函数inet_ntoa是线程不安全的函数。而在网络编程中难免与多线程/多进程打交道,所以我们推荐以下两个函数来代替inet_addr和inet_ntoa。

1.6.1 inet_ntop

将网络字节序的二进制 IP 地址转换为人类可读的字符串形式(二进制形式 → 字符串形式)。

#include <arpa/inet.h>const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
  • 参数
    • af:地址族,AF_INET(IPv4)或 AF_INET6(IPv6)。
    • src:源数据,二进制 IP 地址(如 struct in_addr 或 struct in6_addr 指针)。
    • dst:目标缓冲区,存储转换后的字符串 IP。
  • 返回值:成功返回指向 dst 缓冲区的指针(即转换后的字符串); 失败返回 NULL(如 size 不足,需检查 errno)。
1.6.2 inet_pton

将人类可读的 IP 地址字符串(如 192.168.1.1 或 2001:db8::1)转换为网络字节序的二进制数值(字符串形式 → 二进制形式,供套接字 API 使用)。

#include <arpa/inet.h>int inet_pton(int af, const char *src, void *dst);
  • 参数
    • af:地址族,AF_INET(IPv4)或 AF_INET6(IPv6)。
    • src:源数据,IP 字符串(如 "192.168.1.1")。
    • dst:目标缓冲区,存储转换后的二进制 IP。
    • size:指定 dst 缓冲区的大小(需足够容纳最长的 IP 字符串,如 IPv6 需至少INET6_ADDRSTRLEN 字节)
  • 返回值:成功返回 1(转换有效); 失败返回 0(src 格式无效,非合法 IP 地址); 错误返回 -1(如 af 不是 AF_INET 或 AF_INET6,需检查 errno)。

建议:ip统一使用上述两个接口进行转换,端口号统一使用htons和ntohs进行转换。

2. TCP客户/服务器通信流程

注意,在服务器端套接字分为两种:监听套接字传输套接字

监听套接字:socket接口创建,显式绑定服务器的网络地址信息,然后调用listen接口设置为监听套接字。该套接字不进行数据传输,只负责监听来自客户端的连接请求。当客户端的连接请求被服务器端接收到时,会将该请求放到一个队列当中,此时accept函数就会从队列当中取出一个请求,与其建立连接并创建一个与客户端进行通信的传输套接字。

所以,TCP服务端通常都是多线程/多进程的运行方式:主线程/进程持有监听套接字,每当accept函数返回时就创建一个线程/子进程,使用新获得的传输套接字为客户端提供服务

当使用多进程的实现方式时,父进程需要关闭新获得的传输套接字,子进程需要关闭监听套接字;使用多线程实现方式时新获得的套接字在子线程使用完毕之后自己关闭。

2.1 客户端示例

#include "Common.hpp"void Usage(const std::string &name)
{std::cout << "usage: " << name << " + ip" << " + port" << std::endl;
}int main(int argc, char *args[])
{if (argc != 3){Usage(args[0]);exit(USAGE_ERROR);}in_port_t port = std::stoi(args[2]);InetAddr server(args[1], port);int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1){LOG(LogLevel::FATAL) << "socket: 套接字创建失败! " << std::strerror(errno);exit(SOCKET_ERROR);}int n = connect(sockfd, server.NetAddrPtr(), server.AddrLen());if (n == -1){LOG(LogLevel::FATAL) << "connect: 建立连接失败! " << std::strerror(errno);exit(CONNECT_ERROR);}// 开始与服务器端交互(循环发送读取)std::string message;char buffer[BUFFER_SIZE];while (true){std::cout << "Send to server# ";std::getline(std::cin, message);ssize_t n = write(sockfd, message.c_str(), message.size());if (n == -1){LOG(LogLevel::ERROR) << "write: [" << server.Info() << "]写入数据时出错! " << std::strerror(errno);exit(WRITE_ERROR);}n = read(sockfd, buffer, sizeof(buffer) - 1);if(n == -1){LOG(LogLevel::ERROR) << "read: [" << server.Info() << "]读取数据时出错! " << std::strerror(errno);exit(READ_ERROR);}buffer[n] = 0;std::cout << "Recive from server: " << buffer << std::endl;}return 0;
}

2.3 封装服务器端示例

#pragma once
#include "Common.hpp"
#include <pthread.h>
#include "ThreadPool.hpp"
#define MAX_PENDING_LEN 15// NoCopy类拷贝构造函数和赋值重载都已删掉
// 任何类继承该类就无法被拷贝
class TCPServer : public NoCopy
{
private:// 默认消息处理方式---回显static void DefaultMessageHandler(int sockfd, const InetAddr &client, const std::string &message){ssize_t size = write(sockfd, message.c_str(), message.size());if (size == -1){LOG(LogLevel::ERROR) << "write: [" << client.Info() << "]写入数据时出错! " << std::strerror(errno);exit(WRITE_ERROR);}}// 为客户端提供服务的函数void Service(int sockfd, const InetAddr &client){char buffer[BUFFER_SIZE];int n;while ((n = read(sockfd, buffer, sizeof(buffer) - 1)) > 0){buffer[n] = 0;LOG(LogLevel::INFO) << "[" << client.Info() << "]# " << buffer;_message_handler(sockfd, client, buffer);}if (n == -1){LOG(LogLevel::ERROR) << "read: [" << client.Info() << "]读取数据时出错! " << std::strerror(errno);exit(READ_ERROR);}LOG(LogLevel::INFO) << "[" << client.Info() << "]已断开连接! ";}public:TCPServer(in_port_t port, message_handler_t message_handler = DefaultMessageHandler): _message_handler(message_handler){// 多进程实现方式下,避免等待子进程的推荐方式// signal(SIGCHLD, SIG_IGN);_listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listen_sockfd == -1){LOG(LogLevel::FATAL) << "socket: 套接字创建失败! " << std::strerror(errno);exit(SOCKET_ERROR);}InetAddr local(INADDR_ANY, port);int n = bind(_listen_sockfd, local.NetAddrPtr(), local.AddrLen());if (n == -1){LOG(LogLevel::FATAL) << "bind: 绑定地址失败! " << std::strerror(errno);exit(BIND_ERROR);}}~TCPServer(){close(_listen_sockfd);}void Run(){int n = listen(_listen_sockfd, MAX_PENDING_LEN);if (n == -1){LOG(LogLevel::FATAL) << "listen: 设置监听套接字失败! " << std::strerror(errno);exit(LISTEN_ERROR);}LOG(LogLevel::INFO) << "监听套接字已就绪, 等待连接请求...";while (true){InetAddr client;int sockfd = accept(_listen_sockfd, client.NetAddrPtr(), &client.AddrLen());client = InetAddr(client.NetAddr());if (sockfd == -1){LOG(LogLevel::WARNING) << "accept: [" << client.Info() << "]建立连接失败! " << std::strerror(errno);continue;}LOG(LogLevel::INFO) << "accept: [" << client.Info() << "]建立连接成功! ";// ...与客户端的一个连接建立成功...// 三种处理方式: 多进程、多线程、线程池// todo}}private:int _listen_sockfd;message_handler_t _message_handler; // 客户端消息的处理函数
};

上面的代码中,在accept函数成功与客户端建立连接之后的逻辑没写,这里有三种实现方式。

2.3.1 多进程实现方式
int id = fork();
if (id < 0)
{// 创建子进程失败LOG(LogLevel::FATAL) << "fork: 子进程创建失败! " << std::strerror(errno);exit(FORK_ERROR);
}
else if (id == 0)
{// 子进程// 使用孙子进程, 避免等待子进程if (fork() > 0)exit(NORMAL);close(_listen_sockfd);Service(sockfd, client);exit(NORMAL);
}
else
{// 父进程close(sockfd);
}
2.3.2 多进程实现方式
pthread_t thread;
ThreadData data = {this, sockfd, client};
pthread_create(&thread, nullptr, ThreadHandler, &data);// 需要新增如下内容
struct ThreadData
{TCPServer *self;int sockfd;InetAddr client;
};
static void *ThreadHandler(void *args)
{pthread_detach(pthread_self());ThreadData *data = static_cast<ThreadData *>(args);data->self->Service(data->sockfd, data->client);close(data->sockfd);return nullptr;
}
2.3.3 线程池实现方式
auto task = std::bind(&TCPServer::Service, this, sockfd, client);
ThreadPoolModule::ThreadPool<std::function<void()>>::GetInstance()->PushTask(task);

长服务建议采用多线程/多进程的实现方式(长时间占用一个线程),短服务建议采用线程池的实现方式(频繁提供服务,线程池能减少线程/进程创建或释放的开销)。

3. C/S远程指令执行程序

即,客户端输入命令行指令,客户端远程执行并返回结果。

3.1 Common.hpp

#pragma once
#include <functional>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;using message_handler_t = std::function<void(int, const InetAddr&, const std::string&)>;enum TCPExitCode
{NORMAL = 0,SOCKET_ERROR,BIND_ERROR,LISTEN_ERROR,FORK_ERROR,READ_ERROR,WRITE_ERROR,USAGE_ERROR,CONNECT_ERROR
};class NoCopy
{
public:NoCopy(){}NoCopy(const NoCopy&) = delete;NoCopy& operator=(const NoCopy&) = delete;
};

3.2 InetAddr.hpp

#pragma once
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <cstring>
#include "Log.hpp"
#define BUFFER_SIZE 1024using namespace LogModule;class InetAddr
{
public:// 默认构造函数InetAddr() : _port(0), _len(sizeof(_addr)){memset(&_addr, 0, sizeof(_addr));_addr.sin_family = AF_INET;}// 从sockaddr_in构造InetAddr(const struct sockaddr_in &addr): _addr(addr), _len(sizeof(_addr)){char buffer[BUFFER_SIZE];_ip = inet_ntop(AF_INET, &addr.sin_addr, buffer, sizeof(buffer));_port = ntohs(_addr.sin_port); // 网络字节序转主机字节序}// 从IP字符串和端口构造InetAddr(const std::string &ip, in_port_t port): _ip(ip), _port(port), _len(sizeof(_addr)){memset(&_addr, 0, sizeof(_addr));_addr.sin_family = AF_INET;inet_pton(AF_INET, ip.c_str(), &_addr.sin_addr);_addr.sin_port = htons(port); }// 从网络字节序IP和端口构造(用于INADDR_ANY)InetAddr(in_addr_t ip, in_port_t port): _port(port), _len(sizeof(_addr)){memset(&_addr, 0, sizeof(_addr));_addr.sin_family = AF_INET;_addr.sin_addr.s_addr = ip;_addr.sin_port = htons(port);// 转换为IP字符串char buffer[BUFFER_SIZE];_ip = inet_ntop(AF_INET, &_addr.sin_addr, buffer, sizeof(buffer));}bool operator==(const InetAddr &addr) const{return (_ip == addr._ip && _port == addr._port);}std::string &Ip() { return _ip; }const std::string &Ip() const { return _ip; }in_port_t &Port() { return _port; }const in_port_t &Port() const { return _port; }struct sockaddr_in &NetAddr() { return _addr; }const struct sockaddr_in &NetAddr() const { return _addr; }struct sockaddr *NetAddrPtr() { return (struct sockaddr *)&_addr; }const struct sockaddr *NetAddrPtr() const { return (struct sockaddr *)&_addr; }std::string Info() const { return _ip + ":" + std::to_string(_port); }socklen_t &AddrLen() { return _len; }const socklen_t &AddrLen() const { return _len; }private:std::string _ip;in_port_t _port;          // 主机字节序的端口struct sockaddr_in _addr; // 网络字节序的地址结构socklen_t _len;
};

3.3 Command.hpp

#pragma once
#include <unordered_set>
#include "Common.hpp"
#include <string>class Command
{
public:Command(){// 允许调用的命令_white_list.emplace("ls -l");_white_list.emplace("pwd");_white_list.emplace("tree");_white_list.emplace("whoami");_white_list.emplace("touch");}void Excute(int sockfd, const InetAddr& client, const std::string& command){if(!_white_list.count(command)){std::string info = "非法的命令! [" + command + "]";LOG(LogLevel::ERROR) << info;write(sockfd, info.c_str(), info.size());return;}FILE *fp = popen((command + " 2>&1").c_str(), "r");if(fp == nullptr){std::string info = std::string("popen: 创建子进程或管道失败! ") + strerror(errno);LOG(LogLevel::ERROR) << info;write(sockfd, info.c_str(), info.size());return;}char buffer[BUFFER_SIZE];std::string result;while(fgets(buffer, sizeof(buffer), fp)){result += buffer;}pclose(fp);ssize_t size = write(sockfd, result.c_str(), result.size());if (size == -1){LOG(LogLevel::ERROR) << "write: [" << client.Info() << "]写入数据时出错! " << std::strerror(errno);exit(WRITE_ERROR);}}
private:std::unordered_set<std::string> _white_list;
};

3.4 Server.cpp

#include <iostream>
#include <string>
#include "TCPServer.hpp"
#include "Command.hpp"void Usage(const std::string &name)
{std::cout << "usage: " << name << " + port" << std::endl;
}int main(int argc, char *args[])
{if (argc != 2){Usage(args[0]);exit(USAGE_ERROR);}in_port_t port = std::stoi(args[1]);Command command;auto message_handler = std::bind(&Command::Excute, &command, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);TCPServer tcp(port, message_handler);tcp.Run();return 0;
}

3.5 TCPServer.hpp

#pragma once
#include "Common.hpp"
#include <pthread.h>
#include "ThreadPool.hpp"
#define MAX_PENDING_LEN 15// NoCopy类拷贝构造函数和赋值重载都已删掉
// 任何类继承该类就无法被拷贝
class TCPServer : public NoCopy
{
private:// 默认消息处理方式---回显static void DefaultMessageHandler(int sockfd, const InetAddr &client, const std::string &message){ssize_t size = write(sockfd, message.c_str(), message.size());if (size == -1){LOG(LogLevel::ERROR) << "write: [" << client.Info() << "]写入数据时出错! " << std::strerror(errno);exit(WRITE_ERROR);}}// 为客户端提供服务的函数void Service(int sockfd, const InetAddr &client){char buffer[BUFFER_SIZE];int n;while ((n = read(sockfd, buffer, sizeof(buffer) - 1)) > 0){buffer[n] = 0;LOG(LogLevel::INFO) << "[" << client.Info() << "]# " << buffer;_message_handler(sockfd, client, buffer);}if (n == -1){LOG(LogLevel::ERROR) << "read: [" << client.Info() << "]读取数据时出错! " << std::strerror(errno);exit(READ_ERROR);}LOG(LogLevel::INFO) << "[" << client.Info() << "]已断开连接! ";}// 多线程实现的处理函数=============================================struct ThreadData{TCPServer* self;int sockfd;InetAddr client;};static void *ThreadHandler(void *args){pthread_detach(pthread_self());ThreadData *data = static_cast<ThreadData *>(args);data->self->Service(data->sockfd, data->client);close(data->sockfd);return nullptr;}// ===============================================================
public:TCPServer(in_port_t port, message_handler_t message_handler = DefaultMessageHandler): _message_handler(message_handler){// 多进程实现方式下,避免等待子进程的推荐方式// signal(SIGCHLD, SIG_IGN);_listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listen_sockfd == -1){LOG(LogLevel::FATAL) << "socket: 套接字创建失败! " << std::strerror(errno);exit(SOCKET_ERROR);}InetAddr local(INADDR_ANY, port);int n = bind(_listen_sockfd, local.NetAddrPtr(), local.AddrLen());if (n == -1){LOG(LogLevel::FATAL) << "bind: 绑定地址失败! " << std::strerror(errno);exit(BIND_ERROR);}}~TCPServer(){close(_listen_sockfd);}void Run(){int n = listen(_listen_sockfd, MAX_PENDING_LEN);if (n == -1){LOG(LogLevel::FATAL) << "listen: 设置监听套接字失败! " << std::strerror(errno);exit(LISTEN_ERROR);}LOG(LogLevel::INFO) << "监听套接字已就绪, 等待连接请求...";while (true){InetAddr client;int sockfd = accept(_listen_sockfd, client.NetAddrPtr(), &client.AddrLen());client = InetAddr(client.NetAddr());if (sockfd == -1){LOG(LogLevel::WARNING) << "accept: [" << client.Info() << "]建立连接失败! " << std::strerror(errno);continue;}LOG(LogLevel::INFO) << "accept: [" << client.Info() << "]建立连接成功! ";// 多进程===================================================================// int id = fork();// if(id < 0)// {//     // 创建子进程失败//     LOG(LogLevel::FATAL) << "fork: 子进程创建失败! " << std::strerror(errno);//     exit(FORK_ERROR);// }// else if(id == 0)// {//     // 子进程//     // 使用孙子进程, 避免等待子进程//     if(fork() > 0) exit(NORMAL);//     close(_listen_sockfd);//     Service(sockfd, client);//     exit(NORMAL);// }// else// {//     // 父进程//     close(sockfd);// }// 多进程===================================================================// 多线程===================================================================pthread_t thread;ThreadData data = {this, sockfd, client};pthread_create(&thread, nullptr, ThreadHandler, &data);// 多线程===================================================================// 线程池===================================================================// auto task = std::bind(&TCPServer::Service, this, sockfd, client);// ThreadPoolModule::ThreadPool<std::function<void()>>::GetInstance()->PushTask(task);// 线程池===================================================================}}private:int _listen_sockfd;message_handler_t _message_handler; // 客户端消息的处理函数
};

3.6 Client.cpp

#include "Common.hpp"void Usage(const std::string &name)
{std::cout << "usage: " << name << " + ip" << " + port" << std::endl;
}int main(int argc, char *args[])
{if (argc != 3){Usage(args[0]);exit(USAGE_ERROR);}in_port_t port = std::stoi(args[2]);InetAddr server(args[1], port);int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1){LOG(LogLevel::FATAL) << "socket: 套接字创建失败! " << std::strerror(errno);exit(SOCKET_ERROR);}int n = connect(sockfd, server.NetAddrPtr(), server.AddrLen());if (n == -1){LOG(LogLevel::FATAL) << "connect: 建立连接失败! " << std::strerror(errno);exit(CONNECT_ERROR);}std::string message;char buffer[BUFFER_SIZE];while (true){std::cout << "Send to server# ";std::getline(std::cin, message);ssize_t n = write(sockfd, message.c_str(), message.size());if (n == -1){LOG(LogLevel::ERROR) << "write: [" << server.Info() << "]写入数据时出错! " << std::strerror(errno);exit(WRITE_ERROR);}n = read(sockfd, buffer, sizeof(buffer) - 1);if(n == -1){LOG(LogLevel::ERROR) << "read: [" << server.Info() << "]读取数据时出错! " << std::strerror(errno);exit(READ_ERROR);}buffer[n] = 0;std::cout << "Recive from server: " << buffer << std::endl;}return 0;
}

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

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

相关文章

从零开始的python学习——文件

ʕ • ᴥ • ʔ づ♡ど &#x1f389; 欢迎点赞支持&#x1f389; 个人主页&#xff1a;励志不掉头发的内向程序员&#xff1b; 专栏主页&#xff1a;python学习专栏&#xff1b; 文章目录 前言 一、文件是什么 二、文件路径 三、文件操作 &#xff08;1&#xff09;打开文件 …

set与multset的区别;less greater 函数对象实现比较 作为排序依据

Set 和 multiset 特点 set中元素插入过程是按排序规则插入&#xff0c;所以不能指定插入位置。set不可以直接存取元素。&#xff08;不可以使用at.(pos)与[]操作符&#xff09;。multiset与set的区别&#xff1a;set支持唯一键值&#xff0c;每个元素值只能出现一次&#xff1b…

计算机视觉(八):开运算和闭运算

计算机视觉中的开运算&#xff08;Opening&#xff09;和闭运算&#xff08;Closing&#xff09;是两种非常重要的形态学&#xff08;Morphological&#xff09;图像处理操作。它们主要用于图像的去噪、分割、特征提取等任务。这两种运算都基于两种更基础的操作&#xff1a;腐蚀…

nginx常用命令(备忘)

一、引言&#xff1a;Nginx 为何成为前端开发必备工具 ** 在前端开发的广阔领域中&#xff0c;Nginx 已然成为了一个不可或缺的强大工具。它是一款轻量级的 HTTP 服务器和反向代理服务器&#xff0c;采用事件驱动的异步非阻塞处理方式框架&#xff0c;这赋予了它卓越的 I/O 性…

告别Qt Slider!用纯C++打造更轻量的TpSlider组件

组件运行效果展示 组件概述 TpSlider组件简介 TpSlider是PiXSingleGUI库中的可拖动滑块组件&#xff0c;支持水平和垂直两种方向的滑动操作。TpSlider.h:13-17该组件提供了完整的用户交互功能&#xff0c;包括鼠标拖拽、数值范围设置和实时反馈机制。 核心特性 双向支持&am…

sensitive-word 敏感词性能提升14倍优化全过程 v0.28.0

背景 有一天&#xff0c;群里收到小伙伴提的一个问题&#xff0c;为什么程序 sensitive-word 第一次执行这么慢? sensitive-word-131 初步验证 自己本地用 v0.27.1 验证了一下&#xff0c;确实很奇怪&#xff0c;第一次明显很慢。 为了排除一些干扰项&#xff0c;我们把一些…

4.6 多个光源

1.Include Files 2.The Second Light 3.Point Light1.Include Files 为了在着色器中实现多光源支持, 我们需要添加更多通道; 这些通道会包含几乎相同的代码, 为了避免代码重复, 我们将着色器代码移到一个包含文件中; 与光照着色器相同的文件夹中创建一个后缀为.cginc的文件, 将…

ANSYS HFSS的简单认识

HFSS&#xff08;High Frequency Structure Simulator&#xff09;是ANSYS公司开发的一款用于高频电磁场仿真的行业标准软件。它通过“计算”电磁波在各种结构中的行为&#xff0c;来帮助工程师设计天线、滤波器、微波电路、高速电子封装等。我用一个简单易懂的比喻来帮你理解整…

Codeforces Round 1046 (Div. 2) vp补题

只是签了三道题就燃尽了… 原题连接 A //不可能连续进三球 得分值差最多的只有00X00X00X00 bool jud(int a,int b){if(a!0&&b!0&&max(a,b)-2*(min(a,b)1)>1)return 0;if(a0||b0){if(abs(a-b)>3)return 0;}return 1; } void solve() {int a,b,c,d;cin…

水泵运行组态监控系统御控物联网解决方案

一、方案背景与需求分析随着工业4.0和智慧城市建设的推进&#xff0c;传统水泵监控方式存在数据孤岛、响应滞后、运维成本高等问题。本方案通过物联网&#xff08;IoT&#xff09;技术构建水泵运行组态监控系统&#xff0c;实现设备状态实时感知、故障预警、远程调控及能效优化…

海尔电视刷机

硬盘格式化只有ntfs和exfat怎么办&#xff0c;没有fat32 这台型号le32c31 连有线几天后突然卡系统启动中 电视系统崩溃了怎么办&#xff1f;一直显示启动中&#xff01;三分钟解决问题&#xff0c;只要五元搞定&#xff01;_哔哩哔哩_bilibili format H: /fs:FAT32 慢 disk…

Science Advances副主编:如何提高论文投稿接收率?

国际著名综合性学术期刊《Science Advances》每年可接到约20000份投稿&#xff0c;有高达90%的拒稿率&#xff0c;大部分稿件甚至没有进入评审阶段&#xff0c;作为该期刊的副主编之一&#xff0c;杜克大学的Warren Warren教授撰写了文章&#xff0c;给投稿人提出几点建议以提高…

少儿配音教育:广州声与色在线科技有限公司打造趣味课程,助力青少年语言能力提升

针对青少年语言表达能力培养需求&#xff0c;广州声与色在线科技有限公司推出 “少儿配音趣味课程”&#xff0c;通过动画、童话等青少年喜爱的形式&#xff0c;融合发声训练与兴趣培养&#xff0c;成为少儿素质教育的新选择。课程设计贴合 8-15 岁青少年认知特点&#xff1a;分…

【架构艺术】变更风险防控架构嵌入决策降噪模块的方法

在先前的文章中&#xff0c;我们聊到了一个变更观测任务可以通过什么样的方式对不同的变更防控能力做统一调度&#xff0c;达到优越的变更风险拦截效果。但是在实战当中&#xff0c;变更观测任务集成了很多能力&#xff0c;即便风险拦截率很高&#xff0c;但不同能力效果也有差…

LeetCode算法日记 - Day 33: 最长公共前缀、最长回文子串

目录 1. 最长公共前缀 1.1 题目解析 1.2 解法 1.3 代码实现 2. 最长回文子串 2.1 题目解析 2.2 解法 2.3 代码实现 1. 最长公共前缀 14. 最长公共前缀 - 力扣&#xff08;LeetCode&#xff09; 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&…

Python毕业设计推荐:基于Django的饮食计划推荐与交流分享平台 饮食健康系统 健康食谱计划系统

精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设木哥&#x1f525; &#x1f496; 文章目录 一、项目介绍二…

物联网双轴倾角传感器厂家全面解析

内容概要本文旨在全面解析物联网双轴倾角传感器厂家的核心竞争力&#xff0c;为进口设备代理商及工业物联网项目提供实用选型指南。我们将深入探讨行业领先制造商的研发实力和生产标准&#xff0c;重点分析产品特性如低功耗设计优势、0.2高精度测量特性&#xff0c;以及CAN/电流…

Docker学习笔记-网络类型

Docker 网络类型1、Docker四种网络模式 &#xff08;1&#xff09;docker四种网络模式如下&#xff1a; Bridge contauner 桥接式网络模式Host(open) container 开放式网络模式Container(join) container 联合挂载式网络模式&#xff0c;是host网络模式的延伸None(Close)…

SDRAM详细分析-08 数据手册解读

大家好,这里是大话硬件。 前面我们梳理了很多关于内存的内容,不知道有没有人好奇,为什么要花这么大的精力做这些内容? 在4月份的时候,三星宣布将在2025年逐步停产DDR4内存颗粒,随后海力士和镁光也跟着一起,都宣布逐步停产DDR4颗粒。这三家半导体厂商在内存方面顶了半边…

Windows 环境下部署 MinIO 集群

文章目录介绍软件特点下载多机分布式集群部署1.前提准备2. 新建minio工作目录3. 编写运行命令4. 启动、测试5. nginx配置介绍 MinIO 是一款高性能、开源、云原生的分布式对象存储系统&#xff0c;专为私有云、公有云和边缘计算场景设计&#xff0c;完全兼容 Amazon S3 API&…