记一次远程CMD开发过程

开发初衷:

  有些同学电脑老是要出问题,但又不是什么大问题,通常几句cmd就能搞定。之前解决方案有2:一是远程演示,我口述别人操作;一是我写个cmd脚本,但毕竟不在本机不好调试。(吐槽一下常用的远程控制实在是难用至极)

  解决:远程cmd,能够实时的反馈执行结果,带宽占用少

软件环境:

  windows10, Microsoft Visual C# 2010 Express, 资料都是百度的!

设计以及有些我觉得该思考的问题:

  思考:

    1、终端应该做成能在所有能联网的设备上跑,而不是内网版

    2、假设终端只在内网上运行,那么一个终端需要知道另一个终端的ip和端口(可通过广播,但麻烦了)

    3、做成广域网版使用什么技术(主要指消息是通过转发还是打洞),最后选择转发,因为打洞我还不是很会 -_-''

    4、终端应该区分控制端和被控制端?不用,软件内部功能区分就行了;如果终端分开那么服务器端还要做识别,就设计麻烦了

    5、先就这么多吧...

  大概是这么设计的:

    1、服务器开启后可接受2个客户端连接,不区分【控制端】或【被控制端】,只做消息转发(提高效率);只有当两端都连上后才能传输消息,当只有一端存在时该端发送消息会收到服务器提示 "另一端不在线"

    2、客户端进入时选择是【控制端】还是【被控制端】,连上后提示另一端是否连接上服务器

    3、【控制端】向【被控制端】发送控制命令,【控制端】将收到的消息(期待收到的回显)显示

    4、【被控制端】等待命令,收到后执行并将回显显示于该端,最后将回显发送回【控制端】

实现:

  1 const int PORT = 58888; //Server PORT
  2 const string HOST = "127.0.0.1"; //Server IP
  3 static TcpClient tcp = new TcpClient();
  4 static bool isAlive = false; //连接标识
  5 static bool s=false; //控制端标识,需要传入参数[u999]进入
  6 
  7 //包中第一字节为0x04解析为聊天消息,否则为控制消息;别问为什么用0x04,问就是缘分
  8 
  9 static void Main(string[] args)
 10 {
 11     //控制端判断
 12     if(args.Length!=0)
 13         if(string.Compare(args[0],"u999")==0) //控制端
 14         {
 15             Console.Write("Welcome, controller.");
 16             s=true;
 17         }
 18         else
 19             Console.WriteLine("Hello loser."); //想爆破密码的?
 20     try{
 21         tcp.Connect(HOST, PORT); //连接阻塞
 22         isAlive = true;
 23         Console.WriteLine("-------已连接=" + tcp.Client.RemoteEndPoint + "------");
 24     }catch(Exception e){
 25         Console.WriteLine(e.Message+"\n任意键退出...");
 26         Console.ReadKey();
 27         return;
 28     }
 29 
 30     if(!s) //被控制端
 31     {
 32         //发送线程
 33         ThreadStart ts=new ThreadStart(sendToCtrl);
 34         Thread th=new Thread(ts);
 35         th.Start();
 36 
 37         Process p = new Process();
 38         p.StartInfo.FileName = "cmd.exe"; //程序名
 39         p.StartInfo.UseShellExecute = false; //不使用程序外壳
 40         p.StartInfo.RedirectStandardInput = true; //重定向输入
 41         p.StartInfo.RedirectStandardOutput = true; //重定向输出
 42         p.StartInfo.RedirectStandardError = true; //错误
 43         p.StartInfo.CreateNoWindow = true; //创建窗口
 44         p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived); //订阅输出
 45         p.ErrorDataReceived += new DataReceivedEventHandler(p_OutputDataReceived); //错误输出
 46         p.Start();
 47         p.BeginOutputReadLine(); //程序开始后开始订阅
 48         p.BeginErrorReadLine();
 49 
 50         byte[] recvBuf = new byte[1024];
 51         string command = "";
 52         int recvNum;
 53         while(!p.HasExited) //循环执行
 54         {
 55             recvNum = 0;
 56             try{
 57                 recvNum = tcp.Client.Receive(recvBuf); //服务等待阻塞
 58             }catch(Exception){ //断开连接
 59                 break;
 60             }
 61             if(recvNum == 0) //主动断开:空消息
 62                 break;
 63             if(recvBuf[0]!=0x04) //控制消息
 64             {
 65                 command = Encoding.UTF8.GetString(recvBuf, 0, recvNum); //接收远程命令
 66                 p.StandardInput.WriteLine(command); //执行
 67             }
 68             else
 69                 Console.WriteLine(Encoding.UTF8.GetString(recvBuf,1,recvNum-1));
 70             Thread.Sleep(100); //检测exit命令,while循环条件检查
 71         }
 72         Console.WriteLine("-------End.------\n任意键退出...");
 73         //先关程序,再关连接
 74         p.Close();
 75         p = null;
 76         tcp.Client.Close();
 77         tcp.Close();
 78         tcp=null;
 79     }
 80     else //控制端
 81     {
 82         //接收线程
 83         ThreadStart ts = new ThreadStart(recvMsg);
 84         Thread th = new Thread(ts);
 85         th.Start();
 86 
 87         string input = "";
 88         while(true) //循环发送
 89         {
 90             //这里也输入的是什么消息,反正都当控制消息吧(区分要解析输入)
 91             input = Console.ReadLine();
 92             if(isAlive)
 93                 tcp.Client.Send(Encoding.UTF8.GetBytes(input+'\n'));
 94             else
 95                 break;
 96             Thread.Sleep(100);
 97         }
 98     }
 99     Console.ReadKey(); //这一句没什么卵用但我不想删
