WebServer项目(四)->(基于Proactor的c++)Web服务器简介及简单实现

news2024/9/25 19:24:07

基于Proactor的c++Web服务器项目

  • WebServer项目(四)->(基于Proactor的c++)Web服务器简介及简单实现
  • 1.Web Server(网页服务器)
  • 2.HTTP协议(应用层的协议)
  • 3.HTTP 请求报文格式
  • 4.HTTP响应报文格式
  • 5.HTTP请求方法
  • 6.HTTP状态码
  • 7.服务器编程基本框架
  • 8.两种高效的事件处理模式
    • 同步 I/O 方式如何模拟出 Proactor 模式?
  • 9.线程池
  • 10.有限状态机
  • 11.EPOLLONESHOT事件
  • 12.服务器压力测试

WebServer项目(四)->(基于Proactor的c++)Web服务器简介及简单实现

1.Web Server(网页服务器)

Web Server(网页服务器)是一种软件程序,用于接收和处理来自客户端浏览器的HTTP请求,并向客户端浏览器提供HTML文档、图像、CSS和JavaScript等Web资源。

Web Server通常运行在服务器操作系统上,监听指定的端口,等待客户端浏览器发起HTTP请求。当收到HTTP请求后,Web Server会根据请求的URL路径找到对应的资源,然后将该资源发送给客户端浏览器。Web Server还可以处理一些特殊的HTTP请求,比如CGI请求、ASP.NET请求等。

Web Server的主要功能包括:

  1. 监听网络端口,等待客户端浏览器的连接请求。
  2. 解析HTTP请求,找到请求的资源,并返回HTTP响应。
  3. 处理CGI请求或其他特殊的HTTP请求。
  4. 支持文件上传和下载。
  5. 支持SSL加密,保护Web应用程序的安全性。

常见的Web Server包括Apache、Nginx、Microsoft IIS等。这些Web Server都提供了丰富的功能和插件,可以满足不同的Web应用程序的需求。
在这里插入图片描述

2.HTTP协议(应用层的协议)

HTTP协议(超文本传输协议,Hypertext Transfer Protocol)是一种应用层协议,用于在Web浏览器和Web服务器之间进行通信。HTTP协议定义了Web浏览器和Web服务器之间传输数据的格式和规则。

HTTP协议的主要特点包括:

  1. 简单性:HTTP协议的基本规则和格式非常简单,易于学习和实现。
  2. 无状态性:HTTP协议是一种无状态协议,每个请求和响应都是独立的,服务器不会保存客户端请求的任何信息。
  3. 可扩展性:HTTP协议可以通过添加新的头部字段来扩展其功能。
  4. 支持多媒体:HTTP协议支持传输各种类型的数据,包括文本、图像、音频、视频等多媒体数据。

HTTP协议的基本工作流程如下:

  1. 客户端浏览器向Web服务器发送HTTP请求。
  2. Web服务器接收到HTTP请求,解析请求的URL,找到对应的资源,并生成HTTP响应。
  3. Web服务器将HTTP响应发送回客户端浏览器。
  4. 客户端浏览器接收到HTTP响应,解析响应数据,并根据响应数据更新页面内容。

HTTP协议的版本包括HTTP/1.0、HTTP/1.1、HTTP/2等版本。HTTP/1.1是目前最广泛使用的版本,HTTP/2则是一种新的协议,旨在提高Web性能和安全性。

3.HTTP 请求报文格式

HTTP 请求报文是由客户端浏览器发送给Web服务器的数据,其格式如下:

HTTP Method URL HTTP Version
Header1: Value1
Header2: Value2
...
HeaderN: ValueN

Request Body

其中,各个部分的含义如下:

  1. HTTP Method:HTTP请求方法,如GET、POST等。
  2. URL:请求的资源的URL地址。
  3. HTTP Version:HTTP协议的版本,如HTTP/1.1。
  4. Header:HTTP请求头部,包括一些键值对,用于描述请求的一些详细信息,如User-Agent、Accept等。
  5. Request Body:HTTP请求体,用于传输请求参数等数据。

