Udp实现一个小型shell

实现原理

首先我们要有个客户端和一个服务器,客户端向服务器传递命令。而服务器收到命令后创建一个管道,并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;}//获取客户端的端口和IPstd::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]; //提取服务器的ipstd::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.mzph.cn/news/591466.shtml

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

相关文章

英飞凌TC3xx之一起认识GTM系列(一)先来认识GTM架构

英飞凌TC3xx之一起认识GTM系列(一)先来认识GTM架构 1 先来认识GTM的通用架构2 概览2.1 架构的简要说明2.2 架构概述1 先来认识GTM的通用架构 GTM系统使用GTM全局时钟fGTM 运行(本文称为SYS_CLK)。 特点如下: GTM模块由两个主要部分组成: 由博世设计的GTM IP v3.1.5.1 …

【Java 数组解析:探索数组的奇妙世界】

数组的引入 我们先通过一段简单的代码引入数组的概念。 import java.util.Scanner; public class TestArray01{public static void main(String[] args){//功能&#xff1a;键盘录入十个学生的成绩&#xff0c;求和&#xff0c;求平均数&#xff1a;//定义一个求和的变量&…

【我与CSDN的128天】相识相知相守

目录: 相识相知相守 相识 为什么选择写博客? 写博客的目的,我觉得是因为想要记录。记录学习的过程,整理学过的知识,方便今后的复习。 更重要的是热爱分享,分享给别人知识也是一种快乐。 在某一瞬间教会某一个你不认识的人,难道不是一个很酷的事情吗? 为什么选择CSDN? 作…

企业签名分发对移动应用开发者有什么影响

企业签名分发是移动应用开发者在应用程序发布前测试、内部分发和特定的受众群体分发等方面比较常用的一种工具。那对于应用商城分发有啥区别&#xff0c;下面简单的探讨一下。 独立分发能力 通过企业签名分发开发者可以自己决定应用程序的发布时间和方式&#xff0c;不用受应用…

[2024区块链开发入门指引] - 比特币运行原理

一份为小白用户准备的免费区块链基础教程 工欲善其事,必先利其器 Web3开发中&#xff0c;各种工具、教程、社区、语言框架.。。。 种类繁多&#xff0c;是否有一个包罗万象的工具专注与Web3开发和相关资讯能毕其功于一役&#xff1f; 参见另一篇博文&#x1f449; 2024最全面…

Android14之禁掉Selinux的两种方式(一百七十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

uniapp的分包使用记录

UniApp的分包是一种将应用代码划分为多个包的技术。分包的核心思想是将不同部分的代码划分为不同的包&#xff0c;按需加载&#xff0c;从而提高应用性能。使用UniApp的条件编译功能&#xff0c;开发人员可以根据需要将代码划分为多个包。每个包都包含一组页面和组件&#xff0…

大数据Doris(四十五):物化视图选择最优

文章目录 物化视图选择最优 物化视图选择最优 下面详细解释一下第一步最优物化视图是被如何选择出来的。 这里分为两个步骤: 对候选集合进行一个过滤。只要是查询的结果能从物化视图数据计算(取部分行,部分列,或部分行列的聚合)出都可以留在候选集中,过滤完成后候选集合…

RocketMQ源码解析-主从同步原理(HA)

1、关键组件 主从同步的实现逻辑主要在HAService中&#xff0c;在它的构造函数中实例化了几个对象同时在start()方法内执行启动&#xff1a; public class HAService {public HAService(final DefaultMessageStore defaultMessageStore) throws IOException {this.defaultMes…

李宏毅机器学习第二十三周周报 Flow-based model

文章目录 week 23 Flow-based model摘要Abstract一、李宏毅机器学习1.引言2.数学背景2.1Jacobian2.2Determinant2.3Change of Variable Theorem 3.Flow-based Model4.GLOW 二、文献阅读1. 题目2. abstract3. 网络架构3.1 change of variable formula3.2 Coupling layers3.3Prop…

阿里云域名外部入库流程

注册商是阿里云&#xff0c;且在阿里云管理的&#xff0c;请使用此教程外部入库。 如您的域名注册商是阿里云但在聚名管理&#xff0c;请参考教程&#xff1a;https://www.west.cn/faq/list.asp?unid2539 在外部入库操作之前&#xff0c;请先登录阿里云获取账号ID。详细的账…

软件测试方法分类-按照是否手工执行划分

接上一篇,下来我们再细讲,第二个维度的分类, 软件测试方法分类-按照是否手工执行划分 按是否手工执行划分 1,手工测试(manualTesting) 手工测试是由人一个一个的输入用例,然后观察结果,和机器测试相对应,属于比较原始但是必须的一种。 2,自动化测试(automationTestin…

【刷题日志】深度理解除(/)与取模(%)附水仙花数以及变种水仙花数题解

文章目录 &#x1f680;前言&#x1f680;除与取模&#x1f680;水仙花数&#x1f680;变种水仙花数 &#x1f680;前言 本专栏文章都直奔刷题主题&#xff0c;阿辉都不会在废话了&#xff0c;加油&#xff0c;少年&#xff01;&#xff01;&#xff01; &#x1f680;除与取…

STM32CubeMX教程11 RTC 实时时钟 - 入侵检测和时间戳

目录 1、准备材料 2、实验目标 3、实验流程 3.0、前提知识 3.1、CubeMX相关配置 3.1.1、时钟树配置 3.1.2、外设参数配置 3.1.3、外设中断配置 3.2、生成代码 3.2.1、外设初始化调用流程 3.2.2、外设中断调用流程 3.2.3、添加其他必要代码 4、常用函数 5、烧录验…

探索 CodeWave低代码技术的魅力与应用

目录 前言1 低代码平台2 CodeWave简介3 CodeWave 的独特之处3.1 高保真还原交互视觉需求3.2 擅长复杂应用开发3.3 支持应用导出&独立部署3.4 金融级安全要求3.5 可集成性高3.6 可拓展性强 4 平台架构和核心功能4.1 数据模型设计4.2 页面设计4.3 逻辑设计4.4 流程设计4.5 接…

新能源汽车冷却系统的水道管口类型有哪些?格雷希尔针对这些管口密封的快速接头有哪些?

对于新能源汽车&#xff0c;不仅电池&#xff0c;还有电机、电控、充电单元部件&#xff0c;都需要处于适宜的工作温度&#xff0c;才能维持整车的正常运行。而这些部件在运行过程中会产生大量的热量&#xff0c;如果不及时散热会对汽车的性能、寿命产生影响&#xff0c;甚至可…

Scrapy爬虫中合理使用time.sleep和Request

概述 在Scrapy爬虫中&#xff0c;我们需要深入分析time.sleep和Request对象对并发请求的影响。time.sleep函数用于在发起请求之前等待一段时间&#xff0c;而Request对象用于发送HTTP请求。我们必须仔细考虑这些操作对其他并发请求的潜在影响&#xff0c;以及在异步情况下可能…

TTS | NaturalSpeech语音合成论文详解及项目实现【正在更新中】

----------------------------------&#x1f50a; 语音合成 相关系列直达 &#x1f50a; ------------------------------------- ✨NaturalSpeech&#xff1a;正在更新中~ ✨NaturalSpeech2&#xff1a;TTS | NaturalSpeech2语音合成论文详解及项目实现 本文主要是 讲解了Nat…

基于孔雀优化算法的航线规划

MATLAB2020a下正常运行 上传明细-CSDN创作中心

Excel中部分sheet页隐藏并设置访问密码

1、新建sheet1 2、新建sheet2 3、隐藏sheet2 4、保护工作簿、输密码 5、密码二次确认 6、隐藏的sheet2已经查看不了 7、想要查看时&#xff0c;按图示输入原密码即可 8、查看sheet2内容