HTTPServer改进思路1

news2024/11/26 12:02:02

Nginx源码思考项目改进 

架构模式

  • 事件驱动架构(EDA)用于处理大量并发连接和IO操作
    • 优点:高效处理大量并发请求,减少线程切换和阻塞调用
    • 技术实现:直接使用EPOLL,参考Node.js的http服务器
  • 网络通信
    • 协议:HTTP2
      • 本身是改进的HTTP1.1,保持与HTTP的高兼容性(gRPC虽然可以提供更高效的传输效率,但是与项目的主要功能不符,所以暂时不用)
      • HTTP2,在快速加载静态和动态库的内容中适用,可以提高速度
      • 主要特性
        • 多路复用:在同一连接中并行处理多个请求和响应,减少了延迟。
        • 服务器推送:允许服务器未经请求即推送资源,提高页面加载速度。
        • 头部压缩:通过HPACK压缩协议减少了请求和响应头的大小。
      • 尝试使用nghttp2的库来支持HTTP2
    • gRPC:基于HTTP/2的高效RPC协议,适用于服务间通信。
    • WebSocket:用于实时通信的全双工协议。
  • 并发处理
    • 事件驱动模型
      • Reactor模式:主线程负责监听事件并分发给工作线程处理。适用于高并发、低延迟场景。(大量短请求)
      • Proactor模式:主线程负责完成事件处理,工作线程处理业务逻辑。适用于需要高并发和高吞吐量的场景。
    • 线程池
      • 使用线程池处理并发请求,避免频繁创建和销毁线程的开销。
      • 配置合理的线程池大小,避免过多或过少的线程影响性能。
    • 协程
      • 使用协程实现轻量级并发处理,提高系统资源利用率。协程相比于线程,开销更小,切换更快。
  • 资源管理
    • 内存管理
      • 使用高效的内存分配器,如tcmalloc、jemalloc,减少内存碎片和分配开销。
      • 避免内存泄漏,使用智能指针(如C++的std::shared_ptrstd::unique_ptr)管理内存。
    • 文件描述符管理
      • 合理分配和管理文件描述符,避免资源泄漏。
    • 限流
      • 实现限流策略,防止高并发请求压垮系统。可以使用漏桶算法或令牌桶算法。
  • 缓存
    • 本地缓存
      • 在服务器内存中缓存频繁访问的数据,减少数据库访问次数。
    • 分布式缓存
      • 对于需要高一致性的场景,使用一致性哈希算法管理缓存节点

HTTP请求模块阅读后思考

主要目标是在云服务器上设计一个高性能HTTP服务器,从而实现HTTP请求的高效处理。

改进方向

  • 非阻塞IO和事件驱动模型
    • 使用EPOLL以及reactor去完成高效的I多路复用机制
    • 尝试改进方向
      • EPOLL来处理并发连接
      • 使用非阻塞的IO操作,避免单个连接阻塞整个服务器
    • 内存池管理
      • 使用内存池进行内存管理,减少频繁内存分配和释放内存操作,提高内存利用率,减少内存碎片
      • 改进方向
        • 实现简单的内存管理器,预分配大内存,然后从中分配小内存
        • 请求结束后统一释放内存
    • 请求处理和解析
      • 优化存储的数据结构
    • 反向代理和负载均衡
      • 将请求转发到后端服务器,同时将响应返回给客户端。
      • 改进方向
        • 使用功能HTTP客户端库或者服务器完成HTTP请求转发
        • 尝试使用简单的轮询、加权轮询或者哈希算法实现负载均衡
    • 压缩和缓存
      • 对HTTP响应进行gzip压缩,减少传输数据量。实现缓存功能,缓存后端服务器的响应,减少后端服务器负载,提高响应速度。
      • 改进方向
        • 使用zlib库进行gzip压缩。
        • 使用哈希表或LRU缓存算法实现缓存。
    • 限流和访问控制
      • 限制客户端的请求速率,防止恶意请求或流量攻击。通过IP地址或其他条件进行访问控制,限制特定客户端的访问权限
      • 改进方向
        • 使用令牌桶或漏桶算法实现限流。
        • 使用IP白名单或黑名单进行访问控制

修改服务器架构 

