【Linux网络】构建类似XShell功能的TCP服务器

news2025/4/30 0:36:43

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

在这里插入图片描述

在这里插入图片描述

文章目录

  • 🏳️‍🌈一、TcpServer.hpp
    • 1.1 基本结构
    • 1.2 多线程处理函数
  • 🏳️‍🌈二、Common.hpp
    • 2.1 基本结构
    • 2.2 构造、析构函数
    • 2.3 SafeCheck()
    • 2.4 Excute()
    • 2.5 HandlerCommand()
  • 🏳️‍🌈三、TcpServer.cpp
  • 🏳️‍🌈四、测试
  • 🏳️‍🌈五、整体代码
    • 5.1 TcpServer.hpp
    • 5.2 TcpServer.cpp
    • 5.3 Command.hpp
  • 👥总结


在上一篇文章中,笔者带大家实现了TCP服务器的 4种 客户端与服务端通信的模式 ,分别是单执行流模式、多进程模式、多线程模式、以及线程池模式

这一篇,我将带大家进一步理解TCP,就从用TCP实现类XShell功能服务器 ,采用的是多线程版本

🏳️‍🌈一、TcpServer.hpp

其他部分保持不变,我们添加一个处理回调函数,用来判断和执行相应的 xshell 命令

1.1 基本结构

using handler_t = std::function<std::string(int sockfd, InetAddr addr)>;

class TcpServer{
    public:
        // 构造函数
        TcpServer(handler_t handler,uint16_t port = gport)
            :_handler(handler), _port(port), _isrunning(false){}
        // 初始化
        void InitServer(){}
        // server - 2 多线程版本
        void Loop(){}
        // 析构函数
        ~TcpServer(){}

    private:
        int _listensockfd; // 监听socket
        uint16_t _port;
        bool _isrunning;
        handler_t _handler;  
};

1.2 多线程处理函数

我们之前使用 Server 进行回显处理,这里将 Execute 函数的处理方法从 Server 改成 _handler就行了

// 线程函数
        static void* Execute(void* args){
            ThreadDate* td = static_cast<ThreadDate*>(args);
            // 子线程结束后由系统自动回收资源,无需主线程调用 pthread_join
            pthread_detach(pthread_self()); // 分离新线程,无需主线程回收
            td->_self->_handler(td->_sockfd, td->_addr);
            delete td;
            return nullptr;
        }

🏳️‍🌈二、Common.hpp

2.1 基本结构

Command 类实现类似于XShell的功能,但是需要注意要让所有命令都可以执行,因为可能会导致删库等相关的问题。成员变量可以使用set容器存储允许执行命令的前缀!

class Command{
    public:
        Command(){}
        bool SafeCheck(const std::string& cmdstr){}
        std::string Excute(const std::string& cmdstr){}
        void HandlerCommand(int sockfd, InetAddr addr){}
        ~Command(){}
    private:
        std::set<std::string> _safe_command;  // 允许执行的命令
};

2.2 构造、析构函数

根据自己的需要,在构造函数种将允许使用的命令插入到容器析构函数无需处理!

Command() {
    // 白名单
    _safe_command.insert("ls");
    _safe_command.insert("pwd");
    _safe_command.insert("touch");
    _safe_command.insert("whoami");
    _safe_command.insert("which");
}
~Command(){}

2.3 SafeCheck()

检查当前命令是否在白名单中

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;
        }

2.4 Excute()

写这部分代码时,我们需要用到一个函数 - popen

popen 的作用是 ​创建子进程执行系统命令,并通过管道(Pipe)与其通信,他会启动子进程,调用 /bin/sh(或其他默认 shell)解析并执行指定的命令(如 ls -l、grep “error” 等)。