下面是一个示例HTTP请求报文:

POST /login HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

username=admin&password=123456

在上述示例中,HTTP方法为POST,请求的URL为/login,HTTP版本为HTTP/1.1。请求头部包括Host、User-Agent、Accept、Content-Type和Content-Length等字段。请求体为username=admin&password=123456,表示向服务器提交了用户名和密码参数。

需要注意的是,HTTP请求报文中的换行符必须是“\r\n”,即回车符和换行符的组合。此外,请求头部和请求体之间必须有一个空行。

4.HTTP响应报文格式

HTTP响应报文是由Web服务器发送给客户端浏览器的数据,其格式如下:

HTTP Version Status Code Reason Phrase
Header1: Value1
Header2: Value2
...
HeaderN: ValueN

Response Body

其中,各个部分的含义如下:

  1. HTTP Version:HTTP协议的版本,如HTTP/1.1。

  2. Status Code:HTTP响应状态码,用于表示服务器处理请求的结果,如200表示成功,404表示资源未找到,500表示服务器内部错误等。

  3. Reason Phrase:HTTP响应状态码的描述,用于描述Status Code的含义,例如200的Reason Phrase为OK。

  4. Header:HTTP响应头部,包括一些键值对,用于描述响应的一些详细信息,如Content-Type、Content-Length等。

  5. Response Body:HTTP响应体,用于传输响应的数据,如HTML文档、图片等。

下面是一个示例HTTP响应报文:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1274

<!DOCTYPE html>
<html>
<head>
	<title>Example Website</title>
	<meta charset="utf-8">
	...
</head>
<body>
	...
</body>
</html>

在上述示例中,HTTP版本为HTTP/1.1,状态码为200,Reason Phrase为OK。响应头部包括Content-Type和Content-Length等字段。响应体为一个HTML文档。

需要注意的是,HTTP响应报文中的换行符必须是“\r\n”,即回车符和换行符的组合。响应头部和响应体之间必须有一个空行。

5.HTTP请求方法

HTTP请求方法是指客户端浏览器向Web服务器发送请求时所使用的HTTP协议定义的方法。HTTP协议定义了多种请求方法,常用的有以下几种:

  1. GET:用于请求指定资源,通常用于获取Web页面、图片、文本等静态内容。

  2. POST:用于向指定资源提交数据,通常用于提交表单数据、上传文件等。

  3. PUT:用于向指定资源位置上传新的内容。

  4. DELETE:用于请求服务器删除指定的资源。

  5. HEAD:与GET方法类似,但是只返回响应头部,不返回响应体。

  6. OPTIONS:请求Web服务器告知可用的请求方法和其他一些选项信息。

  7. TRACE:请求Web服务器返回自己收到的请求信息,用于测试或诊断。

  8. CONNECT:HTTP/1.1协议中定义的方法,用于建立与目标资源的双向通信隧道。

其中,GET和POST方法是最常用的两种请求方法。GET方法用于获取资源,只能传递少量的数据,而POST方法用于提交数据,可以传递更多的数据。

需要注意的是,不同的HTTP请求方法对应不同的语义和用途,使用不当可能会导致安全问题或其他错误。因此,在使用HTTP请求方法时需要仔细考虑其用途和限制条件。

6.HTTP状态码

HTTP状态码是Web服务器向客户端浏览器返回的HTTP响应中的一个三位数的数字代码,用于表示服务器处理请求的结果。

