苏州建设工程协会网站,网站推广运营公司,大型网站建设的必须条件,网站建设是怎么一回事实现原理
首先我们要有个客户端和一个服务器#xff0c;客户端向服务器传递命令。而服务器收到命令后创建一个管道#xff0c;并fork一个子进程。随后子进程解析命令#xff0c;再把标准输出换成管道文件#xff0c;因为命令行命令是自动输出到显示器的#xff0c;所以我…实现原理
首先我们要有个客户端和一个服务器客户端向服务器传递命令。而服务器收到命令后创建一个管道并fork一个子进程。随后子进程解析命令再把标准输出换成管道文件因为命令行命令是自动输出到显示器的所以我们要把命令的结果重定向到管道文件。然后服务器主进程等待子进程返回的结果并把结果返回给客户端。 客户端需要做的事情
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::vectorstd::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_ptrUdpServer 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 functionaltypedef std::functionvoid(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 memoryint 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_ptrUdpClient 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 cstringclass 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 [XXXXabcdefg]$ ;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 然后我们启动客户端输入服务器的ip和对应的端口号8080 然后在客户端中执行各种命令 无论是增加文件还是删除文件都是可以进行操作的。所以这就实现了我们的一个远程mini版shell。
代码的git地址