EPOLL事件驱动在HTTP服务器架构下,HTTP请求和HTTP响应分别对应EPOLL的什么状态。

  • EPOLLIN (Readable): 表示有新的数据可读。对于一个HTTP服务器,当一个新的HTTP请求到达时,socket变为可读状态,触发EPOLLIN事件。你需要监听这个事件来读取客户端发送的HTTP请求数据。

  • EPOLLOUT (Writable): 表示可以向socket写入数据。当你需要向客户端发送HTTP响应时,通常会监听这个事件。

  • EPOLLET (Edge Triggered): EPOLL的边缘触发模式。这个模式下,事件只在状态变化时通知,所以需要非阻塞I/O和循环读取/写入数据,直到数据全部处理完毕。对于高性能服务器,这种模式更高效。

事件类型和socket之间的关系分析

  • ADD (EPOLL_CTL_ADD): 当你第一次监听一个socket时,使用EPOLL_CTL_ADD事件类型,将socket添加到EPOLL实例中,并指定你要监听的事件类型(通常是EPOLLIN)。

  • MOD (EPOLL_CTL_MOD): 当你已经在监听一个socket,但想修改其监听的事件类型时,使用EPOLL_CTL_MOD事件类型。例如,当你读取完HTTP请求数据后,想监听EPOLLOUT事件以便发送响应,就可以使用EPOLL_CTL_MOD修改监听事件类型。

  • DEL (EPOLL_CTL_DEL): 当你不再需要监听某个socket时,使用EPOLL_CTL_DEL事件类型将其从EPOLL实例中删除。

HTTP请求后,EPOLL具体实现步骤分析

  • 添加socket到EPOLL实例中: 使用epoll_ctl函数和EPOLL_CTL_ADD事件类型,将监听的socket添加到EPOLL实例中,并指定要监听EPOLLIN事件。
  • 读取请求数据: 当EPOLL检测到EPOLLIN事件时,读取HTTP请求数据。
  • 修改监听事件类型: 如果需要发送响应,可以使用EPOLL_CTL_MOD修改监听事件类型为EPOLLOUT。
  • 发送响应数据: 当EPOLL检测到EPOLLOUT事件时,发送HTTP响应数据。
//简化EPOLL模型

#include <sys/epoll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define MAX_EVENTS 10
#define READ_BUFFER_SIZE 1024

void handle_connection(int client_fd) {
    char buffer[READ_BUFFER_SIZE];
    int bytes_read = read(client_fd, buffer, sizeof(buffer) - 1);

    if (bytes_read > 0) {
        buffer[bytes_read] = '\0'; // Null-terminate the string
        printf("Received request:\n%s\n", buffer);
        // Process the HTTP request and generate a response here.
        // For simplicity, we'll just send a basic HTTP response.
        const char *response = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, World!";
        write(client_fd, response, strlen(response));
    } else if (bytes_read == 0) {
        // Client closed the connection
        close(client_fd);
    } else {
        // Read error
        perror("read");
        close(client_fd);
    }
}

int main() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0); // Create a listening socket
    // Bind and listen steps are omitted for brevity...

    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    struct epoll_event event;
    struct epoll_event events[MAX_EVENTS];

    event.data.fd = listen_fd;
    event.events = EPOLLIN;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {
        perror("epoll_ctl: listen_fd");
        exit(EXIT_FAILURE);
    }

    while (1) {
        int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        for (int i = 0; i < n; i++) {
            if (events[i].events & EPOLLIN) {
                if (events[i].data.fd == listen_fd) {
                    // Handle new connection
                    int client_fd = accept(listen_fd, NULL, NULL);
                    if (client_fd == -1) {
                        perror("accept");
                        continue;
                    }

                    // Add new client socket to EPOLL instance
                    event.data.fd = client_fd;
                    event.events = EPOLLIN;
                    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {
                        perror("epoll_ctl: client_fd");
                        close(client_fd);
                    }
                } else {
                    // Handle data from a connected client
                    handle_connection(events[i].data.fd);
                    // Optionally, modify the socket to listen for EPOLLOUT if needed
                }
            }
        }
    }

    close(listen_fd);
    close(epoll_fd);
    return 0;
}

项目设计分析与部分改进