std::string Excute(const std::string& cmdstr) {
    // 检查是否安全,不安全返回
    if (!SafeCheck(cmdstr)) {
        return "unsafe";
    }
    std::string result;
    // popen 创建子进程执行系统命令,并通过管道(Pipe)与其通信
    // popen(const char* command, const char* type)
    // command: 命令字符串
    // type: 管道类型,"r"表示读,"w"表示写,"r+"表示读写
    // 返回文件指针,失败返回NULL
    FILE* fp = popen(cmdstr.c_str(), "r");
    if (fp) {
        // 读取子进程的输出
        // 一行读取
        char line[1024];
        while (fgets(line, sizeof(line), fp)) {
            result += line;
        }
        return result.empty() ? "success" : result; // 有些命令创建无返回值
    }
}

2.5 HandlerCommand()

命令处理函数是一个长服务(死循环)先接收客户端的信息,如果接收成功则处理收到的消息(命令),并将处理的结果发送给客户端,如果读到文件结尾或者接收失败则退出循环!

void HandlerCommand(int sockfd, InetAddr addr) {
    // 我们把它当作一个长服务
    while (true) {
        char commandbuffer[1024]; // 接收命令的缓冲区
        // 1. 接收消息
        // recv(int sockfd, void* buf, size_t len, int flags)
        // sockfd: 套接字描述符
        // buf: 接收缓冲区
        // len: 接收缓冲区大小
        // flags: 接收标志  0表示阻塞,非0表示非阻塞
        ssize_t n = ::recv(sockfd, commandbuffer, sizeof(commandbuffer) - 1, 0);
        if (n > 0) {
            commandbuffer[n] = 0;
            LOG(LogLevel::INFO) << "get command from client" << addr.AddrStr()
                                << ":" << commandbuffer;
            std::string result = Excute(commandbuffer);

            // 2. 发送消息
            // send(int sockfd, const void* buf, size_t len, int flags)
            // sockfd: 套接字描述符
            // buf: 发送缓冲区
            // len: 发送缓冲区大小
            // flags: 发送标志  0表示不阻塞,非0表示阻塞
            ::send(sockfd, result.c_str(), result.size(), 0);
        }
        // 读到文件结尾
        else if (n == 0) {
            LOG(LogLevel::INFO) << "client " << addr.AddrStr() << " quit";
            break;
        } else {
            LOG(LogLevel::ERROR) << "read error from client " << addr.AddrStr();
            break;
        }
    }
}

🏳️‍🌈三、TcpServer.cpp

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

需要注意的是,Command::HandlerCommand 是 ​非静态成员函数,调用时必须通过 Command 类的实例(如 cmdservice)来访问。

#include "TcpServer.hpp"
#include "Command.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]);

    Command cmdservice;

    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
        // &Command::HandlerCommand:成员函数指针。
        // &cmdservice:Command 对象的实例指针(this 指针)
        // _1 和 _2:占位符,表示回调函数接受两个参数(int sockfd 和 InetAddr addr)
        std::bind(&Command::HandlerCommand, &cmdservice, std::placeholders::_1, std::placeholders::_2),
        port);

    tsvr->InitServer();
    tsvr->Loop();

    return 0;
}

🏳️‍🌈四、测试

在这里插入图片描述

🏳️‍🌈五、整体代码

5.1 TcpServer.hpp

#pragma once

#include <iostream>
#include <cstring>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>

#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

#define BACKLOG 8

using namespace LogModule;
using namespace ThreadPoolModule;

static const uint16_t gport = 8080;


using handler_t = std::function<void(int sockfd, InetAddr addr)>;



class TcpServer{
    public:
        // 构造函数
        TcpServer(handler_t handler,uint16_t port = gport)
            :_handler(handler), _port(port), _isrunning(false){}

        // 初始化
        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. 绑定 socket
            if(::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,失败返回 -1
            if(::listen(_listensockfd, BACKLOG) < 0){
                LOG(LogLevel::ERROR) << "listen socket error: " << strerror(errno);
                Die(4);
            }
            LOG(LogLevel::INFO) << "listen sockfd success: " << _listensockfd;
        }





        // 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_join
            pthread_detach(pthread_self()); // 分离新线程,无需主线程回收
            td->_self->_handler(td->_sockfd, td->_addr);
            delete td;
            return nullptr;
        }



        // 析构函数
        ~TcpServer(){}

    private:
        int _listensockfd; // 监听socket
        uint16_t _port;
        bool _isrunning;

        handler_t _handler;  
};