HTTP状态码的分类如下:

  1. 1xx(信息性状态码):表示服务器已经接收到请求,但是尚未处理。

  2. 2xx(成功状态码):表示服务器已经成功处理了请求。

    • 200 OK:请求成功,服务器返回的响应信息包含在响应体中。
    • 204 No Content:请求成功,但是服务器没有返回任何响应体。
    • 206 Partial Content:请求成功,但是只返回了部分响应体。
  3. 3xx(重定向状态码):表示需要客户端浏览器进行额外的操作才能完成请求。

    • 301 Moved Permanently:请求的URL已经被永久移动到了新的位置,浏览器应该使用新的URL进行访问。
    • 302 Found:请求的URL已经被临时移动到了新的位置,浏览器应该使用新的URL进行访问。
    • 304 Not Modified:客户端浏览器的缓存版本与服务器上的版本相同,不需要重新传输请求内容。
  4. 4xx(客户端错误状态码):表示客户端浏览器发送的请求存在错误或无法完成请求。

    • 400 Bad Request:请求存在语法错误或无法被服务器理解。
    • 401 Unauthorized:请求需要身份验证,但是客户端没有提供合法的身份凭证。
    • 403 Forbidden:请求被服务器拒绝,通常是因为客户端没有访问该资源的权限。
    • 404 Not Found:请求的资源在服务器上不存在。
  5. 5xx(服务器错误状态码):表示服务器无法完成请求。

    • 500 Internal Server Error:服务器内部错误,无法完成请求。
    • 502 Bad Gateway:服务器作为网关或代理,从上游服务器接收到无效的响应。
    • 503 Service Unavailable:服务器过载或正在维护,无法处理请求。

HTTP状态码是HTTP协议的重要组成部分,可以帮助客户端浏览器和开发人员诊断和解决问题。在开发Web应用程序时,需要了解不同的HTTP状态码及其含义,以便更好地处理和响应HTTP请求和响应。

7.服务器编程基本框架

服务器编程的基本框架通常包含以下几个部分:

  1. 网络通信模块:用于处理来自客户端的网络请求和向客户端发送网络响应。通常使用Socket API或者HTTP框架来实现。

  2. 请求解析和处理模块:用于解析客户端发送的请求,并根据请求的内容进行相应的处理。例如,对于HTTP请求,可以解析请求头部和请求体,并根据请求的内容来生成响应。

  3. 数据存储和管理模块:用于管理服务器上的数据,例如用户信息、网页内容等。通常使用数据库或者缓存来实现。

  4. 业务逻辑处理模块:用于实现服务器的业务逻辑,例如用户身份验证、数据处理、业务规则等。

  5. Web框架和模板引擎:用于简化服务器编程的复杂度,提高开发效率。通常使用现有的Web框架和模板引擎,例如Django、Flask等。

  6. 安全和性能优化模块:用于保障服务器的安全性和性能。例如,可以使用SSL/TLS协议来加密客户端和服务器之间的通信,使用CDN来加速静态资源的访问等。

8.两种高效的事件处理模式

在服务器编程中,常用的高效的事件处理模式包括 Reactor 模式和 Proactor 模式。

  1. Reactor 模式
    Reactor 模式是一种基于事件驱动的设计模式,它通过一个中心事件循环来接收和分发事件。当一个请求到达时,事件循环会将其分发给相应的处理器进行处理。处理器通过异步 I/O 操作来处理请求,避免了阻塞等待 I/O 完成的情况。Reactor 模式的优点在于它的高吞吐量和低延迟,适用于高并发的网络应用场景。

  2. Proactor 模式
    Proactor 模式是一种基于事件驱动的设计模式,它通过一个中心事件循环来接收和处理请求。
    与 Reactor 模式不同的是:
    Proactor 模式中的请求处理是由系统自动完成的,而不需要应用程序进行手动处理。当一个请求到达时,事件循环会将其分发给相应的处理器进行处理,处理器通过异步 I/O 操作来处理请求,并在 I/O 操作完成后向事件循环发送完成事件。Proactor 模式的优点在于它的高可靠性和高并发性,适用于需要处理大量 I/O 请求的应用场景,例如数据库访问和文件传输等。

总的来说,Reactor 模式和 Proactor 模式都是基于事件驱动的设计模式,它们通过异步 I/O 操作来避免阻塞等待 I/O 完成的情况,提高了服务器的性能和可靠性。选择哪种模式取决于具体的应用场景和需求。

同步 I/O 方式如何模拟出 Proactor 模式?

