admin 管理员组文章数量: 1086019
2024年3月18日发(作者:如何快速学会plc编程)
1|l ’“ “ ’ 『I『 1 _’ Il
实用第一 智.t彗l2j ̄,密集
rP …
.
NET网络编程技术原理与开发实例
明延堂
摘 要:简述了,NET架构的基本网络编程原理,详细介绍了一个即时消息通信系统的完整的构建
过程。可以从中获得一些面向对象编程封装网络功能、编写自定义网络通信协议、多线程网络监
听技术、自定义网络消息的流转等C≠≠网络开发经验。
关键词:套接字;网络流;即时通信
1 基本原理
扼要地介绍.NET架构中关于套接字、网络编码等底层网
络编程技术原理。将通过完整的C样实例演示如何利用这些技
术进行底层网络编程。
端成功连接后,就可以使用Send、Receive等方法进行数据传
输。当使用完Socket之后,可以使用ShutDown方法使之失效
或用Close方法关闭Socket。
Socket类的基本构造函数包含3个参数类型:AddressFamily、
SocketType及ProtocolType。AddressFamily枚举用于设置Socket
实例使用的地址类型.最常用的是InterNetwork和
InternetworkV6,前者代表IPv4地址,后者代表IPv6地址。
1.1 套接字
套接字是网络通信的基石,是支持TCPflP协议的网络通
信的基本操作单元。可以将Socket看作不同主机间的进程进行
SocketType枚举定义了Socket实例的类型,最常用的是Dgram
和Stream.前者用于UDP协议f数据报格式、非连接性、不可
双向通信的端点,它构成了单个主机内及整个网络问的编程界
面。Socket存在于通信域f为了处理一般的线程通过Socket通
信而引入的一种抽象概念)中。Socket通常和同一个域中的
Socket交换数据。当然,数据交换也可以穿越域的界限,但需
要执行某种特定的解释程序。各种进程在这个域中相互之间利
靠数据传输1,后者用于TCP协议(可靠的双向的有保证的连
接、不保证消息边界、与一个对等终端连接1。ProtocolType枚
举规定了Socket实例所使用的协议,最常用的是TCP和UDP。
Socket实际上是平台无关的,它支持多种地址集、多种Socket
Hj Interact协议簇来进行通信。
Socket有两种不同的类型:流Socket和数据报Socket 应
类型、多种传输层协议。一般网络编程中,最常用的是基于
InterNetwork地址集中的Dgram类型的UDP协议方式和Stream
用程序一般在同一类型Socket之间进行通信。当然,只要底层
的通信协议允许,不同类型的Socket之间也可以通信。
要通过互联网进行通信,至少需要一对Socket,一个运行
于客户端(Client Socket1,另一个运行于服务器端(Server
类型的TCP协议方式。
1.2 网络编程
Socket不仅有阻塞与非阻塞之分.还允许同步或异步方
式进行操作。
Client)。根据连接启动的方式以及本地Socket要连接的目标,
Socket之间的连接过程可以分为3个步骤:
所谓同步方式,就是发送方发送数据分组之后,无需等待
接收方响应。就接着发送下一个数据分组。异步方式是指当发
送方发送一个数据分组之后,一直等待接收方响应后,才接着
发送下一个数据分组。
同步Socket是指在客户端或服务器上执行连接、发送、接
收等操作时.服务器或客户端将会暂停工作。这种编程方式简
(1)服务器监听:Server Socket处于等待连接的状态,实
时监控网络状态,并不定位于具体的客户端
(2)客户端请求:Client Socket发起连接请求,要连接的
目标是Server Socket。为此,它首先需要描述目标Socket(网
络地址及端[_=1号)
(3)连接确认,是指当Server Socket监听到Client Socket
单,适用于简单网络程序。但在高频度的网络操作应用中,就
需要使用异步通信机制。对于服务器端,在同步操作过程中,
』、,一个Socket处于Listen状态时.它可以调用Accept方法来接
的连接请求,它就启动一个新的线程,将自身属性发送到客户
端。客户端一旦确认此属性,Socket连接就可以建立。
.
收远程客户端的连接请求,而在连接请求到来之前,它一直运
行在等待状态。当连接请求到来之后,该方法会返回一个与客
户端相连接的Socket。显然,这种模式降低了网络的负载压
力。因为服务器程序有一个同步Socket在侦听时,程序就处
NET架构提供了一个功能强大的套接字接口:Socket.用
于创建一个受控版本的网络传输服务。在Socket创建之后,就
可以将其与一个地址及端口绑定起来。通过Connect方法与终
56 电 品与雏
N盯WORK&C0硼啊U_lC盯iON” … … … …… … - … -… 一… u … - ”……… …
于暂停状态,无法再建立一个同步的Socket侦听或进行其他
操作。对于网络操作负荷较大的程序,此时就需要异步Socket
操作。
异步Socket可以在侦听的同时进行其他操作。对于服务器
端的异步Socket来说.它需要一个方法开始接受网络连接请
求;一个回调函数用于处理连接请求并从网络中接收数据;一
个回调函数用于终止接收数据。异步Socket使用BeginAccept
方法接收新的连接请求。它有两个参数:一个是AsyncCallback
类型的委托。一个是object类型的传送状态信息。当一个新的
连接请求到来时.BeginAccept方法中的回调函数就会被调用,
负责获取连接的Socket实例,并把它交给处理请求的线程。异
步Socket使用系统线程池来处理连接,一个线程负责处理连
接.另一个线程负责接收连接,还有一个线程用于数据传输。
线程之间可以使用System.Threading.ManualResetEvent来终止主
线程或在运行时通知主线程。
阻塞套接字是指调用此Socket对象执行网络操作时.直
到调用成功才返回,否则此对象就一直阻塞此操作上。而非阻
塞套接字是指在调用此Scoket对象执行网络操作时,不管是否
执行成功.都立即返回 比如,调用StreamReader类的
ReadLine方法读取网络缓冲区中的数据,对于阻塞方式,如果
调用的时候数据没有到达,则此方法一直挂在调用状态,直到
成功读取数据,此函数才返回;对于非阻塞方式,不管数据是
否到达,都立即返回,而不会一直挂在此函数调用上。
在Windows网络通信软件开发技术中,异步非阻塞套接
字,比如常见的C/S软件结构就是这种类型。在C#网络编程
中,并不需要深入理解同步、异步、阻塞和非阻塞的原理和机
制.因为.NET架构已经将它们完美地封装了起来。
1.3 网络流
在网络通信中,流是~个用于传输数据的对象。数据传输
有两个方向:o1如果数据从外部源传输到应用程序中,则为
读取流;o2如果数据从应用程序传输到外部源中,即为写人
流。其中,外部源可以是文件、网络、管道或内存区域。流对
外部数据源不做任何假设。通常.应尽可能地使用一个独立的
对象来传输数据,这样可以将数据传输过程和数据源分离,更
容易切换数据源。
对于这些流的操作,.NET框架提供了基类System.IO.
MemoryStream读写内存数据:基类System.Net.Sockets.Network
Stream处理网络数据:管道数据的读写没有相应的流基类,需
要开发人员编写一个继承自System.IO.Stream的自定义类。
在.NET网络编程中NetworkStream是经常用到的类,它实
现了标准的流机制。它主要提供了以下功能:o1一组统一的
读写网络数据的API;o2兼容其他.NET流,便于移植;o3
数据实时处理.避免应用程序被动等待数据。NetworkStream
的操作源是网络,所以它不支持随机访问。它的CanSeek属性
值永远是False。读取Position属性或调用Seek方法会返回一
个NotSupp0rtedExcepti0n异常。
对于更高层的操作,例如基于HTFP协议的操作.可以不
使用Socket来构建NetworkStream对象。例如,在基于浏览器
的网络应用中.可以使用WebRequest对象实例中的GetRequest
Stream发送数据报文头部并返回相应的流对象,然后就可以调
用BeginWrite、Write、EndWrite方法进行数据发送:可以使用
WebResponse对象实例的GetResponseStream方法获取网络流,
然后调用BeginRead、Read、EndRead接收数据。
除了使用Read、Write方法,还可以使用StreamReader、
StreamWriter等方法进行数据读写。这些对象包含了流数据的
编码和解码等内容,操作更方便。
1.4 IP地址与DNS解析
System.Net空间提供了一些与网络基本操作息息相关的类。
比较重要的是IPAddress、Dns、IPHostEntry、IPEndEntry。
IPAddress类是一个描述IP地址的类。它的静态方法Parse
及TryParse可以将一个IP地址字符串实例化为一个IPAddress
对象。Dns类是一个提供有关域名解析操作的静态类。它将从
网络域名系统中获取IP地址、主机名以及域名的对应关系,
并将这些信息保存在一个IPHostEntry对象中。IPHostEntry的
主要属性成员有AddressList、Aliases和HostName分别保存主
机的IP地址、别名和域名。IPEndPoint类表示一个连接端点,
即IP地址加上端口号构成的一个绑定。
1.5网络编码与解码
由于使用多种编程语言开发的应用程序及多种支撑平台在
网络这个混合体上运行,并且网络中有多种编码方法:ASCII、
Unicode、U rF7、uTF8、Big—Endian等,因此网络编程中经常
要遇到编码、解码的操作。这种现状需要一种将系统内码转换
成网络编码的机制,以便应用程序能够准确地读写数据。.NET
架构的System.Text.Encoding类提供这种功能。
Encoding类提供了字符串、Unicode字符集和相应编码的
字节数组之间的转换(转换的数据必须连续)。事实上,应用程
序一般使用Encoder和Decoder来进行转换。Encoding类包含
常用编码的实例。开发者还可以在Encoder和Decoder的基础
上编写自定义的编码和解码类来处理大量的数据转换工作。
2基于TCP的C/S结构实时通信系统的实现
2.1 系统分析
即时通信系统的总体目标是为企业用户提供一个简单便
利、低成本的即时通信工具。通过与相关人员进行充分沟通,
确定本系统至少需要具备客户端与服务器端两个通信模块。各
(1)客户端:客户端是聊天用户使用的主要工具,它提供
如下功能。
个模块的功能特性如下:
NETWORK&CD啊啊Ⅱ一II 盯ION
PrismConnection直接派生自Component类。限于篇幅,下
面从几个侧面介绍该组件的具体实现。
2.3.1.1开启到服务器端的连接
要开肩新的连接.只需要设置PrismConnection组件的
Active属性为True即可激活它,并触发一系列事件。在Active
内部定义中.它首先创建一个TcpClient实例,并连接到由
Host和Port指定的对象,进而将该TcpClient实例作为参数构
建一个新的PrismNetworkStream类。该属性还开启了一个单独
的后台线程。下面的代码定义了Active属性:
public bool Active
{
get{return_active;)
set
{
if(
_
active!=value)
f
if《value)
(
_
clientSocket=new TcpClient0;
一
clientSocket.Connect(Host,Port);
_
active=true;
一
stream=new PrismNetworkStream(_clientSocket);
一
readerThread=new Thread(ExecuteReaderThread);
一
readerThread.IsBackground=true;
——
readerThread.Start();
)
else
{
trv
{
——
stream.Close();
)
catch
(】
active=value;
Ioggedln=false;
一
.f(Disconnected!=nul1)
DiscOnnected“his,new EventArgs0);
)
)
)
)
2.3.1.2后台线程读取通信命令
Avtive中的后台线程执行ExecuteReaderThread方法来侦
听来自NetworkStream中的数据,并对命令进行解析。下面的
代码实现了该核心方法:
private void ExecuteReaderThread()
{
List<string>tokenList=new List<string>0;
string command;
stnng s:
PrismUser user;
while(Active)
{
trV
(
command=
一
stream.ReadTokens(tokenList)
)
catch
{
Active=false;break;
)
switch(command)
{
case”PI NG”:
WriteTokens( PING“)
break;
case“LOGINERROR“:
if(LoginError l=nul1)
LoginError(this,new MessageEventArgs(tokenList【0】)):
break;
case“LOGlNOK”:
一
user=new Prism User0;
string Token=tokenList[0];
——
user.FromString(ref Token);
一
Ioggedln=true;
if(LoginOK!=nul1)
LoginOK(this,new EventArgs0);
.f(RoomAdded!:nul1)
{
string Rooms=tokenList[1】:
while(Rooms!=””)
{
S=Tokenizer.GetToken(ref Rooms);
RoomAdded(this,new RoomNameEventArgs(s));
)
)
break;
case‘‘ERROR ‘ :
if(P rismErrOr!=nul1)
PrjsmErr0r(this,new MessageEventArgs(tokenList[O]));
break;
case JOINROOM :
—
userList。Clear();
~
roomName=tokenList[0];
if(JoinedRoom l=nul1)
JoinedRoom(this,new RoomNameEventArgs(_roomName))
string UserString=tokenList[1】:
while(UserString!=“ )
{
S=Tokenizer.GetToken(ref UserString);
user=new PrismUser0;
-确器 59—
实用第一智慧密集
user.Fr0mS ng(ref s):
.f(user.UserName=:ThisUser.UserName)
{
S=user.ToString0;
ThisUser.FromString(ref s):
user=ThisUser;
)
——
userList.Add(user);
_f(UserAddedToRoom!=nul1)
UserAddedToRoom(this,new PrismUserEventArgs(user));
}
break;
case‘‘ENTERROOM”:
S=tokenList[0];
user=new PrismUserO;
user.FromSt g(ref s):
——
userList.Add(user);
.f(UserAddedToRoom!=nul1)
UserAddedToRoom(this,New PrismUserEventArgs(user));
break;
case”LEAVEROOM“:
user:FindUser(tokenList{O1):
if(user!=nul1)
{
.f(UserLeftRoom!=nul1)
UserLeftRoom(this,new PrismUserEventArgs(user))
—
userList Remove(user);
)
break;
case“ROOMCOUNTCHANGE“:
if(RoomCountChanged!=nul1)
{
string roomName=tokenList[0];
int count=Int32.Parse{tokenList【1】):
RoomCountChanged(this,new RoomCountEventArgs
《roomName.count));
}
break;
case”STAR丁”:
if(StartSigna r!=nul1)
StartSignal(this,new EventArgs0);
break;
case”ROOMREMOVED”:
if(RoomRemoved!=nuI『)
RoomRemoved(this,new RoomNameEventArgs
ltOkenList【0
break;
case”ROOMADDED“:
if(RoomAdded!=nul1)
RoomAdded(this,new RoomNameEventArgs(tokenList[0]));
break;
case“CHAT“:
user=FindUser(tokenList【0】):
if(user!=nul1)
if(ChatMessageReceived!=nul1)
ChatMessageReceived(this.
new PrismUserMessageEventArgs(user,tokenList[1】))=
break;
case“DA-『_A“:
user=FindUser(tokenList[0】):
if(user!=nul1)
if(DataMessageReceived!:nul1)
DataMessageReceived(this,
new PrismUserMessageEventArgs(user,tokenList[1】))=
break;
case”USERINFOCHANGE”:
S=tokenList[0];
user:new PrismUser0;
user.FrOmSt g(ref s):
PrismUser matchingUser=FindUser(user.UserName);
if(matchingUser!=nul1)
{
S=tokenList[0];
matchingUser.FrOmS ng(ref s);
.f(UserInfoChanged!:nul1)
UserInfOChanged (this,new PrismUserEventArgs
(matchingUser)):
)
break;
case“SERVERSTATS“:
jf{ServerStatsReceived!=nul1)
(
string StatString=tokenList[O];
PrismServerStats stats=new PrismServerStats(ref
StatString);
ServerStatsReceived Ithis,new PrismServerStatsEventArgs
(stats));
)
break;
case”ADMINMSG”:
if(AdminMessageReceived!=null1
AdminMessageReceived(this,new
MessageEVentArgs(tokenList10】)):
break;
case“CUSTOM”:
if(CustomCommandReceived!:nul『)
CustomCommandReceived(this,
new CustomCommandEventArgs{tokenList
【0】,tokenList[1
break;
case“LATENCY“:
string userName=tokenList[0];
int latency:Int32.Parse(tokenList[1 1):
Iock(userList)
…….
NETWORK&e0啊MUNIO盯ION
foreach(PrismUser U in userList)
jf fU.UserName==userName)
{
u.LatencyMS=latency;
;f fUserLatencyUpdated!=nul1)
UserLatencyUpdated(this.new
PrismUserEventArgs(u)):
break;
)
break;
)
Thread.Sleep(1 O):
}
)
2.3.1.3客户端基于服务器不同命令的处理事件
ExecuteReaderThread方法将会触发多种事件,允许客户端
根据接收到的命令执行不同的事件。这些事件允许客户端应用程
序进行用户界面的信息更新。下面的代码定义了各种事件句柄:
public event EventHandler<EventArgs>Disconnected;
public event EventHandler<EventArgs>LoginOK;
public event EventHandler<MessageEventArgs>LoginError;
public event EventHandler<MessageEventArgs>PrismErrOr:
public event EventHandler<PrismUserEventArgs >
UserAddedToRoom;
public event EventHandler<PrismUserEventArgs >
UserLeftRoom;
public event EventHand Jer<RoomNameEventArgs >
JoinedRoom;
public event EventHandIer<EventArgs>StartSignal;
public event EventHandler<RoomNameEventArgs>
RoomRemoved;
public event EventHandler<RoomNameEventArgs>
RoomAdded;
public event EventHandler<PrismUserMessageEventArgs>
ChatMessageReceived;
public event EventHandler<PrismUserMessageEventArgs>
DataMessageReceived;
public event EventHandler<PrismUserEventArgs >
UserlnfoChanged;
public event EventHandler<PrismServerStatsEventArgs>
ServerStatsReceived;
public event EventHandler<MessageEventArgs >
AdminMessaqeReceived:
public event EventHandler<CustomCommandEventArgs>
CustomCommandReceived;
public event EventHandler<PrismUserEventArgs >
UserLatencyUpdated:
public event EventHandler<RoomCountEventArgs >
RoomCountChanged;
2.3.1.4向客户端发送指令
除了从服务器端读取数据外,PrismConnection类也定义了
向服务器发送指令的若干方法。这种命令收发遵循一定的规
范。客户端(PrismConnection组件)和服务器端组件
fPrismServer组件)都使用一个PrismNetworkStream类对象来管
理从连接的Socket读取和写入数据。PrismNetworkStream负责
从底层的Socket读取和写入原始字节,PrismServer使用一个基
于文本的传输协议在多个客户端之间传递格式化的消息。下面
的代码实现PrismConneetion对象调用WriteTokens向服务器端
发送各种指令:
public void Login(string userName,string password)
(
WriteTokens ( “LOGIN“, userName,password,
SubjectName):
}
public void LoginNew(PrismUser newUser)
(
WriteTokens (”LOGfNNEW“, newUser.ToString(),
SubjectName);
)
public void EnterRoom(string roomName)
(
WriteTokens(“JOINR0OM“.roomName);
)
public void CreateRoom(string roomName,int maxUsers)
{
WnteTokens( CREATEROOM“,roomName,maxUsers);
}
public void SendChat(string chatText)
{
WriteTokens(”CHAT¨ chatText);
J
public void SendData(s ng dataText)
{
WriteTokens(”DATA“,dataText);
}
public void SaveUserlnfo(PrismUser user)
{
WriteTokens(”SAVEUSER“.user.ToString0);
)
public void ServerStats()
(
WriteTokens(“SERVERSTATS”):
)
public void CustomCommand(string CommandName,string
Params)
{
WriteTokens(“CUSTOM“,CommandName,Params);
}
private void WriteTokens(params object[】tokens)
{
if(ActiveJ
{
蕾嫡扭2:匠0圬13与.囊19 61
… 』 f EI Iit … . 一 ,J mi J I・ 一fI_14 J_’ J ‘ti n
实用第一 智慧密集
. 。 , … .. ,. …,… .. …. … . …… … .
trV
{
一
stream.WriteT0kens(t0kens):
}
catch
{
Active=false;
}
)
)
2.3.2使用PrismServer组件封装服务器端操作
与PrismConnection封装客户端功能相对应。PrismServer组
件封装了服务器端的功能。它是即时通信服务器应用程序的基
础,封装了多线程环境中的服务器端Socket、管理各种连接、
处理消息传递。
PrismServer组件提供了很多的属性来控制服务器的行为。
1)Port:指定服务器用于侦听连接的端口号。
2)LobbyName:新的客户端将被添加到默认的聊天室。
3)ProhibitSameIP:值为True时,阻止来自相同IP的多
个连接。
4)ProhibitSameUsername:值为True时,阻止来自相同用
户名称的多次登录。
5)Pinglnterval:控制服务器测试客户端连接的时间间隔。
6)Implementation:必须设置为派生自抽象基类PrismServ
erImplementation类的实例。
PrismServer组件也提供了一些方法,允许管理员控制客
户端与服务器的交互行为。
PrismServer组件直接派生自Component类。下面从几个
侧面介绍它的具体实现。
2.3-2.1激活服务器
PrismServer也定义了一个Active属性。当Active属性被
设置为ture时。将首先判断其Implementation属性是否被设置
成一个派生自PrismServerlmplementation的实例。该实例提供
了一个接口用于用户管理.允许服务器使用一个本地文件、数
据库或其他存储机制来保存用户注册信息。在该属性定义中,
还创建了一个新的TcpListener实例,用以侦听客户连接.接
受请求.然后创建Socket类或TcpClient类的实例与客户端进
行通信。如果Active属性设为false,则停止TcpListener的侦
听,并关闭所有的客户端请求。下面的代码定义了PrismServer
类的Active属性:
public bool Active
{
get
{
return
—
active;
)
62 r 涵20 1幢3.醢19岛
与雄訇 蛾
set
{ 。。
if(_active!=value)
{
if(value)
{
if(Implementation==nul1)
throw new PrismServerException(”不能激活服务器!”)
一
listener=new TcpListener(IPAddress.Any,Port);
——
listener.Start();
_
activated:DateTime.Now;
一
listenThread:new Thread(ExecuteListenerThread);
一
listenThread.IsBackground=true;
—
1istenThread.Start();
一
active=true;
)
else
{
一
active:false;
—
1istener.Stop();
for(int i=Guests.Count一1:i>=0:i一一)
Guests[i].Stream.Close();
Guests.Clear();
)
)
}
)
2.3.2.2后台线程获取客户端连接
当PrismServer激活后.后台线程将执行ExecuteListener
Thread方法,循环获取客户端连接,直到PrismServer的Active
属性值变为false为止。期间PrismServer会在一定的时间间隔
内检查客户端的连接状态。如果没有任何响应,则从用户列表
中移除客户。在该方法中调用了一个线程方法ExecuteClientSoc
ketThread.它接受一个PrismUser实例参数,表示与服务器端
进行通信的客户。该方法读取来自客户端的各种Prism协议命
令,然后进行相应的处理。下面的代码实现了ExecuteListencr
Thread方法:
private void ExecuteListenerThread()
{
intCycles=0:
while(Active)
{
while(_listener.Pending())
f
TcpClient clientSocket;
clientSocket=
一
listener.AcceptTcpClientO;
PrismNetworkStream pns=new PrismNetworkStream
(c¨entS0cket):
PrismGuest newGuest=new PrismGuest(this,pns,
c JientSOcket):
if(ProhibitSamelP)
{
lock(一guestList)
f
foreach(PrismGuest guest in Guests)
if(guest.IPAddress.ToString 0==
newGuest.IPAddress.ToString0)
{
if(GuestDisconnectedSamelP!=nul1)
GuestDisconnectedSamelP(this.
new PrismGuestEventArgs(newGuest));
newGuest.WriteTokens(”ERROR ,“统一
lP不能发起多个连接!“):
pns Close();
break;
)
)
)
if(newGuest.Stream.WasCIosed)
continue;
lock(一guestList)
{
guestList.Add(newGuest);
cOnnect Ons++:
if(_guestList.Count>一maxGuestC0unt)
一
n1axGuestCOunt=
一
guestList.Count;
)
jf(GuestCOnnected!:nul1)
GuestCOnnected(this,new PrismGuestEventArgs
(newGuest));
Thread thrdClient=new Thread(ExecuteCIientSOcket
Thread);
thrdClient.IsBackground=true;
thrdClient.Start(newGuest);
}
Thread.Sleep(1 O):
Cycles++;
if(Cycles>=
pingCycles)
{
if(Pinging!=nul1)
Pinging(this,new EventArgs0);
Cycles:O:
lock(一guestList)
{
for《int i=Guests.Count一1:i>:O:i一一)
f
PrismGuest g=Guests[i];
if(g.WaitingForPing)
RemoveGuest(g);
else
g.Ping();
)
)
)
}
}
2.3.3使用PrismNetworkStream组件封装网络流操作
本实时通信系统使用TCP协议保持应用的连通性。
PrismNetworkStream类封装了NetworkStream类,并实现了一些
自定义的方法,从网络流中读写数据。PrismNetworkStream类
还定义了一些属性用于信息统计。
2.3.3.1网络流的初始化
为了初始化网络流对象,需要在PrismNetworkStream类的
构造甬数中传递一个TcpClient实例,然后从该实例中获取
NetworkStream对象实例进行操作。下面的代码实现网络流的
初始化:
public PrismNetworkStream(TcpClient client)
(
一
client=client;
stream=client.GetStream0;
}
2.3.3.2读写Prism协议命令及消息
PrismNetworkStream类采用两个方法ReadTokens和
WriteTokens用于读写指定的Prism协议命令。WriteTokens方
法首先调用Tokenizer类的Tokenize方法,将多个参数合并为
El1分隔标记组成的字符串,然后调用WriteString方法写入到网
络流中。ReadTokens则执行相反的过程,对来自网络流的字符
串进行分解,并返回命令字符串。ReadTokens和WriteTokens
是PrismNetworkStream类的核心方法,它们的实现由另外两个
核心方法ReadString和WriteString方法支撑。下面的代码实现
了这些方法:
public void WriteTekens(params object[】tokens)
{
string S=Tokenizer.TOkenize《tokens)
WriteString(s);
)
public string ReadTokens(Ljst<st ng>stringList)
{
string CommandString=““:
stringList.Clear();
string S=ReadStringO;
if(s.Length>0)
CommandString=Tokenizer.GetToken(ref S)
while(s.Length>0)
stringList.Add(Tokenizer.GetToken(ref s)):
return CommandString;
}
private string ReadString()
{
string ReturnString=“”:
脑螭
器 63
实用第 一 智 慧密集
int Pos;
int BytesRead;
do
{
if(一desiredMessagelength>0)
{
f(一incomingMessage.Length>=一desiredMessagelength)
{
ReturnString:
一
incomingMessage.Substring
(0,一desiredMessagelength);
一
incomingMessage=
一
incomingMessage.Substring
(_desiredMessagelength,一incomingMessage.Length一
_desiredMessagelength);
_desiredMessagelength:0:
)
else
{
BytesRead=
一
stream.Read(一buffer,0,BufferSize);
if《BytesRead==0)
Thread.Sleep(1 O):
else
—
incomingMessage=
一
incomingMessage+
Encoding.Default.GetString(——buffer,0,BytesRead);
)
)
else
(
Pos:
incomingMessage.IndexOf( l ):
if(Pos>0)
(
一
desiredMessagelength=Int32.Parse
(
incomingMessage.Substring(O,Pos)):
一
incomingMessage=
incomingMessage.Substring
(Pos+1,一incomingMessage.Length—Pos一1)
)
else
{
BytesRead=
一
stream.Read(一buffer,0,BufferSize);
if(BytesRead==0)
Thread.Sleep(1 O):
else
—
incomingMessage=
一
incomingMessage+
Encoding Default.GetString(——buffer,0,BytesRead);
)
)
)while《ReturnString=:““&&一stream.CanRead&&
一
closed);
一
bytesRead+=ReturnString.Length;
totalBytesRead+:ReturnString.Length;
_
return ReturnString;
}
private void WriteString(string S)
(
if(一stream.CanWrite)
{
S=s.Length+“l¨+s:
byte[】outputBytes=Encoding.Default.GetBytes(s);
——
stream.Write(outputBytes,0,outputBytes.Length);
bytesWritten+=outputBytes.Length;
一
totalBytesWritten+=outputBytes.Length;
_
}
)
2.3.4使用PrismServerlmplementation组件管理用户信息
PfismServer具有一个Implementation属性。在激活PfismSe
lwer之前.必须为该属性赋值一个派生自PrismServerlmplemen
tation类的实例。PrismServerImplementation类提供了大量管理
用户的框架方法.派生类将重写这些方法实现自定义的管理形
式,比如,可以派生一个PfismServerSqlImplementation组件,
将用户信息存储到SQL Server数据库中;或者派生一个
PrismServerXmllmplmentation组件.将用户信息存储到XML文
件中。
除了构造函数,PrismServerlmplementation中的方法都是虚
方法,派生类通过重载这些方法实现自定义的用户管理功能。
默认情况下.只要用户名和密码不为空,都允许通过验证。类
库中实现了一个派生自 ̄rismServerlmplementation的PrismServer
FileImplementation类。该组件会创建一个名为Users的文件夹,
用于在文件系统中保存用户信息。它主要使用System.10空间
下与文件操作相关的类来操作文件。下面的代码读写文件数据
库验证用户名和密码:
public override bool CheckUserName{string userName,ref
string msg)
{
if《!base CheckUserName(userName,ref msg))
return false;
if(UserExists(userName))
return true;
trv
{
string UserFile=GetUserFileName(userName);
FileStream fs=File Create(User e):
fs.Close();
File.DeIete(User e):
return true;
)
catch
{
msg=”指定的用户名包含无效的字符I”
return false;
)
(下转第70页)
’… ‘ 。
实用第一 智慧密集
… …… … . .…… . . … … …
connection.addEventListener fSecurityErrorEvent.
customClient.onMetaData=metaDataHandler;
stream.client=customClient;
SECURI ERR0R,security ErrorHandler);
//连接
connection.connect(nul1);
video=new Video();
video.attachNetStream(stream);
video.x=flvAttrib.xpos;
video.y=flvAttrib.ypos;
video.width=flvAttrib.w:
网络的连接可能会出现多种情况,比如文件未找到等,可
以根据这些信息进一步地操作:
function netStatusHandIer(event:NetStatusEvent):void
{
switch(event.info.code)
{
case。NetCOnnectiOn.Connect.Success“:
l
video.height=flvAtt b.h:
stream.play(lfvAttrib.src);
∥添加视频信息到场景
st.addCh_ld(video):
∥连接成功,进行流媒体的下载并播放
connectStreamO;
break;
3 结语
该程序用Flash CS3在Windows XP SP2上编译运行通过,
default:
break;
能够很好地根据配置文件完成Flash影片的定制,对于仅仅用
于展示或者具有简单交互的情况下,完全可以只通过配置
XML文件就可以完成 当然,更复杂的Flash设计还需要在图
形界面里面完成。
)
)
private function connectStream0:void
{
//建立流媒体连接
stream=new NetStream(connection);stream.addEvent
Listener (NetStatusEvent.NET—STATUS, netStatusHandler):
stream.addEventListener(AsyncErrorEvent ASYNC—ERROR,
asyncErr0rHandler);
参考文献
[1]Adobe.Action Script 3.0编程手册.
[2】http://www.adobe.com/devnet/actionscript/samples.html
(收稿日期:2013—08—15)
var customClient:Object=new Object();
(上接第64页)
)
public override bool IsPasswordValid(string userName,string
password)
{
PrismUser TempUser=new PrismUserO;
TempUser.UserName=userName;
)
public override void SaveSettings()
{
string S=Tokenizer.Tokenize
(Server.Port,Server.ProhibitSameIP,Server
ProhibitSameUserName);
File.WriteAIIText(一settingsFile,s):
)
PopulateUserlnfo(TempUser);
return(password==TempUser.Password);
)
2.4客户端应用的实现
写了 PrismServerFilelmplementation类同时也重
客户端应用是一个WinForm应用程序,主要的功能都封装在
核心类库中,只需添加引用即可。当用户打开客户端主窗体时,
需要指定服务器信息.允许用户注册或登录.然后连接到通信服
务器端进行通信。限于篇幅,具体的界面编程过程不再赘述。
LoadSettings和SaveSettings方法.用于加载和保存服务器端的
配置信息。下面的代码实现了配置信息的存取:
public override void LoadSettings()
{
2_5服务器端应用的实现
服务器端也是一个WinForm应用程序.它的主要功能也
都封装在核心类库中.在用户界面上,只需要调用核心类库即
if(File.Exists(一settingsFile))
{
string S=File.ReadAIIText(._settingsFile);
Server.Port=Tokenizer.GetTokenInt(ref s):
Server.ProhibitSamelP=Tokenizer.GetTokenBOOI《ref s):
Server.ProhibitSameUserName=Tokenizer.GetTokenBoo1
可。在服务器端可以停止即时通信服务(此时所有的客户端将
无法进行正常的聊天行为)、查看聊天用户信息、连接信息以
及聊天记录、向客户端发送管理信息、查看统计信息等。限于
篇幅.具体的界面编程过程不再赘述。
(收稿日期:2013—05—07)
(ref s):
)
:7oi!:i 与鞋
版权声明:本文标题:.NET网络编程技术原理与开发实例 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1710764664a571925.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论