TcpServer.hpp

  • getinstance()

    • 作用:实现单例模式的方法,用于确保TcpServer类的实例在程序生命周期内只被创建一次
    • 单例模式的目的:一个类只有一个实例,并提供一个全局访问点。
    • 静态互斥锁:线程锁的Lock确保它只可以被初始化一次,所以声明为静态,并在所有的getinstance的调用中共享。
    • 双重检查是否上锁:外部检查避免了在单例实例已经创建获取锁。内部检查确保了线程安全。
    • 类外初始化:类外初始化静态成员svr
    • 私有化构造函数:构造函数私有化,防止直接实例化类

 

 改进思路

 使用C++11的静态局部变量初始化特性,简化代码的同时保证线程安全

  • std::once_flag std::call_once: 使用 std::once_flagstd::call_once 确保 TcpServer 实例只被初始化一次,并且是线程安全的。
  • 静态局部变量: svr 作为静态局部变量,只会被初始化一次,确保单例的唯一性。
  • 构造函数私有化: 将构造函数设为私有,防止类外部直接创建实例。
  • 禁止拷贝和赋值: 删除拷贝构造函数和赋值操作符,防止复制单例实例。
class TcpServer
{
private:
    int port;
    int listen_sock;
    // static TcpServer*svr;
    static std::unique_ptr<TcpServer> svr; 
private:
    TcpServer(int _port):port(_port),listen_sock(-1)
    {}
    TcpServer(const TcpServer&s) = delete;
    TcpServer&operator = (const TcpServer&) = delete;
public:
    //单例模式
    static TcpServer*getinstance(int port)
    {
        // static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
        // if(nullptr == svr)
        // {
        //     pthread_mutex_lock(&lock);
        //     if(nullptr == svr)
        //     {
        //         svr = new TcpServer(port);
        //         svr->InitServer();
        //     }
        //     pthread_mutex_unlock(&lock);
        // }
        // return svr;
        static std::once_flag initFlag;
        std::call_once(initFlag,[&]()
        {
            svr.reset(new TcpServer(port));
            svr->InitServer();    
        });
        return svr.get();
    }

    void InitServer()
    {
    ......
    }
};

// TcpServer*TcpServer::svr = nullptr;
std::unique_ptr<TcpServer> TcpServer::svr = nullptr;

 HttpServer.hpp

InitServer()

  • 忽略SIGPIPE信号,防止写入到已经关闭的套接字时导致服务器崩溃
    • unix系统编程中,SIGPIPE信号通常与管道和套接字的写操作有关,当进程试图向一个已经关闭的管道或者套接字写入数据的时候,系统会向进程发送SIGPIPE信号。如果进程没有处理这个信号,默认行为则是终止进程。所以为了避免这种情况的发生则忽略SIGPIPE信号

 Loop()

  • accept接受新连接,tsvr->sock返回服务器监听的套接字描述符,peer用于保存客户端的地址信息
  • 创建任务并加入线程池
  • Loop函数是服务器的核心,它不断监听客户端连接请求,当接收到新连接的时候,将其封装成任务并提交给线程池处理。通过线程池来提高服务器的并发处理能力,同事避免频繁创建和销毁线程的开销

 

整体代码实现 

#pragma once 

#include<iostream>
#include<pthread.h>
#include<signal.h>
#include"TcpServer.hpp"
#include"log.hpp"
#include"Task.hpp"
#include"ThreadPoll.hpp"

#define PORT 8888

class HttpServer
{
private:
    int port;
    bool stop; //标记服务状态
public:
    HttpServer(int _port=PORT)
    :port(_port),stop(false)
    {}

    void InitServer()
    {
        //避免写入时,server崩溃,忽略SIGPIPE信号
        signal(SIGPIPE,SIG_IGN);
        LOG(INFO,"server initialized");
    }

    //启动服务
    void Loop()
    {
        TcpServer*tsvr = TcpServer::getinstance(port);
        LOG(INFO,"Loop begin")
        while(!stop)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sock = accept(tsvr->Sock(),(struct sockaddr*)&peer,&len);
            if(sock<0)
            {
                continue;
            }
            LOG(INFO,"Get a new link");

            //构建任务
            Task task(sock);
            //向线程池中传入任务z
            ThreadPoll::getinstance()->PushTask(task);
        }
    }
    ~HttpServer()
    {}
};

 ThreadPoll.hpp

