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

【Linux网络】各版本TCP服务器构建 - 从理解到实现

📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 🏳️‍🌈一、TcpServer.cpp
  • 🏳️‍🌈二、TcpServer.hpp
    • 2.1 枚举错误情况
    • 2.2 基本结构
    • 2.3 构造函数、析构函数
    • 2.4 初始化方法
    • 2.5 循环监听
      • 2.5.1 server 0 - 单执行流版本
      • 2.5.2 server 1 - 多进程版本
      • 2.5.3 server 2 - 多线程版本
      • 2.5.4 server 3 - 内存池
  • 🏳️‍🌈三、TcpClient.cpp
  • 👥总结


前面几篇文章中使用UDP协议实现了相关功能这篇使用TCP协议实现客户端与服务端的通信

相比与UDP协议,TCP协议更加可靠,也更加复杂!与UDP类似,我们先写主函数,然后实现相关函数!

🏳️‍🌈一、TcpServer.cpp

服务端主函数使用智能指针构造Server对象,然后调用初始化与执行函数,调用主函数使用该可执行程序 + 端口号!

#include "TcpServer.hpp"int main(int argc, char* argv[]){if(argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;Die(1);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);tsvr->InitServer();tsvr->Loop();return 0;
}

🏳️‍🌈二、TcpServer.hpp

2.1 枚举错误情况

与 UDP 同样的,我们先枚举错误情况,将其放在 common.hpp

enum {USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR
};

2.2 基本结构

我们这里先实现最基本的 TCP 服务器,基本成员有端口号,文件描述符,与运行状态

class TcpServer{public:TcpServer(){}void InitServer(){}void Loop(int sockfd){}~TcpServer(){}private:int _listensockfd; // 监听socketuint16_t _port;bool _isrunning;
};

2.3 构造函数、析构函数

构造函数初始化成员变量,析构函数无需处理!

  • 这里需要端口号设置一个默认值 - 8080
static const uint16_t gport = 8080;
// 构造函数
TcpServer(uint16_t port = gport):_port(port),_sockfd(gsockfd),_isrunning(false){}
// 析构函数
~TcpServer(){}

2.4 初始化方法

初始化函数主要分为三步:

  1. 创建 socket (类型与UDP不同)
  2. bind sockfdsocket addr
  3. 获取连接(与UDP不同)

获取连接需要使用 listen 函数(将套接字设置为监听模式,以便能够接受进入的连接请求)

·listen· 需要设置一个队列,用来保存等待连接地客户端,我们可以事先设置一个 #define BACKLOG 8 来定义这个队列长度

void InitServer() {// 1. 创建 socket_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0) {LOG(LogLevel::ERROR) << "create socket error: " << strerror(errno);Die(2);}LOG(LogLevel::INFO) << "create sockfd success: " << _listensockfd;struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = htonl(INADDR_ANY);// 2. 绑定 socketif (::bind(_listensockfd, CONV(&local), sizeof(local)) < 0) {LOG(LogLevel::ERROR) << "bind socket error: " << strerror(errno);Die(3);}LOG(LogLevel::INFO) << "bind sockfd success: " << _listensockfd;// 3. 因为 tcp 是面向连接的,tcp需要未来不断地获取连接// listen 就是监听连接的意思,所以需要设置一个队列,来保存等待连接的客户端// 队列的长度为 8,表示最多可以有 8 个客户端等待连接// listen(int sockfd, int backlog)// sockfd 就是之前创建的 socket 句柄// backlog 就是队列的长度// 返回值:成功返回 0,失败返回 -1if (::listen(_listensockfd, BACKLOG) < 0) {LOG(LogLevel::ERROR) << "listen socket error: " << strerror(errno);Die(4);}LOG(LogLevel::INFO) << "listen sockfd success: " << _listensockfd;
}

我们先将 Loop() 函数设计成死循环,验证一下 初始化函数 的正确性

// 测试
void Loop() {_isrunning = true;while (_isrunning) {sleep(1);}_isrunning = false;
}

在这里插入图片描述

2.5 循环监听

执行服务函数主要分为两步:

  1. 获取新连接 ( accept函数 [从已完成连接队列的头部返回下一个已完成连接,如果队列为空,则阻塞调用进程])

在这里插入图片描述
2. 执行服务 (前提是获取到新连接)

2.5.1 server 0 - 单执行流版本

工作机制:

  • 单线程通过while(1)循环依次处理每个客户端请求,处理完一个请求后才能处理下一个。

注意:tcp协议可以直接使用read,write函数读写文件描述符的内容(因为tcp是面向字节流的)!

