Json-Rpc框架(Muduo库快速上手)

news2024/11/17 13:23:48

阅读导航

  • 引言
  • 一、Muduo库简介
  • 二、Muduo库常见接口
    • 1. TcpServer类基础介绍
    • 2. EventLoop类基础介绍
    • 3. TcpConnection类基础介绍
    • 4. TcpClient类基础介绍
    • 5. Buffer类基础介绍
  • 三、Muduo库使用示例
    • ⭕英译汉服务器
    • ⭕英译汉客户端

引言

在上一篇文章中,我们简要介绍了在项目中使用的JsonCpp库,这是一个广泛使用的C++ JSON解析和生成库,它为我们的项目提供了高效、灵活的数据序列化与反序列化能力。然而,在构建服务架构时,仅依靠数据序列化是远远不够的。高效的网络通信是确保系统稳定运行、提升用户体验的关键环节。为此,在本篇文章中,我们将聚焦于项目中用到的另一个重要库——Muduo网络库

Muduo是一个用于Linux多线程服务器的C++非阻塞网络库,它基于Reactor模式设计,提供了高性能的网络通信能力。它支持TCP、UDP等多种协议,并且拥有良好的可扩展性和灵活性。

一、Muduo库简介

Muduo,由陈硕大佬精心开发,是一个基于非阻塞IO和事件驱动的高性能C++ TCP网络编程库。它采用了主从Reactor模型,这种模型特别适用于构建高并发的网络服务器。在Muduo中,使用的线程模型被称为“one loop per thread”,这一理念的核心在于:

  • 一个线程对应一个事件循环(EventLoop):这意味着每个线程都维护着它自己的事件循环,该循环专门用于响应和处理该线程内的计时器事件以及IO事件。这样的设计有助于减少线程间的竞争和同步开销,提高系统的并发处理能力。

  • 一个文件描述符(或TCP连接)由单一线程处理:在Muduo中,每个TCP连接(或更一般地说,每个文件描述符)都被绑定到特定的EventLoop上,并由该EventLoop所在的线程负责其读写操作。这种绑定关系确保了数据的一致性和线程安全,避免了多线程同时操作同一资源可能导致的竞态条件和数据不一致问题。
    在这里插入图片描述

二、Muduo库常见接口

1. TcpServer类基础介绍

#include <memory>  
#include <functional>  
#include <string>  
#include "muduo/net/EventLoop.h"  
#include "muduo/net/InetAddress.h"  
#include "muduo/net/Timestamp.h"  
#include "muduo/net/Buffer.h"  
  
typedef std::shared_ptr<TcpConnection> TcpConnectionPtr;  
typedef std::function<void(const TcpConnectionPtr&)> ConnectionCallback;  
typedef std::function<void(const TcpConnectionPtr&, Buffer*, Timestamp)> MessageCallback;  
  
class InetAddress : public muduo::copyable {  
public:  
    InetAddress(StringArg ip, uint16_t port, bool ipv6 = false);  
};  
  
class TcpServer : noncopyable {  
public:  
    enum Option {  
        kNoReusePort,  
        kReusePort,  
    };  
  
    TcpServer(EventLoop* loop,  
              const InetAddress& listenAddr,  
              const std::string& nameArg,  
              Option option = kNoReusePort);  
  
    void setThreadNum(int numThreads);  
    void start();  
  
    // 当一个新连接建立成功的时候被调用  
    void setConnectionCallback(const ConnectionCallback& cb) {  
        connectionCallback_ = cb;  
    }  
  
    // 消息的业务处理回调函数---这是收到新连接消息的时候被调用的函数  
    void setMessageCallback(const MessageCallback& cb) {  
        messageCallback_ = cb;  
    }  
  
private:  
    ConnectionCallback connectionCallback_;  
    MessageCallback messageCallback_;  
};  

2. EventLoop类基础介绍

class EventLoop : noncopyable  
{  
public:  
    // 开始事件循环,直到调用quit()方法为止  
    void loop();  
      
