当前位置: 首页 > 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.dtcms.com/a/349220.html

相关文章:

  • 苍穹外卖项目实战(日记十)-记录实战教程及问题的解决方法-(day3-2)新增菜品功能完整版
  • 不再让Windows更新!Edge游戏助手卸载及关闭自动更新
  • Leetcode 3661. Maximum Walls Destroyed by Robots
  • 阿里AI模型获FDA突破性医疗器械认定,AI医疗走向国际舞台,来近屿智能系统学习AIGC大模型技术
  • 芋道前端项目部署后刷新 404 的解决办法(Nginx 配置教程)
  • 计算机网络:聊天室(UDP)
  • 器件(十)——经典封装类型总结
  • JUC之ThreadLocal
  • MySQL的安装和卸载指南(入门到入土)
  • python写上位机并打包250824
  • 第04章 SPSS简介与数据库构建
  • 2025最新ncm转MP3,网易云ncm转mp3格式,ncm转mp3工具!
  • C6.1:发射极偏置放大器
  • 支持多种模型,无限AI生图工具来了
  • 智元精灵GO1 agibot数据转换Lerobot通用格式数据脚本
  • 3.2 半导体随机存取存储器 (答案见原书 P168)
  • 你在四阶段数据成熟度旅程中处于哪个阶段?
  • 高数 不定积分(4-3):分部积分法
  • APP逆向——某站device-id参数(2)
  • 56 C++ 现代C++编程艺术5-万能引用
  • Linux内核ELF文件签名验证机制的设计与实现(C/C++代码实现)
  • DeepSeek对采用nginx实现透传以解决OpenShift 4.x 私有数据中心和公有云混合部署一套集群的解答
  • 机床智能健康管理系统:工业母机数字化转型的核心引擎​
  • 在mysql中,modify ,change ,rename to的作用是什么
  • AI使用日志(一)--Cursor和Claude code初体验
  • 用 Python 探索二分查找算法:从基本原理到实战最佳实践
  • 自回归(Auto-Regressive, AR),自回归图像生成过程
  • 【Canvas与旗帜】蓝圈汤加旗
  • 基于蓝牙的stm32智能火灾烟雾报警系统设计
  • 一个高度精简但结构完整的微服务示例