// server - 0 单执行流版本
// ​工作机制:单线程通过while(1)循环依次处理每个客户端请求,处理完一个请求后才能处理下一个。
void Loop() {_isrunning = true;while (_isrunning) {struct sockaddr_in client;socklen_t len = sizeof(client);// 1. 获取新连接int sockfd = ::accept(_listensockfd, CONV(&client), &len);if (sockfd < 0) {LOG(LogLevel::ERROR) << "accept socket error: " << strerror(errno);continue;}InetAddr cli(client);LOG(LogLevel::INFO) << "accept new connection from " << cli.AddrStr()<< " sockfd: " << sockfd;// 获取成功Server(sockfd, cli);}
}
void Server(int sockfd, InetAddr& cli) {// 长服务while (true) {char inbuffer[1024]; // 当作字符串// 1. 读文件// read// 函数的第一个参数是文件描述符,第二个参数是读入缓冲区,第三个参数是读入的字节数// 返回值:成功返回读入的字节数,失败返回 -1ssize_t n = ::read(sockfd, inbuffer, sizeof(inbuffer) - 1);if (n > 0) {inbuffer[n] = 0;LOG(LogLevel::INFO)<< "get msg from " << cli.AddrStr() << " msg: " << inbuffer;std::string echo_string = "[server echo]# ";echo_string += inbuffer;// 2. 写文件write(sockfd, echo_string.c_str(), echo_string.size());} else if (n == 0) {LOG(LogLevel::INFO) << "client " << cli.AddrStr() << " closed";break;} else {LOG(LogLevel::ERROR) << "read socket error: " << strerror(errno);break;}}::close(sockfd);
}

2.5.2 server 1 - 多进程版本

​工作机制:

  • 父进程通过fork()为每个新连接创建子进程,子进程处理完请求后退出。
  • 但是进程创建/销毁开销大,​高并发时资源耗尽​
void Loop() {_isrunning = true;while (_isrunning) {struct sockaddr_in client;socklen_t len = sizeof(client);// 1. 获取新连接int sockfd = ::accept(_listensockfd, CONV(&client), &len);if (sockfd < 0) {LOG(LogLevel::ERROR) << "accept socket error: " << strerror(errno);continue;}InetAddr cli(client);LOG(LogLevel::INFO) << "accept new connection from " << cli.AddrStr()<< " sockfd: " << sockfd;// 获取成功pid_t id = fork();if (id == 0) {              // 子进程::close(_listensockfd); // 关闭监听套接字(子进程不需要监听)// 孙子进程的创建if (fork() > 0)exit(0); // 父进程(子进程)退出,孙子进程继续运行// 孙子进程执行实际服务逻辑Server(sockfd, cli);exit(0);}// 父进程(主进程)::close(sockfd); // 关闭连接套接字(父进程不处理具体业务)int n = waitpid(id, nullptr, 0); // 等待子进程退出if (n > 0) {LOG(LogLevel::INFO) << "wait chid success";}}_isrunning = false;
}
void Server(int sockfd, InetAddr& cli) {// 长服务while (true) {char inbuffer[1024]; // 当作字符串// 1. 读文件// read// 函数的第一个参数是文件描述符,第二个参数是读入缓冲区,第三个参数是读入的字节数// 返回值:成功返回读入的字节数,失败返回 -1ssize_t n = ::read(sockfd, inbuffer, sizeof(inbuffer) - 1);if (n > 0) {inbuffer[n] = 0;LOG(LogLevel::INFO)<< "get msg from " << cli.AddrStr() << " msg: " << inbuffer;std::string echo_string = "[server echo]# ";echo_string += inbuffer;// 2. 写文件write(sockfd, echo_string.c_str(), echo_string.size());} else if (n == 0) {LOG(LogLevel::INFO) << "client " << cli.AddrStr() << " closed";break;} else {LOG(LogLevel::ERROR) << "read socket error: " << strerror(errno);break;}}::close(sockfd);
}

双重 fork() 的核心目的

  1. 避免僵尸进程​
    • 若子进程直接处理请求并退出,父进程需通过 waitpid 回收资源,否则子进程会成为僵尸进程。
    • 高并发场景下,父进程可能因频繁调用 waitpid 而阻塞,无法及时处理新连接。
  • ​解决方案:
    • 子进程创建孙子进程后立即退出,孙子进程成为 ​孤儿进程,由 init 进程(PID=1)接管并自动回收资源。
    • 父进程只需等待子进程(短暂存在)退出,避免阻塞。
  1. 父进程快速回到主循环
    • 父进程的 waitpid 仅需等待子进程(而非孙子进程),子进程退出速度极快(仅执行 fork() 和 exit()),父进程迅速返回 accept 循环。
    • 提升服务器并发处理能力。

2.5.3 server 2 - 多线程版本

​工作机制:

  • 为每个新连接分配独立的线程处理业务逻辑
  1. 主线程Loop 函数)​通过 accept 循环监听新连接,为每个新连接创建线程(pthread_create)传递连接信息给子线程(通过 ThreadDate 结构体)
  2. 子线程Execute 函数)​​调用 pthread_detach 分离自身(避免主线程调用 pthread_join)执行 Server 函数处理具体业务逻辑线程结束后自动释放资源(通过 delete td)
  3. 业务处理Server 函数)​循环读取客户端数据(read),处理业务逻辑(示例中的回显服务),发送响应(write)关闭连接(close(sockfd))
        // server - 2 多线程版本// 为每个新连接分配独立的线程处理业务逻辑// (Loop 函数)通过 accept 循环监听新连接,为每个新连接创建线程(pthread_create)传递连接信息给子线程(通过 ThreadDate 结构体)// (Execute 函数)​​调用 pthread_detach 分离自身(避免主线程调用 pthread_join)执行 Server 函数处理具体业务逻辑线程结束后自动释放资源(通过 delete td)// (Server 函数)​循环读取客户端数据(read),处理业务逻辑(示例中的回显服务),发送响应(write)关闭连接(close(sockfd))void Loop(){_isrunning = true;while(_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);// 1. 获取新连接int sockfd = ::accept(_listensockfd, CONV(&client), &len);if(sockfd < 0){LOG(LogLevel::ERROR) << "accept socket error: " << strerror(errno);continue;}InetAddr cli(client);LOG(LogLevel::INFO) << "accept new connection from " << cli.AddrStr() << " sockfd: " << sockfd;// 获取成功pthread_t tid;ThreadDate* td = new ThreadDate(sockfd, this, cli);// pthread_create 第一个参数是线程id,第二个参数是线程属性,第三个参数是线程函数,第四个参数是线程函数参数pthread_create(&tid, nullptr, Execute, td);}_isrunning = false;}// 线程函数参数对象class ThreadDate{public:int _sockfd;TcpServer* _self;InetAddr _addr;public:ThreadDate(int sockfd, TcpServer* self, const InetAddr& addr): _sockfd(sockfd), _self(self), _addr(addr){}};// 线程函数static void* Execute(void* args){ThreadDate* td = static_cast<ThreadDate*>(args);// 子线程结束后由系统自动回收资源,无需主线程调用 pthread_joinpthread_detach(pthread_self()); // 分离新线程,无需主线程回收td->_self->Server(td->_sockfd, td->_addr);delete td;return nullptr;}void Server(int sockfd, InetAddr& cli){// 长服务while(true){char inbuffer[1024]; // 当作字符串// 1. 读文件// read 函数的第一个参数是文件描述符,第二个参数是读入缓冲区,第三个参数是读入的字节数// 返回值:成功返回读入的字节数,失败返回 -1ssize_t n = ::read(sockfd, inbuffer, sizeof(inbuffer) - 1);if(n > 0){inbuffer[n] = 0;LOG(LogLevel::INFO) << "get msg from " << cli.AddrStr() << " msg: " << inbuffer;std::string echo_string = "[server echo]# ";echo_string += inbuffer;// 2. 写文件write(sockfd, echo_string.c_str(), echo_string.size());}else if(n == 0){LOG(LogLevel::INFO) << "client " << cli.AddrStr() << " closed";break;}else{LOG(LogLevel::ERROR) << "read socket error: " << strerror(errno);break;}}::close(sockfd);}