    // 请求退出事件循环  
    void quit();  
      
    // 在指定的时间运行回调函数一次  
    // 参数time是回调应该被调用的时间戳  
    // 参数cb是当时间到达时应该被调用的回调函数  
    // 返回TimerId,可用于取消定时器  
    TimerId runAt(Timestamp time, TimerCallback cb);  
      
    // 在当前时间加上指定的延迟后运行回调函数一次  
    // 参数delay是延迟时间(秒)  
    // 参数cb是当延迟时间过后应该被调用的回调函数  
    // 返回TimerId,可用于取消定时器  
    TimerId runAfter(double delay, TimerCallback cb);  
      
    // 每隔指定的时间间隔重复运行回调函数  
    // 参数interval是时间间隔(秒)  
    // 参数cb是每隔interval秒应该被调用的回调函数  
    // 返回TimerId,可用于取消定时器  
    TimerId runEvery(double interval, TimerCallback cb);  
      
    // 取消指定的定时器  
    // 参数timerId是之前通过runAt、runAfter或runEvery方法返回的定时器ID  
    void cancel(TimerId timerId);  
      
private:  
    // 原子变量,用于指示事件循环是否应该退出  
    std::atomic<bool> quit_;
      
    // 指向Poller对象的智能指针,Poller负责轮询I/O事件
    std::unique_ptr<Poller> poller_;  
      
    // 互斥锁,用于保护多线程访问共享数据  
    mutable MutexLock mutex_;  
      
    // 存储待执行函数的向量,这些函数将在事件循环的某个点被执行
    std::vector<Functor> pendingFunctors_ GUARDED_BY(mutex_);  
};  

3. TcpConnection类基础介绍

class TcpConnection : noncopyable,  
                      public std::enable_shared_from_this<TcpConnection>  
{  
public:  
    // 构造函数,用于创建TcpConnection对象  
    // 参数包括事件循环指针、连接名称、套接字文件描述符、本地地址和远程地址  
    TcpConnection(EventLoop* loop,  
                  const string& name,  
                  int sockfd,  
                  const InetAddress& localAddr,  
                  const InetAddress& peerAddr);  
  
    // 检查连接是否已建立  
    bool connected() const { return state_ == kConnected; }  
  
    // 检查连接是否已断开  
    bool disconnected() const { return state_ == kDisconnected; }  
  
    // 发送字符串消息(使用C++11的移动语义)  
    void send(string&& message);  
  
    // 发送原始数据  
    void send(const void* message, int len);  
  
    // 使用StringPiece发送消息(StringPiece是Google的字符串切片类,用于高效处理字符串片段)  
    void send(const StringPiece& message);  
  
    // 发送Buffer对象中的数据  
    void send(Buffer* message);  
  
    // 关闭连接  
    void shutdown();  
  
    // 设置连接上下文,上下文可以是任意类型的数据,通过boost::any存储  
    void setContext(const boost::any& context)  
    { context_ = context; }  
  
    // 获取连接上下文(常量引用)  
    const boost::any& getContext() const  
    { return context_; }  
  
    // 获取连接上下文的可修改指针(注意:这可能不是线程安全的,使用时需要小心)  
    boost::any* getMutableContext()  
    { return &context_; }  
  
    // 设置连接建立时的回调函数  
    void setConnectionCallback(const ConnectionCallback& cb)  
    { connectionCallback_ = cb; }  
  
    // 设置接收到消息时的回调函数  
    void setMessageCallback(const MessageCallback& cb)  
    { messageCallback_ = cb; }  
  
private:  
    // 连接的状态枚举  
    enum StateE { kDisconnected, kConnecting, kConnected, kDisconnecting };  
  
    // 指向事件循环的指针,用于在连接上执行定时任务或异步操作  
    EventLoop* loop_;  
  
    // 连接建立时的回调函数  
    ConnectionCallback connectionCallback_;  
  
    // 接收到消息时的回调函数  
    MessageCallback messageCallback_;  
  
    // 发送完成时的回调函数
    WriteCompleteCallback writeCompleteCallback_;  
  
    // 上下文信息,可以是任意类型的数据,通过boost::any存储  
    boost::any context_;  
  
    // 连接的状态
    StateE state_;  
 
};