成员变量分析

  • num: 线程池中线程的数量。
  • stop: 标记线程池是否停止。
  • task_queue: 存储待处理任务的队列。
  • lock: 保护任务队列的互斥量。
  • cond: 用于线程间同步的条件变量。

单例模式设计

  •  single_instance:静态指针,指向唯一的线程池实例
  • getinstance():使用双重检查锁定模式确保线程池实例在多线程环境下安全初始化

 

任务管理模块

  •  pushTask:将任务添加到任务队列,并唤醒一个线程
  • popTask:从任务队列中取出任务

 

 InitThreadPoll()

线程池初始化,创建num个线程,并启动它们执行ThreadRoutine函数。线程池层创建多个工作线程,每个线程在后台等待并处理任务队列中的任务。通过线程池提高服务器的并发处理能力,避免了频繁创建和销毁线程的开销。

 

 ThreadRoutine(void* args)

每个线程在任务队列为空时等待任务,一旦有任务则取出并进行处理。无限循环的从任务队列中获取任务进行处理,通过加锁确保访问任务队列是线程安全的,while循环检查任务队列是否为空,等待任务队列的到来,防止虚假唤醒。

 

改进思路

  •  添加停止机制:添加stopAll方法,安全的停止所有线程
  • 条件变量广播:使用pthread_cond_broadcast唤醒所有线程,以便在停止线程池的时候所有线程都能够及时退出
  • 改进线程池的单例模式:使用C++11中std::call_once和std::once_flag确保线程池实例的线程安全初始化。同时使用std::unique_ptr管理单例实例的生命周期
    • 线程安全:std::call_once和std::once_flag确保初始化操作线程安全
    • 自动资源管理:使用std::unique_ptr管理单例实例,确保资源在程序结束的时候自动释放,避免内存泄漏

 

 ThreadPoll单例模式实现过程分析

  • std::call_once(initInstanceFlag, []() { ... }); 确保 lambda 表达式只会被调用一次。
  • single_instance.reset(new ThreadPoll()); 创建一个新的 ThreadPoll 实例,并将其指针管理权交给 single_instance
  • single_instance->InitThreadPoll(); 初始化线程池。
  • return single_instance.get(); 返回 ThreadPoll 实例的指针。

当其他线程同时调用getinstance的时候,由于std::call_once的保证,lambda表达式只会在第一个线程调用时执行一次,后续的调用都不会重复执行对象的创建和初始化,所有的线程都会返回同一个ThreadPoll实例

 std::call_once函数

  • C++11引入的一个标准库函数,用于确保某个操作只被执行一次,即使有多个线程同时进行该操作。与std::once_flag配合使用,能够方便的实现线程安全的初始化。
  • 参数分析
    • flag: 一个 std::once_flag 对象,用来保证所调用的函数只执行一次。
    • f: 要执行的函数,可以是函数指针、函数对象或 lambda 表达式。
    • args...: 要传递给函数 f 的参数。

single_instance.reset(new ThreadPoll())

  • sigle_instance是一个unique_ptr对象,而reset是unique_ptr的成员函数
  • reset:成员函数用于重置智能指针,释放其当前持有的对象,并让其持有新的对象

 

线程池的优缺点 

优点

  • 提高并发性能
    • 减少线程创建和销毁的内存开销:线程池中的线程是在初始化的时候创建的,而不是等待每次任务到来的时候才创建的,从而减少频繁创建和销毁线程所带来的系统开销。
    • 提高资源利用效率:通过限制最大线程数量,从而避免系统资源被过度消耗,从而防止因为创建过度线程导致的性能下降。
  • 简化编程
    • 代码逻辑清晰:任务处理逻辑与线程管理逻辑分离,从而让代码更便于理解和维护。
    • 任务提交简单:利用线程池技术,只需要将任务提交给线程池,线程池就会自己调度和执行任务,不需要程序员管理线程的声明周期。
  • 线程复用
    • 提高线程利用效率:线程池中的线程是可以重复进行使用,提高线程的使用效率以及系统的响应速度。
  • 负载均衡
    • 均衡的分配任务:线程池可以自动均匀的将任务分配给线程,从而实现负载均衡,避免个别线程过载
  • 减少上下文切换