2.5.4 server 3 - 内存池

将执行服务的函数入线程池队列,该函数需要是参数为空和返回值为void的函数,因此需要bind绑定函数!

using task_t = std::function<void(int sockfd, InetAddr& cli)>;
// server - 3 内存池版本
void Loop() {_isrunning = true;while (_isrunning) {struct sockaddr_in client;socklen_t len = sizeof(client);// 1. 获取新连接int sockfd = ::accept(_listensockfd, CONV(&client), &len);if (sockfd < 0) {LOG(LogLevel::ERROR) << "accept socket error: " << strerror(errno);continue;}InetAddr cli(client);LOG(LogLevel::INFO) << "accept new connection from " << cli.AddrStr()<< " sockfd: " << sockfd;// 获取成功task_t task = std::bind(&TcpServer::Server, this, sockfd, cli);ThreadPool<task_t>::getInstance()->Equeue(task);}_isrunning = false;
}
void Server(int sockfd, InetAddr& cli) {// 长服务while (true) {char inbuffer[1024]; // 当作字符串// 1. 读文件// read// 函数的第一个参数是文件描述符,第二个参数是读入缓冲区,第三个参数是读入的字节数// 返回值:成功返回读入的字节数,失败返回 -1ssize_t n = ::read(sockfd, inbuffer, sizeof(inbuffer) - 1);if (n > 0) {inbuffer[n] = 0;LOG(LogLevel::INFO)<< "get msg from " << cli.AddrStr() << " msg: " << inbuffer;std::string echo_string = "[server echo]# ";echo_string += inbuffer;// 2. 写文件write(sockfd, echo_string.c_str(), echo_string.size());} else if (n == 0) {LOG(LogLevel::INFO) << "client " << cli.AddrStr() << " closed";break;} else {LOG(LogLevel::ERROR) << "read socket error: " << strerror(errno);break;}}::close(sockfd);
}

🏳️‍🌈三、TcpClient.cpp

  1. ​创建Socket,调用 socket(AF_INET, SOCK_STREAM, 0) 创建TCP套接字。失败时打印错误并退出。
  2. ​设置服务器地址,通过 sockaddr_in 结构指定服务器的IP和端口。inet_pton 将字符串IP转换为网络字节序。
  3. 建立连接,调用 connect 主动连接服务器。失败时打印错误并退出。
  4. ​交互式通信,循环读取用户输入(如 hello)。通过 write 发送消息到服务器。通过 read 读取服务器回显的消息并显示。若服务器断开或读失败,退出循环。
  5. ​关闭连接,调用 close 关闭套接字。
#include "TcpClient.hpp"int main(int argc, char* argv[]){if(argc != 3){std::cerr << "Usage: " << argv[0] << " <server_ip> <server_port>" << std::endl;Die(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 1. 创建套接字// AF_INET: IPv4协议// SOCK_STREAM: TCP协议// 0: 表示默认协议int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){std::cerr << "Create socket error: " << std::strerror(errno) << std::endl;Die(2);}// 2. 设置服务器地址struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;    // IPv4协议server.sin_port = htons(serverport); // 端口号// 这句话表示将字符串形式的IP地址转换为网络字节序的IP地址// inet_pton函数的作用是将点分十进制的IP地址转换为网络字节序的IP地址// 这里的AF_INET表示IPv4协议// 这里的serverip.c_str()表示IP地址的字符串形式// &server.sin_addr表示将IP地址存储到sin_addr成员变量中::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);   // IP地址// 3. 与服务器建立连接int n = ::connect(sockfd, CONV(&server), sizeof(server));if(n < 0){std::cerr << "Connect error: " << std::strerror(errno) << std::endl;Die(5);}// 4. 发送消息while(true){std::string mag;std::cout << "Enter# ";std::getline(std::cin, mag);write(sockfd, mag.c_str(), mag.size());char echo_buf[1024];n = read(sockfd, echo_buf, sizeof(echo_buf));if(n > 0){echo_buf[n] = 0;std::cout << "Echo: " << echo_buf << std::endl;}else{break;}}// 5. 关闭套接字::close(sockfd);return 0;
}