在同步 I/O 环境下,可以通过多线程来模拟异步 I/O 操作,从而实现 Proactor 模式。具体实现步骤如下:

  1. 创建一个线程池,用于处理请求。可以使用线程池模型来管理线程池,以便于控制线程的数量和复用线程资源。

  2. 当一个请求到达时,主线程将其加入请求队列中。

  3. 线程池中的线程从请求队列中获取请求,并使用同步 I/O 操作来处理请求。由于同步 I/O 操作会阻塞线程,因此线程池需要足够的线程数量来处理并发请求。

  4. 当 I/O 操作完成后,线程将请求的处理结果加入结果队列中,并向主线程发送处理完成事件。

  5. 主线程从结果队列中获取处理结果,并将其发送给客户端。

通过以上步骤,可以模拟出 Proactor 模式的效果,实现高并发的请求处理。但是需要注意的是,在同步 I/O 环境下,线程池的数量和线程的复用需要进行合理的规划,以避免线程过多或者线程资源浪费的情况。同时,还需要考虑线程安全和数据同步等问题,以保证程序的正确性和可靠性。

9.线程池

线程池是一种常用的并发编程技术,它通过预先创建一组线程来处理任务,从而避免了线程创建和销毁的开销,提高了程序的性能和响应速度。

线程池通常包含以下几个组成部分:

  1. 任务队列:用于存储待处理的任务,线程池中的线程从任务队列中获取任务并进行处理。

  2. 线程池管理器:用于管理线程池中的线程,包括线程创建、销毁、线程数量控制等。

  3. 工作线程:线程池中的线程,用于处理任务。

线程池的使用流程如下:

  1. 创建一个线程池,设置线程池的大小和任务队列的大小。

  2. 将任务加入任务队列中。

  3. 线程池中的线程从任务队列中获取任务,并进行处理。

  4. 处理完成后,线程将处理结果返回给主线程。

  5. 如果任务队列为空,线程会等待新的任务加入。

线程池的优点在于它可以避免线程的创建和销毁的开销,提高了程序的性能和响应速度。同时,线程池还可以控制线程的数量,避免线程数量过多导致的资源浪费和线程调度开销。线程池的缺点在于它需要占用一定的内存资源,而且线程池的设计和实现比较复杂,需要考虑线程安全和任务调度等问题。

// threadpool.h
// 写一个线程池,声明和定义就都写在这个头文件中了
#ifndef THREAD_POOL_H
#define THREAD_POOL_H

#include <pthread.h>
#include <list>
#include "locer.h"
#include <exception>
#include <cstdio>

// 线程池主要还是找一个线程去处理这个任务
// 为了更加通用,我们用模板表示任务类,为了让代码的复用
template <class T> // T就是任务类
class threadpool
{
public:
    threadpool(int thread_number = 8, int max_requests = 1000);
    ~threadpool();
    bool append(T *request); // 添加任务

private:
    static void *worker(void *arg);
    void run();

private:
    // 线程的数量
    int m_thread_number;
    // 线程池数组,大小为m_thread_number
    pthread_t *m_threads;
    // 请求队列中最多允许的,等待处理的请求数量
    int m_max_requests;
    // 请求队列,list中装的是任务类list,使用list,模拟队列
    std::list<T *> m_workqueue;
    // 互斥锁
    locker m_queuelocker;
    // 信号量 用来判断是否有任务需要处理
    sem m_queuestat;
    // 是否结束线程
    bool m_stop;
};

