开发初衷:

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

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

软件环境:

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

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

  思考:

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

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

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

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

    5、先就这么多吧...

  大概是这么设计的:

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

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

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

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

实现:

 const int PORT = ; //Server PORT
const string HOST = "127.0.0.1"; //Server IP
static TcpClient tcp = new TcpClient();
static bool isAlive = false; //连接标识
static bool s=false; //控制端标识,需要传入参数[u999]进入 //包中第一字节为0x04解析为聊天消息,否则为控制消息;别问为什么用0x04,问就是缘分 static void Main(string[] args)
{
//控制端判断
if(args.Length!=)
if(string.Compare(args[],"u999")==) //控制端
{
Console.Write("Welcome, controller.");
s=true;
}
else
Console.WriteLine("Hello loser."); //想爆破密码的?
try{
tcp.Connect(HOST, PORT); //连接阻塞
isAlive = true;
Console.WriteLine("-------已连接=" + tcp.Client.RemoteEndPoint + "------");
}catch(Exception e){
Console.WriteLine(e.Message+"\n任意键退出...");
Console.ReadKey();
return;
} if(!s) //被控制端
{
//发送线程
ThreadStart ts=new ThreadStart(sendToCtrl);
Thread th=new Thread(ts);
th.Start(); Process p = new Process();
p.StartInfo.FileName = "cmd.exe"; //程序名
p.StartInfo.UseShellExecute = false; //不使用程序外壳
p.StartInfo.RedirectStandardInput = true; //重定向输入
p.StartInfo.RedirectStandardOutput = true; //重定向输出
p.StartInfo.RedirectStandardError = true; //错误
p.StartInfo.CreateNoWindow = true; //创建窗口
p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived); //订阅输出
p.ErrorDataReceived += new DataReceivedEventHandler(p_OutputDataReceived); //错误输出
p.Start();
p.BeginOutputReadLine(); //程序开始后开始订阅
p.BeginErrorReadLine(); byte[] recvBuf = new byte[];
string command = "";
int recvNum;
while(!p.HasExited) //循环执行
{
recvNum = ;
try{
recvNum = tcp.Client.Receive(recvBuf); //服务等待阻塞
}catch(Exception){ //断开连接
break;
}
if(recvNum == ) //主动断开:空消息
break;
if(recvBuf[]!=0x04) //控制消息
{
command = Encoding.UTF8.GetString(recvBuf, , recvNum); //接收远程命令
p.StandardInput.WriteLine(command); //执行
}
else
Console.WriteLine(Encoding.UTF8.GetString(recvBuf,,recvNum-));
Thread.Sleep(); //检测exit命令,while循环条件检查
}
Console.WriteLine("-------End.------\n任意键退出...");
//先关程序,再关连接
p.Close();
p = null;
tcp.Client.Close();
tcp.Close();
tcp=null;
}
else //控制端
{
//接收线程
ThreadStart ts = new ThreadStart(recvMsg);
Thread th = new Thread(ts);
th.Start(); string input = "";
while(true) //循环发送
{
//这里也输入的是什么消息,反正都当控制消息吧(区分要解析输入)
input = Console.ReadLine();
if(isAlive)
tcp.Client.Send(Encoding.UTF8.GetBytes(input+'\n'));
else
break;
Thread.Sleep();
}
}
Console.ReadKey(); //这一句没什么卵用但我不想删
} //控制端回显消息
static void recvMsg()
{
byte[] recvBuf = new byte[];
int recvNum;
while(true) //循环接收
{
recvNum = ;
try{
recvNum = tcp.Client.Receive(recvBuf);
}catch(Exception){
break;
}
if(recvNum == )
break;
//以上为TCP断开判断 //以下为什么要分开写呢?因为合起来麻烦了...
if(recvBuf[]!=0x04) //被控端回显
Console.Write(Encoding.UTF8.GetString(recvBuf,, recvNum));
else //聊天消息
Console.WriteLine(Encoding.UTF8.GetString(recvBuf,, recvNum-));
}
tcp.Client.Close();
tcp.Close();
isAlive = false;
Console.WriteLine("-------Server down------");
} //被控端消息发送
static string output = "";
static void sendToCtrl()
{
int outputFlag = ; //被控端超时发送标记
string outputNow = "";
byte[] sendBytesT = null, sendBytes=null;
while(true) //循环检测缓冲池
{
Thread.Sleep();
if(!isAlive || output.Length == ) //未连接 或 无缓存数据
continue;
if(output.Length > || outputFlag > ) //缓存满4k 或 超时传送150ms
{
outputNow = output;
output = ""; //这段可能有点low, 望大佬告知该怎么写
sendBytesT = Encoding.UTF8.GetBytes(outputNow);
sendBytes = new byte[sendBytesT.Length + ];
sendBytes[] = 0x04;
sendBytesT.CopyTo(sendBytes, ); try{
tcp.Client.Send(sendBytes);
}catch(Exception){
break;
}
outputFlag = ;
continue;
}
++outputFlag;
}
} //被控端输出入池
static void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if(!string.IsNullOrEmpty(e.Data))
{
Console.WriteLine(e.Data);
output += e.Data + '\n';
}