👥总结

本篇博文对 【Linux网络】各版本TCP服务器构建 - 从理解到实现 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

相关文章:

【Linux网络】各版本TCP服务器构建 - 从理解到实现

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…...

航电系统多模态融合技术要点

一、技术要点 1. 多模态数据特性分析 异构数据对齐&#xff1a;需处理不同传感器&#xff08;如雷达、摄像头、IMU、ADS-B等&#xff09;在时间、空间、精度和采样率上的差异&#xff0c;需设计同步机制&#xff08;如硬件时钟同步、软件插值对齐&#xff09;。 数据预处…...

【Git】branch合并分支

在 Git 中&#xff0c;将分支合并到 main 分支是一个常见的操作。以下是详细的步骤和说明&#xff0c;帮助你完成这个过程。 1. 确保你在正确的分支上 首先&#xff0c;你需要确保当前所在的分支是 main 分支&#xff08;或者你要合并到的目标分支&#xff09;。 检查当前分支…...

uniapp-商城-33-shop 布局搜索页面以及u-search

shop页面上有一个搜索&#xff0c;可以进行商品搜索&#xff0c;这里我们先做一个页面布局&#xff0c;后面再来进行数据i联动。 1、shop页面的搜索 2、搜索的页面代码 <navigator class"searchView" url"/pagesub/pageshop/search/search"> …...

蓝桥杯常考的找规律题

目录 灵感来源&#xff1a; B站视频链接&#xff1a; 找规律题具有什么样的特点&#xff1a; 报数游戏&#xff08;Java组&#xff09;&#xff1a; 题目描述&#xff1a; 题目链接&#xff1a; 思路详解&#xff1a; 代码详解&#xff1a; 阶乘求和&#xff08;Java组…...

