您的位置:首页 > 编程语言 > C#

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命令码选项码
一个个的解释.

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

拒绝选项请求

选项协商:4种请求

1)WILL:发送方本身将激活选项

2)DO:发送方想叫接受端激活选项

3)WONT:发送方本身想禁止选项

4)DONT:发送方想让接受端去禁止选项

紧接着就是选项码

选项标识
名称
1
回显
3
抑制继续进行
5
状态
6
定时标记
24
终端类型
31
窗口大小
32
终端速度
33
远程流量控制
34
行方式
36
环境变量
ok,为了搞掂这个telnet链接,

我特地装了个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和写这篇日子,

希望能对看这篇文章的人产生一点帮助吧.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