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;
            }
            //获取客户端的端口和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.mfbz.cn/a/284771.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis Cluster集群模式学习

Redis Cluster集群模式 Redis哨兵模式&#xff1a;https://blog.csdn.net/liwenyang1992/article/details/133956200 Redis Cluster集群模式示意图&#xff1a; Cluster模式是Redis3.0开始推出采用无中心结构&#xff0c;每个节点保存数据和整个集群状态&#xff0c;每个节点都…

常见位运算模板方法总结(包含五道例题)

哈喽大家好&#xff0c;今天博主给大家带来算法基础常见位运算的模板&#xff0c;可以说大家遇到的百分之九十与位运算有关的题都可以用得上。话不多上我们上干货&#xff1a; 一.基础位运算符 << 左移运算符 >> 右移运算符 ~ 取反 & 与运算 | …

爱思唯尔的KBS——模板、投稿、返修、接收的总结

第二篇论文终于是接受了QAQ&#xff0c;被审稿人疯狂拖时间&#xff0c;KBS是真难绷啊 由于之前发布过关于爱思唯尔旗下的ESWA博客&#xff0c;KBS和ESWA是类似的&#xff0c;因此本篇博客主要说下区别以及期间碰到的各种情况&#xff0c;有疑问依然可以在评论区说&#xff0c;…

消息中间件常见知识点

一&#xff1a;消息队列的主要作用是什么&#xff1f; 1.消息队列的特性&#xff1a; 业务无关&#xff0c;一个具有普适性质的消息队列组件不需要考虑上层的业务模型&#xff0c;只做好消息的分发就可以了&#xff0c;上层业务的不同模块反而需要依赖消息队列所定义的规范进行…

(五)分文件编程

文章目录 为什么要引入分文件编程.C文件怎么添加.H文件怎么书写以及如何进行链接.H书写格式&#xff1a;“有头有尾标识符”例如&#xff08;timer.h) .H链接链接到头文件所在路径的文件夹路径即可 提供一个分文件编程的一种代码最后附上视频演示 为什么要引入分文件编程 C程序…

前端 js 基础(1)

js 结果输出 &#xff08;点击按钮修改文字 &#xff09; <!DOCTYPE html> <html> <head></head><body><h2>Head 中的 JavaScript</h2><p id"demo">一个段落。</p><button type"button" onclic…

gnu工程的编译 - 以libiconv为例

文章目录 gnu工程的编译 - 以libiconv为例概述gnu官方源码包的发布版从官方的代码库直接迁出的git版源码如果安装了360, 需要添加开发相关的目录到信任区生成 configrue 的方法备注END gnu工程的编译 - 以libiconv为例 概述 gnu工程的下载分2种: gnu官方源码包的发布版 这种…

es简单入门

星光下的赶路人star的个人主页 努力努力再努力 文章目录 1、简介2、使用场景3、基本知识4、中文文档和官网链接5、增删改查&#xff08;php代码&#xff09;6、基本查询7、HTTP操作7.1 索引操作7.1.1 创建索引 7.2 文档操作7.2.1 创建文档7.2.2 查看文档7.2.3 修改文档7.2.4 修…

看了好多烟花,自己也来了段

<!DOCTYPE html> <!DOCTYPE html> <html lang"zh-CN"> <meta charset"UTF-8"> <title>烟花动画</title> <style>body, html { height: 100%; margin: 0; }canvas { position: absolute; } </style> </…

Group k-fold解释和代码实现

Group k-fold解释和代码实现 文章目录 一、Group k-fold解释和代码实现是什么&#xff1f;二、 实验数据设置2.1 实验数据生成代码2.2 代码结果 三、实验代码3.1 实验代码3.2 实验结果3.3 结果解释 四、总结 一、Group k-fold解释和代码实现是什么&#xff1f; 0&#xff0c;1…

【分布式微服务专题】SpringSecurity快速入门

目录 前言阅读对象阅读导航前置知识笔记正文一、Spring Security介绍1.1 什么是Spring Security1.2 它是干什么的1.3 Spring Security和Shiro比较 二、快速开始2.1 用户认证2.1.1 设置用户名2.1.1.1 基于application.yml配置文件2.1.1.2 基于Java Config配置方式 2.1.2 设置加密…

Mysql 高级语句

目录 高阶查询select语句&#xff1a; 显示表格中一个或数个字段的所有数据记录&#xff1a; 不显示重复的数据记录&#xff1a;distinct and且&#xff0c;or或 显示已知的值的数据记录&#xff1a;in 显示两个值范围内的数据记录&#xff1a;between 通配符&#xff1…

基于rk3568 Android H265推流SRS低延迟网页播放方案

在音视频领域&#xff0c;融合推流&#xff0c;低码流&#xff0c;低延迟&#xff0c;浏览器H5化是一个降低成本&#xff0c;提升用户体验的重要手段。同时适配现有直播的生态也是一个必要条件。 在满足上述要求的情况下&#xff0c;我做了以下实践&#xff0c;取得了良好的效果…

【ROS2】MOMO的鱼香ROS2(四)ROS2入门篇——ROS2节点通信之话题与服务

ROS2节点通信之话题与服务点 引言1 理解从通信开始1.1 TCP&#xff08;传输控制协议&#xff09;1.2 UDP&#xff08;用户数据报协议&#xff09;1.3 基于共享内存的IPC方式 2 ROS2话题2.1 ROS2话题指令2.2 话题之RCLPY实现2.2.1 编写发布者2.2 2 编写订阅者2.2.3 运行测试 3 R…

总结:回顾我的2023年

文章目录 心得体会接触博客接触竞赛接触自学接触环境一些收获 2024年的目标 2023年已经结束了&#xff0c;那么特此在这里记录这一年的心得体会&#xff0c;同时也给明年定下目标吧 心得体会 接触博客 本人于2022年9月开始的自己的大学生活&#xff0c;是一个很普通的双非院…

c++_08_操作符重载(操作符重定义) 友元

1 操作符标记 单目操作符&#xff1a; - -- * -> 等 双目操作符&#xff1a; - > < - << >> 等 三木操作符&#xff1a; ? : 2 操作符函数 2.0 前言 C编译器有能力把一个由操作…

Origin绘制频数分布直方图+曲线拟合分布

问题描述 有组数据大概分布如下&#xff0c;现在想在Origin中绘制出以下效果 流程 如果我们想要人为每个柱子的边界&#xff0c;以方便展示&#xff0c;需要新建一列&#xff0c;输入数据分布的大概区间。 需要注意的是&#xff0c;C(Y)列中删除数据时若留下的“-”符合存…

鸿蒙开发第1篇__网络请求

先访问 OpenAtom OpenHarmony &#xff0c; 浏览 Http数据请求&#xff0c;

CSS 缩减顶部动画

<template><!-- mouseenter"startAnimation" 表示在鼠标进入元素时触发 startAnimation 方法。mouseleave"stopAnimation" 表示在鼠标离开元素时触发 stopAnimation 方法。 --><!-- 容器元素 --><div class"container" mou…

Linux:apache优化(7)—— 日志分割|日志合并

作用&#xff1a;随着网站访问量的增加&#xff0c;访问日志中的信息会越来越多&#xff0c; Apache 默认访问日志access_log单个文件会越来越大&#xff0c;日志文件体积越大&#xff0c;信息都在一个文件中&#xff0c;查看及分析信息会及不方便。 分割 实现方式&#xff1a…
最新文章