4. TcpClient类基础介绍

class TcpClient : noncopyable  
{  
public:  
    // 构造函数,用于创建 TcpClient 对象。  
    // 需要提供事件循环指针、服务器地址和客户端名称。  
    TcpClient(EventLoop* loop,  
              const InetAddress& serverAddr,  
              const string& nameArg);  
  
    // 析构函数,声明为 out-of-line(在类定义外部实现),  
    // 以便处理 std::unique_ptr 成员(尽管在这个类的定义中没有直接显示)。  
    ~TcpClient();  
  
    // 连接到服务器。  
    void connect();  
  
    // 关闭连接。  
    void disconnect();  
  
    // 停止客户端操作,可能包括关闭连接和清理资源。  
    void stop();  
  
    // 获取客户端对应的通信连接 TcpConnection 对象的接口。  
    // 注意:在发起 connect 后,连接可能尚未建立成功。  
    TcpConnectionPtr connection() const  
    {  
        MutexLockGuard lock(mutex_); // 加锁以保护 connection_  
        return connection_; // 返回当前连接(如果有的话)  
    }  
  
    // 设置连接成功时的回调函数。  
    void setConnectionCallback(ConnectionCallback cb)  
    {  
        connectionCallback_ = std::move(cb); // 使用移动语义设置回调  
    }  
  
    // 设置收到服务器发送的消息时的回调函数。  
    void setMessageCallback(MessageCallback cb)  
    {  
        messageCallback_ = std::move(cb); // 使用移动语义设置回调  
    }  
  
private:  
    EventLoop* loop_; // 指向事件循环的指针,用于处理异步事件  
    ConnectionCallback connectionCallback_; // 连接成功时的回调函数  
    MessageCallback messageCallback_; // 收到消息时的回调函数  
    // 注意:WriteCompleteCallback 在此类的定义中没有直接出现,但可能在其他地方使用  
    TcpConnectionPtr connection_ GUARDED_BY(mutex_); // 当前连接(受 mutex_ 保护)  
    mutable MutexLock mutex_; // 用于保护 connection_ 的互斥锁  
};  
  
// CountDownLatch 类是一个同步辅助类,用于让一个或多个线程等待直到其他线程的一系列操作完成。  
// 它继承自 noncopyable 以防止被复制。  
class CountDownLatch : noncopyable  
{  
public:  
    // 构造函数,初始化计数器。  
    explicit CountDownLatch(int count);  
  
    // 等待计数器变为零。如果计数器不为零,则当前线程将阻塞。  
    void wait()  
    {  
        MutexLockGuard lock(mutex_); // 加锁以保护 count_ 和 condition_  
        while (count_ > 0) // 如果计数器大于零,则等待  
        {  
            condition_.wait(); // 释放锁并进入等待状态,直到被唤醒  
        }  
    }  
  
    // 将计数器减一。如果计数器变为零,则唤醒所有等待的线程。  
    void countDown()  
    {  
        MutexLockGuard lock(mutex_); // 加锁以保护 count_ 和 condition_  
        --count_; // 计数器减一  
        if (count_ == 0) // 如果计数器变为零  
        {  
            condition_.notifyAll(); // 唤醒所有等待的线程  
        }  
    }  
  
    // 获取当前计数器的值(主要用于调试)。  
    int getCount() const;  
  
private:  
    mutable MutexLock mutex_; // 用于保护 count_ 和 condition_ 的互斥锁  
    Condition condition_ GUARDED_BY(mutex_); // 条件变量,与 mutex_ 一起使用以实现等待/通知机制  
    int count_ GUARDED_BY(mutex_); // 计数器,表示需要等待的操作数量  
};