客户端代码

 //服务器端不区分控制端被控制端,只作数据转发
const int PORT = ; //Server PORT
static TcpClient[] tcp=new TcpClient[];
static int alive=; //二进制位标识该终端是否在线=00|01|10|11
static void Main()
{
//消息线程
ParameterizedThreadStart ts=new ParameterizedThreadStart(user); TcpListener listen=new TcpListener(IPAddress.Any,PORT);
TcpClient tcpT=null;
while(true) //循环监听
{
if(alive==) //两个终端==11
{
Thread.Sleep();
continue;
}
listen.Start();
Console.WriteLine("Listen="+PORT);
tcpT=listen.AcceptTcpClient(); //监听阻塞
Console.WriteLine("Accept="+tcpT.Client.RemoteEndPoint);
if(alive<) //==00|01
{
tcp[]=tcpT;
alive|=;
Thread th=new Thread(ts);
th.Start();
}
else //==10,不可能==11(当==11时两个线程都在消息循环中)
{
tcp[]=tcpT;
alive|=;
Thread th=new Thread(ts);
th.Start();
}
listen.Stop();
}
Console.ReadKey();
} //处理一个终端,传入本段代码控制的tcp序号==0|1
static void user(object index0)
{
int index=(int)index0;
if(index!= && index!=) //非法验证
return;
byte[] sendBytesT=Encoding.UTF8.GetBytes(alive==?"Another is connected.\n":"Only you, wait.\n");
byte[] sendBytes=new byte[sendBytesT.Length+];
sendBytes[]=0x04;
sendBytesT.CopyTo(sendBytes,);
tcp[index].Client.Send(sendBytes); byte[] recvBuf = new byte[];
int recvNum;
while(true) //接收[转发]消息
{
recvNum=;
try{
recvNum = tcp[index].Client.Receive(recvBuf);
}catch(Exception){
break;
}
if(recvNum == )
break;
//以上为TCP断开判断 被控制端断开 //Console.WriteLine(recvNum+","+Encoding.UTF8.GetString(recvBuf,0,recvNum));
if(alive==) //两端在线
{
byte[] sendBuf=new byte[recvNum];
Array.Copy(recvBuf,sendBuf,recvNum);
tcp[index==?:].Client.Send(sendBuf);
}
else //一端不在线
{
sendBytesT=Encoding.UTF8.GetBytes("Another is disconnected.Ctrl+C to EXIT.\n");
sendBytes=new byte[sendBytesT.Length+];
sendBytes[]=0x04;
sendBytesT.CopyTo(sendBytes,);
tcp[index].Client.Send(sendBytes);
}
}
tcp[index].Client.Close();
tcp[index].Close();
tcp[index]=null;
alive&=index==?:;
}

服务器代码

一些解释:

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

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

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

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

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

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

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

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

