编程实战:自己编写HTTP服务器(系列5:执行后台shell命令)

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。


系列入口:编程实战:自己编写HTTP服务器(系列1:概述和应答)-CSDN博客

         本文介绍执行后台命令的shell.asp的实现。

目录

一、概述

二、主体代码

三、详解

3.1 参数

3.2 设置进程组和打开管道执行命令

3.3 读取数据和返回码处理


一、概述

        这个功能就相当于一个终端,不过只能执行一个命令。有什么好处看自己,可以加入自己喜欢的特性。

        入口:

        别的不说了,主体代码是doPageShell()。

二、主体代码

        主体代码如下:

		bool doPageShell()
		{
#ifdef _MS_VC
			return true;
#else
			FILE * fp;
			string changedir=m_request.GetParam("changedir");
			string curdir=m_request.GetParam("curdir");
			string cmd=m_request.GetParam("command");
			bool noform=(m_request.GetParam("noform")=="true");
			bool term=(m_request.GetParam("term")=="true");
			long bufsize=1024*1024;
			char * buf=new char[bufsize];
			if(NULL==buf)
			{
				m_respond.AppendBody("<P><FONT color=RED>内存不足</FONT><P>");
				return true;
			}

			//切换路径
			if(0!=curdir.size())
			{
				if(0!=chdir(curdir.c_str()))
				{
					m_respond.AppendBody("<P><FONT color=RED>设置初始路径出错</FONT><P>"+curdir+"<P>");
					return true;
				}
			}
			if(0!=changedir.size())
			{
				if(0!=chdir(changedir.c_str()))
				{
					m_respond.AppendBody("<P><FONT color=RED>切换工作路径出错</FONT><P>"+changedir+"<P>");
					return true;
				}
			}

			//执行命令
			if(0==cmd.size())
			{
				m_respond.AppendBody("<P>空命令<P>");
			}
			else
			{
				if(0!=setpgid(getpid(),getpid()))
				{
					m_respond.AppendBody("设置进程组ID出错<P>");
				}
				if(NULL==(fp=popen((cmd+" 2>&1").c_str(),"r")))
				{
					m_respond.AppendBody("<P><FONT color=RED>无法执行,原因:popen error</FONT><P>");
					if(!m_respond.Flush(m_s))return true;
				}
				else
				{
					int fd=fileno(fp);
					int flags;
					fd_set fdset;
					struct timeval tv;

					tv.tv_sec=300;
					tv.tv_usec=0;
					flags = fcntl(fd, F_GETFL, 0);
					flags |= O_NONBLOCK;
					fcntl(fd, F_SETFL, flags);

					char * tmpp;
					m_respond.AppendBody("开始执行&nbsp;");
					m_respond.AppendBody(CHtmlDoc::HTMLEncode(cmd));
					m_respond.Flush(m_s);
					m_respond.AppendBody("<HR></HR><CODE>");
					while(true)
					{
						FD_ZERO(&fdset);
						FD_SET(fd,&fdset);
#ifdef _HPOS
						int selectret=select(fd+1,(int *)&fdset,NULL,NULL,&tv);
#else
						int selectret=select(fd+1,&fdset,NULL,NULL,&tv);
#endif
						if(selectret<0)
						{
							LOG<<"select error"<<ENDE;
						}
						else if(0==selectret)
						{//超时没有数据
							m_respond.AppendBody("注意,长时间没有收到输出.");
							if(!m_respond.Flush(m_s))
							{
								LOG<<"发送失败,客户端已断开,直接退出"<<ENDI;
								if(term)
								{
									if(0!=kill(0,SIGTERM))
									{
										LOG<<"发送停止信号出错,shell会持续执行到命令结束"<<ENDE;
									}
								}
								return true;
							}
							continue;
						}
						else
						{
						}

						bool fileend=false;
						while(true)
						{
							tmpp=fgets(buf,int(bufsize-1),fp);
							if(NULL==tmpp)
							{
								//正常结束
								if(0!=feof(fp))
								{
									fileend=true;
									break;
								}

								if(EWOULDBLOCK==errno || EAGAIN==errno)
								{//无数据
									break;
								}
								else
								{//出错结束
									m_respond.AppendBody("<P><FONT color=RED>执行出错,原因:read error</FONT><P>");
									m_respond.AppendBody(strerror(errno));
									m_respond.Flush(m_s);
									fileend=true;
									break;
								}
							}
							else
							{
								m_respond.AppendBody(LogToHtml(buf,false,false));
								m_respond.AppendBodyHtmlScroll();
								if(!m_respond.Flush(m_s))
								{
									LOG<<"发送失败,客户端已断开,直接退出"<<ENDI;
									if(term)
									{
										if(0!=kill(0,SIGTERM))
										{
											LOG<<"发送停止信号出错,shell会持续执行到命令结束"<<ENDE;
										}
									}
									return true;
								}
							}
						}
						if(fileend)break;
					}
					m_respond.AppendBody("</CODE><HR></HR>");
					int ret=pclose(fp);
					if(0!=ret)
					{
						if(WIFEXITED(ret))
						{
							sprintf(buf,"<FONT color=RED>执行完毕,返回代码 %d 。</FONT><BR>",WEXITSTATUS(ret));//(0xFF00&ret)/256);
						}
						else if(WIFSIGNALED(ret))
						{
							sprintf(buf,"<FONT color=RED>被信号终止,信号 %d 。</FONT><BR>",WTERMSIG(ret));
						}
						else if(WCOREDUMP(ret))
						{
							sprintf(buf,"<FONT color=RED>执行失败,COREDUMP。</FONT><BR>");
						}
						else
						{
							sprintf(buf,"<FONT color=RED>未知的返回值:%d。</FONT><BR>",ret);
						}
					}
					else sprintf(buf,"执行完毕,返回代码 %d 。<BR>",ret);
					m_respond.AppendBody(buf);
				}
			}

			if(!noform)
			{
				char cwd[1024];
				if(NULL!=getcwd(cwd,1024))
				{
					sprintf(buf,
						"<FORM ACTION=\"/shell.asp\" METHOD=\"GET\" >\n"
						"当前路径:%s<BR>"
						"<INPUT TYPE=\"hidden\" NAME=\"curdir\" VALUE=\"%s\" >\n"
						"切换路径到:<INPUT TYPE=\"text\" SIZE=\"30\" NAME=\"changedir\" ><BR>\n"
						"Shell命令: <INPUT TYPE=\"text\" SIZE=\"30\" NAME=\"command\" VALUE=\"%s\">\n"
						"<INPUT TYPE=SUBMIT VALUE=\"执行\" >\n"
						"</FORM>\n"
						,cwd,cwd,cmd.c_str());
				}
				else
				{
					sprintf(buf,"获取当前工作路径出错");
				}
				m_respond.AppendBody(buf);
			}

			delete[] buf;
			return true;
#endif
		}