// inline可写可不写
template <class T>
inline threadpool<T>::threadpool(int thread_number, int max_requests) : m_thread_number(thread_number), m_max_requests(max_requests),
                                                                        m_stop(false), m_threads(NULL)
{
    if (thread_number <= 0 || max_requests <= 0)
    {
        throw std::exception();
    }
    // 创建数组
    m_threads = new pthread_t[m_thread_number];
    if (!m_threads)
    { // 数组创建失败
        throw std::exception();
    }
    // 创建thread_number个线程,并将它们设置为线程脱离
    for (int i = 0; i < m_thread_number; i++)
    {
        printf("create thread %d\n", i); // 正在创建第几个线程
        int ret = pthread_create(&m_threads[i],
                                 NULL,
                                 worker,        // 线程执行的函数,这个函数在c++中必须是静态的函数
                                 (void *)this); // 给这个work传参
        if (ret)                                // 线程创建失败
        {
            delete[] m_threads;
            throw std::exception();
        }
        // 说明线程创建成功,设置线程分离
        if (pthread_detach(m_thread[i]))
        {
            // 如果出错
            delete[] m_threads;     // 释放数组
            throw std::exception(); // 抛异常
        }
    }
}

template <class T>
inline threadpool<T>::~threadpool()
{
    delete[] m_threads;
    m_stop = true;
}

template <class T>
inline bool threadpool<T>::append(T *request) // 添加任务T
{
    // 保证线程同步
    m_queuelocker.lock();
    if (m_workqueue.size() > m_max_requests)
    {
        // 说明超出最大量
        m_queuelocker.unlock();
        return false;
    }
    m_workqueue.push_back(request);
    m_queuelocker.unlock();
    m_queuestat.post(); // 信号量增加
    return true;
}

template <class T>
inline void *threadpool<T>::worker(void *arg)
{
    threadpool *pool = (threadpool *)arg; // 创建线程池
    pool->run();
    return pool;
}

template <class T>
inline void threadpool<T>::run()
{
    while (!m_stop)
    {
        m_queuestat.wait();
        // 有数据
        m_queuelocker.lock();
        if (m_workqueue.empty())
        {
            m_queuelocker.unlock();
            continue;
        }
        T *request = m_workqueue.front();
        m_workqueue.pop_front();
        m_queuelocker.unlock();

        if (!request)
        {
            continue;
        }

        request->process(); // 让任务运行的类
    }
}

#endif // THREAD_POOL_H
// locker.h
//  线程同步封装类
#ifndef LOCKER_H
#define LOCKER_H

#include <phread.h>
#include <exception>
#include <semaphore.h>

// 互斥锁类
class locker
{
public:
    locker()
    {
        if (pthread_mutex_init(&m_mutex, NULL))
        {
            // perror("pthread_mutex_init");
            throw std::exception();
        }
    }
    ~locker()
    {
        if (pthread_mutex_destroy(&m_mutex))
        {
            // perror("pthread_mutex_destroy");
            throw std::exception();
        }
    }

    // 上锁
    bool lock()
    {
        return pthread_mutex_lock(&m_mutex) == 0;
    }
    // 解锁
    bool unlock()
    {
        return pthread_mutex_unlock(&m_mutex) == 0;
    }
    // 获取成员
    pthread_mutex_t *get()
    {
        return &m_mutex;
    }

private:
    phread_mutex_t m_mutex;
};

// 条件变量类
class cond
{
public:
    cond()
    {
        if (pthread_cond_init(&m_cond, NULL))
        {
            // perror("pthread_cond_init");
            throw std::exception();
        }
    }
    ~cond()
    {
        if (phread_cond_destroy(&m_cond))
        {
            // perror("pthread_cond_destroy");
            throw std::exception();
        }
    }

    bool wait(pthread_mutex_t *mutex)
    {
        return pthread_cond_wait(&m_cond, mutex) == 0; // 条件变量,互斥锁
    }

    bool timewait(pthread_mutex_t *mutex, struct timespec t)
    {
        return pthread_cond_timedwait(&m_cond, mutex, &t) == 0; // 条件变量,互斥
    }

    // 唤醒一个或多个
    bool signal()
    {
        return pthread_cond_signal(&m_cond) == 0;
    }
    // 唤醒所有线程
    bool broadcast()
    {
        return pthread_cond_broadcast(&m_cond) == 0;
    }

private:
    phread_cond_t m_cond;
};