记一次远程CMD开发过程的更多相关文章

  1. 记一个社交APP的开发过程——基础架构选型(转自一位大哥)

    记一个社交APP的开发过程——基础架构选型 目录[-] 基本产品形态 技术选型 最近两周在忙于开发一个社交App,因为之前做过一点儿社交方面的东西,就被拉去做API后端了,一个人头一次完整的去搭这么一 ...

  2. 记一次解决cmd中执行java提示"找不到或无法加载主类"的问题

    今天遇到一个问题:在cmd命令行中,用javac编译java文件可以成功,但是用java执行却提示“找不到或无法加载主类”.现将该问题的原因以及解决办法记录一下. 先理解一下系统变量path和clas ...

  3. 10.17小作业 基于TCP开发一款远程CMD程序

    基于TCP开发一款远程CMD程序 客户端连接服务器后,可以向服务器发送命令 服务器收到命令后执行,无论执行是否成功,无论执行几遍,都将执行结果返回给客户端 注意: 执行系统指令使用subprocess ...

  4. 远程cmd操作

    <<PSTools.zip>><<Install_PowerCmd.exe>><<cmder_mini.zip>><< ...

  5. python练习-Socket实现远程cmd命令

    需求:基于tcp的套接字实现远程执行命令的操作 代码示例: # 编辑者:闫龙 #Client端部分 import socket #导入骚凯特模块 CmdObj = socket.socket(sock ...

  6. 匿名管道 远程cmd

    管道是单向的,传送数据的方向是固定的,所以互相通信需要两个管道. STARTUPINFO si; ZeroMemory(&si,sizeof(si)); si.dwFlags = STARTF ...

  7. C语言Socket-模拟远程CMD(客户端向服务器发送命令,服务器执行该命令)

    服务端(server) #include <stdio.h> #include <winsock2.h> #pragma comment(lib,"ws2_32.li ...

  8. 记一次使用cmd执行java文件遇到的坑...包括“使用java命令运行class文件提示“错误:找不到或无法加载主类“的问题”

    今天写了一个java文件,类似聊天软件的东西.在eclipse里输入输出显得没感觉,于是乎就准备在cmd里输入和显示输出.如下图,我准备运行的是ChatDemo.class文件.路径是:D:\work ...

  9. Springcloud踩坑记---使用feignclient远程调用服务404

    公司项目进行微服务改造,由之前的dubbo改用SpringCloud,微服务之间通过FeignClient进行调用,今天在测试的时候,eureka注册中心有相应的服务,但feignclient就是无法 ...

随机推荐

  1. Spring与IoC

    控制反转(IOC,Inversion of Control),是一个概念,是一种思想. 指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理.控制反转就是对对象控制权的转移 ...

  2. Spark之SparkSql

    -- Spark SQL 以编程方式指定模式 val sqlContext = new org.apache.spark.sql.SQLContext(sc) val employee = sc.te ...

  3. Electron构建一个文件浏览器应用(二)

    在前一篇文章我们已经学习到了使用Electron来构建我们的文件浏览器了基础东西了,我们之前已经完成了界面功能和显示文件或文件夹的功能了,想看之前文章,请点击这个链接  .现在我们需要在之前的基础上来 ...

  4. LR编写Socket脚本方法1(XML/16进制报文data.ws格式)

    本文主要讲述了Socket协议脚本的基础知识和编写方法,让大家能够在短时间内快速掌握简单的Socket协议脚本的编写方法.1.socket协议介绍Socket协议有万能协议之称,很多系统底层都是用的s ...

  5. Java:Web Service初入门

    前言 Web Service技术在我第一次接触,又没有实际使用时完全不理解这是什么.以为是一种类似Spring,Shiro的编程框架.后来渐渐理解,WS(即Web Service缩写)是一种通用的接口 ...

  6. mysql 正确清理binlog日志的两种方法

    前言: MySQL中的binlog日志记录了数据库中数据的变动,便于对数据的基于时间点和基于位置的恢复,但是binlog也会日渐增大,占用很大的磁盘空间,因此,要对binlog使用正确安全的方法清理掉 ...

  7. STM32 HAL库学习系列第7篇---定时器TIM 输入捕获功能

    测量脉冲宽度或者测量频率   基本方法 1.设置TIM2 CH1为输入捕获功能:  2.设置上升沿捕获:  3.使能TIM2 CH1捕获功能:  4.捕获到上升沿后,存入capture_buf[0], ...

  8. 并发编程-concurrent指南-ConcurrentMap

    ConcurrentMap 是个接口,你想要使用它的话就得使用它的实现类之一. ConcurrentMap,它是一个接口,是一个能够支持并发访问的java.util.map集合: 在原有java.ut ...

  9. Codeforces Gym100543L:Outer space invaders(区间DP)

    题目链接 题意 有n个人,每个人有一个出现时间a和一个开枪时间b和一个距离d,在任意一个时刻,你可以选择炸人,你要炸一个人的花费是和他的距离d,并且所有的已经出现并且还没开枪的和你距离<=d的人 ...

  10. c++学习书籍推荐《C++编程思想第二卷》下载

    百度云及其他网盘下载地址:点我 编辑推荐 <C++编程思想>(第2卷)是惟一一本如此清晰地阐述如何重新思考以面向对象方法构造程序的书籍.<C++编程思想>(第2卷)介绍实用的编 ...