三、详解

3.1 参数

        最关键命令参数:command,包含要执行的命令,可以是一串命令的组合,也就是你能输到控制台运行的东西都行。

        运行命令一定需要工作目录,显然不能在服务进程的工作目录下执行,谁知道会发生什么呢。目录用curdir和changedir参数来控制,如果curdir是当前工作目录,具体就是这个页面执行命令的工作目录,changedir用于切换目录,可以是相对目录。执行时首先将目录切换到curdir(因为服务进程有自己的工作目录,和期待的执行命令的工作目录不同),然后再切换到changedir,其实就是执行两次chdir()。

        代码如下:

			//切换路径
			if(0!=curdir.size())
			{
				if(0!=chdir(curdir.c_str()))
				{
					m_respond.AppendBody("<P><FONT color=RED>设置初始路径出错</FONT><P>"+curdir+"<P>");
					return true;
				}
			}
			if(0!=changedir.size())
			{
				if(0!=chdir(changedir.c_str()))
				{
					m_respond.AppendBody("<P><FONT color=RED>切换工作路径出错</FONT><P>"+changedir+"<P>");
					return true;
				}
			}

3.2 设置进程组和打开管道执行命令

         启动新进程一般都要设置点东西,解除新进程和服务进程的关系,主要是进程间使用进程组广播信号的问题,新进程压根不应该知道服务进程存在。

        popen()很实用的运行命令并获取输出的方法,其内部会打开单向管道,运行命令,返回输出(文件句柄)。

        为了能看到命令的所有输出,我们需要在命令后面追加“ 2>&1”将标准出错重定向到标准输出。

        相关代码如下:

				if(0!=setpgid(getpid(),getpid()))
				{
					m_respond.AppendBody("设置进程组ID出错<P>");
				}
				if(NULL==(fp=popen((cmd+" 2>&1").c_str(),"r")))
				{
					m_respond.AppendBody("<P><FONT color=RED>无法执行,原因:popen error</FONT><P>");
					if(!m_respond.Flush(m_s))return true;
				}
				else
				{

        后面就是常规的从文件描述符读取数据直到结束。当然里面混合了格式化、发送,以及命令返回码的处理。

3.3 读取数据和返回码处理

       select可以检查是否有数据,这个函数一般用于socket,但是其实是针对文件描述符的。

       用fgets()逐行读取数据,如果出错errno会有各种结果,需要分别判断。

       pclose获得返回码,具体分析和进程返回码是一样的。


(这里是结束,但不是整个系列的结束)

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值