// 信号量类
class sem
{
public:
    sem()
    {
        if (sem_init(&m_sem, 0, 0))
        {
            // perror("sem_init");
            throw std::exception();
        }
    }
    sem(int num)
    {
        if (sem_init(&m_sem, 0, num))
        {
            // perror("sem_init");
            throw std::exception();
        }
    }
    ~sem()
    {
        if (sem_destroy(&m_sem))
        {
            // perror("sem_destroy");
            throw std::exception();
        }
    }

    // 等待信号量
    bool wait()
    {
        return sem_wait(&m_sem) == 0;
    }
    // 增加信号量
    bool post()
    {
        return sem_post(&m_sem) == 0;
    }

private:
    sem_t m_sem;
};
#endif

10.有限状态机

有限状态机(Finite State Machine,FSM)是一种用于描述系统行为的数学模型。它由一组状态、一组输入和一组输出组成,其中状态表示系统的内部状态,输入表示系统的外部条件,输出表示系统对外部条件的响应。

在有限状态机中,系统的状态转移是通过输入事件触发的。当系统处于某一状态时,如果发生了输入事件,系统就会根据当前状态和输入事件进行状态转移,并产生相应的输出。这个过程可以用状态转移图或状态转移表来表示。

有限状态机广泛应用于计算机科学和工程领域,例如编译器、网络协议、自动控制系统等。在软件开发中,有限状态机可以帮助开发人员更好地理解和设计系统行为,从而提高系统的可靠性和性能。

有限状态机可以分为两种类型:确定性有限状态机(DFA)和非确定性有限状态机(NFA)。DFA中每个状态只有一条出边与一个输入相关联,而NFA中每个状态可以有多条出边与一个输入相关联。DFA具有更好的可预测性和可维护性,而NFA则具有更高的表达能力和灵活性。

11.EPOLLONESHOT事件

EPOLLONESHOT 是 Linux 下 epoll I/O 多路复用机制中的一种事件类型。当一个文件描述符上注册了 EPOLLONESHOT 事件后,它在被触发后只会被触发一次,除非重新设置 EPOLLONESHOT 事件。EPOLLONESHOT 事件的主要作用是确保一个文件描述符在任意时刻只被一个线程处理。

在使用 EPOLLONESHOT 事件时,需要注意以下几点:

  1. 需要使用 epoll_ctl 函数将文件描述符注册到 epoll 实例中,并设置 EPOLLONESHOT 事件。

  2. 当文件描述符上的 EPOLLONESHOT 事件被触发时,需要使用 epoll_ctl 函数重新设置 EPOLLONESHOT 事件,以确保下一次事件触发。

  3. 需要使用线程安全的方式处理文件描述符上的事件,以避免多个线程同时处理同一个文件描述符的事件。

在实际应用中,EPOLLONESHOT 事件通常用于多线程网络编程中,以确保每个文件描述符只被一个线程处理,避免多个线程同时处理同一个文件描述符的事件,提高程序的性能和稳定性。

12.服务器压力测试

Webbench 是一款基于 HTTP 协议的压力测试工具,可以模拟多个并发用户访问 Web 服务器,测试服务器的性能和稳定性。以下是使用 Webbench 进行服务器压力测试的示例:

  1. 安装 Webbench:
    在 Ubuntu 系统中,可以使用以下命令安装 Webbench:
sudo apt-get install webbench
  1. 运行 Webbench:
    运行 Webbench 的命令格式如下:
webbench -c 并发用户数 -t 测试时间 URL

其中,-c 选项表示并发用户数,-t 选项表示测试时间,URL 是要测试的 URL。

例如,要模拟 100 个并发用户访问 http://example.com/,测试时间为 10 秒,可以使用以下命令:

webbench -c 100 -t 10 http://example.com/
  1. 分析测试结果:
    Webbench 执行完测试后,会输出测试结果,包括每个连接的响应时间、平均响应时间、吞吐量等指标。可以根据测试结果分析服务器的性能和稳定性,排查潜在的瓶颈和问题。