缺点

  • 实现复杂,不方便进行调优
  • 资源竞争
    • 同步开销:多线程环境下,多个线程竞争访问共享资源从而带来同步开销,从而影响性能
    • 死锁风险:如果对同步和锁不当的管理,会造成死锁
  • 增加内存占用
    • 提前创建线程会增加内存开销:如果处理的任务量过多,线程的创建会占用更多的内存。
  • 响应时间不确定

 单例模式改进后代码

#pragma once 

#include<iostream>
#include<queue>
#include<pthread.h>
#include<functional>
#include<memory>
#include<mutex>
#include<unistd.h>
#include"Task.hpp"

#define NUM 6

class ThreadPoll
{
    private:
        int num;//线程池中线程个数
        bool stop;//错误处理标志
        std::queue<Task> task_queue;//任务队列
        pthread_mutex_t lock;//互斥量
        pthread_cond_t cond;//条件变量

        ThreadPoll(int _num = NUM):num(_num),stop(false)
        {
            pthread_mutex_init(&lock,nullptr);
            pthread_cond_init(&cond,nullptr);
        }
        ~ThreadPoll()
        {
            pthread_mutex_destroy(&lock);
            pthread_cond_destroy(&cond);
        } 
        
        //改进1
        // ThreadPoll(const ThreadPoll&){};
        ThreadPoll(const ThreadPoll&) = delete;
        ThreadPoll&operator = (const ThreadPoll&) = delete;

        static std::unique_ptr<ThreadPoll>single_instance;
        static std::once_flag initInstanceFlag;

    public:

        //线程池单例设计
        static ThreadPoll*getinstance()
        {
            // static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
            // if(single_instance == nullptr)
            // {
            //     pthread_mutex_lock(&_mutex);
            //     if(single_instance == nullptr)
            //     {
            //         //类内创建线程池并完成初始化
            //         single_instance = new ThreadPoll();
            //         single_instance->InitThreadPoll();
            //     }
            //     pthread_mutex_unlock(&_mutex);
            // }
            // return single_instance;

            //改进3,使用C++11对单例模式进行升级
            std::call_once(initInstanceFlag,[](){
                single_instance.reset(new ThreadPoll());
                single_instance->InitThreadPoll();
            });
            return single_instance.get();
        }

        bool InitThreadPoll()
        {
            for(int i =0;i<num;i++)
            {
                pthread_t tid;
                //线程从线程池中拿取任务执行,需要通过this指针
                if(pthread_create(&tid,nullptr,ThreadRoutine,this)!=0)
                {
                    LOG(FATAL,"create thread pool error ");
                    return false;
                }
            }
            LOG(INFO,"create thread pool success ");
            return true;
        }

        void PushTask(const Task&task)
        {
            Lock();
            task_queue.push(task);//将任务放到任务队列中
            Unlock();
            ThreadWakeup();
        }

        //改进2
        void StopAll()
        {
            Lock();
            stop = true;
            //唤醒所有线程
            pthread_cond_broadcast(&cond);
            Unlock();
        }

        //判断线程是否退出
        bool IsStop()
        {
            return stop;
        }

        bool TaskQueueIsEmpty()
        {
            return task_queue.size()==0?true:false;
        }

        void Lock()
        {
            pthread_mutex_lock(&lock);
        }

        void Unlock()
        {
            pthread_mutex_unlock(&lock);
        }

        void ThreadWait()
        {
            //使用条件变量,条件变量满足时唤醒线程继续执行任务
             pthread_cond_wait(&cond,&lock);
        }

        void ThreadWakeup()
        {
            pthread_cond_signal(&cond);
        }

        static void *ThreadRoutine(void*args)
        {
            ThreadPoll*tp = (ThreadPoll*)args;
            
            while(true)
            {
                Task t;
                tp->Lock();
                //如果任务队列中没有任务,则让线程进行休眠;防止伪唤醒的出现,使用while循环
                while (tp->TaskQueueIsEmpty() && !tp->IsStop())
                {
                   tp->ThreadWait();
                }
                if(tp->IsStop())
                {
                    tp->Unlock();
                    break;
                }
                tp->PopTask(t);
                tp->Unlock();
                t.ProcessOn();
            }
            return nullptr;
        }  
        void PopTask(Task&task)
        {
            task = task_queue.front();
            task_queue.pop();
        }


};

