构建基于Silverlight的网络聊天应用
1. 引言
在网络编程中,构建一个支持多用户实时通信的聊天应用是一个有趣且具有挑战性的任务。这里将介绍如何使用Silverlight构建一个简单的聊天应用,包括客户端和服务器端的实现。同时,还会涉及到策略文件的使用和管理,以确保Silverlight应用能够正常连接到服务器。
2. 网络连接协议
Silverlight除了支持TCP套接字连接外,还提供了对UDP套接字连接的底层支持。UDP是一种轻量级协议,在速度比可靠性更重要的场景中使用,例如视频流传输。不过,这里主要关注TCP连接。
3. 构建聊天应用
要构建一个简单的聊天应用,需要使用Silverlight构建套接字客户端,使用.NET构建套接字服务器。这个应用允许多个用户同时登录并相互发送消息。
4. 理解策略文件
在设计套接字服务器之前,需要先开发一个策略服务器,它用于告诉Silverlight哪些客户端被允许连接到套接字服务器。Silverlight在访问内容或调用Web服务时,要求域名必须有
clientaccesspolicy.xml
或
crossdomain.xml
文件明确允许。套接字服务器也有类似限制,除非提供让客户端下载允许远程访问的
clientaccesspolicy.xml
文件的方式,否则Silverlight将拒绝建立连接。
以下是一个示例策略文件:
<?xml version="1.0" encoding="utf-8" ?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from>
<domain uri="*"/>
</allow-from>
<grant-to>
<socket-resource port="4502-4534" protocol="tcp"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
这个策略文件建立了三条规则:
- 允许访问4502到4534的所有端口,这是Silverlight支持的完整范围。若要更改此设置,可修改
<socket-resource>
元素的
port
属性。
- 通过
<socket-resource>
元素的
protocol
属性允许TCP访问。
- 允许来自任何域名的调用者,即建立连接的Silverlight应用可以托管在任何网站上。若要更改此设置,可修改
<domain>
元素的
uri
属性。
提供策略文件有两种方式:
-
添加到Web服务器
:将策略文件复制到托管Silverlight应用的Web服务器的根目录(可能不是实际网页所在的文件夹)。如果设置正确,无需编写代码或进行额外配置。但Web服务器必须与套接字服务器运行在同一台计算机上。例如,连接到地址为102.168.212.226的服务器时,Silverlight会尝试从
http://102.168.212.226/clientaccesspolicy.xml
检索策略文件(Silverlight始终使用端口80下载策略文件)。此方法适用于部署的Silverlight应用,但在测试时不太方便,因为Visual Studio内置的测试Web服务器不使用端口80,无法向Silverlight客户端提供策略文件。需要安装并配置IIS,并将策略文件复制到根目录(通常是
c:\inetpub\wwwroot
)。
-
创建专用策略服务器
:这种方法在某些特定场景下更好,且更便于测试。下面将详细介绍如何创建策略服务器。
5. 创建策略服务器
策略服务器是一个基于套接字的应用,客户端可以向其发送策略请求。创建策略服务器,首先需要创建一个.NET应用,通常选择简单的命令行控制台应用。创建策略服务器项目后,添加策略文件(命名为
clientaccesspolicy.xml
),并将其“复制到输出目录”设置为“始终复制”,这样在构建项目时,策略文件会被复制到与策略服务器应用相同的文件夹中。
策略服务器的功能主要由两个关键类实现:
-
PolicyServer类
:负责等待和监听连接。当接收到连接时,将其传递给
PolicyConnection
类的新实例。
Public Class PolicyServer
Private policy As Byte()
Public Sub New(ByVal policyFile As String)
' Load the policy file.
Dim policyStream As New FileStream(policyFile, FileMode.Open)
policy = New Byte(policyStream.Length - 1) {}
policyStream.Read(policy, 0, policy.Length)
policyStream.Close()
End Sub
Private listener As TcpListener
Public Sub Start()
' Create the listener.
listener = New TcpListener(IPAddress.Any, 943)
' Begin listening. This method returns immediately.
listener.Start()
' Wait for a connection. This method returns immediately.
' The waiting happens on a separate thread.
listener.BeginAcceptTcpClient(OnAcceptTcpClient, Nothing)
End Sub
Public Sub OnAcceptTcpClient(ByVal ar As IAsyncResult)
If isStopped Then Return
Console.WriteLine("Received policy request.")
' Wait for the next connection.
listener.BeginAcceptTcpClient(AddressOf OnAcceptTcpClient, Nothing)
' Handle this connection.
Try
Dim client As TcpClient = listener.EndAcceptTcpClient(ar)
Dim policyConnection As New PolicyConnection(client, policy)
policyConnection.HandleRequest()
Catch err As Exception
Console.WriteLine(err.Message)
End Try
End Sub
Private isStopped As Boolean
Public Sub [Stop]()
isStopped = True
Try
listener.Stop()
Catch err As Exception
Console.WriteLine(err.Message)
End Try
End Sub
End Class
启动策略服务器的代码如下:
Shared Sub Main(ByVal args As String())
Dim policyServer As New PolicyServer("clientaccesspolicy.xml")
policyServer.Start()
Console.WriteLine("Policy server started.")
Console.WriteLine("Press Enter to exit.")
' Wait for an Enter key. You could also wait for a specific input
' string (like "quit") or a single key using Console.ReadKey().
Console.ReadLine()
policyServer.Stop()
Console.WriteLine("Policy server shut down.")
End Sub
- PolicyConnection类 :负责处理连接并发送策略文件。
Public Class PolicyConnection
Private client As TcpClient
Private policy As Byte()
Public Sub New(ByVal client As TcpClient, ByVal policy As Byte())
Me.client = client
Me.policy = policy
End Sub
' The request that the client sends.
Private Shared policyRequestString As String = "<policy-file-request/>"
Public Sub HandleRequest()
Dim s As Stream = client.GetStream()
' Read the policy request string.
' This code doesn't actually check the content of the request string.
' Instead, it returns the policy for every request.
Dim buffer As Byte() = New Byte(policyRequestString.Length - 1) {}
' Only wait 5 seconds. That way, if you attempt to read the request string
' and it isn't there or it's incomplete, the client only waits for 5
' seconds before timing out.
client.ReceiveTimeout = 5000
s.Read(buffer, 0, buffer.Length)
' Send the policy.
s.Write(policy, 0, policy.Length)
' Close the connection.
client.Close()
Console.WriteLine("Served policy file.")
End Sub
End Class
6. 策略服务器工作流程
graph TD;
A[启动策略服务器] --> B[创建TcpListener并开始监听];
B --> C[等待连接请求];
C --> D{收到连接请求};
D -- 是 --> E[创建PolicyConnection实例];
E --> F[处理请求并发送策略文件];
F --> C;
D -- 否 --> C;
7. 总结
通过以上步骤,已经完成了策略服务器的创建。但由于Silverlight不允许显式请求策略文件,需要在构建基于套接字的应用客户端之前,先构建服务器端。接下来将介绍如何构建消息服务器。
构建基于Silverlight的网络聊天应用
8. 构建消息服务器
虽然可以将消息服务器作为一个单独的应用程序创建,但将其与策略服务器放在同一个应用程序中会更简洁。因为策略服务器在单独的线程上进行监听和请求处理工作,所以消息服务器可以同时工作。
消息服务器也分为两个类:
-
MessengerServer类
:负责监听请求并跟踪客户端。
-
MessengerConnection类
:负责处理单个客户端的交互。
以下是消息服务器与策略服务器的主要区别:
-
监听端口
:Silverlight允许基于套接字的应用程序使用4502到4534范围内的任何端口,消息服务器使用端口4530。
listener = New TcpListener(IPAddress.Any, 4530)
-
处理连接请求
:当消息服务器收到连接请求时,除了创建
MessengerConnection实例处理通信外,还会将客户端添加到一个集合中,以便跟踪所有当前连接的用户。
Private clientNum As Integer
Private clients As New List(Of MessengerConnection)
clientNum += 1
Console.WriteLine("Messenger client #" & clientNum.ToString() & " connected.")
' Create a new object to handle this connection.
Dim clientHandler As New MessengerConnection(client, "Client " & _
clientNum.ToString(), Me)
clientHandler.Start()
SyncLock clients
clients.Add(clientHandler)
End SyncLock
- 停止服务器 :当消息服务器停止时,会遍历整个客户端集合,确保每个客户端都断开连接。
For Each client As MessengerConnection In clients
client.Close()
Next
9. 实现消息传递功能
要实现消息传递功能,需要在
MessengerConnection
类中处理消息提交,在
MessengerServer
类中处理消息传递。
-
MessengerConnection类
:当
MessengerConnection对象创建并调用其Start()方法时,开始监听任何数据。
Public Sub Start()
Try
' Listen for messages.
client.Client.BeginReceive(message, 0, message.Length, SocketFlags.None, _
New AsyncCallback(AddressOf OnDataReceived), Nothing)
Catch se As SocketException
Console.WriteLine(se.Message)
End Try
End Sub
当客户端发送数据时,
OnDataReceived()
回调被触发,读取数据并将其传递给
MessengerServer.DeliverMessage()
方法,然后继续监听下一条消息。
Public Sub OnDataReceived(ByVal asyn As IAsyncResult)
Try
Dim bytesRead As Integer = client.Client.EndReceive(asyn)
If bytesRead > 0 Then
' Ask the server to send the message to all the clients.
server.DeliverMessage(message, bytesRead)
' Listen for more messages.
client.Client.BeginReceive(message, 0, message.Length, _
SocketFlags.None, New AsyncCallback(AddressOf OnDataReceived), _
Nothing)
End If
Catch err As Exception
Console.WriteLine(err.Message)
End Try
End Sub
-
MessengerServer类
:
MessengerServer.DeliverMessage()方法会遍历客户端集合,调用每个客户端的ReceiveMessage()方法传递消息。为避免线程问题,先创建客户端集合的快照副本,然后使用该副本传递消息。
Public Sub DeliverMessage(ByVal message As Byte(), ByVal bytesRead As Integer)
Console.WriteLine("Delivering message.")
' Duplicate the collection to prevent threading issues.
Dim connectedClients As MessengerConnection()
SyncLock clients
connectedClients = clients.ToArray()
End SyncLock
For Each client As MessengerConnection In connectedClients
Try
client.ReceiveMessage(message, bytesRead)
Catch
' Client is disconnected.
' Remove the client to avoid future attempts.
SyncLock clients
clients.Remove(client)
End SyncLock
client.Close()
End Try
Next
End Sub
- MessengerConnection类的ReceiveMessage方法 :将消息数据写回网络流,以便客户端接收。
Public Sub ReceiveMessage(ByVal data As Byte(), ByVal bytesRead As Integer)
client.GetStream().Write(data, 0, bytesRead)
End Sub
10. 修改启动代码
需要修改启动代码,以便应用程序同时创建和启动策略服务器和消息服务器。
Shared Sub Main(ByVal args As String())
Dim policyServer As New PolicyServer("clientaccesspolicy.xml")
policyServer.Start()
Console.WriteLine("Policy server started.")
Dim messengerServer As New MessengerServer()
messengerServer.Start()
Console.WriteLine("Messenger server started.")
Console.WriteLine("Press Enter to exit.")
' Wait for an Enter key. You could also wait for a specific input
' string (like "quit") or a single key using Console.ReadKey().
Console.ReadLine()
policyServer.Stop()
Console.WriteLine("Policy server shut down.")
messengerServer.Stop()
Console.WriteLine("Messenger server shut down.")
End Sub
11. 消息服务器工作流程
graph TD;
A[启动消息服务器] --> B[创建TcpListener并开始监听];
B --> C[等待连接请求];
C --> D{收到连接请求};
D -- 是 --> E[创建MessengerConnection实例];
E --> F[将客户端添加到集合];
F --> G[开始监听客户端数据];
G --> H{收到消息};
H -- 是 --> I[将消息传递给所有客户端];
I --> G;
H -- 否 --> G;
D -- 否 --> C;
12. 总结
通过以上步骤,完成了基于Silverlight的网络聊天应用的服务器端构建,包括策略服务器和消息服务器。策略服务器用于处理客户端的策略请求,消息服务器用于处理客户端之间的消息传递。整个应用程序通过多线程处理多个客户端的连接和消息传递,确保了系统的并发性能。可以在此基础上,进一步开发客户端应用程序,实现完整的聊天功能。
基于Silverlight构建网络聊天应用
超级会员免费看
33

被折叠的 条评论
为什么被折叠?