5. Buffer类基础介绍

// Buffer 类是一个字节缓冲区类,支持从两端读写数据,以及处理整数和基本字符串。  
// 它继承自 muduo::copyable,表明这个类是可以被拷贝的。  
class Buffer : public muduo::copyable  
{  
public:  
    // 定义了一个便宜的前置空间大小,用于优化读操作。  
    static const size_t kCheapPrepend = 8;  
    // 定义了缓冲区的初始大小。  
    static const size_t kInitialSize = 1024;  
  
    // 构造函数,接受一个可选的初始大小参数。  
    // 缓冲区实际大小为 kCheapPrepend + initialSize,其中 kCheapPrepend 用于优化读操作。  
    explicit Buffer(size_t initialSize = kInitialSize)  
        : buffer_(kCheapPrepend + initialSize),  
          readerIndex_(kCheapPrepend),  
          writerIndex_(kCheapPrepend)  
    {}  
  
    // 与另一个Buffer对象交换内容。  
    void swap(Buffer& rhs);  
  
    // 返回可读字节数,即 writerIndex_ - readerIndex_。  
    size_t readableBytes() const;  
  
    // 返回可写字节数,即 buffer_.size() - writerIndex_。  
    size_t writableBytes() const;  
  
    // 返回一个指向可读数据的指针,但不移动读写索引。  
    const char* peek() const;  
  
    // 查找并返回指向缓冲区中第一个EOL(如"\r\n")的指针,从头开始搜索。  
    const char* findEOL() const;  
  
    // 查找并返回指向缓冲区中从指定位置开始的第一个EOL的指针。  
    const char* findEOL(const char* start) const;  
  
    // 从缓冲区中移除指定长度的数据。  
    void retrieve(size_t len);  
  
    // 移除并返回缓冲区中下一个 int64_t 类型的数据。  
    void retrieveInt64();  
  
    // 移除并返回缓冲区中下一个 int32_t 类型的数据。  
    void retrieveInt32();  
  
    // 移除并返回缓冲区中下一个 int16_t 类型的数据。  
    void retrieveInt16();  
  
    // 移除并返回缓冲区中下一个 int8_t 类型的数据。  
    void retrieveInt8();  
  
    // 移除并返回缓冲区中所有可读数据作为字符串。  
    string retrieveAllAsString();  
  
    // 移除并返回缓冲区中指定长度的数据作为字符串。  
    string retrieveAsString(size_t len);  
  
    // 向缓冲区末尾追加 StringPiece 对象。  
    void append(const StringPiece& str);  
  
    // 向缓冲区末尾追加指定长度的数据。  
    void append(const char* /*restrict*/ data, size_t len);  
  
    // 向缓冲区末尾追加指定长度的数据(泛型版本)。  
    void append(const void* /*restrict*/ data, size_t len);  
  
    // 返回一个指向缓冲区末尾(用于写入)的指针。  
    char* beginWrite();  
  
    // 返回一个指向缓冲区末尾(用于写入)的常量指针。  
    const char* beginWrite() const;  
  
    // 更新写入索引,表示已经写入了指定长度的数据。  
    void hasWritten(size_t len);  
  
    // 向缓冲区末尾追加一个 int64_t 类型的数据。  
    void appendInt64(int64_t x);  
  
    // 向缓冲区末尾追加一个 int32_t 类型的数据。  
    void appendInt32(int32_t x);  
  
    // 向缓冲区末尾追加一个 int16_t 类型的数据。  
    void appendInt16(int16_t x);  
  
    // 向缓冲区末尾追加一个 int8_t 类型的数据。  
    void appendInt8(int8_t x);  
  
    // 从缓冲区读取一个 int64_t 类型的数据,并移动读索引。  
    int64_t readInt64();  
  
    // 从缓冲区读取一个 int32_t 类型的数据,并移动读索引。  
    int32_t readInt32();  
  
