简介
这篇文章我TelnetSocket类,这是一个System.Net.Sockets.TcpClient周围的包装,并增加了一些简单的Telnet选项协商处理。虽然一个TelnetSocket实例可单独使用(见演示),TelnetSocket的许多功能是在回应我通信的脚本引擎(将很快有其自己的文章)需求。
什么是这里介绍(当我写的另一篇文章)是几年演变的结果;原来,插座和脚本引擎结合一类,但我后来决定,可用于发动机其他类型的通信链路上执行的脚本,所以我分裂他们除了。背景
更令人侧目的,但挑战和最终奖励的事情,我必须完成我的最后一个作业的自动化远程登录到一个Unix系统,运行第三方的应用程序,浏览一个基于字符的菜单/制度的形式,输入数据,并反馈 - 仍然整齐地退出。它很快便显现出来,我需要我的代码写一个脚本,然后有一些执行的脚本。由于种种原因,我决定写我自己的脚本语言和解释。要做到这一点,我需要一个类,它会处理整个网络的通信,和TcpClient的是不够的。
我就是这样一个在C#(yay!)编写的类,提供网上搜索。我自从失去了原有的代码,我下载了,我不记得是谁写的,我不记得在那里我得到了它(它是不是在CodeProject)。我只是想至少在这里承认,即使我相信,所有的代码这里是我的,我曾帮助入门。
最近在C#论坛,有人问到Telnet到另一个系统类,所以我觉得是时候终于发布。我希望有人发现它有用(至少他自己的阶级基础)。IScriptableCommunicator
如上所述,在我的设计和实施这一类的主要关注的是能够有一个脚本引擎使用它用在Unix系统上的第三方应用程序的交互自动化。该脚本内执行一个Windows服务 - 有没有需要的用户界面,所以没有关心的是互动的过程中使用。
结果是下面的界面:public delegate void DataReceived ( string Data ) ;
public delegate void ExceptionCaught ( System.Exception Exception ) ;
public interface IScriptableCommunicator : System.IDisposable
{
void Connect ( string Host ) ;
void WriteLine ( string Data , params object[] Parameters ) ;
void Write ( string Data , params object[] Parameters ) ;
void Close () ;
System.TimeSpan ResponseTimeout { get ; set ; }
System.Text.Encoding Encoding { get ; set ; }
event DataReceived OnDataReceived ;
event ExceptionCaught OnExceptionCaught ;
}
一个类实现这个接口的一个实例,你的代码可以:设置超时(连接将中止,如果超时过期,默认为一分钟)指定编码使用[]和string(默认是ASCII字节之间进行转换)连接到Telnet服务器将数据发送到服务器接收返回数据接收发送数据时发生的异常ScriptableCommunicator
...和抽象类:{C}TelnetSocket
一旦它被剥夺其脚本职责,TelnetSocket变得相当简单。字段和构造函数
有底层的TcpClient,一个线程从套接字异步读取,和一个布尔值,让线程知道它应该停止的领域。
构造函数只是设置默认的超时和编码。
连接public sealed class TelnetSocket : PIEBALD.Types.ScriptableCommunicator
{
private System.Net.Sockets.TcpClient socket = null ;
private System.Net.Sockets.NetworkStream stream = null ;
private System.Threading.Thread reader = null ;
private bool abort = false ;
public TelnetSocket
(
)
{
this.ResponseTimeout = new System.TimeSpan ( 0 , 1 , 0 ) ;
this.Encoding = System.Text.Encoding.ASCII ;
return ;
}
}
连接两个重载执行输入验证。
真正的工作是通过DoConnect;它实例化的TcpClient,并产生了为读者线程。的WriteLine /写
的WriteLine只是增加了一个回车数据,并通过它来写。
写入到数据格式的参数,并使用选定的编码字符串转换成一个字节数组,然后处理数据写入和捕捉和例外报告。
关闭public override void
WriteLine
(
string Format
,
params object[] Parameters
)
{
this.Write ( Format + "\r" , Parameters ) ;
return ;
}
public override void
Write
(
string Format
,
params object[] Parameters
)
{
if ( this.socket == null )
{
throw ( new System.InvalidOperationException (
"The socket appears to be closed" ) ) ;
}
try
{
if ( ( Parameters != null ) && ( Parameters.Length > 0 ) )
{
Format = System.String.Format
(
Format
,
Parameters
) ;
}
byte[] data = this.Encoding.GetBytes ( Format ) ;
lock ( this.stream )
{
this.stream.Write ( data , 0 , data.Length ) ;
}
}
catch ( System.Exception err )
{
this.RaiseExceptionCaught ( err ) ;
throw ;
}
return ;
}
关闭发起一个异步读取器线程的关闭和断开的插座。
退出public override void
Close
(
)
{
this.Abort() ;
if ( this.socket != null )
{
this.stream = null ;
this.socket.Close() ;
this.socket = null ;
}
return ;
}
尝试的异步读取器线程的有序关闭,但会采取中止它,如果它不关闭在15秒内自行关闭。
读者private void
Abort
(
)
{
this.abort = true ;
if ( this.reader != null )
{
if ( this.reader.IsAlive )
{
if ( !this.reader.Join ( 15000 ) )
{
this.reader.Abort() ;
this.reader.Join ( 15000 ) ;
}
}
this.reader = null ;
}
return ;
}
读者是读者线程执行,并执行从插座上读取数据的方法。它使用的谈判类的一个实例来处理Telnet选项协商。任何非命令数据传递的OnDataReceived事件。如果信号或Abort方法,如果有超时期限内没有收到任何数据,该方法将退出。
谈判private void
Reader
(
)
{
using
(
Negotiator neg
=
new Negotiator ( this.stream )
)
{
byte[] buffer = new byte [ this.socket.ReceiveBufferSize ] ;
int bytes ;
System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch() ;
timer.Start() ;
while ( !this.abort )
{
bytes = 0 ;
lock ( this.stream )
{
if ( this.socket.Available > 0 )
{
bytes = this.stream.Read ( buffer , 0 , buffer.Length ) ;
}
}
if ( bytes > 0 )
{
timer.Reset() ;
bytes = neg.Negotiate ( buffer , bytes ) ;
if ( bytes > 0 )
{
this.RaiseDataReceived (
this.Encoding.GetString ( buffer , 0 , bytes ) ) ;
}
timer.Start() ;
}
else
{
/* Expired time limit will trigger an abort */
if ( timer.Elapsed > this.ResponseTimeout )
{
this.abort = true ;
}
else
{
System.Threading.Thread.Sleep ( 100 ) ;
}
}
}
timer.Stop() ;
}
return ;
}
谈判代表类封装的Telnet命令的谈判进程。
谈判private sealed class Negotiator : System.IDisposable
{
private System.Net.Sockets.NetworkStream stream ;
public Negotiator
(
System.Net.Sockets.NetworkStream Stream
)
{
this.stream = Stream ;
return ;
}
public void
Dispose
(
)
{
if ( this.stream != null )
{
this.stream = null ;
}
return ;
}
}
Negotiate方法检测并提取Telnet命令,从接收到的数据(和转移任何以下数据)。这个过程的实现是基于别人的工作,但我精简显著。我没有对这个问题的专家,我所知道的最重要的是,我从原来的代码,我下载了解到。
基本上,有许多选项,可以在Telnet会话期间使用的主机和客户端必须协商选项将被用于在一个特定的会话。简单的形式是LT; IACgt; LT; actiongt; LT; optiongt;例如:LT; IACgt; LT; WILLgt,LT; ECHOgt;还有一个扩展形式,我不会在这里讨论。
为这一类的选项要求很简单:不提供任何选项,不要求任何选项,拒绝执行任何选择,如果要求,拒绝任何其他选项,比回声抑制反超。 Telnet命令,你可以找到一些信息:{A1}]{A2}]{A3}]
这里使用的基本算法是:迭代跨传入字节假设一个国际综艺合家欢"(255字节)是两个或三个字节的Telnet命令和处理:如果两个IACS在一起,他们代表一个数据字节255忽略反超命令响应WONT所有的DOS和DONTsDONT响应所有WONTs响应,为会响应并抑制反超DONT所有其他遗嘱任何其他字节的数据;忽略空值,并转移必要的休息返回的字节,取出后,Telnet命令,而忽略空值保持
使用代码public int
Negotiate
(
byte[] Buffer
,
int Count
)
{
int resplen = 0 ;
int index = 0 ;
while ( index < Count )
{
if ( Buffer [ index ] == TelnetByte.IAC )
{
try
{
switch ( Buffer [ index + 1 ] )
{
/* If two IACs are together they represent one data byte 255 */
case TelnetByte.IAC :
{
Buffer [ resplen++ ] = Buffer [ index ] ;
index += 2 ;
break ;
}
/* Ignore the Go-Ahead command */
case TelnetByte.GA :
{
index += 2 ;
break ;
}
/* Respond WONT to all DOs and DONTs */
case TelnetByte.DO :
case TelnetByte.DONT :
{
Buffer [ index + 1 ] = TelnetByte.WONT ;
lock ( this.stream )
{
this.stream.Write ( Buffer , index , 3 ) ;
}
index += 3 ;
break ;
}
/* Respond DONT to all WONTs */
case TelnetByte.WONT :
{
Buffer [ index + 1 ] = TelnetByte.DONT ;
lock ( this.stream )
{
this.stream.Write ( Buffer , index , 3 ) ;
}
index += 3 ;
break ;
}
/* Respond DO to WILL ECHO and WILL SUPPRESS GO-AHEAD */
/* Respond DONT to all other WILLs */
case TelnetByte.WILL :
{
byte action = TelnetByte.DONT ;
if ( Buffer [ index + 2 ] == TelnetByte.ECHO )
{
action = TelnetByte.DO ;
}
else if ( Buffer [ index + 2 ] == TelnetByte.SUPP )
{
action = TelnetByte.DO ;
}
Buffer [ index + 1 ] = action ;
lock ( this.stream )
{
this.stream.Write ( Buffer , index , 3 ) ;
}
index += 3 ;
break ;
}
}
}
catch ( System.IndexOutOfRangeException )
{
/* If there aren't enough bytes to form a command, terminate the loop */
index = Count ;
}
}
else
{
if ( Buffer [ index ] != 0 )
{
Buffer [ resplen++ ] = Buffer [ index ] ;
}
index++ ;
}
}
return ( resplen ) ;
}
该zip文件包含ScriptableCommunicator.cs,TelnetSocket.cs,TelnetSocketDemo.cs。演示可以用下面的命令编译:csc TelnetSocketDemo.cs TelnetSocket.cs ScriptableCommunicator.cs
提示在封闭的版本中都设置了我的一个AlphaServers沟通。如果你有一个Telnet服务器方便,你可以改变的演示程序,与之相匹配的,编译它,并尝试一下。
通知,关于如何在演示中使用的TelnetSocket是重要的事情是多么的乏味是写一个程序,将只是一个类似这样的代码开始看起来几乎像一个简单的类的Telnet主机交互脚本。然后,您可以看到一个全面的脚本可以为您节省了不少麻烦,并为您提供了很大的灵活性如何从硬编码的命令飞跃。TelnetSocketDemo
这里的演示程序,它是非常简单,它是用于测试和演示目的只打算。
TelnetSocket和基本能力,等待某些文本在主机的响应到达之前发送下一个命令的事件的处理程序。实例化一个TelnetSocket,附加的处理程序,连接,交互,并断开。
相互作用的形式为:等待提示,发送一些数据,重复。namespace TelnetSockectDemo
{
public static class TelnetSocketDemo
{
private static readonly System.Text.StringBuilder response =
new System.Text.StringBuilder() ;
private static void
DataReceived
(
string Data
)
{
lock ( response )
{
response.Append ( Data ) ;
}
return ;
}
private static void
ExceptionCaught
(
System.Exception Exception
)
{
throw ( Exception ) ;
}
private static void
WaitFor
(
string Prompt
)
{
while ( response.ToString().IndexOf ( Prompt ) == -1 )
{
System.Threading.Thread.Sleep ( 100 ) ;
}
lock ( response )
{
System.Console.Write ( response ) ;
response.Length = 0 ;
}
return ;
}
[System.STAThreadAttribute()]
public static int
Main
(
string[] args
)
{
int result = 0 ;
try
{
if ( args.Length > 3 )
{
using
(
PIEBALD.Types.TelnetSocket socket
=
new PIEBALD.Types.TelnetSocket()
)
{
socket.OnDataReceived += DataReceived ;
socket.OnExceptionCaught += ExceptionCaught ;
socket.Connect ( args [ 0 ] ) ;
WaitFor ( "Username:" ) ;
socket.WriteLine ( args [ 1 ] ) ;
WaitFor ( "Password:" ) ;
socket.WriteLine ( args [ 2 ] ) ;
WaitFor ( "mJB>" ) ;
for ( int i = 3 ; i < args.Length ; i++ )
{
socket.WriteLine ( args [ i ] ) ;
WaitFor ( "mJB>" ) ;
}
socket.WriteLine ( "lo" ) ;
WaitFor ( "logged out" ) ;
socket.Close() ;
}
}
else
{
System.Console.WriteLine ( "Syntax: TelnetSocketDemo" +
" host username password command..." ) ;
}
}
catch ( System.Exception err )
{
System.Console.WriteLine ( err ) ;
}
return ( result ) ;
}
}
}
硬编码的提示是这样的实用工具的作用是一个限制因素。另一个限制因素是缺乏的分行或其他变量的情况下作出反应的能力。对于这些和其他原因,我认为有必要开发一种脚本语言,我应该有一个书面不久的文章。历史2010-03-03:首先提交2010-04-22:更新对代码进行更改,同时发展ProcessCommunicator| PIEBALDconsult