5.2 TcpServer.cpp

#include "TcpServer.hpp"
#include "Command.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]);

    Command cmdservice;

    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
        // &Command::HandlerCommand:成员函数指针。
        // &cmdservice:Command 对象的实例指针(this 指针)
        // _1 和 _2:占位符,表示回调函数接受两个参数(int sockfd 和 InetAddr addr)
        std::bind(&Command::HandlerCommand, &cmdservice, std::placeholders::_1, std::placeholders::_2),
        port);

    tsvr->InitServer();
    tsvr->Loop();

    return 0;
}

5.3 Command.hpp

#pragma once


#include <iostream>
#include <set>
#include <cstring>
#include <cstdio>

#include "InetAddr.hpp"
#include "Log.hpp"

using namespace LogModule;

class Command{
    public:
        Command(){
            // 白名单
            _safe_command.insert("ls");
            _safe_command.insert("pwd");
            _safe_command.insert("touch");
            _safe_command.insert("whoami");
            _safe_command.insert("which");
        }
        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 Excute(const std::string& cmdstr){
            // 检查是否安全,不安全返回
            if(!SafeCheck(cmdstr)){
                return "unsafe";
            }
            std::string result;
            FILE* fp = popen(cmdstr.c_str(), "r");
            if(fp){
                char line[1024];
                while(fgets(line, sizeof(line), fp)){
                    result += line;
                }
                return result.empty() ? "success" : result; // 有些命令创建无返回值
            }
            return "Execute error";
        }
        std::string Excute(std::string& cmdstr){
            // 检查是否安全,不安全返回
            if(!SafeCheck(cmdstr)){
                return "unsafe";
            }
            std::string result;
            // popen 创建子进程执行系统命令,并通过管道(Pipe)与其通信
            // popen(const char* command, const char* type)
            // command: 命令字符串
            // type: 管道类型,"r"表示读,"w"表示写,"r+"表示读写
            // 返回文件指针,失败返回NULL
            FILE* fp = popen(cmdstr.c_str(), "r");
            if(fp){
                // 读取子进程的输出
                // 一行读取
                char line[1024];
                while(fgets(line, sizeof(line), fp)){
                    result += line;
                }
                return result.empty() ? "success" : result; // 有些命令创建无返回值
            }
        }
        void HandlerCommand(int sockfd, InetAddr addr){
            // 我们把它当作一个长服务
            while(true){
                char commandbuffer[1024];   // 接收命令的缓冲区
                // 1. 接收消息
                // recv(int sockfd, void* buf, size_t len, int flags)
                // sockfd: 套接字描述符
                // buf: 接收缓冲区
                // len: 接收缓冲区大小
                // flags: 接收标志  0表示阻塞,非0表示非阻塞
                ssize_t n = ::recv(sockfd, commandbuffer, sizeof(commandbuffer) - 1, 0);    
                if(n > 0){
                    commandbuffer[n] = 0;
                    LOG(LogLevel::INFO) << "get command from client" << addr.AddrStr() << ":" << commandbuffer;
                    std::string result = Excute(commandbuffer);

                    // 2. 发送消息
                    // send(int sockfd, const void* buf, size_t len, int flags)
                    // sockfd: 套接字描述符
                    // buf: 发送缓冲区
                    // len: 发送缓冲区大小
                    // flags: 发送标志  0表示不阻塞,非0表示阻塞
                    ::send(sockfd, result.c_str(), result.size(), 0);
                }
                // 读到文件结尾
                else if(n == 0){
                    LOG(LogLevel::INFO) << "client " << addr.AddrStr() << " quit";
                    break;
                }
                else{
                    LOG(LogLevel::ERROR) << "read error from client " << addr.AddrStr();
                    break;
                }
            }
        }
        ~Command(){}
    private:
        std::set<std::string> _safe_command;  // 允许执行的命令
};

👥总结

本篇博文对 【Linux网络】构建类似XShell功能的TCP服务器 做了一个较为详细的介绍,不知道对你有没有帮助呢

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

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

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