    // 从缓冲区读取一个 int16_t 类型的数据,并移动读索引。  
    int16_t readInt16();  
  
    // 从缓冲区读取一个 int8_t 类型的数据,并移动读索引。  
    int8_t readInt8();  
  
    // 从缓冲区中查看(不移动读索引)下一个 int64_t 类型的数据。  
    int64_t peekInt64() const;  
  
    // 从缓冲区中查看(不移动读索引)下一个 int32_t 类型的数据。  
    int32_t peekInt32() const;  
  
    // 从缓冲区中查看(不移动读索引)下一个 int16_t 类型的数据。  
    int16_t peekInt16() const;  
  
    // 从缓冲区中查看(不移动读索引)下一个 int8_t 类型的数据。  
    int8_t peekInt8() const;  
  
    // 在缓冲区开头(readerIndex_ 之前)追加一个 int64_t 类型的数据。  
    void prependInt64(int64_t x);  
  
    // 在缓冲区开头(readerIndex_ 之前)追加一个 int32_t 类型的数据。  
    void prependInt32(int32_t x);  
  
    // 在缓冲区开头(readerIndex_ 之前)追加一个 int16_t 类型的数据。  
    void prependInt16(int16_t x);  
  
    // 在缓冲区开头(readerIndex_ 之前)追加一个 int8_t 类型的数据。  
    void prependInt8(int8_t x);  
  
    // 在缓冲区开头(readerIndex_ 之前)追加指定长度的数据。  
    void prepend(const void* /*restrict*/ data, size_t len);  
  
private:  
    std::vector<char> buffer_; // 存储字节数据的向量。  
    size_t readerIndex_; // 读索引,指向下一个可读字节的位置。  
    size_t writerIndex_; // 写索引,指向下一个可写字节的位置。  
    static const char kCRLF[]; // 可能的行结束符,如 "\r\n"。  
};

三、Muduo库使用示例

接下来,我们将利用Muduo网络库来构建一个基础的英译汉翻译服务器及其对应的客户端。

⭕英译汉服务器

#include <muduo/net/TcpServer.h>  
#include <muduo/net/EventLoop.h>  
#include <muduo/net/TcpConnection.h>  
#include <muduo/net/Buffer.h>  
#include <iostream>  
#include <string>  
#include <unordered_map>  
  
// 定义一个翻译服务器类  
class DictServer {  
public:  
    // 构造函数,初始化服务器监听端口  
    DictServer(int port)   
        : _server(&_baseloop, // 使用_baseloop事件循环  
                  muduo::net::InetAddress("0.0.0.0", port), // 监听地址和端口  
                  "DictServer", // 服务器名称  
                  muduo::net::TcpServer::kReusePort) // 启用端口复用  
    {  
        // 设置连接建立/断开的回调函数  
        _server.setConnectionCallback(std::bind(&DictServer::onConnection, this, std::placeholders::_1));  
        // 设置接收到消息的回调函数  
        _server.setMessageCallback(std::bind(&DictServer::onMessage, this,   
            std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));  
    }  
  
    // 启动服务器  
    void start() {  
        _server.start(); // 开始监听端口  
        _baseloop.loop(); // 进入事件循环,等待并处理事件  
    }  
  
private:  
    // 连接建立或断开的回调函数  
    void onConnection(const muduo::net::TcpConnectionPtr &conn) {  
        if (conn->connected()) {  
            std::cout << "连接建立!\n";  
        } else {  
            std::cout << "连接断开!\n";  
        }  
    }  
  
    // 接收消息的回调函数  
    void onMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp) {  
        // 静态的字典映射,用于翻译  
        static std::unordered_map<std::string, std::string> dict_map = {  
            {"hello",  "你好"},  
            {"world",  "世界"},  
            {"apple",  "苹果"}  
        };  
  
        // 从缓冲区中检索所有消息作为字符串  
        std::string msg = buf->retrieveAllAsString();  
        std::string res;  
  
        // 在字典中查找单词  
        auto it = dict_map.find(msg);  
        if (it != dict_map.end()) {  
            res = it->second; // 找到,返回对应的翻译  
        } else {  
            res = "未知单词!"; // 未找到,返回未知单词消息  
        }  
  
        // 发送翻译结果给客户端  
        conn->send(res);  
    }  
  