std::unique_ptr<ThreadPoll> ThreadPoll::single_instance;
std::once_flag ThreadPoll::initInstanceFlag;

当前线程池的替换方案

  • 异步编程
    • C++的boost::asiolibuv
      • boost.Asio是Boost库的一部分,主要提供了异步IO的操作,同时支持TCP、UDP等协议
      • libuv 是一个多平台的支持异步 I/O 的 C 库,最初用于 Node.js。它支持事件循环和异步 I/O 操作,使得构建高性能的网络应用程序变得更加容易
      • 优点:高效处理IO操作,减少线程切换带来的开销
      • 缺点:编程模型复杂,需要管理异步操作的生命周期
    • 协程
      • 协程是轻量化的用户态线程,可以在单个线程中实现类似于多线程的并发操作
      • 优点:简化异步编程模型,代码可读性高,性能开销小
      • 缺点:需要现代编译器的支持
    • Reactor/Proactor模型
      • 事件驱动的并发模型,boost::asio就是基于Reactor模型实现的
      • 优点:高效处理并发IO请求
      • 缺点:需要理解和实现事件驱动机制,编程模型复杂
    • Actor模型
      • 并发编程模型,通过消息传递进行通信,常见的包括Erlang和Akka
      • 优点:支持并发和分布式,模型清晰
      • 缺点:代码改动大
    • IO密集型应用适合异步编程或者Reactor模型;CPU密集型任务则使用线程池;携程和Actor模型在高并发或者分布系统中表现出色。

总结:主要是修改思路的一些总结,后面代码的具体改进也是基于此处的一些探索,非最终版本。 

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

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

相关文章

day6 io线程

获取终端输入的字符

记录 cocos 开发问题 ,微信 wx.xxx函数 报找不到名称“wx”

今天写微信排行榜遇到 问题分享一下。 目前&#xff0c;微信、百度 和 抖音 小游戏这些平台为了保护其社交关系链数据&#xff0c;增加了 开放数据域 的概念&#xff0c;这是一个单独的游戏执行环境。开放数据域中的资源、引擎、程序&#xff0c;都和主游戏&#xff08;主域&a…

【2】Spring Cloud 工程搭建

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;Spring Cloud实战&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 1.声明项目依赖和项目构建插件 2.完善子项目订单服务 2.1完善启动…

[Spring] Spring配置文件

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

Spring推出Spring AI框架,看看怎么个事

文章目录 概述SpringAI介绍Spring AI 提供以下功能支持的聊天模型有哪些支持的文生图的模型有哪些支持的音频到文本模型支持的嵌入模型有哪些支持的矢量数据库有哪些 概述 在当今快速发展的技术时代&#xff0c;人工智能&#xff08;AI&#xff09;已经成为各行各业的关键驱动…

Linux——awk操作符

[rootlocalhost ~] # awk BEGIN{x2;y3;print x**y,x^y,x*y,x/y,xy,x-y,x%y} 8 8 6 0 .666667 5 -1 2 赋值运算符 条件运算符 awk 中的条件运算符只有一个&#xff0c;其语法如下&#xff1a; expression?value1:value2 这是一个三目运算符&#xff0c;当表达式 expre…

#三元运算符(python/java/c)

引入&#xff1a;什么是三元运算符呢&#xff1f;无疑其操作元有三个&#xff0c;一个是条件表达式&#xff0c;剩余两个为值&#xff0c;条件表达式为真时运算取第一个值&#xff0c;为假时取第二个值。 一 Python true_expression if condition else false_expressi…

网络通讯实验报告

拓扑图 需求 1、通过DHCP服务&#xff0c;给PC4和PC5分配IP地址、网关、掩码、DNS服务器IP地址 2、Client-1要求手工配置IP地址&#xff0c;为192.168.1.1, c 3、telnet客户端可以远程登录telnet服务器进行设备管理&#xff0c;并成功修改telnet服务器的名字为123 &#xff0c…

两轮差速拖动机械臂瞄准接收口目标

下图所示&#xff0c;关节2为无动力旋转关节&#xff0c;关节4为无动力移动关节&#xff0c;关节5为旋转关节&#xff0c;差速轮可绕轮中心点4自由旋转&#xff0c;差速轮带动2和4关节运动。设计差速轮和机械臂解算方法&#xff0c;使其相互配合到达接收口&#xff0c;瞄准目标…