全球化2.0 | 云轴科技ZStack亮相2025香港国际创科展

4月13-16日&#xff0c;由香港特别行政区政府、香港贸发局主办的2025香港国际创科展&#xff08;InnoEX&#xff09;在香港会议展览中心举办&#xff0c;作为亚洲最具影响力的科技盛会之一&#xff0c;本届展会吸引了来自17个国家和地区的500余家顶尖科技企业、科研机构及行业先…...

【Python进阶】数据可视化:Matplotlib从入门到实战

Python数据可视化&#xff1a;Matplotlib完全指南 前言技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块说明技术选型对比 二、实战演示环境配置要求核心代码实现案例1&#xff1a;折线图&#xff08;股票趋势&#…...

操作系统——堆与栈详解:内存结构全面科普

文章目录 堆与栈详解&#xff1a;内存结构全面科普一、程序内存结构总览二、各段介绍及特点1. 代码段 .text2. 数据段 .data3. BSS段 .bss4. 堆区 Heap5. 栈区 Stack 三、C语言实例分析四、深入理解&#xff1a;为什么堆空间可能不连续&#xff1f;1. 堆内部结构&#xff1a;链…...

Mysql面试知识点详解

Mysql面试知识点详解 Mysql 是 Java 开发领域中常用的持久层框架&#xff0c;在面试和实际开发中都占据重要地位。本文将深入剖析 Mysql的核心知识点&#xff0c;并结合实战案例&#xff0c;帮助读者全面掌握相关技能。 一、慢查询定位与分析 &#xff08;一&#xff09;定位…...

数智读书笔记系列030《曲折的职业道路:在终身工作时代找准定位》与《做自己的教练:战胜工作挑战掌控职业生涯》

书籍简介 《曲折的职业道路:在终身工作时代找准定位》由英国职业发展专家海伦塔珀(Helen Tupper)和莎拉埃利斯(Sarah Ellis)合著,旨在帮助读者应对现代职场中日益普遍的“非直线型”职业路径。两位作者是“神奇的如果”(Amazing If)公司的联合创始人,曾为李维斯、沃达…...

Linux内核之文件驱动随笔

前言 近期需要实现linux系统文件防护功能&#xff0c;故此调研了些许知识&#xff0c;如何实现文件防护功能从而实现针对文件目录防护功能。当被保护的目录&#xff0c;禁止增删改操作。通过内核层面实现相关功能&#xff0c;另外在通过跟应用层面交互从而实现具体的业务功能。…...

【python】如何将文件夹及其子文件夹下的所有word文件汇总导出到一个excel文件里?

根据你的需求,这里提供一套完整的Python解决方案,支持递归遍历子文件夹、提取Word文档内容(段落+表格),并整合到Excel中。以下是代码实现及详细说明: 一个单元格一个word的全部内容 完整代码 # -*- coding: utf-8 -*- import os from docx import Document import pand…...

IDEA中如何统一项目名称/复制的项目如何修改根目录名称

1、问题概述&#xff1f; 在开发中&#xff0c;有时候为了方便&#xff0c;我们会复制一个新的项目&#xff0c;结果出现如下提示&#xff1a; 会在工程的后面提示工程原来的名字。 这种情况就是复制之后名字修改不彻底造成的。 2、彻底的修改工程的名字 2.1、修改pom.xml中…...

Ubuntu-Linux中vi / vim编辑文件,保存并退出

1.打开文件 vi / vim 文件名&#xff08;例&#xff1a; vim word.txt &#xff09;。 若权限不够&#xff0c;则在前方添加 sudo &#xff08;例&#xff1a;sudo vim word.txt &#xff09;来增加权限&#xff1b; 2.进入文件&#xff0c;按 i 键进入编辑模式。 3.编辑结…...

如何在idea里创建注释模版

✅ 步骤&#xff1a;创建一个类注释的 Live Template&#xff08;缩写为 cls&#xff09; ① 打开设置 IDEA 菜单栏点击&#xff1a;File > Settings&#xff08;或按快捷键 Ctrl Alt S&#xff09; ② 进入 Live Templates 设置 在左侧菜单找到&#xff1a;Editor > …...

IntelliJ IDEA 新版本中 Maven 子模块不显示的解决方案

一、问题现象与背景 在使用 IntelliJ IDEA 2024 版本开发 Maven 多模块项目时&#xff0c;我发现一个令人困惑的现象&#xff1a;父模块的子模块未在右侧 Maven 工具窗口中显示&#xff0c;仅显示父模块名称&#xff08;且无 (root) 标识&#xff09;。而此前在 IntelliJ IDEA…...