private:  
    // 事件循环对象,用于处理网络事件  
    muduo::net::EventLoop _baseloop;  
    // TCP服务器对象,用于监听和接受连接  
    muduo::net::TcpServer _server;  
};  
  
int main()  
{  
    // 创建并初始化翻译服务器,监听9090端口  
    DictServer server(9090);  
    // 启动服务器  
    server.start();  
    return 0;  
}

⭕英译汉客户端

#include <muduo/net/TcpClient.h>  
#include <muduo/net/EventLoop.h>  
#include <muduo/net/EventLoopThread.h>  
#include <muduo/net/TcpConnection.h>  
#include <muduo/net/Buffer.h>  
#include <muduo/base/CountDownLatch.h>  
#include <iostream>  
#include <string>  
  
// 定义一个字典客户端类  
class DictClient {  
public:  
    // 构造函数,初始化客户端  
    DictClient(const std::string &sip, int sport)  
        : _loopthread(), // 创建EventLoopThread对象,但此时不启动  
          _baseloop(_loopthread.startLoop()), // 启动EventLoopThread并获取其EventLoop  
          _downlatch(1), // 初始化CountDownLatch,计数为1,用于等待连接建立  
          _client(_baseloop, muduo::net::InetAddress(sip, sport), "DictClient") // 初始化TcpClient  
    {  
        // 设置连接事件(连接建立/断开)的回调函数  
        _client.setConnectionCallback(std::bind(&DictClient::onConnection, this, std::placeholders::_1));  
        // 设置接收到消息的回调函数  
        _client.setMessageCallback(std::bind(&DictClient::onMessage, this,   
            std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));  
          
        // 连接到服务器  
        _client.connect();  
        // 等待连接建立,这里使用CountDownLatch来阻塞当前线程  
        _downlatch.wait();  
    }  
  
    // 发送消息到服务器  
    bool send(const std::string &msg) {  
        if (!_conn->connected()) { // 检查连接是否仍然有效  
            std::cout << "连接已经断开,发送数据失败!\n";  
            return false;  
        }  
        _conn->send(msg); // 发送消息  
        return true;  
    }  
  
private:  
    // 连接事件回调,处理连接建立或断开的情况  
    void onConnection(const muduo::net::TcpConnectionPtr &conn) {  
        if (conn->connected()) {  
            std::cout << "连接建立!\n";  
            _downlatch.countDown(); // 连接建立后,减少CountDownLatch的计数  
            _conn = conn; // 保存TcpConnectionPtr对象  
        } else {  
            std::cout << "连接断开!\n";  
            _conn.reset(); // 清除TcpConnectionPtr对象  
        }  
    }  
  
    // 消息接收回调,处理从服务器接收到的消息  
    void onMessage(const muduo::net::TcpConnectionPtr &conn, muduo::net::Buffer *buf, muduo::Timestamp) {  
        std::string res = buf->retrieveAllAsString(); // 从缓冲区中检索所有消息  
        std::cout << res << std::endl; // 打印接收到的消息  
    }  
  
private:  
    muduo::net::TcpConnectionPtr _conn; // TcpConnection的智能指针,用于存储连接对象  
    muduo::CountDownLatch _downlatch; // 计数器,用于等待连接建立  
    muduo::net::EventLoopThread _loopthread; // 事件循环线程  
    muduo::net::EventLoop *_baseloop; // 指向EventLoopThread中EventLoop的指针  
    muduo::net::TcpClient _client; // TcpClient对象,用于连接和发送消息  
};  
  
