Udp实现一个小型shell

news2025/3/1 9:11:19

实现原理

首先我们要有个客户端和一个服务器,客户端向服务器传递命令。而服务器收到命令后创建一个管道,并fork一个子进程。随后子进程解析命令,再把标准输出换成管道文件,因为命令行命令是自动输出到显示器的,所以我们要把命令的结果重定向到管道文件。然后服务器主进程等待子进程返回的结果,并把结果返回给客户端。

image.png
客户端需要做的事情:

1. 读取用户输入的命令

2. 把输入的命令发送给服务器

3. 读取服务器返回的结果并回显显示器

服务器需要做的事情:

1. 读取客户端发来的命令

2. 创建一个管道

3. 创建一个子进程

4. 关闭管道的写端(管道是单向通信的)

5. 等待子进程的返回结果(返回结果会在管道中)

6. 把结果发送给客户端

服务器的子进程需要做的事情

1. 关闭管道读端(管道会继承自父进程)

2. 把字符串拆分,例如: ls -a -l 拆分成ls,a,l这样的单个字符串

3. 把标准输出替换成管道的写端(这种行为也叫重定向)

4. 把拆分的字符串组织起来进行进程替换

server端代码

我们明白了shell的实现原理之后,那么我们先来编写服务器。服务器负责接收客户端发来的命令把把命令递交给子进程,由子进程进行程序替换来返回结果。子进程的返回结果本来会返回到显示器上,但是我们修改了子进程的标准输出,那么就会重定向到管道中。

server.cc代码:

#include "server.hpp"
#include <memory>
#include <unistd.h>
#include <fcntl.h>
#include <vector>
#include <sys/wait.h>
#include <cstring>

//请求处理函数
void CommandMessage(int sockfd,std::string ip , uint16_t port, std::string message)
{
    //1创建管道
    int fds[2];

    if(pipe(fds) != 0)
    {
        std::cerr << "input pipe failed in " << ip << "-" << port << std::endl;
        return;
    }
    int pid = fork();
    if(pid > 0)
    {
        //父进程关闭写
        close(fds[1]);
        char buff[1024 * 4] = {0};
        waitpid(pid,nullptr,0);
        int n = read(fds[0],buff,sizeof buff - 1);
        std::cout << buff << std::endl;
        //把返回的结果发给客户端
        struct sockaddr_in client; 
        client.sin_addr.s_addr = inet_addr(ip.c_str());
        client.sin_port = htons(port);
        client.sin_family = AF_INET;
        sendto(sockfd,buff,strlen(buff),0,(struct sockaddr*)&client,sizeof client);

    }else if(pid == 0)
    {
        //子进程关闭读
        close(fds[0]); 
        char buff[1024] = {0};
        
        //解析命令行
        int idx = 0 ;
        std::vector<std::string> cmds;
        //把命令行参数分解到cmds中
        while(true)
        {
            int pos = message.find(" ",idx); 
            if(pos == std::string::npos) 
            {
            // std::cout<< message << pos << std::endl;
                cmds.push_back(message.substr(idx,pos - idx)); 
                break;
            }
            if(idx != pos) 
            {
                cmds.push_back(message.substr(idx,pos - idx)); 
            }
            idx = pos + 1;   
        }
        const char* ev[128] = {0};  //存储所有的参数

        //把cmds中所有的参数放进ev中
        for(int i = 0; i < cmds.size() ;i++){ ev[i] = cmds[i].c_str(); }
        dup2(fds[1],1);// 相当于close(1) -> close(fds[1]) -> open(fds[1])
        execvp(ev[0],(char* const *)ev); //程序替换
        exit(1);
    } 
}


int main(int argc , char* argv[])
{
    if(argc != 2) //命令行参数不为2就退出
    {
        std::cout << "Usage : " << argv[0] << "   bindport" << std::endl;  //打印使用手册
        exit(1);
    }
    uint16_t port = atoi(argv[1]); //命令行传的端口转换成16位整形
    std::unique_ptr<UdpServer> s(new UdpServer(port,CommandMessage)); //创建UDP服务器,并传入一个回调函数处理请求
    s->init(); //初始化服务器,创建 + 绑定
    s->start(); //运行服务器
}

