記一次遠程CMD開發過程

開發初衷:

  有些同窗電腦總是要出問題,但又不是什麼大問題,一般幾句cmd就能搞定。以前解決方案有2:一是遠程演示,我口述別人操做;一是我寫個cmd腳本,但畢竟不在本機很差調試。(吐槽一下經常使用的遠程控制實在是難用至極)windows

  解決:遠程cmd,可以實時的反饋執行結果,帶寬佔用少緩存

軟件環境:

  windows10, Microsoft Visual C# 2010 Express, 資料都是百度的!服務器

設計以及有些我以爲該思考的問題:

  思考:

    一、終端應該作成能在全部能聯網的設備上跑,而不是內網版網絡

    二、假設終端只在內網上運行,那麼一個終端須要知道另外一個終端的ip和端口(可經過廣播,但麻煩了)tcp

    三、作成廣域網版使用什麼技術(主要指消息是經過轉發仍是打洞),最後選擇轉發,由於打洞我還不是很會 -_-''ide

    四、終端應該區分控制端和被控制端?不用,軟件內部功能區分就好了;若是終端分開那麼服務器端還要作識別,就設計麻煩了測試

    五、先就這麼多吧...優化

  大概是這麼設計的:

    一、服務器開啓後可接受2個客戶端鏈接,不區分【控制端】或【被控制端】,只作消息轉發(提升效率);只有當兩端都連上後才能傳輸消息,當只有一端存在時該端發送消息會收到服務器提示 "另外一端不在線"spa

    二、客戶端進入時選擇是【控制端】仍是【被控制端】,連上後提示另外一端是否鏈接上服務器線程

    三、【控制端】向【被控制端】發送控制命令,【控制端】將收到的消息(期待收到的回顯)顯示

    四、【被控制端】等待命令,收到後執行並將回顯顯示於該端,最後將回顯發送回【控制端】

實現:

  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 }
服務器代碼

一些解釋:

  一、服務器只容許兩個終端鏈接應該能知足通常使用場景,至少能解決開始敘述的問題

  二、客戶端開始那個密碼區分控制端的東西,其實沒多大用,編譯出來的exe我用16進制打開都能找到 "密碼",但願會密碼學的大神不要噴,我確實不大會

  三、【被控制端】那段調用cmd的代碼網上很多,略有借鑑...

  四、【被控制端】消息回顯爲何不直接發送回【控制端呢】?由於【被控制端】每輸出一行就會產生一個輸出重定向,若是每個重定向都直接使用網絡傳送,那麼將致使網絡負荷過大(我猜的),因此根據網絡原理我優化了發送界定爲:150ms的超時傳送 或 4k的超限傳送

  五、服務器端能夠作成多終端鏈接,但消息轉發/羣發會麻煩,可改進用於一個終端控制多個終端,考慮使用特徵編號之類的識別控制羣

  六、消息前加不可打印字符最開始是因爲:測試開啓兩個【被控制端】時,會形成消息循環發送,當區分控制消息和顯示消息後,顯示消息就不會執行並循環控制

  七、以後測試開啓兩個【控制端】,【控制端】之間能夠互相發送消息,而且都不會當成控制消息(由於該流程裏沒有執行這個步驟),因此只能分類處理一下顯示出來;估計...大概至關於個能聊天的東西了吧

  八、最後我以爲數據轉發應該有更牛逼的辦法,好比服務器直接把網絡流量重定向之類的;說實話我以爲我寫的服務器這種接收再轉發——實在是low爆了,真的但願大神能幫我改進下

相關文章
相關標籤/搜索