int main()  
{  
    // 创建字典客户端实例,连接到本地9090端口的服务器  
    DictClient client("127.0.0.1", 9090);  
    while(1) {  
        std::string msg;  
        std::cin >> msg; // 读取用户输入的消息  
        client.send(msg); // 发送消息到服务器  
    }  
    return 0;
}

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

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

相关文章

业务资源管理模式语言19

相关模式&#xff1a; 如果你考虑类“Resource Maintenance”和“Part used in maintenance”&#xff0c;那么是“Transaction-Transaction Line Item”模式的一个特例[Coa 97]。如果你考虑类“Part”和“Part used in maintenance”&#xff0c;那么是“Item Line Item”模式…

力扣 简单 104.二叉树的最大深度

文章目录 题目介绍解法 题目介绍 解法 如果知道了左子树和右子树的最大深度 l 和 r&#xff0c;那么该二叉树的最大深度即为max(l,r)1&#xff0c;而左子树和右子树的最大深度又可以以同样的方式进行计算。因此我们可以用递归的方法来计算二叉树的最大深度。具体而言&#xff…

动态规划(有背包问题)

目录 1.动态规划的介绍 2.动态规划的例题 第1道题 数字三角形 (如果想看递归写法可以到我的记忆化递归里去看看记忆化递归_将递归程序记忆化-CSDN博客) 第2道题最长公共子序列(模板) 第3道题 最长上升子序列 第4道题最大子段和 背包系列问题 01背包 完全背包 1.动态规…

scrapy爬虫基础

一、初识 创建项目&#xff1a; scrapy startproject my_one_project # 创建项目命令 cd my_one_project # 先进去&#xff0c; 后面在里面运行 运行爬虫命令为&#xff1a;scrapy crawl tk spiders下创建test.py 其中name就是scrapy crawl tk &…

LeetCode讲解篇之5. 最长回文子串

文章目录 题目描述题解思路题解代码 题目描述 题目链接 题解思路 从中心点先寻找和中心点相等的左右端点&#xff0c;在基于左右端点进行往外扩散&#xff0c;直至左右端点不相等或者越界&#xff0c;然后左右端点这个范围内就是我们找寻的回文串&#xff0c;我们遍历中心点…

VS Code 配置 Anaconda Python 环境

VS Code 配置 Anaconda Python 环境 董同学是使用 PyCharm 进行 python 开发的老选手了&#xff0c;但同事们都在用 VS Code。为了更好的和大家交流&#xff0c;转身投入 VS Code 的怀抱&#xff08;当然我都要&#xff09;。当我兴致盎然打开 VS Code 软件&#xff0c;真丝滑…

自动化测试实例:Web登录功能性测试(无验证码)

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、什么是自动化测试 把人为驱动的测试行为转化为机器执行的一种过程称为自动化测试。(来自百度百科)本质上来说&#xff0c;自动化测试对比起手工测试除了需要…

自动化check是不是测试?

这篇文章是reddit上关于质量保障讨论的比较多的帖子&#xff0c;我把它转为中文版&#xff0c;供大家交流学习&#xff0c;由于直接用的翻译软件以及截图&#xff0c;大家凑合看下哈。 自动化检查并不是真正的“测试”&#xff1f;编写自动化检查确实很重要——但编写自动化测…

笔记整理—linux进程部分(1)进程终止函数注册、进程环境、进程虚拟地址

对于mian()函数而言&#xff0c;执行前也需要先执行一段引导代码才会去执行main()函数&#xff0c;该部分的代码包含构建c语言的运行环境等配置&#xff0c;如清理bss段等。 在使用gcc去编译程序的时候&#xff0c;使用gcc -v xxx.c可见链接过程。在编译完成后可见xxx.out文件。…

[产品管理-33]:实验室技术与商业化产品的距离,实验室技术在商业化过程中要越过多少道“坎”?