day48—双指针-通过删除字母匹配到字典最长单词(LeetCode-524)

题目描述 给你一个字符串 s 和一个字符串数组 dictionary &#xff0c;找出并返回 dictionary 中最长的字符串&#xff0c;该字符串可以通过删除 s 中的某些字符得到。 如果答案不止一个&#xff0c;返回长度最长且字母序最小的字符串。如果答案不存在&#xff0c;则返回空字…...

美乐迪电玩大厅加载机制与 RoomList 配置结构分析

本篇为《美乐迪电玩全套系统搭建》系列的第三篇&#xff0c;聚焦大厅与子游戏的动态加载机制&#xff0c;深入解析 roomlist.json 的数据结构、解析流程、入口配置方式与自定义接入扩展技巧。通过本篇内容&#xff0c;开发者可实现自由控制子游戏接入与分发策略。 一、RoomList…...

局域网内,将linux(Ubuntu)的硬盘映射成Windows上,像本地磁盘一样使用

如何把同处一个局域网内的Ubuntu硬盘&#xff0c;映射到Windows上&#xff0c;使得Windows就像使用本地磁盘一样使用Ubuntu的磁盘&#xff1f; 要在同一局域网内的Windows上像本地磁盘一样使用Ubuntu硬盘&#xff0c;可以按照以下步骤操作&#xff1a; 1. 在Ubuntu上设置Samba…...

界面控件DevExpress WPF v25.1预览 - 支持Windows 11系统强调色

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…...

【Hive入门】Hive架构与组件深度解析:从核心组件到生态协同

目录 1 Hive架构全景图 2 核心组件运维职责详解 2.1 Metastore元数据中心 2.2 Driver驱动组件 2.3 Executor执行引擎 3 与HDFS/YARN的协同关系 3.1 HDFS协同架构 3.2 YARN资源调度 4 运维实战案例 4.1 Metastore连接泄露 4.2 小文件合并 5 最佳实践总结 5.1 性能优…...

【图像识别改名】如何批量识别多个图片的区域内容给图片改名,批量图片区域文字识别改名,基于WPF和腾讯OCR的实现方案和步骤

基于WPF和腾讯OCR的批量图像区域文字识别改名方案 本方案适用于以下场景: 大量扫描文档需要根据文档中的特定区域内容(如编号、标题等)进行重命名证件照片需要根据证件号码或姓名进行整理归档企业档案管理需要根据文件上的编号自动分类教育机构需要根据学生试卷上的学号自动…...

从ChatGPT到GPT-4:大模型如何重塑人类认知边界?

从ChatGPT到GPT-4&#xff1a;大模型如何重塑人类认知边界&#xff1f; 在人工智能&#xff08;AI&#xff09;领域&#xff0c;近年来最引人注目的进展之一是大型语言模型的发展。从最初的GPT-1到现在的GPT-4&#xff0c;这些模型不仅在技术上取得了显著的进步&#xff0c;而…...

QEMU源码全解析 —— 块设备虚拟化(21)

接前一篇文章:QEMU源码全解析 —— 块设备虚拟化(20) 本文内容参考: 《趣谈Linux操作系统》 —— 刘超,极客时间 《QEMU/KVM源码解析与应用》 —— 李强,机械工业出版社 特此致谢! 上一回开始解析blockdev_init函数,讲到了其中调用的blk_new_open函数,该函数的作用…...

vue2练习项目 家乡特色网站—前端静态网站模板

最近一直在学习前端 vue2 开发&#xff0c;基础知识已经学习的差不多了&#xff0c;那肯定需要写几个项目来练习一下自己学习到的知识点。今天就分享一个使用 vue2 开发的一个前端静态网站&#xff0c;【家乡特色网站】 先给大家看一下网站的样式&#xff1a; 这里就只简单的…...

CFIS-YOLO:面向边缘设备的木材缺陷检测轻量级网络解析

论文地址:https://arxiv.org/pdf/2504.11305 目录 一、论文核心贡献 二、创新点详解 2.1 CARAFE动态上采样 工作原理 优势对比 2.2 C2f_FNB轻量模块 计算效率 2.3 Inner-SIoU损失函数 三、实验验证 3.1 消融实验 3.2 对比实验 四、应用部署 4.1 边缘设备部署流程…...

vue3 + element-plus中el-dialog对话框滚动条回到顶部

对话框滚动条回到顶部 1、需要对话框显示后 2、使用 nextTick 等待 Dom 更新完毕 3、通过开发者工具追查到滚动条对应的标签及class“el-overlay-dialog”。追查方法&#xff1a; 4、设置属性 scrollTop 0 或者 执行方法 scrollTo(0, 0) // 对话框显示标识 const dialogVi…...