树上前缀和树状数组结合

怎么去分析这个题目&#xff0c;一开始我想的就是暴力dfs&#xff0c;接着枚举删除的节点&#xff0c;但是只过了百分之四十个点&#xff0c;代码如下&#xff1a;(这个代码有点像树形dp&#xff09; #include<bits/stdc.h> using namespace std;#define int long long c…

SHL笔试测评题型题库大揭秘适用公司通过技巧神助攻

⭕SHL题库外企应用的比较多&#xff0c;整体来看分为三类。 ✅第一类是综合能力&#xff0c;考察综合素质&#xff0c;要求36分钟完成24道题&#xff0c;适用范围最广。题型有安排时间问题、比例题、记忆题、排序题、日历题、图形变换题、图形推理题、拖线条题等等&#xff0c…

Linux_生产消费者模型

目录 1、生产消费者模型示意图 2、生产者消费者之间的关系 3、定义交易场所 4、实现生产消费者模型 5、伪唤醒 6、多生产多消费者的实际运用 7、POSIX信号量 7.1 初始化信号量 7.2 销毁信号量 7.3 等待信号量 7.4 发布信号量 8、生产消费的环形队列模型 8.1…

接口测试之测试原则、测试用例、测试流程详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、接口的介绍 软件测试中&#xff0c;常说的接口有两种&#xff1a;图形用户接口&#xff08;GUI&#xff0c;人与程序的接口&#xff09;、应用程序编程接口&…

AWS云服务器购买:亚马逊云服务器的价格真的那么贵吗?一年要花多少钱?

亚马逊云服务器是全球领先的云计算服务提供商之一&#xff0c;其服务覆盖全球多个地区&#xff0c;拥有众多的客户和合作伙伴。然而&#xff0c;对于很多人来说&#xff0c;AWS的价格一直是一个热门话题。那么&#xff0c;亚马逊云服务器的价格真的那么贵吗&#xff1f;一年要花…

python爬虫Selenium模块及测试案例详解

什么是selenium&#xff1f; &#xff08;1&#xff09;Selenium是一个用于Web应用程序测试的工具。 &#xff08;2&#xff09;Selenium 测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。 &#xff08;3&#xff09;支持通过各种driver&#xff08;FirfoxDrive…

粘包问题、mmap和分片上传

一、粘包问题&#xff1a; 如果一端要把文件发给另一端&#xff0c;要发送两个部分的数据&#xff1a;其一是文件名&#xff0c;用于对端创建文件&#xff1b;另一个部分是文件内容。服务端在接收文件名&#xff0c;实际上并不知道有多长&#xff0c; 所以它会试图把网络缓冲区…

怎么把照片变漫画?学会这几招让照片秒变漫画

在这个追求图片创意与趣味性的时代&#xff0c;照片的“变身”游戏正悄然风靡。 从滤镜的巧妙运用到拍摄姿势的创新突破&#xff0c;人们不断探索着让照片焕发新生的无限可能。 而今&#xff0c;一股将照片转化为漫画风格的新潮流正席卷而来&#xff0c;它不仅保留了照片的记…

【PPT方案】大数据湖建设方案

背 景&#xff1a;大数据湖的发展背景与建设理念 体 系&#xff1a;大数据湖体系规划与建设思路 生态圈&#xff1a;探索新兴业务入湖建设模式 共 享&#xff1a;大数据湖统一访问共享规划 运 营&#xff1a;大数据湖一体化运营管理建设 软件全套资料部分文档清单&…

恐怖数字暗影:猜中才能逃离

大家可以看看这个&#xff0c;也很有意思&#xff01; 猜数字游戏&#xff08;老六版&#xff09;-CSDN博客 1、 剧情介绍 在一个阴暗潮湿的古堡中&#xff0c;你独自一人走进了一间散发着诡异气息的房间。房间的正中央有一张古老的桌子&#xff0c;上面放着一本泛黄的羊皮卷…

Java二十三种设计模式-装饰器模式(7/23)

装饰器模式&#xff1a;动态扩展功能的灵活之选 引言 装饰器模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;用于在不修改对象自身的基础上&#xff0c;通过添加额外的职责来扩展对象的功能。 基础知识&#xff0c;java设计模式总体来说设计…