目录 一、实验室技术 1.1 实验室研究性技术 1.2 技术发展的S曲线 技术发展S曲线的主要阶段和特点 技术发展S曲线的意义和应用 二、实验室技术商业化的路径 2.1 实验室技术与商业化产品的距离 1、技术成熟度与稳定性 - 技术自身 2、市场需求与适应性 - 技术是满足需求 …

计算机毕业论文及毕业设计题目,计算机专业大专本科学位毕业论文题目推荐大全集

目录 一 软件工程方向 二 网络安全方向 三 人工智能与机器学习方向 四 大数据方向 五 云计算与虚拟化方向 六 数据库与信息系统方向 计算机专业的毕业论文或毕业设计题目通常需要结合当前的技术趋势以及个人兴趣来确定。一个好的选题不仅能够体现学生的学术水平和技术能…

【行业报告】AI大模型对我国劳动力市场潜在影响研究报告(2024),附PDF下载!!

前言 9月13日&#xff0c;北京大学国家发展研究院联合智联招聘在中国国际服务贸易交易会上发布的《AI大模型对我国劳动力市场潜在影响研究&#xff1a;2024》&#xff08;以下简称“报告”&#xff09;显示&#xff0c;2024年上半年&#xff0c;招聘职位数同比增速前五的人工智…

【含文档】基于Springboot+Vue的高校自习室预约系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

git小乌龟

下载git小乌龟 官方地址 Download – TortoiseGit – Windows Shell Interface to Git git小乌龟下载 选择自己对应的版本进行下载 安装完成后我们会发现是英文&#xff0c;这对我们这些英语不好的很不友好&#xff0c;所以就需要下载语言包 下载对应语言包 安装完成后我们…

自然语言处理实战项目:从基础到实战

自然语言处理实战项目&#xff1a;从基础到实战 自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;是人工智能的重要分支&#xff0c;致力于让计算机能够理解、生成和处理人类语言。NLP 在搜索引擎、智能客服、语音助手等场景中扮演着关键角色。本文将带…

HTML5--裸体回顾

免责声明&#xff1a;本文仅做分享~ 详情请参考以下&#xff1a; HTML 系列教程 (w3school.com.cn) 菜鸟教程 - 学的不仅是技术&#xff0c;更是梦想&#xff01; --本文是光秃秃的空壳. 标题标签 段落标签 换行和水平线 文本格式化标签 &#xff08;一般用左边的&#xff…

2024年10月CISAW课程安排

信息安全保障人员&#xff08;CISAW&#xff09;各方向的第一阶段和第二阶段培训本月将以线上线下模式开展 CISAW根据专业领域分为多个类别&#xff0c;如安全集成、安全运维、风险评估、应急服务、软件安全开发等&#xff0c;通过培训后可获得全国通用的信息安全保障人员认证证…

Linux驱动编程——根文件系统

一 、续上节 TFTP为简单文件传输协议&#xff0c;底层基于UDP。 ★1、arm上电后两种方式启动原理 &#xff08;1&#xff09;Nor flash可直接寻址&#xff0c;前面在使用2440时&#xff0c;均使用的是Nor flash&#xff1b; pc指令开始为0&#xff0c;上电后先指向地址为0的…

240929-DCGAN生成漫画头像

240929-DCGAN生成漫画头像 DCGAN是GAN的直接扩展&#xff0c;简单从命名来理解&#xff0c;DCGAN&#xff08;Deep Convolutional Generative Adversarial Networks&#xff09;就是比GAN&#xff08;Generative Adversarial Networks&#xff09;多了DC&#xff08;Deep Conv…

《黑神话:悟空》天命人速通法宝 | 北通鲲鹏20智控游戏手柄评测

《黑神话:悟空》天命人速通法宝 | 北通鲲鹏20智控游戏手柄评测 哈喽小伙伴们好&#xff0c;我是Stark-C~ 截止目前&#xff0c;《黑神话:悟空》已经面世一个多月&#xff0c;不知道还有多少天命人没有通关呢&#xff1f; 作为国内首款真正意义上的3A大作&#xff0c;《黑神话…