server.hpp代码:


#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
#include <functional>

typedef std::function<void(int,std::string,uint16_t,std::string)> func_t;

class UdpServer
{
private:
    int _sock; 
    uint16_t _port;
    func_t _callback;
public:
    UdpServer(uint16_t port,func_t callback): _port(port) ,_callback(callback){ }
    ~UdpServer() { close(_sock); }
    void init()
    {
        _sock = socket(AF_INET,SOCK_DGRAM,0);  //创建套接字
        if(_sock < 0)
        {
            //创建失败
            std::cout << "create socket failed...." << std::endl;
            abort();
        }
        //绑定 
        struct sockaddr_in ser; 
        ser.sin_port = htons(_port);  //填入端口
        ser.sin_family = AF_INET; // 填入域
        ser.sin_addr.s_addr = INADDR_ANY; //填入IP地址
        if(bind(_sock,(sockaddr*)&ser,sizeof ser) != 0) //绑定
        {
            //绑定失败
            std::cout << "bind socket failed...." << std::endl;
            abort();
        }
    }

    void start()
    {
        struct sockaddr_in peer; //对端
        socklen_t peer_len = sizeof peer;
        char buff[1024] = {0};   
        while(1)
        {
            int n = recvfrom(_sock,buff,1023,0,(struct sockaddr*)&peer,&peer_len); 
            buff[n] = 0;
            if(read == 0)
            {
                std::cout << "one client quit..." << std::endl;
                continue;
            }else if(read < 0)
            {
                 std::cout << "read error..." << std::endl;
                 break;
            }
            //获取客户端的端口和IP
            std::string clientip = inet_ntoa(peer.sin_addr);
            uint16_t clientport = ntohs(peer.sin_port);
            std::cout << buff << std::endl; //回显客户端信息
            //调用回调函数处理数据
            _callback(_sock,clientip,clientport,buff);
        }
    } 
};

client端代码

client端必须是先给服务端发送数据的,不过首先要先输入命令,然后把命令发给服务器。之后只需要等待服务器传回的结果,再把结果打印到显示器即可。

client.cc代码:

#include "client.hpp"
#include <memory>

int main(int argc , char* argv[])
{
    if(argc != 3) //必须 ./client 服务器ip  服务器端口  才能成功运行客户端
    {
        std::cout << "Usage : " << argv[0] << "   serverip  serverport" << std::endl; 
        exit(1);
    }
    uint16_t port = atoi(argv[2]); //提取服务器的端口
    std::string ip = argv[1]; //提取服务器的ip
    std::unique_ptr<UdpClient> cli(new UdpClient(port,ip));  //创建客户端
    cli->init(); //客户端初始化
    cli->start(); //客户端启动!
}

client.hpp代码:

#pragma once
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <iostream>
#include <arpa/inet.h>
#include <cstdio>
#include <cstring>

class UdpClient
{
public: 
    UdpClient(uint16_t port , const std::string& ip) : _port(port), _svr_ip(ip){}
    ~UdpClient(){ close(_sock); }

    void init()
    {
         _sock = socket(AF_INET,SOCK_DGRAM,0); 
        if(_sock < 0)
        {
            std::cout << "create socket failed...." << std::endl;
            abort();
        }   
        svr.sin_port = htons(_port); 
        svr.sin_addr.s_addr = inet_addr(_svr_ip.c_str()); 
        svr.sin_family = AF_INET;

    }
    void start()
    {
        int i = 1; 
        char sendbuff[1024] = {0};
        while(1)
        {
            //输入命令行
            std::cout << "[XXXX@abcdefg]$ ";
            fgets(sendbuff,sizeof sendbuff -1 , stdin); 
            sendbuff[strlen(sendbuff) - 1] = 0;
            std::string message = sendbuff;   
            //发送命令信息
            sendto(_sock,message.c_str(),message.size(),0,(struct sockaddr*)&svr,sizeof svr);
            //收服务器请求
            char recvbuff[1024 * 4] = {0};
            recvfrom(_sock,recvbuff,sizeof recvbuff - 1,0,nullptr,nullptr);
            //打印回收到的消息
            std::cout << recvbuff;
        }
    }

private: 
    int _sock;
    uint16_t _port;
    std::string _svr_ip;  
    struct sockaddr_in svr;

};

