【计算机网络】TCP(传输控制协议)套接字,多线程远程执行命令编程
📚 博主的专栏
🐧 Linux | 🖥️ C++ | 📊 数据结构 | 💡C++ 算法 | 🅒 C 语言 | 🌐 计算机网络
上篇文章:UDP套接字编程(英汉字典以及多线程聊天室编写)
下篇文章:应用层自定义协议与序列化
目录
Echo Server
1. 创建套接字(socket)创建一个通信端点(套接字描述符)
2. 绑定地址(bind)将套接字与本地IP地址和端口绑定
3. 监听连接(listen)监听客户端连接请求
4. 接受连接(accept)返回新套接字用于与客户端通信
Echo Server
5. 客户端连接(connect)
客户端:TcpClientMain.cpp
V1 - Echo Server 多进程版本
V2 - Echo Server 多线程版本
V3 ----Echo Server 线程池版本
V3-1 - 多线程远程命令执行
改造前面的代码:
添加以及修改:
Command.hpp
TcpServerMain.cc
6. 数据传输(send/recv)
7.popen 函数详解
Command.hpp
本文摘要:
本文深入解析TCP套接字编程的核心接口(socket、bind、listen、accept、connect),逐步实现单线程Echo服务器,并通过多进程、多线程及线程池版本优化并发能力。结合代码示例,探讨了父子进程资源管理、线程分离、任务队列等关键技术。进一步扩展服务器功能,通过白名单机制实现安全的远程命令执行(如
ls
、pwd
),并分析recv/send
与popen
的用法。最后预告应用层协议设计,解决TCP粘包与全双工通信问题,为构建高可靠网络服务提供完整实践指南。
Echo Server
首先我们介绍几个TCP套接字的接口
1. 创建套接字(socket)创建一个通信端点(套接字描述符)
函数原型:
int socket(int domain, int type, int protocol);
功能:创建一个通信端点(套接字描述符)。
参数:
domain
:地址族,常用AF_INET
(IPv4)或AF_INET6
(IPv6)。
type
:套接字类型,SOCK_STREAM
表示TCP协议。
protocol
:通常设为0
,由系统自动选择。返回值:成功返回套接字描述符(非负整数),失败返回
-1
。示例:
int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE); }
2. 绑定地址(bind)将套接字与本地IP地址和端口绑定
函数原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将套接字与本地IP地址和端口绑定。
参数:
sockfd
:套接字描述符。
addr
:指向sockaddr
结构体的指针,需填充本地地址信息。
addrlen
:地址结构体的长度。返回值:成功返回
0
,失败返回-1
。地址填充示例:
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; // 绑定所有可用IPif (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE); }
3. 监听连接(listen)监听客户端连接请求
函数原型:
int listen(int sockfd, int backlog);
功能:将套接字设为被动模式,监听客户端连接请求。
参数:
backlog
:等待连接队列的最大长度。返回值:成功返回
0
,失败返回-1
。示例:
if (listen(sockfd, 5) < 0) { // 最多允许5个客户端在队列中等待perror("listen failed");close(sockfd);exit(EXIT_FAILURE); }
4. 接受连接(accept)返回新套接字用于与客户端通信
函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:接受客户端连接请求,返回新套接字用于与客户端通信。
参数:
addr
:保存客户端地址信息的结构体指针。
addrlen
:地址结构体的长度(需初始化为sizeof(struct sockaddr_in)
)。返回值:成功返回新套接字描述符,失败返回
-1
。示例:
struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); int client_sock = accept(sockfd, (struct sockaddr*)&client_addr, &addr_len); if (client_sock < 0) {perror("accept failed");close(sockfd);exit(EXIT_FAILURE); }
具体accept的操作是:通过sockfd来获取每个客户端和服务端的链接,每获取到一个新的(不同的客户端)链接,就会有一个新的套接字描述符返回值,将来再提供服务的就是这个返回值套接字(io套接字),sockfd协助获取新的链接(不进行收发信息、listen(监听)套接字)。这种服务会随着客户端的链接增多,内部所维护的文件描述符会变多。
我们需要准备几个文件:许多文件可以参照上文:UDP编程
Makefile
.PHONY:all
all:tcpserver tcpclient
tcpserver:TcpServerMain.ccg++ -o $@ $^ -std=c++14
tcpclient:TcpClientMain.ccg++ -o $@ $^ -std=c++14
.PHONY:clean
clean: rm -rf tcpserver tcpclient
根据上篇博客内容,整理好思路再结合上面所列出的接口可以写好服务端第一阶段代码:
Echo Server
TcpServer.hpp1.0
【创建好套接字、绑定sockfd 和 socket addr、监听客户端连接需求】
#pragma once #include <iostream> #include <cstring> #include <functional> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/wait.h>#include "Log.hpp"using namespace log_ns;enum {SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERROR };const static int gport = 8888; const static int glistensockfd = -1; const static int gblcklog = 8; class TcpServer {public:TcpServer(uint16_t port = gport) : _port(port), _listensockfd(glistensockfd), _isrunning(false){}void InitServer(){// 1.创建socket_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(FATAL, "socker create error\n");exit(SOCKET_ERROR);}LOG(INFO, "socket create success, sockfd: %d\n", _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 = INADDR_ANY;// 2.绑定sockfd 和 socket addrif (::bind(_listensockfd, (struct sockaddr *)&local, sizeof(local)) < 0){LOG(FATAL, "bind error \n");exit(BIND_ERROR);}LOG(INFO, "bind success \n");// 3.因为tcp是面向连接的,就需要tcp未来不断的能够做到获取链接// 让套接字设置为listen状态if (::listen(_listensockfd, gblcklog)){LOG(FATAL, "listen error \n");exit(LISTEN_ERROR);}LOG(INFO, "listen success \n");}void Loop(){_isrunning = true;while (_isrunning){sleep(1);}_isrunning = false;}~TcpServer(){}private:uint16_t _port;int _listensockfd; // todobool _isrunning; };
Main函数测试验证:
#include "TcpServer.hpp" #include <memory>// ./tcpserver 8888 int main(int argc, char *argv[]) {if (argc != 2){std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;exit(0);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);tsvr->InitServer();tsvr->Loop();return 0; }
运行结果:
通过指令:具体指令的讲解可以看:计算机网络常用的几个指令
netstat -tlp
可以发现此时进程43712的状态就是LISTEN处于监听状态
Loop函数
在运行状态时,需要监听套接字不断地获取是否有新客户端连接请求,再去做对应的服务。提供对应的服务就需要得到客户端的IP、端口号、以及地址相关信息等:因此需要用到上篇文章所封装的:InetAddr.hpp
因为TCP是面向字节流的、因此收发信息的接口与UDP(UDP是:sendto、recvfrom)是不一样的
TCP可以直接使用文件的read和write来实现收发消息:不靠谱版本
void Loop(){_isrunning = true;while (_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);// 4.获取新链接// 从监听套接字获取新的套接字、获取客户端信息int sockfd = ::accept(_listensockfd, (struct sockaddr *)&client, &len);// 获取连接失败、继续获取if (sockfd < 0){LOG(WARNING, "accept error\n");continue;}InetAddr addr(client);// 获客成功,提供服务LOG(INFO, "get a new link, client info : %s\n", addr.AddrStr().c_str());// 不靠谱版本Service(sockfd, addr);}_isrunning = false;}void Service(int sockfd, InetAddr addr){// 长服务while (true){// 缺点:inbuffer不是动态的,每次只能读取这么多,或者说,明明只发了5个字节,但是却还是要读取1023个字节char inbuffer[1024];// n是实际读取的字节数 // 当做字符串ssize_t n = ::read(sockfd, inbuffer, sizeof(inbuffer) - 1); //(留一个\n的位置)if (n > 0){// 1.读取消息std::string echo_string = "[server echo]# ";echo_string += inbuffer;// 2.拼接好信息之后发回去,证明消息接收成功::write(sockfd, echo_string.c_str(), echo_string.size());}else if (n == 0) // 返回值为0,表示客户端结束(文件中读到尾){LOG(INFO, "client %s quit\n", addr.AddrStr().c_str());break;}else{LOG(ERROR, "read error: %s\n", addr.AddrStr().c_str());break;}}::close(sockfd);}
5. 客户端连接(connect)
函数原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:客户端主动连接服务器。
参数:
addr
:指向服务器地址的结构体指针。返回值:成功返回
0
,失败返回-1
。示例:
struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 将IP字符串转为二进制if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {perror("connect failed");close(sockfd);exit(EXIT_FAILURE); }
客户端:TcpClientMain.cpp
#include <iostream>
#include <cstring>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>#include "Log.hpp"
using namespace log_ns;int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 1.创建socketint sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "create socket error" << std::endl;exit(1);}// 2.不需要显示的bind,但是一定要有自己的IP和port,所以需要隐式的bind,OS会自动bind sockfd,用自己的IP和随机算口号// 什么时候进行自动bind,if the connection or binding succeedsstruct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);// server.sin_addr.s_addr =// 进程序列转为网络序列::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);int n = ::connect(sockfd, (struct sockaddr *)&server, sizeof(server));if(n < 0){//连接失败std::cerr << "connect socket error" <<std::endl;exit(2);}while(true){std::string message;std::cout << "Enter # ";std::getline(std::cin, message);//将消息发给服务器write(sockfd, message.c_str(), message.size());char echo_buffer[1024];//读取服务端返回的消息int n = read(sockfd, echo_buffer, sizeof(echo_buffer));if(n > 0){echo_buffer[n] = '\0';std::cout << echo_buffer <<std::endl;}else{break;}}::close(sockfd);return 0;
}
运行结果:
以上我们的代码只能实现一个客户端和服务端的链接,接下来我们引入多进程版本:
V1 - Echo Server 多进程版本
注意以前讲过,子进程会继承父进程的文件描述符表因此他们同样的向标注输出输入错误打印。
因此子进程可以关闭监听套接字,而要求父进程一定关闭其余套接字,保留监听套接字,这是为了,避免文件描述符泄漏
最后,使用子进程给客户端提供服务:
我们期待的是,父进程将自己的工作做完,就直接回到accept继续接受连接,子进程转而继续执行服务,父进程正在继续接受连接,子进程同时在进行服务对新链接进行处理 ,从而实现采用多进程方式实现并发处理链接。而实际上,子进程退出后会先进入僵尸状态,此时我们采用的是0方式(int n = waitpid(id, nullptr, 0);),也就是父进程必须阻塞直到等待到子进程。因此此时无法实现并发。
解决办法:
1.子进程在退出时是会向父进程发送信号的(SIGCHILD),如果在Linux环境里,我们对SIGCHILD信号进行忽略(signal(SIGCHLD, SIG_IGN);),父进程就再也不用等待子进程,只管创建子进程。---> 最优法
2.我们使用的是:
在子进程内部在创建一个进程(孙子进程),孙子进程创建好之后,直接将子进程退出,父进程等待成功,则继续循环,而孙子进程则开始服务处理链接。孙子进程最终被OS领养,被OS回收
if(id == 0){//child//注意以前讲过,子进程会继承父进程的文件描述符表,父子进程会指向同一个文件,因此子进程需要关闭监听套接字::close(_listensockfd);//创建了孙子进程,让子进程直接退出,父进程就能等待到子进程//,此后让孙子进程去执行任务,孙子进程最后会成为孤儿进程,被OS领养,被OS回收if(fork() > 0) exit(0);//子进程来处理服务Service(sockfd, addr);exit(0);}
测试:当一个服务端去请求好链接之后,父进程就开始等待新的客户端了
V2 - Echo Server 多线程版本
直接使用原生线程:
注定还是会存在问题,主线程就会循环当中阻塞等待,操作又是串行。所以如何让新线程去处理任务,让主线程继续去接受连接,实现并发呢?
pthread_t tid;pthread_create(&tid, nullptr, Execute, nullptr);pthread_join(tid);
解决办法:线程分离,不然主线程去等待新线程,默认线程创建出来是joinable,必须得被join。
static void *Execute(void *args){pthread_detach(pthread_self());}
新线程共享主线程的虚拟地址空间,有独立的栈结构,不能让主线程直接关闭fd了,也不需要。
由于发给线程的方法是返回值void*,参数void*的,因此在类内部我们使用static,保证没有隐藏的this指针传入。但是当想要执行Service服务方法的时候需要用到this指针,新获得的文件描述符,也要交给新线程,以及addr。
解决方法:将线程需要使用到的数据做一个打包封装,一起通过参数传给Execute。
定义内部类:
class ThreadData{public:int _sockfd;TcpServer *_self;InetAddr _addr;//给InetAddr再添加一个无参构造public:ThreadData(int sockfd, TcpServer *self, const InetAddr &addr) : _sockfd(sockfd), _self(self), _addr(addr) {}};
创建td对象、将td以参数的方式传给Execute
pthread_t tid;ThreadData* td = new ThreadData(sockfd, this, addr);pthread_create(&tid, nullptr, Execute, td);
分离线程后,获得td对象,从td对象中拿到需要的数据,并且执行Service,执行完毕后,delete掉td,并且返回空值。
static void *Execute(void *args){pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData *>(args);td->_self->Service(td->_sockfd, td->_addr);delete td;return nullptr;}
由于主线程不需要再因为等待join新线程而阻塞,因此在新线程执行Service的时候,主线程开始新的循环去接受连接,从而实现并发。
运行结果:
V3 ----Echo Server 线程池版本
// version 3 ---- 线程池版本 int sockfd, InetAddr addrtask_t t = std::bind(&TcpServer::Service, this, sockfd, addr);//把任务添加到任务队列当中ThreadPool<task_t>::GetInstance()->Equeue(t);
V3-1 - 多线程远程命令执行
改造前面的代码:
首先我们直接从前面的代码进行修改,删除不需要的代码:
我们使用的是多线程版本,将进程池版本删除掉
想让服务器提供服务,并且实现解耦
在TcpServer.hpp中只接受连接,创建线程,执行线程。对文件描述符的处理(1.怎么读怎么写2.怎么进行处理)交给外部。
将业务,都提到外面:注释掉Service函数。
写一个函数对象:用于处理业务
//设置函数对象 using command_service_t = std::function<void(int sockfd, InetAddr addr)>;
将函数对象设置为成员变量:
command_service_t _service;
构造函数初始化:
public:TcpServer(command_service_t service, uint16_t port = gport) : _port(port), _listensockfd(glistensockfd), _isrunning(false), _service(service){}
直接回调我们定义的函数对象
static void *Execute(void *args){pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData *>(args);//直接回调我们定义的函数对象td->_self->_service(td->_sockfd, td->_addr);//处理完了,直接关闭::close(td->_sockfd);delete td;return nullptr;}
添加以及修改:
Command.hpp
• 命令类, 用来执行命令, 并获取结果
• 这里暂停, 做一个多线程的小业务
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <set>
#include "Log.hpp"
#include "InetAddr.hpp"class Command
{
public:Command(){}~Command(){}// 约定跑的是字符串void Execute(const std::string &cmdstr){}// 处理命令void HandlerCommand(int sockfd, InetAddr addr){}private:
};
TcpServerMain.cc
#include "TcpServer.hpp"
#include <memory>
#include "Command.hpp"
// ./tcpserver 8888
int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;exit(0);}uint16_t port = std::stoi(argv[1]);Command cmdservice;std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::bind(&Command::HandlerCommand,&cmdservice, std::placeholders::_1,std::placeholders::_2),port);tsvr->InitServer();tsvr->Loop();return 0;
}
认识新的套接字接口:用于替代write和read
6. 数据传输(send/recv)
函数原型:
ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:发送或接收数据。TCP保证数据可靠传输,无丢失或乱序。
参数:
buf
:数据缓冲区指针。
len
:数据长度。
flags
:通常设为0
。返回值:成功返回实际发送/接收的字节数,失败返回
-1
。示例:
char buffer[1024]; ssize_t bytes_received = recv(client_sock, buffer, sizeof(buffer) - 1, 0); if (bytes_received > 0) {buffer[bytes_received] = '\0';printf("Received: %s\n", buffer); } else if (bytes_received == 0) {printf("Connection closed by client\n"); } else {perror("recv failed"); }
认识新接口:
7.popen
函数详解
popen
是 C 语言标准库中的一个高阶函数,用于简化与外部进程的通信。它通过创建管道(pipe)并启动新进程来执行系统命令,允许父进程读取子进程的输出或向子进程输入数据
1. 基本概念
功能:执行外部命令,并通过管道实现进程间单向通信。
原型:
FILE *popen(const char *command, const char *mode);
int pclose(FILE *stream);
参数:
command
:要执行的命令(如ls -l
或grep "text"
)。
mode
:管道模式,"r"
(读模式,获取子进程输出)或"w"
(写模式,向子进程输入数据)。
2. 返回值
成功:返回一个
FILE*
指针,用于通过标准文件操作函数(如fgets
、fprintf
)读写数据。失败:返回
NULL
,并设置errno
。
3. 使用场景
读取命令输出:例如执行
ls
并获取文件列表。向命令输入数据:例如向
grep
传递待过滤的文本。快速实现进程间通信:无需手动管理
fork
、exec
和管道。
Command.hpp
#pragma once#include <iostream> #include <string> #include <cstring> #include <cstdio> #include <set> #include "Log.hpp" #include "InetAddr.hpp"using namespace log_ns; class Command { public:Command(){// 白名单_safe_command.insert("ls");_safe_command.insert("touch"); // touch filename_safe_command.insert("pwd");_safe_command.insert("whoami");_safe_command.insert("which"); // which pwd}~Command(){ }//漏洞: ls; rm -rf ///检查是否属于白名单里的命令bool SafeCheck(const std::string &cmdstr){for(auto &cmd :_safe_command){//进行前缀的比较if(strncmp(cmd.c_str(), cmdstr.c_str(), cmd.size()) == 0){return true;}}return false;}// 约定跑的是字符串std::string Execute(const std::string &cmdstr){if(!SafeCheck(cmdstr)){return "unsafe";}FILE *fp = popen(cmdstr.c_str(), "r");std::string result;if (fp){char line[1024];// 从fp中读取到line里面while (fgets(line, sizeof(line), fp)){result += line;}//例如touch的返回值是空的,就认为成功了:return result.empty() ? "success" : result;}return "execute error";}// 处理命令void HandlerCommand(int sockfd, InetAddr addr){// 将命令的执行当成一个 长服务(例如:在xshell当中可以隔许久再在命令行输入下一次命令)while (true){// 缺点:inbuffer不是动态的,每次只能读取这么多,或者说,明明只发了5个字节,但是却还是要读取1023个字节char commandbuf[1024];// n是实际读取的字节数 // 当做字符串ssize_t n = ::recv(sockfd, commandbuf, sizeof(commandbuf) - 1, 0); //(留一个\n的位置)if (n > 0){commandbuf[n] = 0;LOG(INFO, "get command from client %s, command: %s\n", addr.AddrStr().c_str(), commandbuf);// 处理命令:std::string result = Execute(commandbuf);// 2.拼接好信息之后发回去,证明消息接收成功::send(sockfd, result.c_str(), result.size(), 0);}else if (n == 0) // 返回值为0,表示客户端结束(文件中读到尾){LOG(INFO, "client %s quit\n", addr.AddrStr().c_str());break;}else{LOG(ERROR, "recv error: %s\n", addr.AddrStr().c_str());break;}}}private:// 只允许执行个别命令std::set<std::string> _safe_command; };
运行结果: 实现了远程执行命令
实际上,我们还需要解决一个问题:为什么recv是不对的不完善的
ssize_t n = ::recv(sockfd, commandbuf, sizeof(commandbuf) - 1, 0); //(留一个\n的位置)
在下篇文章:
应用层自定义协议与序列化进行讲解:重新理解 read、 write、 recv、 send 和 tcp 为什么支持全双工
结语:
随着这篇博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。
你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容。
相关文章:
【计算机网络】TCP(传输控制协议)套接字,多线程远程执行命令编程
📚 博主的专栏 🐧 Linux | 🖥️ C | 📊 数据结构 | 💡C 算法 | 🅒 C 语言 | 🌐 计算机网络 上篇文章:UDP套接字编程(英汉字典以及多线程聊天室编写)…...
PostgreSQL 中 VACUUM FULL 对索引的影响
PostgreSQL 中 VACUUM FULL 对索引的影响 是的,VACUUM FULL 会重建表上的所有索引。这是它与普通 VACUUM 命令的一个重要区别。 一、VACUUM FULL 的工作原理 表重建过程: 创建表的全新副本只将有效数据写入新存储删除原始表文件将新文件重命名为原表名…...
DeepSeek本地部署及WebUI可视化完全指南
以下是为您整理的DeepSeek本地部署及WebUI可视化完全指南,整合了官方文档及社区实践的最佳方案: 一、环境准备 1. 硬件需求 CPU:推荐支持AVX2指令集的Intel i7或AMD Ryzen 7及以上处理器 。 GPU(可选但推荐)…...
大模型时代的新燃料:大规模拟真多风格语音合成数据集
以大模型技术为核心驱动力的人工智能变革浪潮中,语音交互领域正迎来广阔的成长空间,应用场景持续拓宽与延伸。 其中,数据作为驱动语音大模型进化的关键要素,重要性愈发凸显。丰富多样的高质量数据能够让语音大模型充分学习到语音…...
单体项目到微服务的架构演变与K8s发展是否会代替微服务
单体项目到微服务的架构演变与K8s发展是否会代替微服务 在互联网大厂Java求职者的面试中,经常会被问到关于单体项目到微服务的架构演变以及Kubernetes(k8s)的发展是否会代替微服务的相关问题。本文通过一个故事场景来展示这些问题的实际解决…...
AI驱动的决策智能系统(AIDP)和自然语言交互式分析
在当今快速变化的商业环境中,以下几个企业级系统领域最有可能成为新的热点,其驱动力来自数字化转型加速、AI技术爆发、全球化协同需求以及ESG(环境、社会、治理)合规压力的叠加 1. AI驱动的决策智能系统(AIDP…...
kubernetes》》k8s》》Service 、Ingress 区别
K8S>>Service 资料 K8S >>Ingress 资料 Ingress VS Service 物理层数据链路层网络层传输层会话层表示层应用层 Ingress是一种用于暴露HTTP和HTTPS路由的资源,它提供了七层(应用层)的负载均衡功能。Ingress可以根据主机名、…...
全面接入!Qwen3现已上线千帆
百度智能云千帆正式上线通义千问团队开源的最新一代Qwen3系列模型,包括旗舰级MoE模型Qwen3-235B-A22B、轻量级MoE模型Qwen3-30B-A3B。千帆大模型平台开源模型进一步扩充,以多维开放的模型服务、全栈模型开发、应用开发工具链、多模态数据治理及安全的能力…...
Python-日志检测异常行为的详细技术方案
以下是根据行为日志检测异常行为的详细技术方案,涵盖数据收集、特征工程、模型选择、部署与优化的全流程: 1. 数据收集与预处理 1.1 数据来源 行为日志通常包括以下类型: 用户行为日志:点击、登录、交易、页面停留时间等。系统…...
DeepSeek-Prover-V2-671B最新体验地址:Prover版仅适合解决专业数学证明问题
DeepSeek-Prover-V2-671B最新体验地址:Prover版仅适合解决专业数学证明问题 DeepSeek 团队于 2025 年 4 月 30 日正式在Hugging Face开源了其重量级新作 —— DeepSeek-Prover-V2-671B,这是一款专为解决数学定理证明和形式化推理任务而设计的超大规模语…...
Java写数据结构:队列
1.概念: 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列…...
LeetCode 2905 找出满足差值条件的下标II 题解
示例 nums [8, 3, 12, 5, 1, 10, 7, 13] indexDifference 3 valueDifference 6答案 [maxIdx, j] [0, 4]我的思路是直接枚举写,但这题是中等题,一定不会让你好过的,所以也是喜提了超时,先说一下我的做题思路吧。 其实很简单就…...
【思考】欧洲大停电分析
当地时间4月28日中午,西班牙和葡萄牙发生了大规模停电事故,两国多个地区的电力供应中断,波及超过5000万伊比利亚半岛民众,交通、通信、医疗等关键领域受到影响,马德里网球公开赛因停电被迫暂停,周边法国、意…...
[论文精读]Agent综述—— A survey on large language model based autonomous agents
A survey on large language model based autonomous agents ⏲️年份: 2024 👀期刊: Frontiers of Computer Science 🌱影响因子:3.4 📚数字对象唯一标识符DOl: 10.1007/s11704-024-40231-1 🤵作者: Wang Lei,Ma Chen,Feng X…...
金融风控的“天眼”:遥感技术的创新应用
在金融市场的复杂博弈中,风险管控一直是金融机构的核心竞争力。然而,传统的风控手段在应对现代金融市场的快速变化时,往往显得捉襟见肘。 如今,遥感技术的创新应用为金融风控带来了全新的视角和手段。星图云开放平台的遥感金融立体…...
SpringMVC知识点总结(速查速记)
文章目录 前言1、MVC是什么2、SpringMVC是什么3、SpringMVC请求流程 && 环境搭建3.1 SpringMVC请求流程3.2 搭建环境3.2.1开发环境3.2.2 环境配置步骤 4. url地址映射 && 参数绑定4.1 url地址映射之RequestMapping①、映射单个url②、映射多个url③、映射url到…...
配置 Odoo 的 PostgreSQL 数据库以允许远程访问的步骤
1. 修改 PostgreSQL 配置文件 a. 修改 postgresql.conf 找到 PostgreSQL 的主配置文件 postgresql.conf,通常位于 /etc/postgresql/<版本号>/main/ 目录下。修改 listen_addresses 项的值为 *,表示允许来自任何 IP 地址的连接: sudo…...
涨薪技术|0到1学会性能测试第42课-apache监控与调优
前面的推文我们学习了操作系统性能监控与调优知识,如CPU、内存、磁盘、网络监控等,今天开始分享中间件apache监控与调优知识,后续文章都会系统分享干货,带大家从0到1学会性能测试! Apache是世界上使用最多的web服务器软件一种,它可以运行在几乎所有广泛使用的计算机平台上…...
【学习笔记】Shell编程--Bash变量
变量类型说明环境变量 与Shell的执行环境相关的一些变量。如PATH,HOME等,用户可重新定义。 一、环境变量的创建:export, export ABCD2 二、环境变量的查看 使用echo命令查看单个环境变量。如: echo $PATH 使用printenv…...
SpringBoot+Redis全局唯一ID生成器
📦 优雅版 Redis ID 生成器工具类 支持: 项目启动时自动初始化起始值获取自增 ID 方法yml 配置化起始值可灵活扩展多业务线 ID 📌 application.yml 配置 id-generator:member-start-value: 1000000000📌 配置类:IdG…...
micro-app前端微服务原理解析
一、核心设计思想 基于 WebComponents 的组件化渲染 micro-app 借鉴 WebComponents 的 CustomElement 和 ShadowDom 特性,将子应用封装为类似 WebComponent 的自定义标签(如 <micro-app>)。通过 ShadowDom 的天然隔离机制,实…...
大连理工大学选修课——机器学习笔记(7):集成学习及随机森林
集成学习及随机森林 集成学习概述 泛化能力的局限 每种学习模型的能力都有其上限 限制于特定结构受限于训练样本的质量和规模 如何再提高泛化能力? 研究新结构扩大训练规模 提升模型的泛化能力 创造性思路 组合多个学习模型 集成学习 集成学习不是特定的…...
[特殊字符] Spring Cloud 微服务配置统一管理:基于 Nacos 的最佳实践详解
在微服务架构中,配置文件众多、管理复杂是常见问题。本文将手把手演示如何将配置集中托管到 Nacos,并在 Spring Cloud Alibaba 项目中实现统一配置管理 自动刷新机制。 一、为什么要使用 Nacos 统一配置? 传统方式下,每个服务都…...
【mysql】执行过程,背诵版
sql执行再mysql的执行过程 1. 建立连接 sql通过tcp/ip发送到服务器服务器检查用户名,密码,权限创建线程处理连接 如果是sql8.0之前,select会先从缓存中查找,命中则返回,由于表结构变更会导致缓存失效,已废…...
[Survey] Image Segmentation in Foundation Model Era: A Survey
BaseInfo TitleImage Segmentation in Foundation Model Era: A SurveyAdresshttps://arxiv.org/pdf/2408.12957Journal/Time-Author北理工、上交、浙大 CCAI 、瑞士苏黎世联邦理工学院、德国慕尼黑工业大学Codehttps://github.com/stanley-313/ImageSegFM-Survey 1. Introdu…...
关于杰理ac791切换版本, git clone下来仍然是最新版本问题
在git clone 之后,在本地切换分支 常规流程:git clone →git branch →git branch -a → git checkout 分支名...
生成项目.gitignore文件的多种高效方式
在使用 Git 进行版本控制时,.gitignore 文件是不可或缺的配置文件。它可以帮助我们指定哪些文件或目录不需要被 Git 跟踪,从而避免将不必要的文件(如临时文件、编译生成的文件等)提交到仓库中。这篇文章将介绍几种生成 .gitignore…...
2025年“深圳杯”数学建模挑战赛D题-法医物证多人身份鉴定问题
法医物证多人身份鉴定问题 小驴数模 犯罪现场法医物证鉴定是关系到国家安全、公共安全、人民生命财产安全和社会稳定的重大问题。目前法医物证鉴定依赖DNA分析技术不断提升。DNA检验的核心是STR(Short Tandem Repeat,短串联重复序列)分析技术…...
嵌入式开发高频面试题全解析:从基础编程到内存操作核心知识点实战
一、数组操作:3x3 数组的对角和、偶数和、奇数和 题目 求 3x3 数组的对角元素和、偶数元素和、奇数元素和。 知识点 数组遍历:通过双重循环访问数组的每个元素,外层循环控制行,内层循环控制列。对角元素判断: 主对…...
JAVA SE 反射,枚举与lambda表达式
文章目录 📕1. 反射✏️1.1 反射相关的类✏️1.2 Class类中的相关方法✏️1.3 Field类中的相关方法✏️1.4 Method类中的相关方法✏️1.5 Constructor类中的相关方法✏️1.6 获取Class对象的三种方式✏️1.7 反射的使用 📕2. 枚举2.1 枚举的定义✏️2.2 …...
每日算法-250430
每日算法 - 2025年4月30日 记录下今天解决的两道题目。 870. 优势洗牌 (Advantage Shuffle) 题目描述 解题思路与方法 核心思想:贪心策略 (田忌赛马) 这道题的目标是对于 nums1 中的每个元素,找到 nums2 中一个比它小的元素进行配对(如果…...
MacOS 安装 cocoapods
MacOS 安装 cocoapods 下面使用 HomeBrew 安装 cocoapods 一、检测 HomeBrew 是否安装 打开终端执行命令 brew -v #如果安装,输出如 Homebrew 4.5.0如果未安装 Mac HomeBrew安装 二、检测 ruby 是否安装 系统一般自带了 ruby 但是这个升级有些麻烦,我…...
MATLAB绘制饼图(二维/三维)
在数据分析与展示领域,饼图是一种直观且高效的可视化工具,能够在瞬间传递各部分与整体的比例关系。今天,我将分享一段 MATLAB 绘制二维及三维饼图的代码,助你轻松将数据以饼图形式呈现于众人眼前。 无论是二维饼图的简洁明了&…...
python将字符串转成二进制数组
python将字符串转成二进制数组 功能概述: save_binary_to_json() 函数:将字符串转换为二进制数据(字节的整数表示),并保存到JSON文件中。 load_binary_from_json() 函数:从JSON文件中读取二进制数据并还原…...
防止HTTPS页面通过<iframe>标签嵌入HTTP内容
防止HTTPS页面通过<iframe>标签嵌入HTTP内容 出于安全考虑,现代浏览器实施了严格的规则来防止HTTPS页面通过<iframe>标签嵌入HTTP内容。这种行为主要是为了防止所谓的“混合内容”问题,即在一个安全(加密)的页面中…...
windows 使用websocket++ (C++环境)
一、简介 websocket官方网址:http://websocket.org/ websocketpp官方网址:https://www.zaphoyd.com/websocketpp websocketpp使用手册:https://www.zaphoyd.com/websocketpp/manual/ websocketpp 是 C 的 WebSocket 客户端/服务器库. 它是…...
无水印短视频素材下载网站有哪些?十个高清无水印视频素材网站分享
你知道怎么下载无水印视频素材吗?今天小编就给大家推荐十个高清无水印视频素材下载的网站,如果你也是苦于下载高清无水印的短视频素材,赶紧来看看吧~ 1. 稻虎网 首推的是稻虎网。这个网站简直就是短视频创作者的宝库。无论你需要…...
【dify—5】Dify关联Ollama
目录 一、修改.env文件 二、启动dify 三、访问dify 四、设置关联 五、添加模型插件 5.1 添加模型 5.2 配置信息编辑 第一部分 安装difydocker教程:【difydocker安装教程】-CSDN博客 第二部分 dock重装教程: 【dify—2】docker重装-CSDN博客 第三…...
PostgreSQL可串行化快照隔离和冻结处理
1.可串行化快照隔离 可串行化快照隔离SSI已经嵌入到快照隔离SI中,以实现真正的可串行化隔离等级。 SSI实现基本策略 使用SIREDA锁记录事务访问的所有对象(元组,页面,关系)。 当写入任何堆元组/索引元组时ÿ…...
Java 多线程进阶:什么是线程安全?
在多线程编程中,“线程安全”是一个非常重要但又常被误解的概念。尤其对于刚接触多线程的人来说,不理解线程安全的本质,容易写出“偶尔出错”的代码——这类 bug 往往隐蔽且难以复现。 本文将用尽可能通俗的语言,从三个角度解释线…...
Java导出带图片的Excel
使用easypoi导出带图片的Excel, 引入依赖 依赖中着重要剔除可能会造成冲突的依赖,不剔除的话可能会报错 Exception in thread “main” java.lang.NoSuchFieldError: Class org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook does not …...
Keysight万用表使用指南及基于Python采集数据生成Excel文件
文章目录 说明使用的库openpyxlpyvisa 代码说明效果展示参考代码 说明 本文介绍了 Keysight 34465A 的基本使用和 SCPI 指令设置,演示了使用 Python 的 PyVISA 库控制两台 34465A 同时采集数据的完整流程,包括设置采样参数、触发测量、读取数据、使用 O…...
HarmonyOS Next-DevEco Studio(5.0.2)无网络环境配置(详细教程)
开发者如果电脑处于完全无网环境,可以参考下面文档进行相关配置 DevEco Studio(5.0.2)开发环境一览: 工具版本DevEco Studio5.0.2openHarmonySDK14ohpm5.0.11node.js18.20.1hypium1.0.21 一、下载DevEco Studio(5.0.2 Release)…...
数字中国的建设之路:超聚变以“智算数能”四大密钥,共建智能体时代
文 | 智能相对论 作者 | 陈泊丞 即便是数字中国建设这样的宏大叙事,在长期的行业实践与业务聚焦之下,未来的发展路径也将会越来越清晰。日前,第八届数字中国建设峰会在福建拉开序幕,各大论坛、企业、机构、组织等纷纷围绕数字中…...
PageOffice在线打开word文件,并实现切换文件
本示例关键代码的编写位置,请参考“PageOffice 开发者中心-快速起步–开始 - 快速上手”里您所使用的开发语言框架的最简集成代码 注意 本文中展示的代码均为关键代码,复制粘贴到您的项目中,按照实际的情况,例如文档路径ÿ…...
Ubuntu 24.04 终端美化
参考文章:Ubuntu终端美化(tabbyoh-my-zsh)-Ubuntu系列03 有些步骤和 Ubuntu 24.04 不太适配,而且逻辑不太适合小白,故写此文。 1. 安装 Tabby 参考文章的 tabby 版本过老,如果在 Ubuntu 24.04 装会报一些依…...
python合并word中的run
在处理Word文档时,使用python-docx库可以读取文档中的段落,并将每个段落中的多个run合并为一个run。run对象用于表示段落中具有相同格式的文本部分。将多个run合并为一个run可以帮助简化文档结构,尤其是在格式一致的情况下。 以下是一个示例…...
微前端框架选型指南
微前端框架选型指南 一、写在前面 微前端架构为大型前端系统提供了分而治之的能力,不同团队可以独立开发、部署和维护各自的模块。然而,当前市面上存在多种微前端框架(如 Qiankun、Wujie、micro-app、Hel、Emp 等),选…...
Tailwind CSS实战技巧:从核心类到高效开发
使用 Kooboo平台 训练实战技巧,无需配置安装,直接引入CDN就可以在线练习了!具体操作流程:进入Kooboo后,选择创建空白站点 -> 站点开发 -> 控制面板 -> 页面 ->新建普通页面 -> 编写代码 一、核心布局类…...
C# 事件与委托
一、委托基础 1. 委托定义 委托是一种类型安全的函数指针,它允许将方法作为参数传递给其他方法。 // 声明一个委托类型 public delegate void MyDelegate(string message);// 使用委托 public class Program {public static void Main(){// 创建委托实例并指向方…...