需要注意的是,Webbench 是一款比较古老的压力测试工具,不支持 HTTPS 协议,且测试结果可能不够准确,建议结合其他压力测试工具使用。
其他压力测试工具:
在 Linux 中,可以使用多种工具进行服务器压力测试,包括 Apache JMeter、ab、siege、wrk 等。

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

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

相关文章

分布式搜索技术elasticsearch概念篇

文章目录 一、分布式搜索技术二、elasticsearch2.1 初识elasticsearch2.2 正向索引和倒排索引2.2.1 介绍2.2.2 优缺点 2.3 elasticsearch和mysql的对比 一、分布式搜索技术 分布式搜索技术是一种基于分布式计算的搜索引擎技术&#xff0c;它使用多台计算机协同工作来处理大规模…

ElementUI登陆表单中常用的标签属性

ElementUI官网 为登陆框添加一个边角弧度 <style> .className{/*设置div边边框角的弧度*/border-radius: 10px; } </style><el–input>标签常用属性 <!--使用prefix属性添加一个前缀图标--> <el-input prefix-icon"el-icon-user-solid"&g…

深度学习第J6周:ResNeXt-50实战解析

目录 一、模型结构介绍 二、前期准备 三、模型 三、训练运行 3.1训练 3.2指定图片进行预测 &#x1f368; 本文为[&#x1f517;365天深度学习训练营]内部限免文章&#xff08;版权归 *K同学啊* 所有&#xff09; &#x1f356; 作者&#xff1a;[K同学啊] &#x1f4cc; …

大数据技术之集群数据迁移

在大数据集群数据迁移的项目中涉及到很多技术细节&#xff0c;本博客记录了迁移的大致的操作步骤。 迁移借用Hadoop自带的插件&#xff1a;distcp。 一、Hadoop集群数据迁移 **DistCp&#xff08;分布式拷贝&#xff09;**是用于大规模集群内部和集群之间拷贝的工具。它使用M…

Meson构建系统的使用

一、前言 Meson 是用于自动化构建的自由软件&#xff0c;使用Python语言编写&#xff0c;在 Apache 许可证 2.0版本下发布&#xff0c;主要目标是为了让开发者节约用于配置构建系统的时间。 特点如下&#xff1a; 多平台支持&#xff0c;包括 GNU/Linux、Windows、MacOS、GCC、…

A_A01_008 STM32F103系列标准库移植经验分享

A_A01_008 STM32F103系列标准库移植经验分享 一、所需材料二、移植步骤三、注意事项四、参考资料与友情链接 一、所需材料 1.MDK开发环境 此处版本V5.15 2.相关启动文件 此处用野火点灯例程 因为启动文件完整 方便更换 其它工程没有的可以直接复制这些启动文件过去 3.相关开…

Java版工程管理系统源代码-软件自主研发,工程行业适用

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示…

8年测开年薪30W,为什么从开发转型为测试?谈谈这些年的心路历程……

谈谈我的以前&#xff0c;从毕业以来从事过两个多月的Oracle开发后转型为软件测试&#xff0c;到现在已近过去8年成长为一个测试开发工程师&#xff0c;总结一下之间的心路历程&#xff0c;希望能给徘徊在开发和测试之前的同学一点小小参考。 一、测试之路伏笔 上学偷懒&#…

EGO-Link FPGA智慧教育社区介绍:用 leetcode 的方式练习 verilog 语言

文章目录 介绍FPGA 语法例1&#xff1a;P1203 1输入1输出例2&#xff1a;P1204 3输入4输出例3&#xff1a;P1207 P1208 P1205 与或非门例4&#xff1a;P1200 半加器例5&#xff1a;P1201 4位二进制转余3循环码例6&#xff1a;P1215 2选1多路选择器例7&#xff1a;P1236 D触发器…

【C语言】浅涉结构体(声明、定义、类型、定义及初始化、成员访问及传参)

简单不先于复杂&#xff0c;而是在复杂之后。 目录 1. 结构体的声明 1.1 结构体的基础知识 1.2 结构的声明 1.3 结构成员的类型 1.4 结构体变量的定义和初始化 2. 结构体成员的访问 3. 结构体传参 1. 结构体的声明 1.1 结构体的基础知识 结构是一些值的集合&…