相关文章

Spring Boot 配置源详解(完整版)

Spring Boot 配置源详解&#xff08;完整版&#xff09; 一、配置源加载顺序与优先级 配置源类型优先级顺序&#xff08;从高到低&#xff09;对应配置类/接口是否可覆盖典型文件/来源命令行参数&#xff08;--keyvalue&#xff09;1&#xff08;最高&#xff09;SimpleComman…

puppeteer注入浏览器指纹过CDP

一、背景 通过puppeteer爬取目标网站时&#xff0c;经常会被对方网站检测到&#xff0c;比如原生puppeteerCDP特征非常明显&#xff0c;另外指纹如果一直不变&#xff0c;也会引发风控 二、实现 通过以下几行代码即可轻松过大部分检测点&#xff0c;并且能够切换指纹&#x…

软件项目实施全流程及交付物清单

需求分析 -> 概要设计 -> 详细设计 -> 开发实现 -> 测试 -> 部署 -> 运维 一、确认项目目标、范围和团队成员 二、收集和分析客户需求&#xff0c;确定需求规格 三、制定详细的项目计划&#xff0c;包括时间表、资源计划、预算 四、系统架构设计&#xf…

【2025计算机网络-面试常问】http和https区别是什么,http的内容有哪些,https用的是对称加密还是非对称加密,流程是怎么样的

HTTP与HTTPS全面对比及HTTPS加密流程详解 一、HTTP与HTTPS核心区别 特性HTTPHTTPS协议基础明文传输HTTP SSL/TLS加密层默认端口80443加密方式无加密混合加密&#xff08;非对称对称&#xff09;证书要求不需要需要CA颁发的数字证书安全性易被窃听、篡改、冒充防窃听、防篡改…

从梯度消失到百层网络:ResNet 是如何改变深度学习成为经典的?

自AlexNet赢得2012年ImageNet竞赛以来&#xff0c;每个新的获胜架构通常都会增加更多层数以降低错误率。一段时间内&#xff0c;增加层数确实有效&#xff0c;但随着网络深度的增加&#xff0c;深度学习中一个常见的问题——梯度消失或梯度爆炸开始出现。 梯度消失问题会导致梯…

Uni-App 多端电子合同开源项目介绍

项目概述 本项目是一款基于 uni-app框架开发的多端电子合同管理平台&#xff0c;旨在为企业及个人用户提供高效、安全、便捷的电子合同签署与管理服务。项目创新性地引入了 “证据链”与“非证据链”两种签署模式&#xff0c;满足不同场景下的签署需求&#xff0c;支持多种签署…

多语言笔记系列:共享数据

在笔记中共享数据(变量) 使用 .NET 交互式内核&#xff0c;可以在单个笔记本中以多种语言编写代码。为了利用每种语言的不同优势&#xff0c;您会发现在它们之间共享数据很有用。即一种语言的变量&#xff0c;可以在其它语言中使用。 默认情况下&#xff0c;.NET Interactive …

如何使用SeedProd创建无缝的WordPress维护页面

不管您刚接触 WordPress &#xff0c;还是经验丰富的站长&#xff0c;SeedProd 都是创建网站维护页面的得力助手。通过SeedProd&#xff0c;您可以轻松创建一个与网站风格一致、功能齐全的维护页面&#xff0c;让您的用户在网站维护期间也能感受到您的专业与关怀。本文将为您提…

使用Python设置excel单元格的字体(font值)

一、前言 通过使用Python的openpyxl库&#xff0c;来操作excel单元格&#xff0c;设置单元格的字体&#xff0c;也就是font值。 把学习的过程分享给大家。大佬勿喷&#xff01; 二、程序展示 1、新建excel import openpyxl from openpyxl.styles import Font wb openpyxl.…

求解,如何控制三相无刷电机?欢迎到访评论

问题&#xff1a;通过一个集成的TF2104芯片控制H桥上桥臂和下桥臂&#xff0c;如何控制&#xff1f;还是说得需要PWM_UH和PWM_UL分开控制&#xff1f;

