当前位置: 首页 > news >正文

TCP--执行Linux命令(虚拟xshell)

一:Tcp的服务端

TCP这个服务端的结构是

    int _listensockfd; //这个文件描述符 只负责监听(也就是送客人,不负责招待)uint16_t _port;std::string _ip;bool _isrunning;

1.1首先我们创建TCP的套接字。

依旧是调用接口--socket--它的作用就是打开一个网络端口进行通信,其实呢就是像文件一样 ,返回一个文件描述符,往这个网络文件里输入内容,就可以进行网络同通信了。

 _listensockfd = ::socket(AF_INET,SOCK_STREAM,0);if(_listensockfd < 0){LOG(LogLevel::FATAL) << "create sockfd false";Die(1);}

第一个参数协议族--用的是网络通信,就用AF_INET,第二个参数是套接字类型,用的网络协议是TCP的也就是可靠的,连接的,全双工的,面向字节流的。第三个默认为0就可以。

1.2 bind绑定

        struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = ::htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_listensockfd,CONV(&local),sizeof(local));

绑定网络信息,通常要看是是本地通信还是网络通信,最后统一接口到sockaddr,前十六位会使底层辨认出这是什么通信,我们使网络通信,就用sockaddr_in承载网络信息就可以了。

绑定的作用就是将sockfd(套接字)和sockaddr(套接字地址结构)绑定在一起,这样sockfd就可以进行明确的网络通信了。

因为服务器可能有多个IP所以服务器绑定的信息是INADDR_ANY,就是任意地址就可以。

1.3 TCP面向连接

服务器需要等待客户端与他建立连接,我们把这种叫做监听,要一直去监听有没有人和他建立连接,所以我们也罢sockfd的名字改成了_listensockfd

listen函数将使sockfd进入监听状态,允许最多backlog个客户端排队等待连接。成功后返回0,失败返回-1。

1.4建立连接

监听成功,接收客户端的连接--accept

在网络协议里,TCP使用的是三次握手协议,当三次握手成功过后,就开始连接,accept要在_listensockfd上面接入连接,sockaddr* 是传入型参数,这代表了服务端的信息,该调用返回一个专门用于数据传输的新文件描述符,后续的通信操作都通过这个描述符进行,而原始的_listensockfd仅用于监听新连接请求。

            struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = ::accept(_listensockfd,CONV(&peer),&len);//建立连接之后,这个对应的文件描述符才负责传信(接待)

1.5运行尝试

我们可以试着用telnet尝试连接,然后进行实验,可以在图里看到accept成功的,然后有客户端练车成功的

但是我们这个有一个问题,就是,他是一个单进程版,如果这个客户端不断开连接,就不能接收新的客户端,所以我们以后要修改的就是能让其他客户端连接。

1.6多进程

多进程的话,父进程负责等待连接,子进程完成处理工作。

父子进程有两张描述符表,数据共用,如果有修改就再拷贝,父进程负责监听,但是他不用传输信息,所以它可以关闭通信的sockfd关掉,子进程负责传输,不用监听,所以就可以关闭_listensockfd,这样关闭也不会有什么影响,因为这个和管道的道理一样,进行的是引用计数,所以只要有人用,就不会关闭。但是也可以不管,因为权责分明,自己管理自己的就行。

父子进程还有一个问题就是,父进程会阻塞等待回收子进程,那这样又会和单进程一样,所以我们再开一个孙子进程,让子进程退出,这样孙子进程就由系统进程1来回收了。

或者另一个方法是把信号SIGCHLD设置为忽略,父进程就不会去等待回收子进程。

            //version -1 多线程版本pid_t pid = fork();if(pid == 0){//子进程再创建孙子进程,子进程直接退掉,由系统进程1 来回收管理孙子进程//子进程和父进程 各有一张文件描述符表 文件都是通过引用计数进行管理的//就像管道一样::close(_listensockfd);if(fork() > 0){exit(0);}HandlerRequest(sockfd);exit(0);}// 给出建议父进程不要关闭文件描述符,这个设计叫权责分明//现在语法执行没错,如果修改内容容易有错::close(sockfd);pid_t waitid = ::waitpid(pid,nullptr,0);if(waitid<0){LOG(LogLevel::ERROR)<<"回收孙子进程失败";}

1.7 用多线程

多线程不用再关心网络文件描述符,因为是共享的,所以不用关心了。