探索Apache Hudi核心概念 (4) - Clustering

Clustering是Hudi在0.7.0版本引入的一项特性&#xff0c;用于优化文件布局&#xff0c;提升读写性能&#xff0c;现在它已经成为Hudi的一项重要性能优化手段。本文我们会通过Notebook介绍并演示Clustering的运行机制&#xff0c;帮助您理解其工作原理和相关配置。 1. 运行 Not…

RocketMQ 多级存储设计与实现

作者&#xff1a;张森泽 随着 RocketMQ 5.1.0 的正式发布&#xff0c;多级存储作为 RocketMQ 一个新的独立模块到达了 Technical Preview 里程碑&#xff1a;允许用户将消息从本地磁盘卸载到其他更便宜的存储介质&#xff0c;可以用较低的成本延长消息保留时间。本文详细介绍 …

记录贴:EasyPoi word导出问题一览

项目场景&#xff1a; EasyPoi word导出 问题描述1 easypoi 模板导出 我直接在map的value输入空格或"",出来的是{{,两个左花括号,咋解决 解决方案&#xff1a; exportMap.put("key", "\u00A0"); //空格前端效果&#xff1a; 其他无效解决方案…

Redis安装配置操作记录

Redis 官网&#xff1a;https://redis.io/ 中文文档&#xff1a;https://www.redis.com.cn/documentation.html 在线命令参考&#xff1a;http://doc.redisfans.com 一&#xff0c;Redis下载安装与配置 下载网站&#xff0c;可下载安装包然后安装或可使用brew来安装Redis&#…

LeetCode——前K个高频单词

692. 前K个高频单词 给定一个单词列表 words 和一个整数 k &#xff0c;返回前 k 个出现次数最多的单词。 返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率&#xff0c; 按字典顺序 排序。 示例 1&#xff1a; 输入: words [“i”, “love”, “le…

太阳能电池测试解决方案NS-9001

前言 太阳能行业的快速发展提高了对太阳能电池测试和精确测量解决方案要求&#xff0c;伴随着太阳能电池尺寸大小质量的提升&#xff0c;充电电池测试必须更多的电流和更高输出功率水准&#xff0c;这就更加需要灵活多变的测试方案支持。 现阶段&#xff0c;太阳能电池测试 解…

Java学习星球,Java学习路线

目录 一、Java学习路线二、学习计划三、为何会有Java学习星球&#xff1f;四、加入星球后&#xff0c;你可以得到什么&#xff1f;五、如何加入Java学习星球&#xff1f;六、打卡挑战 大家好&#xff0c;我是哪吒&#xff0c;一个靠着热情攀登至C站巅峰的中年男子&#xff0c;C…

【历史上的今天】3 月 20 日:cURL 二十五周年;Docker 发布;思科收购 Linksys

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 3 月 20 日&#xff0c;在 1999 年的今天&#xff0c;人类首次成功乘热气球环球飞行。在 24 年的今天&#xff0c;瑞士人皮尔卡、英国人琼斯经过近 20 天的飞行…

JavaWeb—HTTP协议

目录 1.HTTP协议 1.1 HTTP-概述 1.1.1 介绍 2.1.2 特点 1.2 HTTP-请求协议 HTTP-请求数据格式 GET请求和POST请求的区别&#xff1a; 1.3 HTTP-响应协议 1.3.1 格式介绍 1.3.2 响应状态码 常见响应状态码 1.4 HTTP-协议解析 1.HTTP协议 1.1 HTTP-概述 1.1.1 介绍 HTT…

ModuleNotFoundError: No module named ‘cuda‘、‘tensorrt‘

1、 ModuleNotFoundError: No module named ‘cuda’ python -m pip install --upgrade pip pip install cuda-python2、 ModuleNotFoundError: No module named ‘tensorrt’ 2.1 依赖库 先安装两个TensorRT的依赖库 python -m pip install --upgrade pip pip install nvi…