Telnet协议详解及使用C# 用Socket 编程来实现Telnet协议
2011-04-22 11:55
567 查看
转自:
http://www.cnblogs.com/jicheng1014/archive/2010/01/28/1658793.html
同步发行到atpking.com......
这因为有个任务涉及到使用telnet来连接远端的路由器,获取信息,之后进行处理.
所以需要写一个自动telnet登录到远端,之后获取信息进行处理的程序.
自己C++一塌糊涂,所以几乎最开始就没打算用C++或者C写
论自己的实力,还是走C#路线稍微稳妥一点吧,
因为telnet是使用tcp/ip协议折腾的事情
很容易的想到使用socket来实现telnet
(当然你可以在进程里启用telnet命令,只不过总觉得那样不够技术,
而且操作不自由--受限于telnet这个指令)
ok,翻协议,弄清原理,结果比预想的难度要大一些
定义
============================================================
Telnet协议是TCP/IP协议族中应用最广泛的协议。
它允许用户(Telnet客户端)通过一个协商过程来与一个远程设备进行通信。
Telnet协议是基于网络虚拟终端NVT(NetworkVirtualTermina1)的实现,
NVT是虚拟设备,连接双方(客户机和服务器)都必须把它们的物理终端和NVT进行相互转换
============================================================
大概意思就是跟远端通信的一套协议,之后这个协议无视你机器是啥型号,啥样子
只要是用telnet的,统统都可以看成是NVT
(类似面向对象中的继承关系:NVT是父类,各种实用telnet的都继承与NVT)
好处非常明显,可以无视型号而直接使用标准命令,任何服从NVT的设备都能通信
当然不可避免的,标准也同时代表着性能的损失:
由于NVT得顾及到所有的各种型号的机器,所以他定义的操作十分有限
(因为考虑到包括要支持类似9城小霸王那些性能很差,系统简单的机器),
为了解决NVT这个"为了照顾小霸王,而导致高端设备的功能不能用"的这个弊病,
Telnet琢磨出了一个比较好的解决方案
"用于扩展基本NVT功能的协议,提供了选项协商的机制"来解决问题
类似那个经典的英国绵羊笑话
使用英文描述两只绵羊在路上碰到后发生的故事
=========================
绵羊A:Hi,Sheep!
绵羊B:Hi,CanyouspeakChinese?
绵羊A:yes,"jintianchilema?(今天吃了吗?)"
绵羊B:"chila,henshuang!(吃啦,很爽!)"
.....省略500字
改卷的英国人累牛满面,因为他不会中文,
但又不能说这篇文章有问题.
=========================
这里英文就可以理解为NVT的标准功能,为通用语,
而后来的中文拼音,就是扩展.
ok,原理就是那么回事,讲讲细节吧
telnet来连接的时候,需要发送一系列的指令来协商(绵羊协商)通信,
流程图类似这个
ok,那么,具体的命令是怎样的呢?
很无趣的,
就是telnet的命令格式
一个个的解释.
IAC:命令解释符,说白了就是每条指令的前缀都得是它,固定值255(11111111B)
命令码:一系列定义:(最常用的250~254咱加粗表示)
选项协商:4种请求
1)WILL:发送方本身将激活选项
2)DO:发送方想叫接受端激活选项
3)WONT:发送方本身想禁止选项
4)DONT:发送方想让接受端去禁止选项
紧接着就是选项码
ok,为了搞掂这个telnet链接,
我特地装了个linux作为telnet的链接对象进行telnet远程登录
之后写了一个恶心的代码来帮助我进行调试
额,在写这个程序之前,我搜了将近1天时间的网路
发现大多数代码注释的不是太和谐,读起来很难理解
所以自己根据网上的一个win程序改出了一个console的程序
同时,我特地花了两天时间,几乎把每一句能写注释的都写了,
基本上可以说是我目前注释写的最多的一次代码了,
代码特别庞大,就做个窗口放上去了
效果如下:
首先,收到远程服务端的信息(第一次接)
25525324255253322552533525525339
远程服务器说
/*========================
我想要求客户端激活终端类型
我想要求客户端激活终端速度
我想要求客户端激活39功能(手册没写是啥)
=========================*/
之后,我们客户端返回以下信息(第一次发)
25525224255252322552523525525239
客户端说
/*==================================
客户端想禁止你说的所有的功能(24,32,35,39)
==================================*/
服务器收到了我们发出的信息之后,又发出以下信息(第二次接)
2552513255253125525331255251525525333
服务器又说
/*======================================
我自己将激活回抑制继续进行
我希望客户端激活回显功能
我希望客户端激活窗口大小
我自己将激活状态
我希望客户端激活远程流量控制
======================================*/
之后我们客户端返回以下信息(第二次发)
2552533255251125525231255254525525233
客户端说
/*======================================
我希望服务器端激活抑制继续进行
我自己将激活回显功能
我自己想禁止窗口大小功能
我希望服务端禁止状态功能
我自己想禁止远程流量控制
======================================*/
服务器收到我们消息之后,又给我们消息(第三次接)
25525412552511
意思为
/*=====================================
我想让客户端禁用回显
我想自己使用回显
=====================================*/
我们客户端接着发(第三次发)
25525212552531
意思为
/*=====================================
我也不想自己开启回显
同事我也觉得你开启回显很合适
=====================================*/
服务器端终于结束验证了,开始发正文显示......(第四次接)
解析过来就是
Ubuntu9.04
atpking-desktoplogin:
之后为了告诉服务器咱们收到消息了(第四次发送消息)
2552521255253125525212552531
客户端
=====================================
我的,禁止回显的设置
服务器的,请你接受回显
我自己想禁用回显
我希望服务器接受回显
======================================
登录画面
登录成功~
================================================
后记
确实通信那块很嚼人,
而且现在也还是半懂不懂的状态(不知道为什么第四次回发消息的时候,服务器就不再发消息了等)
只不过最起码的,从cocket本身模拟来说,还算能写篇blog
花了不少时间研究socket和写这篇日子,
希望能对看这篇文章的人产生一点帮助吧.
同步发行到atpking.com......
这因为有个任务涉及到使用telnet来连接远端的路由器,获取信息,之后进行处理.
所以需要写一个自动telnet登录到远端,之后获取信息进行处理的程序.
自己C++一塌糊涂,所以几乎最开始就没打算用C++或者C写
论自己的实力,还是走C#路线稍微稳妥一点吧,
因为telnet是使用tcp/ip协议折腾的事情
很容易的想到使用socket来实现telnet
(当然你可以在进程里启用telnet命令,只不过总觉得那样不够技术,
而且操作不自由--受限于telnet这个指令)
ok,翻协议,弄清原理,结果比预想的难度要大一些
定义
============================================================
Telnet协议是TCP/IP协议族中应用最广泛的协议。
它允许用户(Telnet客户端)通过一个协商过程来与一个远程设备进行通信。
Telnet协议是基于网络虚拟终端NVT(NetworkVirtualTermina1)的实现,
NVT是虚拟设备,连接双方(客户机和服务器)都必须把它们的物理终端和NVT进行相互转换
============================================================
大概意思就是跟远端通信的一套协议,之后这个协议无视你机器是啥型号,啥样子
只要是用telnet的,统统都可以看成是NVT
(类似面向对象中的继承关系:NVT是父类,各种实用telnet的都继承与NVT)
好处非常明显,可以无视型号而直接使用标准命令,任何服从NVT的设备都能通信
当然不可避免的,标准也同时代表着性能的损失:
由于NVT得顾及到所有的各种型号的机器,所以他定义的操作十分有限
(因为考虑到包括要支持类似9城小霸王那些性能很差,系统简单的机器),
为了解决NVT这个"为了照顾小霸王,而导致高端设备的功能不能用"的这个弊病,
Telnet琢磨出了一个比较好的解决方案
"用于扩展基本NVT功能的协议,提供了选项协商的机制"来解决问题
类似那个经典的英国绵羊笑话
使用英文描述两只绵羊在路上碰到后发生的故事
=========================
绵羊A:Hi,Sheep!
绵羊B:Hi,CanyouspeakChinese?
绵羊A:yes,"jintianchilema?(今天吃了吗?)"
绵羊B:"chila,henshuang!(吃啦,很爽!)"
.....省略500字
改卷的英国人累牛满面,因为他不会中文,
但又不能说这篇文章有问题.
=========================
这里英文就可以理解为NVT的标准功能,为通用语,
而后来的中文拼音,就是扩展.
ok,原理就是那么回事,讲讲细节吧
telnet来连接的时候,需要发送一系列的指令来协商(绵羊协商)通信,
流程图类似这个
ok,那么,具体的命令是怎样的呢?
很无趣的,
就是telnet的命令格式
IAC | 命令码 | 选项码 |
IAC:命令解释符,说白了就是每条指令的前缀都得是它,固定值255(11111111B)
命令码:一系列定义:(最常用的250~254咱加粗表示)
名称 | 代码(十进制) | 描述 |
EOF | 236 | 文件结束符 |
SUSP | 237 | 挂起当前进程(作业控制) |
ABORT | 238 | 异常中止进程 |
EOR | 239 | 记录结束符i |
SE | 240 | 自选项结束 |
NOP | 241 | 无操作 |
DM | 242 | 数据标记 |
BRK | 243 | 中断 |
IP | 244 | 中断进程 |
AO | 245 | 异常中止输出 |
AYT | 246 | 对方是否还在运行? |
EC | 247 | 转义字符 |
EL | 248 | 删除行 |
GA | 249 | 继续进行 |
SB | 250 | 子选项开始 |
WILL | 251 | 同意启动(enable)选项 |
WONT | 252 | 拒绝启动选项 |
DO | 253 | 认可选项请求 |
DONT | 254 | 拒绝选项请求 |
1)WILL:发送方本身将激活选项
2)DO:发送方想叫接受端激活选项
3)WONT:发送方本身想禁止选项
4)DONT:发送方想让接受端去禁止选项
紧接着就是选项码
选项标识 | 名称 |
1 | 回显 |
3 | 抑制继续进行 |
5 | 状态 |
6 | 定时标记 |
24 | 终端类型 |
31 | 窗口大小 |
32 | 终端速度 |
33 | 远程流量控制 |
34 | 行方式 |
36 | 环境变量 |
我特地装了个linux作为telnet的链接对象进行telnet远程登录
之后写了一个恶心的代码来帮助我进行调试
额,在写这个程序之前,我搜了将近1天时间的网路
发现大多数代码注释的不是太和谐,读起来很难理解
所以自己根据网上的一个win程序改出了一个console的程序
同时,我特地花了两天时间,几乎把每一句能写注释的都写了,
基本上可以说是我目前注释写的最多的一次代码了,
代码特别庞大,就做个窗口放上去了
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Collections;
namespace ConsoleApplication1
{
public
class
Program
{
#region
一些telnet的数据定义,先没看懂没关系
///<summary>
///标志符,代表是一个TELNET指令
///</summary>
readonly
CharIAC=Convert.ToChar(255);
///<summary>
///表示一方要求另一方使用,或者确认你希望另一方使用指定的选项。
///</summary>
readonly
CharDO=Convert.ToChar(253);
///<summary>
///表示一方要求另一方停止使用,或者确认你不再希望另一方使用指定的选项。
///</summary>
readonly
CharDONT=Convert.ToChar(254);
///<summary>
///表示希望开始使用或者确认所使用的是指定的选项。
///</summary>
readonly
CharWILL=Convert.ToChar(251);
///<summary>
///表示拒绝使用或者继续使用指定的选项。
///</summary>
readonly
CharWONT=Convert.ToChar(252);
///<summary>
///表示后面所跟的是对需要的选项的子谈判
///</summary>
readonly
CharSB=Convert.ToChar(250);
///<summary>
///子谈判参数的结束
///</summary>
readonly
CharSE=Convert.ToChar(240);
const
CharIS='0'
;
const
CharSEND='1'
;
const
CharINFO='2'
;
const
CharVAR='0'
;
const
CharVALUE='1'
;
const
CharESC='2'
;
const
CharUSERVAR='3'
;
///<summary>
///流
///</summary>
byte
[]m_byBuff=new
byte
[100000];
///<summary>
///收到的控制信息
///</summary>
private
ArrayListm_ListOptions=new
ArrayList();
///<summary>
///存储准备发送的信息
///</summary>
string
m_strResp;
///<summary>
///一个Socket套接字
///</summary>
private
Sockets;
#endregion
///<summary>
///主函数
///</summary>
///<paramname="args"></param>
static
void
Main(string
[]args)
{
//实例化这个对象
Programp=new
Program();
//启动socket进行telnet链接
p.doSocket();
}
///<summary>
///启动socket进行telnet操作
///</summary>
private
void
doSocket()
{
//获得链接的地址,可以是网址也可以是IP
Console.WriteLine("ServerAddress:"
);
//解析输入,如果是一个网址,则解析成ip
IPAddressimport=GetIP(Console.ReadLine());
//获得端口号
Console.WriteLine("ServerPort:"
);
int
port=int
.Parse(Console.ReadLine());
//建立一个socket对象,使用IPV4,使用流进行连接,使用tcp/ip协议
s=new
Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//获得一个链接地址对象(由IP地址和端口号构成)
IPEndPointaddress=new
IPEndPoint(import,port);
/*
*说明此socket不是处于阻止模式
*
*msdn对阻止模式的解释:
*============================================================
*如果当前处于阻止模式,并且进行了一个并不立即完成的方法调用,
*则应用程序将阻止执行,直到请求的操作完成后才解除阻止。
*如果希望在请求的操作尚未完成的情况下也可以继续执行,
*请将Blocking属性更改为false。Blocking属性对异步方法无效。
*如果当前正在异步发送和接收数据,并希望阻止执行,
*请使用ManualResetEvent类。
*============================================================
*/
s.Blocking=false
;
/*
*开始一个对远程主机连接的异步请求,
*因为Telnet使用的是TCP链接,是面向连接的,
*所以此处BeginConnect会启动一个异步请求,
*请求获得与给的address的连接
*
*此方法的第二个函数是一个类型为AsyncCallback的委托
*
*这个AsyncCallbackmsdn给出的定义如下
*===================================================================
*使用AsyncCallback委托在一个单独的线程中处理异步操作的结果。A
*syncCallback委托表示在异步操作完成时调用的回调方法。
*回调方法采用IAsyncResult参数,该参数随后可用来获取异步操作的结果。
*===================================================================
*这个方法里的委托实际上就是当异步请求有回应了之后,执行委托的方法.
*委托里的参数,实际上就是BeginConnect的第三个参数,
*此处为socket本身
*
*我比较懒,写了一个匿名委托,实际上跟AsyncCallback效果一个样.
*
*/
s.BeginConnect(
address,
delegate
(IAsyncResultar)
/*
*此处为一个匿名委托,
*实际上等于
*建立一个AsyncCallback对象,指定后在此引用一个道理
*
*ok这里的意义是,
*当远程主机连接的异步请求有响应的时候,执行以下语句
*/
{
try
{
//获得传入的对象(此处对象是BeginConnect的第三个参数)
Socketsock1=(Socket)ar.AsyncState;
/*
*如果Socket在最近操作时连接到远程资源,则为true;否则为false。
*
*以下是MSDN对Connected属性的备注信息
*=========================================================================
*Connected属性获取截止到最后的I/O操作时Socket的连接状态。
*当它返回false时,表明Socket要么从未连接,要么已断开连接。
*
*Connected属性的值反映最近操作时的连接状态。如果您需要确定连接的当前状态,
*请进行非阻止、零字节的Send调用。
*如果该调用成功返回或引发WAEWOULDBLOCK错误代码(10035),
*则该套接字仍然处于连接状态;否则,该套接字不再处于连接状态。
*=========================================================================
*/
if
(sock1.Connected)
{
AsyncCallbackrecieveData=new
AsyncCallback(OnRecievedData);
/*
*此处没再用匿名委托的原因是,
*一个匿名委托嵌套一个匿名委托,我自己思路跟不上来了...
*
*ok,这里是当Connected为true时,
*使用BeginReceive方法
*开始接收信息到m_byBuff(我们在类中定义的私有属性)
*
*以下是MSDN对BeginReceive的一些说明
*=========================================================================
*异步BeginReceive操作必须通过调用EndReceive方法来完成。
*通常,该方法由callback委托调用。此方法在操作完成前不会进入阻止状态。
*若要一直阻塞到操作完成时为止,请使用Receive方法重载中的一个。
*若要取消挂起的BeginReceive,请调用Close方法。
*==========================================================================
*
*当接收完成之后,他们就会调用OnRecievedData方法
*我在recieveData所委托的方法OnRecievedData中调用了sock.EndReceive(ar);
*/
sock1.BeginReceive(m_byBuff,0,m_byBuff.Length,SocketFlags.None,recieveData,sock1);
}
}
catch
(Exceptionex)
{
Console.WriteLine("初始化接收信息出错:"
+ex.Message);
}
},
s);
//此处是为了发送指令而不停的循环
while
(true
)
{
//发送读出的数据
DispatchMessage(Console.ReadLine());
//因为每发送一行都没有发送回车,故在此处补上
DispatchMessage("/r/n"
);
}
}
///<summary>
///当接收完成后,执行的方法(供委托使用)
///</summary>
///<paramname="ar"></param>
private
void
OnRecievedData(IAsyncResultar)
{
//从参数中获得给的socket对象
Socketsock=(Socket)ar.AsyncState;
/*
*EndReceive方法为结束挂起的异步读取
*(貌似是在之前的beginReceive收到数据之后,
*socket只是"挂起",并未结束)
*之后返回总共接收到的字流量
*
*以下是MSDN给出的EndReceive的注意事项
*=========================================================================================
*EndReceive方法完成在BeginReceive方法中启动的异步读取操作。
*
*在调用BeginReceive之前,需创建一个实现AsyncCallback委托的回调方法。
*该回调方法在单独的线程中执行并在BeginReceive返回后由系统调用。
*回调方法必须接受BeginReceive方法所返回的IAsyncResult作为参数。
*
*在回调方法中,调用IAsyncResult的AsyncState方法以获取传递给BeginReceive方法的状态对象。
*从该状态对象提取接收Socket。在获取Socket之后,可以调用EndReceive方法以成功完成读取操作,
*并返回已读取的字节数。
*
*EndReceive方法将一直阻止到有数据可用为止。
*如果您使用的是无连接协议,则EndReceive将读取传入网络缓冲区中第一个排队的可用数据报。
*如果您使用的是面向连接的协议,则EndReceive方法将读取所有可用的数据,
*直到达到BeginReceive方法的size参数所指定的字节数为止。
*如果远程主机使用Shutdown方法关闭了Socket连接,并且所有可用数据均已收到,
*则EndReceive方法将立即完成并返回零字节。
*
*若要获取接收到的数据,请调用IAsyncResult的AsyncState方法,
*然后提取所产生的状态对象中包含的缓冲区。
*
*若要取消挂起的BeginReceive,请调用Close方法。
*=========================================================================================
*/
int
nBytesRec=sock.EndReceive(ar);
//如果有接收到数据的话
if
(nBytesRec>0)
{
//将接收到的数据转个码,顺便转成string型
string
sRecieved=Encoding.GetEncoding("utf-8"
).GetString(m_byBuff,0,nBytesRec);
//声明一个字符串,用来存储解析过的字符串
string
m_strLine=""
;
//遍历Socket接收到的字符
/*
*此循环用来调整linux和windows在换行上标记的区别
*最后将调整好的字符赋予给m_strLine
*/
for
(int
i=0;i<nBytesRec;i++)
{
Charch=Convert.ToChar(m_byBuff[i]);
switch
(ch)
{
case
'/r'
:
m_strLine+=Convert.ToString("/r/n"
);
break
;
case
'/n'
:
break
;
default
:
m_strLine+=Convert.ToString(ch);
break
;
}
}
try
{
//获得转义后的字符串的长度
int
strLinelen=m_strLine.Length;
//如果长度为零
if
(strLinelen==0)
{
//则返回"/r/n"即回车换行
m_strLine=Convert.ToString("/r/n"
);
}
//建立一个流,把接收的信息(转换后的)存进mToProcess中
Byte[]mToProcess=new
Byte[strLinelen];
for
(int
i=0;i<strLinelen;i++)
mToProcess[i]=Convert.ToByte(m_strLine[i]);
//Processtheincomingdata
//对接收的信息进行处理,包括对传输过来的信息的参数的存取和
string
mOutText=ProcessOptions(mToProcess);
//解析命令后返回显示信息(即除掉了控制信息)
if
(mOutText!=""
)
Console.Write(mOutText);
//Respondtoanyincomingcommands
//接收完数据,处理完字符串数据等一系列事物之后,开始回发数据
RespondToOptions();
}
catch
(Exceptionex)
{
throw
new
Exception("接收数据的时候出错了!"
+ex.Message);
}
}
else
//如果没有接收到任何数据的话
{
//输出关闭连接
Console.WriteLine("Disconnected"
,sock.RemoteEndPoint);
//关闭socket
sock.Shutdown(SocketShutdown.Both);
sock.Close();
Console.Write("GameOver"
);
Console.ReadLine();
}
}
///<summary>
///发送数据的函数
///</summary>
private
void
RespondToOptions()
{
try
{
//声明一个字符串,来存储接收到的参数
string
strOption;
/*
*此处的控制信息参数,是之前接受到信息之后保存的
*例如25525323等等
*具体参数的含义需要去查telnet协议
*/
for
(int
i=0;i<m_ListOptions.Count;i++)
{
//获得一个控制信息参数
strOption=(string
)m_ListOptions[i];
//根据这个参数,进行处理
ArrangeReply(strOption);
}
DispatchMessage(m_strResp);
m_strResp=""
;
m_ListOptions.Clear();
}
catch
(Exceptioners)
{
Console.WriteLine("错错了,在回发数据的时候"
+ers.Message);
}
}
///<summary>
///解析接收的数据,生成最终用户看到的有效文字,同时将附带的参数存储起来
///</summary>
///<paramname="m_strLineToProcess">收到的处理后的数据</param>
///<returns></returns>
private
string
ProcessOptions(byte
[]m_strLineToProcess)
{
string
m_DISPLAYTEXT=""
;
string
m_strTemp=""
;
string
m_strOption=""
;
string
m_strNormalText=""
;
bool
bScanDone=false
;
int
ndx=0;
int
ldx=0;
char
ch;
try
{
//把数据从byte[]转化成string
for
(int
i=0;i<m_strLineToProcess.Length;i++)
{
Charss=Convert.ToChar(m_strLineToProcess[i]);
m_strTemp=m_strTemp+Convert.ToString(ss);
}
//此处意义为,当没描完数据前,执行扫描
while
(bScanDone!=true
)
{
//获得长度
int
lensmk=m_strTemp.Length;
//之后开始分析指令,因为每条指令为255开头,故可以用此来区分出每条指令
ndx=m_strTemp.IndexOf(Convert.ToString(IAC));
//此处为出错判断,本无其他含义
if
(ndx>lensmk)
ndx=m_strTemp.Length;
//此处为,如果搜寻到IAC标记的telnet指令,则执行以下步骤
if
(ndx!=-1)
{
#region
如果存在IAC标志位
//将标志位IAC的字符赋值给最终显示文字
m_DISPLAYTEXT+=m_strTemp.Substring(0,ndx);
//此处获得命令码
ch=m_strTemp[ndx+1];
//如果命令码是253(DO)254(DONT)521(WILL)252(WONT)的情况下
if
(ch==DO||ch==DONT||ch==WILL||ch==WONT)
{
//将以IAC开头3个字符组成的整个命令存储起来
m_strOption=m_strTemp.Substring(ndx,3);
m_ListOptions.Add(m_strOption);
//将标志位IAC的字符赋值给最终显示文字
m_DISPLAYTEXT+=m_strTemp.Substring(0,ndx);
//将处理过的字符串删去
string
txt=m_strTemp.Substring(ndx+3);
m_strTemp=txt;
}
//如果IAC后面又跟了个IAC(255)
else
if
(ch==IAC)
{
//则显示从输入的字符串头开始,到之前的IAC结束
m_DISPLAYTEXT=m_strTemp.Substring(0,ndx);
//之后将处理过的字符串排除出去
m_strTemp=m_strTemp.Substring(ndx+1);
}
//如果IAC后面跟的是SB(250)
else
if
(ch==SB)
{
m_DISPLAYTEXT=m_strTemp.Substring(0,ndx);
ldx=m_strTemp.IndexOf(Convert.ToString(SE));
m_strOption=m_strTemp.Substring(ndx,ldx);
m_ListOptions.Add(m_strOption);
m_strTemp=m_strTemp.Substring(ldx);
}
#endregion
}
//若字符串里已经没有IAC标志位了
else
{
//显示信息累加上m_strTemp存储的字段
m_DISPLAYTEXT=m_DISPLAYTEXT+m_strTemp;
bScanDone=true
;
}
}
//输出人看到的信息
m_strNormalText=m_DISPLAYTEXT;
}
catch
(ExceptioneP)
{
throw
new
Exception("解析传入的字符串错误:"
+eP.Message);
}
return
m_strNormalText;
}
///<summary>
///获得IP地址
///</summary>
///<paramname="import"></param>
///<returns></returns>
private
static
IPAddressGetIP(string
import)
{
IPHostEntryIPHost=Dns.GetHostEntry(import);
return
IPHost.AddressList[0];
}
#region
magicFunction
//解析传过来的参数,生成回发的数据到m_strResp
private
void
ArrangeReply(string
strOption)
{
try
{
CharVerb;
CharOption;
CharModifier;
Charch;
bool
bDefined=false
;
//排错选项,无啥意义
if
(strOption.Length<3)return
;
//获得命令码
Verb=strOption[1];
//获得选项码
Option=strOption[2];
//如果选项码为回显(1)或者是抑制继续进行(3)
if
(Option==1||Option==3)
{
bDefined=true
;
}
//设置回发消息,首先为标志位255
m_strResp+=IAC;
//如果选项码为回显(1)或者是抑制继续进行(3)==true
if
(bDefined==true
)
{
#region
继续判断
//如果命令码为253(DO)
if
(Verb==DO)
{
//我设置我应答的命令码为251(WILL)即为支持回显或抑制继续进行
ch=WILL;
m_strResp+=ch;
m_strResp+=Option;
}
//如果命令码为254(DONT)
if
(Verb==DONT)
{
//我设置我应答的命令码为252(WONT)即为我也会"拒绝启动"回显或抑制继续进行
ch=WONT;
m_strResp+=ch;
m_strResp+=Option;
}
//如果命令码为251(WILL)
if
(Verb==WILL)
{
//我设置我应答的命令码为253(DO)即为我认可你使用回显或抑制继续进行
ch=DO;
m_strResp+=ch;
m_strResp+=Option;
//break;
}
//如果接受到的命令码为251(WONT)
if
(Verb==WONT)
{
//应答我也拒绝选项请求回显或抑制继续进行
ch=DONT;
m_strResp+=ch;
m_strResp+=Option;
//break;
}
//如果接受到250(sb,标志子选项开始)
if
(Verb==SB)
{
/*
*因为启动了子标志位,命令长度扩展到了4字节,
*取最后一个标志字节为选项码
*如果这个选项码字节为1(send)
*则回发为250(SB子选项开始)+获取的第二个字节+0(is)+255(标志位IAC)+240(SE子选项结束)
*/
Modifier=strOption[3];
if
(Modifier==SEND)
{
ch=SB;
m_strResp+=ch;
m_strResp+=Option;
m_strResp+=IS;
m_strResp+=IAC;
m_strResp+=SE;
}
}
#endregion
}
else
//如果选项码不是1或者3
{
#region
底下一系列代表,无论你发那种请求,我都不干
if
(Verb==DO)
{
ch=WONT;
m_strResp+=ch;
m_strResp+=Option;
}
if
(Verb==DONT)
{
ch=WONT;
m_strResp+=ch;
m_strResp+=Option;
}
if
(Verb==WILL)
{
ch=DONT;
m_strResp+=ch;
m_strResp+=Option;
}
if
(Verb==WONT)
{
ch=DONT;
m_strResp+=ch;
m_strResp+=Option;
}
#endregion
}
}
catch
(Exceptioneeeee)
{
throw
new
Exception("解析参数时出错:"
+eeeee.Message);
}
}
///<summary>
///将信息转化成charp[]流的形式,使用socket进行发出
///发出结束之后,使用一个匿名委托,进行接收,
///之后这个委托里,又有个委托,意思是接受完了之后执行OnRecieveData方法
///
///</summary>
///<paramname="strText"></param>
void
DispatchMessage(string
strText)
{
try
{
//申请一个与字符串相当长度的char流
Byte[]smk=new
Byte[strText.Length];
for
(int
i=0;i<strText.Length;i++)
{
//解析字符串,将其存储到char流中去
Bytess=Convert.ToByte(strText[i]);
smk[i]=ss;
}
//发送char流,之后发送完毕后执行委托中的方法(此处为匿名委托)
/*MSDN对BeginSend的解释
*=======================================================================================================
*BeginSend方法可对在Connect、BeginConnect、Accept或BeginAccept方法中建立的远程主机启动异步发送操作。
*如果没有首先调用Accept、BeginAccept、Connect或BeginConnect,则BeginSend将会引发异常。
*调用BeginSend方法将使您能够在单独的执行线程中发送数据。
*您可以创建一个实现AsyncCallback委托的回调方法并将它的名称传递给BeginSend方法。
*为此,您的state参数至少必须包含用于通信的已连接或默认Socket。
*如果回调需要更多信息,则可以创建一个小型类或结构,用于保存Socket和其他所需的信息。
*通过state参数将此类的一个实例传递给BeginSend方法。
*回调方法应调用EndSend方法。
*当应用程序调用BeginSend时,系统将使用一个单独的线程来执行指定的回调方法,
*并阻止EndSend,直到Socket发送了请求的字节数或引发了异常为止。
*如果希望在调用BeginSend方法之后使原始线程阻止,请使用WaitHandle.WaitOne方法。
*当需要原始线程继续执行时,请在回调方法中调用T:System.Threading.ManualResetEvent的Set方法。
*有关编写回调方法的其他信息,请参见Callback示例。
*=======================================================================================================
*/
IAsyncResultar2=s.BeginSend(smk,0,smk.Length,SocketFlags.None,delegate
(IAsyncResultar)
{
//当执行完"发送数据"这个动作后
//获取Socket对象,对象从beginsend中的最后个参数上获得
Socketsock1=(Socket)ar.AsyncState;
if
(sock1.Connected)//如果连接还是有效
{
//这里建立一个委托
AsyncCallbackrecieveData=new
AsyncCallback(OnRecievedData);
/*
*此处为:开始接受数据(在发送完毕之后-->出自于上面的匿名委托),
*当接收完信息之后,执行OnrecieveData方法(由委托传进去),
*注意,是异步调用
*/
sock1.BeginReceive(m_byBuff,0,m_byBuff.Length,SocketFlags.None,recieveData,sock1);
}
},s);
/*
*结束异步发送
*EndSend完成在BeginSend中启动的异步发送操作。
*在调用BeginSend之前,需创建一个实现AsyncCallback委托的回调方法。
*该回调方法在单独的线程中执行并在BeginSend返回后由系统调用。
*回调方法必须接受BeginSend方法所返回的IAsyncResult作为参数。
*
*在回调方法中,调用IAsyncResult参数的AsyncState方法可以获取发送Socket。
*在获取Socket之后,则可以调用EndSend方法以成功完成发送操作,并返回发送的字节数。
*/
s.EndSend(ar2);
}
catch
(Exceptioners)
{
Console.WriteLine("出错了,在回发数据的时候:"
+ers.Message);
}
}
#endregion
}
}
效果如下:
首先,收到远程服务端的信息(第一次接)
25525324255253322552533525525339
远程服务器说
/*========================
我想要求客户端激活终端类型
我想要求客户端激活终端速度
我想要求客户端激活39功能(手册没写是啥)
=========================*/
之后,我们客户端返回以下信息(第一次发)
25525224255252322552523525525239
客户端说
/*==================================
客户端想禁止你说的所有的功能(24,32,35,39)
==================================*/
服务器收到了我们发出的信息之后,又发出以下信息(第二次接)
2552513255253125525331255251525525333
服务器又说
/*======================================
我自己将激活回抑制继续进行
我希望客户端激活回显功能
我希望客户端激活窗口大小
我自己将激活状态
我希望客户端激活远程流量控制
======================================*/
之后我们客户端返回以下信息(第二次发)
2552533255251125525231255254525525233
客户端说
/*======================================
我希望服务器端激活抑制继续进行
我自己将激活回显功能
我自己想禁止窗口大小功能
我希望服务端禁止状态功能
我自己想禁止远程流量控制
======================================*/
服务器收到我们消息之后,又给我们消息(第三次接)
25525412552511
意思为
/*=====================================
我想让客户端禁用回显
我想自己使用回显
=====================================*/
我们客户端接着发(第三次发)
25525212552531
意思为
/*=====================================
我也不想自己开启回显
同事我也觉得你开启回显很合适
=====================================*/
服务器端终于结束验证了,开始发正文显示......(第四次接)
解析过来就是
Ubuntu9.04
atpking-desktoplogin:
之后为了告诉服务器咱们收到消息了(第四次发送消息)
2552521255253125525212552531
客户端
=====================================
我的,禁止回显的设置
服务器的,请你接受回显
我自己想禁用回显
我希望服务器接受回显
======================================
登录画面
登录成功~
================================================
后记
确实通信那块很嚼人,
而且现在也还是半懂不懂的状态(不知道为什么第四次回发消息的时候,服务器就不再发消息了等)
只不过最起码的,从cocket本身模拟来说,还算能写篇blog
花了不少时间研究socket和写这篇日子,
希望能对看这篇文章的人产生一点帮助吧.
相关文章推荐
- (转载)Telnet协议详解及使用C# 用Socket 编程来实现Telnet协议
- Telnet协议详解及使用C# 用Socket 编程来实现Telnet协议
- Telnet协议详解及使用C# 用Socket 编程来实现Telnet协议 - Atpking - 博客园
- Telnet协议详解及使用C# 用Socket 编程来实现Telnet协议【转】
- Telnet协议详解及使用C# 用Socket 编程来实现Telnet协议
- Telnet协议详解及使用C# 用Socket 编程来实现Telnet协议
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- [转]在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架
- [转载]在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分 .
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)----使用方法
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)----使用方法
- C# socket编程 使用fleck轻松实现对话 https://github.com/statianzo/Fleck
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- C# socket编程 使用fleck轻松实现对话
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)----使用方法