100 }
101 
102 //控制端回显消息
103 static void recvMsg()
104 {
105     byte[] recvBuf = new byte[8192];
106     int recvNum;
107     while(true) //循环接收
108     {
109         recvNum = 0;
110         try{
111             recvNum = tcp.Client.Receive(recvBuf);
112         }catch(Exception){
113             break;
114         }
115         if(recvNum == 0)
116             break;
117         //以上为TCP断开判断
118 
119         //以下为什么要分开写呢?因为合起来麻烦了...
120         if(recvBuf[0]!=0x04) //被控端回显
121             Console.Write(Encoding.UTF8.GetString(recvBuf,0, recvNum));
122         else //聊天消息
123             Console.WriteLine(Encoding.UTF8.GetString(recvBuf,1, recvNum-1));
124     }
125     tcp.Client.Close();
126     tcp.Close();
127     isAlive = false;
128     Console.WriteLine("-------Server down------");
129 }
130 
131 //被控端消息发送
132 static string output = "";
133 static void sendToCtrl()
134 {
135     int outputFlag = 0; //被控端超时发送标记
136     string outputNow = "";
137     byte[] sendBytesT = null, sendBytes=null;
138     while(true) //循环检测缓冲池
139     {
140         Thread.Sleep(50);
141         if(!isAlive || output.Length == 0) //未连接 或 无缓存数据
142             continue;
143         if(output.Length > 4096 || outputFlag > 2) //缓存满4k 或 超时传送150ms
144         {
145             outputNow = output;
146             output = "";
147 
148             //这段可能有点low, 望大佬告知该怎么写
149             sendBytesT = Encoding.UTF8.GetBytes(outputNow);
150             sendBytes = new byte[sendBytesT.Length + 1];
151             sendBytes[0] = 0x04;
152             sendBytesT.CopyTo(sendBytes, 1);
153 
154             try{
155                 tcp.Client.Send(sendBytes);
156             }catch(Exception){
157                 break;
158             }
159             outputFlag = 0;
160             continue;
161         }
162         ++outputFlag;
163     }
164 }
165 
166 //被控端输出入池
167 static void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
168 {
169     if(!string.IsNullOrEmpty(e.Data))
170     {
171         Console.WriteLine(e.Data);
172         output += e.Data + '\n';
173     }
客户端代码
 1 //服务器端不区分控制端被控制端,只作数据转发
 2 const int PORT = 58888; //Server PORT
 3 static TcpClient[] tcp=new TcpClient[2];
 4 static int alive=0; //二进制位标识该终端是否在线=00|01|10|11
 5 static void Main()
 6 {
 7     //消息线程
 8     ParameterizedThreadStart ts=new ParameterizedThreadStart(user);
 9 
10     TcpListener listen=new TcpListener(IPAddress.Any,PORT);
11     TcpClient tcpT=null;
12     while(true) //循环监听
13     {
14         if(alive==3) //两个终端==11
15         {
16             Thread.Sleep(4000);
17             continue;
18         }
19         listen.Start(1);
20         Console.WriteLine("Listen="+PORT);
21         tcpT=listen.AcceptTcpClient(); //监听阻塞
22         Console.WriteLine("Accept="+tcpT.Client.RemoteEndPoint);
23         if(alive<2) //==00|01
24         {
25             tcp[0]=tcpT;
26             alive|=2;
27             Thread th=new Thread(ts);
28             th.Start(0);
29         }
30         else //==10,不可能==11(当==11时两个线程都在消息循环中)
31         {
32             tcp[1]=tcpT;
33             alive|=1;
34             Thread th=new Thread(ts);
35             th.Start(1);
36         }
37         listen.Stop();
38     }
39     Console.ReadKey();
40 }
41 
42 //处理一个终端,传入本段代码控制的tcp序号==0|1
43 static void user(object index0)
44 {
45     int index=(int)index0;
46     if(index!=0 && index!=1) //非法验证
47         return;
48     byte[] sendBytesT=Encoding.UTF8.GetBytes(alive==3?"Another is connected.\n":"Only you, wait.\n");
49     byte[] sendBytes=new byte[sendBytesT.Length+1];
50     sendBytes[0]=0x04;
51     sendBytesT.CopyTo(sendBytes,1);
52     tcp[index].Client.Send(sendBytes);
53 
54     byte[] recvBuf = new byte[8192];
55     int recvNum;
56     while(true) //接收[转发]消息
57     {
58         recvNum=0;
59         try{
60             recvNum = tcp[index].Client.Receive(recvBuf);
61         }catch(Exception){
62             break;
63         }
64         if(recvNum == 0)
65             break;
66         //以上为TCP断开判断 被控制端断开
67 
68         //Console.WriteLine(recvNum+","+Encoding.UTF8.GetString(recvBuf,0,recvNum));
69         if(alive==3) //两端在线
70         {
71             byte[] sendBuf=new byte[recvNum];
72             Array.Copy(recvBuf,sendBuf,recvNum);
73             tcp[index==0?1:0].Client.Send(sendBuf);
74         }
75         else //一端不在线
76         {
77             sendBytesT=Encoding.UTF8.GetBytes("Another is disconnected.Ctrl+C to EXIT.\n");
78             sendBytes=new byte[sendBytesT.Length+1];
79             sendBytes[0]=0x04;
80             sendBytesT.CopyTo(sendBytes,1);
81             tcp[index].Client.Send(sendBytes);
82         }
83     }
84     tcp[index].Client.Close();
85     tcp[index].Close();
86     tcp[index]=null;
87     alive&=index==0?1:2;
88 }
服务器代码

一些解释:

  1、服务器只允许两个终端连接应该能满足一般使用场景,至少能解决开始叙述的问题

  2、客户端开始那个密码区分控制端的东西,其实没多大用,编译出来的exe我用16进制打开都能找到 "密码",希望会密码学的大神不要喷,我确实不大会

  3、【被控制端】那段调用cmd的代码网上不少,略有借鉴...

  4、【被控制端】消息回显为什么不直接发送回【控制端呢】?因为【被控制端】每输出一行就会产生一个输出重定向,如果每一个重定向都直接使用网络传送,那么将导致网络负荷过大(我猜的),所以根据网络原理我优化了发送界定为:150ms的超时传送 或 4k的超限传送

  5、服务器端可以做成多终端连接,但消息转发/群发会麻烦,可改进用于一个终端控制多个终端,考虑使用特征编号之类的识别控制群

  6、消息前加不可打印字符最开始是由于:测试开启两个【被控制端】时,会造成消息循环发送,当区分控制消息和显示消息后,显示消息就不会执行并循环控制

  7、之后测试开启两个【控制端】,【控制端】之间可以互相发送消息,并且都不会当成控制消息(因为该流程里没有执行这个步骤),所以只能分类处理一下显示出来;估计...大概相当于个能聊天的东西了吧

  8、最后我觉得数据转发应该有更牛逼的办法,比如服务器直接把网络流量重定向之类的;说实话我觉得我写的服务器这种接收再转发——实在是low爆了,真的希望大神能帮我改进下

 

posted @ 2019-06-14 15:26  i0  阅读(557)  评论(0编辑  收藏  举报