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 与鞋 


本文标签: 网络 连接 方法 数据 客户端