赛灵思Xilinx FPGa XCKU15P‑2FFVA1156I AMD Kintex UltraScale+

XCKU15P‑2FFVA1156I 是 AMD Kintex UltraScale 系列中的高性能 FPGA&#xff0c;基于 16 nm FinFET UltraScale 架构 制造&#xff0c;兼顾卓越的性能与功耗比&#xff0c;该器件集成 1,143,450 个逻辑单元和 82,329,600 位片上 RAM&#xff0c;配备 1,968 个 DSP 切片&#…...

力扣2492:并查集/dfs

方法一&#xff1a;并查集。如果不仔细读题&#xff0c;可能会想着把每一个一维数组下标为2的位置进行排序即可。但这是不行的。因为有可能有一些节点在其它集合当中。如果这些节点之间存在一个边权值比节点1所在集合的最小边权值还要小&#xff0c;那么求出来的答案就是错的。…...

宝塔面板引发的血案:onlyoffice协作空间无法正常安装的案例分享

今天和客户一起解决:onlyoffice协作空间的安装问题,本来已经发现由于客户用的机械硬盘,某些安装步骤等待的时间不够,已经加了处理。但是安装成功后,登录系统一直提示报错如下 检查docker容器都是正常的,并且health也是正确的,登录就一直报错。后面发现用免费版的安装程序可以正…...

【阿里云大模型高级工程师ACP习题集】2.1 用大模型构建新人答疑机器人

练习题 【单选题】1. 在调用通义千问大模型时,将API Key存储在环境变量中的主要目的是? A. 方便在代码中引用 B. 提高API调用的速度 C. 增强API Key的安全性 D. 符合阿里云的规定 【多选题】2. 以下哪些属于大模型在问答场景中的工作阶段?( ) A. 输入文本分词化 B. Toke…...

C++中的算术转换、其他隐式类型转换和显示转换详解

C中的类型转换&#xff08;Type Conversion&#xff09;是指将一个数据类型的值转换为另一个数据类型的过程&#xff0c;主要包括&#xff1a; 一、算术类型转换&#xff08;Arithmetic Conversions&#xff09; 算术类型转换通常发生在算术运算或比较中&#xff0c;称为**“标…...

Python自动化selenium-一直卡着不打开浏览器怎么办?

Python自动化selenium 如果出现卡住不打开&#xff0c;就把驱动放当前目录并指定 from selenium import webdriver from selenium.webdriver.chrome.service import Service import time import osdef open_baidu():# 获取当前目录中的chromedriver.exe的绝对路径current_di…...

AI Agent开发第35课-揭秘RAG系统的致命漏洞与防御策略

第一章 智能客服系统的安全悖论 1.1 系统角色暴露的致命弱点 当用户以"你好"开启对话后追问"你之前说了什么",看似无害的互动实则暗藏杀机。2024年数据显示,93%的开源RAG系统在该场景下会完整复述初始化指令,导致系统角色定义(如电商导购)被完全暴露…...

【MySQL】数据库安装

数据库安装 一. Ubantu下安装 MySQL 数据库1. 查看Linux系统版本2. 添加 MySQL APT 源1. Windows 下载发布包2. 上传发布包到 Linux3. 安装发布包 3. 安装 MySQL4. 查看 MySQL 状态5. 开启自启动6. 登录 MySQL 一. Ubantu下安装 MySQL 数据库 1. 查看Linux系统版本 操作系统版…...

OpenGL shader开发实战学习笔记:第十二章 深入光照

1. 深入光照 1.1. 平行光 我们在前面的章节中&#xff0c;已经介绍了平行光的基本原理和实现步骤 平行光的基本原理是&#xff0c;所有的光都从同一个方向照射到物体上&#xff0c;这个方向就是平行光的方向。 1.2. 点光源 点光源的基本原理是&#xff0c;所有的光都从一个…...

1-1 什么是数据结构

1.0 数据结构的基本概念 数据结构是计算机科学中一个非常重要的概念&#xff0c;它是指在计算机中组织、管理和存储数据的方式&#xff0c;以便能够高效地访问和修改数据。简而言之&#xff0c;数据结构是用来处理数据的格式&#xff0c;使得数据可以被更有效地使用。 数据结构…...

【MySQL】:数据库事务管理

一&#xff1a;学习路径 &#xff08;1&#xff09;下载安装mysql &#xff08;2&#xff09;学习语言&#xff1a;SQL(操作数据库&#xff09; &#xff08;3&#xff09;mysql集群&#xff08;提升数据库存储效率&#xff09; &#xff08;4&#xff09;SQL使用&#xff0c;M…...