接下来我们可以看看运行结果:

我们先启动服务器,并且为服务器绑定端口号8080

image.png

然后我们启动客户端,输入服务器的ip和对应的端口号8080

在这里插入图片描述

然后在客户端中执行各种命令

在这里插入图片描述

无论是增加文件还是删除文件,都是可以进行操作的。所以这就实现了我们的一个远程mini版shell。

代码的git地址:

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

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

相关文章

程序员必备IDEA插件,什么是是IDE?

IDEA是一款功能强大的集成开发环境&#xff08;IDE&#xff09;插件&#xff0c;它可以帮助开发人员更加高效地编写、调试和部署软件应用程序。 我们在编写完接口代码后需要进行接口调试等操作&#xff0c;一般需要打开额外的调试工具。今天就给大家介绍一款IDEA插件&#xff…

【MATLAB第87期】#源码分享 | 基于MATLAB的增量神经系统网络SFAM多输入单输出多分类预测模型

【MATLAB第87期】#源码分享 | 基于MATLAB的增量神经系统网络SFAM多输入单输出多分类预测模型 前言 SFAM是一种增量神经网络分类器。它是模糊ARTMAP&#xff08;FAM&#xff09;的一个简单而快速的版本。如果输入相同,FAM和SFAM的产出相同。 参考文献: [1] Kasuba, T. (1993)…

【Redis-03】Redis数据结构与对象原理 -下篇

承接上篇【Redis-02】Redis数据结构与对象原理 -上篇 8. type-字符串string 8.1 字符串的三种encoding编码&#xff08;int embstr raw&#xff09; 如果保存的是整型&#xff0c;并且可以用long类型标识&#xff08;-9223372036854775808到9223372036854775807&#xff09…

Git:常用命令(二)

查看提交历史 1 git log 撤消操作 任何时候&#xff0c;你都有可能需要撤消刚才所做的某些操作。接下来&#xff0c;我们会介绍一些基本的撤消操作相关的命令。请注意&#xff0c;有些操作并不总是可以撤消的&#xff0c;所以请务必谨慎小心&#xff0c;一旦失误&#xff0c…

大连理工大学软件学院2022年秋季学期《矩阵与数值分析》上机作业

文章目录 《计算机科学计算》第二版162页第12题&#xff08;1&#xff09;162页第16题216页第12题 《数值分析方法与应用》一、基础知识部分1、5、 二、线性方程组求解2、6、 三、非线性方程组求解1、4、 四、插值与逼近1、5、7、 五、数值积分2、 六、微分方程数值解法1、 《计…

com.microsoft.sqlserver.jdbc.SQLServerException: 驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“The

配置文件示例: # SQL Server 数据源配置 spring.datasource.dynamic.datasource.sqlserver.urljdbc:sqlserver://100.100.0.0\\shili;databaseNamecs; spring.datasource.dynamic.datasource.sqlserver.usernamesa spring.datasource.dynamic.datasource.sqlserver.password sp…

C#,入门教程(03)——Visual Studio 2022编写彩色Hello World与动画效果

C#&#xff0c;入门教程(01)—— Visual Studio 2022 免费安装的详细图文与动画教程https://blog.csdn.net/beijinghorn/article/details/123350910 C#&#xff0c;入门教程(02)—— Visual Studio 2022开发环境搭建图文教程https://blog.csdn.net/beijinghorn/article/detail…

毫秒格式化

## 计算当前毫秒数&#xff1a; const [start,setStart] useState(new Date().getTime())useEffect(()>{setInterval(()>{setCurrMill(new Date().getTime()-start)},1)},[]) ## 格式化毫秒 function formatMilliseconds(milliseconds) {const totalSeconds Math.flo…

WEB 3D技术 three.js通过 GLTFLoader 导入并应用 gltf/glb 3D资源

上文 WEB 3D技术 three.js 雾 基础使用讲解我们讲了雾的基本使用方法 但是 如果我们要做一个树林 一颗一颗树去加 那真的是要累死了 我们一定是在建模软件上 建模好这样的模型 然后将模型导入到场景中 官网中搜索 GLTFLoader 在我们日常WEB开发中 用的最多的3D格式 就是 GLTF…

