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
英文地址,請注明出處:http://en.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&…