leetcode 647. Palindromic Substrings

题目描述 代码&#xff1a; class Solution { public:int countSubstrings(string s) {int n s.size();//i<j,dp[i][j]表示子字符串s[i,j]是否是回文子串,i>j的dp[i][j]不定义vector<vector<int>> dp(n,vector<int>(n,false));int res 0;for(int i …...

Linux-scp命令

scp&#xff08;Secure Copy Protocol&#xff09;是基于 SSH 的安全文件传输命令&#xff0c;用于在本地和远程主机之间加密传输文件或目录。以下是详细用法和示例&#xff1a; 基本语法 scp [选项] 源文件 目标路径常用选项 选项描述-P 端口号指定 SSH 端口&#xff08;默认…...

在CSDN的1095天(创作纪念日)

一早上收到CSDN官方的私信&#xff0c;时间飞逝&#xff0c;转眼间3年了…… 一些碎碎念… 算起来也断更一年多了&#xff0c;上一次更博客是去年的3月份&#xff0c;那时候还在实习&#xff0c;同时也是去年的三月份结束了第一段实习回学校准备考研&#xff0c;考完研12月开始…...

STM32——新建工程并使用寄存器以及库函数进行点灯

本文是根据江协科技提供的教学视频所写&#xff0c;旨在便于日后复习&#xff0c;同时供学习嵌入式的朋友们参考&#xff0c;文中涉及到的所有资料也均来源于江协科技&#xff08;资料下载&#xff09;。 新建工程并使用寄存器以及库函数进行点灯操作 新建工程步骤1.建立工程2.…...

【Linux】多线程任务模块

创建多个线程&#xff0c;同时完成任务 task.c #include <sys/types.h> #include <unistd.h> #include<stdio.h> #include <sys/wait.h> int create_process_tasks(Task_fun_t tasks[],int tsak_cnt) {pid_t pid;int i 0;for(i 0;i < 4;i){pid …...

Maxscript调用Newtonsoft.Json解析Json

Maxscript调用Newtonsoft.Json解析Json_newtonsoft.json maxscript-CSDN博客...

【前端】【面试】【业务场景】前端如何获取并生成设备唯一标识

✅ 总结 问题&#xff1a;前端如何获取并生成设备唯一标识&#xff1f; 核心要点&#xff1a;浏览器原生信息有限&#xff0c;但通过组合多个维度可生成设备指纹&#xff08;Device Fingerprint&#xff09;&#xff0c;用于唯一标识设备。 常见方式&#xff1a; 浏览器信息&…...

《Java面试通关宝典:基础篇》——Java面试题系列(持续更新)

《Java面试通关宝典&#xff1a;基础篇》是一篇针对Java编程初学者的面试宝典&#xff0c;旨在帮助大家快速复习Java编程语言的基础知识&#xff0c;提高面试竞争力。本文详细介绍了Java基础知识的各个方面&#xff0c;包括语言基础、面向对象、集合框架、异常处理等内容。同时…...

学习笔记(C++篇)--- Day 3

1.析构函数 析构函数不是完成对对象本身的销毁&#xff0c;C规定对象在销毁时会自动调用析构函数&#xff0c;完成对象中资源的清理释放工作。&#xff08;严格说&#xff0c;Date是不要析构函数的&#xff09; 特点&#xff1a; ①析构函数名是在类名钱加上字符~。 ②无参数&a…...

消息队列知识点详解

消息队列场景 什么是消息队列 可以把消息队列理解一个使用队列来通信的组件&#xff0c;它的本质是交换机队列的模式&#xff0c;实现发送消息&#xff0c;存储消息&#xff0c;消费消息的过程。 我们通常说的消息队列&#xff0c;MQ其实就是消息中间件&#xff0c;业界中比较…...

AI 赋能 3D 创作!Tripo3D 全功能深度解析与实操教程

大家好&#xff0c;欢迎来到本期科技工具分享&#xff01; 今天要给大家带来一款革命性的 AI 3D 模型生成平台 ——Tripo3D。 无论你是游戏开发者、设计师&#xff0c;还是 3D 建模爱好者&#xff0c;只要想降低创作门槛、提升效率&#xff0c;这款工具都值得深入了解。 接下…...

DeepSeek赋能Nuclei:打造网络安全检测的“超级助手”

引言 各位少侠&#xff0c;周末快乐&#xff0c;幸会幸会&#xff01; 今天唠一个超酷的技术组合——用AI大模型给Nuclei开挂&#xff0c;提升漏洞检测能力&#xff01; 想象一下&#xff0c;当出现新漏洞时&#xff0c;少侠们经常需要根据Nuclei模板&#xff0c;手动扒漏洞文章…...