门控循环单元(GRU)-多输入时序预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分代码&#xff1a; 四、完整代码数据下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译…

【一分钟】ThinkPHP v6.0 (poc-yaml-thinkphp-v6-file-write)环境复现及poc解析

写在前面 一分钟表示是非常短的文章&#xff0c;只会做简单的描述。旨在用较短的时间获取有用的信息 环境下载 官方环境下载器&#xff1a;https://getcomposer.org/Composer-Setup.exe 下载文档时可以设置代理&#xff0c;不然下载不上&#xff0c;你懂的 下载成功 cmd cd…

骑砍战团MOD开发(29)-module_scenes.py游戏场景

骑砍1战团mod开发-场景制作方法_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Cw411N7G4/ 一.骑砍游戏场景 骑砍战团中进入城堡,乡村,战斗地图都被定义为场景,由module_scenes.py进行管理。 scene(游戏场景) 天空盒(Skyboxes.py) 地形(terrain code) 场景物(scene_…

跨境电商的语言障碍:翻译工具的必要性

随着全球化的加速和电子商务的普及&#xff0c;跨境电商逐渐成为企业拓展市场的重要渠道。然而&#xff0c;跨境电商在带来无限商机的同时&#xff0c;也面临着语言障碍的挑战。由于不同国家和地区的语言和文化差异&#xff0c;跨境电商在产品描述、用户沟通、广告宣传等方面需…

ETL项目实战--学习笔记

ELT基本概念 1&#xff0c;什么时ELT&#xff1f; E: Extract&#xff0c;数据抽取 > 抽取的是其他数据源中的数据 T: Transform&#xff0c;数据转换 > 将数据转换为统一的格式,消除异常值,缺失值,对于错误的逻辑进行修改 L: Load&#xff0c;数据加载 > 将不同数据…

RAID的介绍和选择

RAID 类型&#xff1a;什么是 RAID 以及哪种 RAID 级别适合您&#xff1f; 一、RAID 简介 在2021年6月11日&#xff0c;亚瑟迪特纳进行了一场关于RAID技术的技术讲座。RAID&#xff0c;即独立磁盘冗余阵列&#xff0c;是将多个硬盘驱动器协同工作的技术。不同的RAID类型各有优…

双指针刷题(三)

所有算法文章链接&#xff08;最底部&#xff09; http://t.csdnimg.cn/IbllR 1.有效三角形个数 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 1.分析题意 给一个非负的数组&#xff0c;判断这个数组能组成多少个三角形。 2.解题思路 补充知识…

如何获取 ChatGPT 的 OpenAI API 密钥

为什么需要 OpenAI API 密钥&#xff1f; 拥有 OpenAI API 密钥可以解锁多种强大的功能。您可以享受以下一些好处&#xff1a; 访问先进的人工智能模型 OpenAI 开发了 GPT-3 和 Codex 等多种先进的人工智能模型。借助 API 密钥&#xff0c;您可以利用这些模型的功能来执行自然…

【Java】ThreadLocal原理与使用场景

ThreadLocal原理&#xff1a; 字段&#xff1a; //ThreadLocal对象的哈希码 private final int threadLocalHashCode nextHashCode();//生成ThreadLocal对象的哈希码时&#xff0c;需要用到该对象&#xff0c;从0开始 private static AtomicInteger nextHashCode new Atomic…

文件分片上传(模拟网盘效果)

文件分片上传&#xff08;模拟网盘效果&#xff09; 文章说明简单模拟拖拽文件夹和选择文件的进度条效果效果展示结合后端实现文件上传效果展示加上分片的效果效果展示加上MD5的校验&#xff0c;实现秒传和分片的效果后续开发说明源码下载 文章说明 文章主要为了学习文件上传&a…

图像拼接——基于homography的特征匹配算法

目录 1. 任务要求2. 数据集3. 基于homography的特征匹配算法4. 拼接流程展示4.1 图片实例4.2 特征点位图4.3 特征点匹配结果4.4 相机校准结果4.5 拼接结果 5. 部分图像拼接结果展示 1. 任务要求 输入&#xff1a;同一个场景的两张待拼接图像&#xff08;有部分场景重合&#x…