365打卡第R3周: RNN-心脏病预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊 &#x1f3e1; 我的环境&#xff1a; 语言环境&#xff1a;Python3.10 编译器&#xff1a;Jupyter Lab 深度学习环境&#xff1a;torch2.5.1 torchvision0…

【实战】基于强化学习的 Agent 训练框架全流程拆解

一、引言 在人工智能蓬勃发展的今天&#xff0c;强化学习&#xff08;Reinforcement Learning, RL&#xff09;作为让智能体&#xff08;Agent&#xff09;在复杂环境中自主学习并做出最优决策的核心技术&#xff0c;正日益受到关注。从游戏领域中击败人类顶尖选手的 AlphaGo&a…

【音视频】⾳频处理基本概念及⾳频重采样

一、重采样 1.1 什么是重采样 所谓的重采样&#xff0c;就是改变⾳频的采样率、sample format、声道数等参数&#xff0c;使之按照我们期望的参数输出。 1.2 为什么要重采样 为什么要重采样? 当然是原有的⾳频参数不满⾜我们的需求&#xff0c;⽐如在FFmpeg解码⾳频的时候…

Prompt 结构化提示工程

Prompt 结构化提示工程 目前ai开发工具都大同小异&#xff0c;随着deepseek的流行&#xff0c;ai工具的能力都差不太多&#xff0c;功能基本都覆盖到了。而prompt能力反而是需要更加关注的&#xff08;说白了就是能不能把需求清晰的输出成文档&#xff09;。因此大家可能需要加…

Pycharm 代理配置

Pycharm 代理配置 文章目录 Pycharm 代理配置1. 设置系统代理1.1 作用范围1.2 使用场景1.3 设置步骤 2. 设置 python 运行/调试代理2.1 作用范围2.2 使用场景2.3 设置步骤 Pycharm 工具作为一款强大的 IDE&#xff0c;其代理配置在实际开发中也是必不可少的&#xff0c;下面介绍…

Spring Native:GraalVM原生镜像编译与性能优化

文章目录 引言一、Spring Native与GraalVM基础1.1 GraalVM原理与优势1.2 Spring Native架构设计 二、原生镜像编译实践2.1 构建配置与过程2.2 常见问题与解决方案 三、性能优化技巧3.1 内存占用优化3.2 启动时间优化3.3 实践案例分析 总结 引言 微服务架构的普及推动了轻量级、…

药监平台上传数据报资源码不存在

问题&#xff1a;电子监管码上传药监平台提示“导入的资源码不存在” 现象&#xff1a;从生产系统导出的关联关系数据包上传到药监平台时显示&#xff1a; 原因&#xff1a;上传数据包的通道的资源码与数据包的资源码不匹配。 解决方法&#xff1a;检查药监平台和生产系统的药…

【Linux应用】交叉编译环境配置,以及最简单粗暴的环境移植(直接从目标板上复制)

【Linux应用】交叉编译环境配置&#xff0c;以及最简单粗暴的环境移植&#xff08;直接从目标板上复制&#xff09; 文章目录 交叉编译器含有三方库的交叉编译直接从目标板上复制编译环境glibc库不一致报错方法1方法2 附录&#xff1a;ZERO 3烧录ZERO 3串口shell外设挂载连接Wi…

CSS3布局方式介绍

CSS3布局方式介绍 CSS3布局&#xff08;Layout&#xff09;系统是现代网页设计中用于构建页面结构和控制元素排列的一组强大工具。CSS3提供了多种布局方式&#xff0c;每种方式都有其适用场景&#xff0c;其中最常用的是Flexbox和CSS Grid。 先看传统上几种布局方式&#xff…

FPGA设计 时空变换

1、时空变换基本概念 1.1、时空概念简介 时钟速度决定完成任务需要的时间&#xff0c;规模的大小决定完成任务所需要的空间&#xff08;资源&#xff09;&#xff0c;因此速度和规模就是FPGA中时间和空间的体现。 如果要提高FPGA的时钟&#xff0c;每个clk内组合逻辑所能做的事…