线程也会join等待回收,那么我们的做法和上面道理一样,让线程和主线程分离,这样就不会阻塞等待了。

        struct Thread {int sockfd;TcpServer* self;};static void* ThreadHandler(void* args){//用线程也要等待回收(join) 必须等待回收的话就会阻塞,所以让线程自己结束释放资源pthread_detach(pthread_self());Thread* tmp = (Thread*)args;tmp->self->HandlerRequest(tmp->sockfd);return nullptr;}pthread_t pid;Thread* data = new Thread;data->self = this;data->sockfd = sockfd;pthread_create(&pid,nullptr,ThreadHandler,data);

1.8线程池

如果是用到需要处理多个客户端,那么就要有更高的效率,以及更长期的服务,我们用之前封装好的线程池。把任务传给线程就行了。

       using task_t = std::function<void()>;using handler_t = std::function<std::string(std::string& tmp)>;//version 3 线程池task_t f = std::bind(&TcpServer::HandlerRequest,this,sockfd);ThreadPool<task_t>::getInstance()->Equeue([&sockfd,this](){this->HandlerRequest(sockfd);});

所以我们服务端也要多一个处理任务的成员

private:int _listensockfd; //这个文件描述符 只负责监听(也就是送客人,不负责招待)uint16_t _port;std::string _ip;bool _isrunning;handler_t _hander;

1.9 如何执行命令和让服务端回调执行命令

我们可以用popen函数,这个函数的作用--创建一个管道(pipe),并启动一个子进程来执行一个 Linux命令,同时将该子进程的标准输入或标准输出与调用进程(父进程)连接起来,实现父进程与子进程之间的通信。然后我们把管道中的结果读出来。

std::string Exc(std::string& command){if(!check(command)){std::cout<<command <<"不合法"<<std::endl;return std::string("命令不合法");}FILE* pp=::popen(command.c_str(),"r");if(pp==nullptr){return std::string("popen false");}char buffer[1024];std::string result;while(true){char* get = ::fgets(buffer,sizeof(buffer),pp);if(!get){break;}result+=buffer;}::pclose(pp);return result;}

我们把这个执行命令封装在一个类里面,再限制几个命令,以免做了像 rm 等操作对系统进行影响

class CommandExc
{public:CommandExc(){_white_list.insert("ls");_white_list.insert("pwd");_white_list.insert("ls -l");_white_list.insert("ll");_white_list.insert("touch");_white_list.insert("who");_white_list.insert("whoami");}~CommandExc(){}bool check(std::string& message){auto pos = _white_list.find(message);return pos != _white_list.end();}std::string Exc(std::string& command){if(!check(command)){std::cout<<command <<"不合法"<<std::endl;return std::string("命令不合法");}FILE* pp=::popen(command.c_str(),"r");if(pp==nullptr){return std::string("popen false");}char buffer[1024];std::string result;while(true){char* get = ::fgets(buffer,sizeof(buffer),pp);if(!get){break;}result+=buffer;}::pclose(pp);return result;}private:std::set<std::string> _white_list;};

我们有了对应的处理命令的函数,接着我们要传给我们的线程池,让线程池回调函数,处理客户端发来的命令。

int main()
{CommandExc comd;std::shared_ptr<TcpServer> tserver = std::make_shared<TcpServer>([&comd](std::string command){return comd.Exc(command);});tserver->InitServer();tserver->Start();return 0;}
uint16_t defaultport = 8080;
std::string defaultip = "127.0.0.1";class TcpServer
{public:TcpServer(command_t command,uint16_t port = defaultport, std::string ip = defaultip): _port(port), _ip(ip), _isrunning(false), _listensockfd(-1),_command(command){}
}

这样的话,封装和解耦都做好了。

二 客户端

客户端就很简单了,依旧是创建套接字,不用绑定,发送消息接收消息就行了,一定记得需要把套接字类型设置成SOCK_STREAM。

if(argc != 3 ){std::cout<<"Clinet need two arguments"<<std::endl;return 1;}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);int sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){LOG(LogLevel::ERROR) << "create scokfd false";}struct sockaddr_in peer;memset(&peer,0,sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = ::htons(port);peer.sin_addr.s_addr = ::inet_addr(ip.c_str());

在TCP中我们面向连接的,所以进行绑定的话和UDP还是有区别的,UDP是发送第一条消息就会自动绑定,而TCP是用connect 建立连接之后进行了绑定。

  int n = ::connect(sockfd,CONV(&peer),sizeof(peer));if(n<0){LOG(LogLevel::ERROR)<<"connect false";return  1;}std::string message;while(true){std::cout<<"Please enter"<<std::endl;getline(std::cin,message);char inbuffer[1024];n = ::write(sockfd, message.c_str(), message.size());if(n > 0){int m = ::read(sockfd, inbuffer, sizeof(inbuffer));if(m > 0){inbuffer[m] = 0;std::cout << inbuffer << std::endl;}elsebreak;}else break;}::close(sockfd);

欧克下次我们学习序列化和反序列化

http://www.xdnf.cn/news/1360783.html

相关文章:

  • 数据建模怎么做?一文讲清数据建模全流程
  • 一、基因组选择(GS)与基因组预测(GP)
  • 网络安全转型书籍清单
  • 【Java开发日记】我们来讲一讲 Channel 和 FileChannel
  • 深度学习之第一课深度学习的入门
  • VirtualBox安装openEuler24.03
  • daily notes[5]
  • 前端 vs 后端请求:核心差异与实战对比
  • 05 线性代数【动手学深度学习v2】
  • 中介者模式与几个C++应用实例
  • imx6ull-驱动开发篇39——Linux INPUT 子系统实验
  • 【基础算法】初识搜索:递归型枚举与回溯剪枝
  • 【ElasticSearch】springboot整合es案例
  • Smooze Pro for mac 鼠标手势增强软件
  • 【C语言练习】青蛙跳台阶
  • Vue状态管理工具pinia的使用以及Vue组件通讯
  • 强光干扰下检出率↑93%!陌讯多模态融合算法在充电桩车位占用检测的实战解析
  • 力扣【1277. 统计全为1的正方形子矩阵】——从暴力到最优的思考过程
  • 【网络运维】Shell脚本编程:函数
  • 深度学习之第二课PyTorch与CUDA的安装
  • AOSP构建指南:从零开始的Android源码之旅
  • Docker 容器(一)
  • 【Docker基础】Docker-compose常用命令实践(三):镜像与配置管理
  • 【零代码】OpenCV C# 快速开发框架演示
  • 电路学习(四)二极管
  • 【计算机视觉】CaFormer
  • Java:LinkedList的使用
  • 【Protues仿真】基于AT89C52单片机的温湿度测量
  • 【文献阅读】生态恢复项目对生态系统稳定性的影响
  • 在JavaScript中,比较两个数组是否有相同元素(交集)的常用方法