博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
你得学会并且学得会的Socket编程基础知识(续)——Silverlight客户端(转)
阅读量:6149 次
发布时间:2019-06-21

本文共 15096 字,大约阅读时间需要 50 分钟。

上一篇,我给大家讲解了关于Socket编程的基础知识

 

本文将在这个案例的基础上,加入一个特殊场景,利用Silverlight来实现客户端。有的朋友可能会说,其实是一样的吧。请不要急于下结论,有用过Silverlight的朋友都有这种体会,很多在标准.NET Framework编程中能用的技术,到了Silverlight里面,或多或少会有些限制。不幸的是,Socket这个功能就是其中一个。这本身没有什么好不好的问题,Silverlight首先是运行在一个特殊的沙盒中,受到一些限制也是意料之中的,毕竟安全第一嘛

 

我总结Silverlight中应用Socket的几点特殊之处

1.所有的操作都必须的异步的,包括连接,发送和接收消息

2.Silverlight只能做客户端,不能做服务器(虽然这句看起来说的有点多余,不过确实有朋友想这么做呢)

3.Silverlight的Socket只能访问如下端口,4502-4530,只能用TCP。

4.Silverlight的Socket收到访问策略的限制,服务端必须监听,并提供ClientAccessPolicy的支持。通常是在943端口(TCP)进行监听,也可以在HTTP 80端口监听。

本文完整代码如下 

 

 

那么,我们就来通过例子学习一下在Silverlight中如何使用Socket技术与服务端通讯吧

第一步:创建Silverlight项目

第二步:设计Silverlight界面

这个界面看起来像下面这样

 

第三步:编写基本的客户端代码

using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; //导入命名空间 using System.Net.Sockets; namespace SocketSilverlightClient {
/// /// 演示如何在Silverlight中使用Socket技术 /// 作者:陈希章 /// public partial class MainPage : UserControl {
public MainPage() {
InitializeComponent(); } /// /// 尝试连接到服务器 /// /// /// private void btConnect_Click(object sender, RoutedEventArgs e) {
//创建一个套接字 var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //准备一个异步参数(这是特有的) var args = new SocketAsyncEventArgs(); //设置远程服务器地址,这里用DnsSafeHost,可以获取到宿主远程服务器的主机名称 args.RemoteEndPoint = new DnsEndPoint(App.Current.Host.Source.DnsSafeHost, 4530); //注册Completed事件处理程序 args.Completed += (o, a) => {
if(a.SocketError > 0)//0表示成功,其他的表示有错误 {
//注意,因为Completed方法是在工作线程触发的,所以要对主线程进行访问,必须使用Dispatcher机制 this.Dispatcher.BeginInvoke(() => {
MessageBox.Show("Connect fail:" + a.SocketError.ToString()); }); } else {
this.Dispatcher.BeginInvoke(() => {
MessageBox.Show("Connect success"); }); } }; //发起异步的连接请求 socket.ConnectAsync(args); } private void btSend_Click(object sender, RoutedEventArgs e) {
} } }

【注意】在Silverlight中使用Socket的代码,与一般的客户端还是不同的。最主要的区别在于异步模型。

目前,我这里只编写了Connect的代码,是因为这里就会遇到连接问题,其他代码先不着急写出来。我们可以运行起来看看

点击“Connect”之后,我们发现有一个错误,是AccessDenied。这就是说,Silverlight遇到了权限问题无法直接访问到服务器。

我们都知道,Silverlight是运行在一个沙盒里面的,它要访问宿主网站之外的资源,是受到很多限制的。它会先尝试检查目标资源是否有一个ClientAccessPolicy的设置。

这里有一篇详细的介绍 

 

第四步:为服务器添加PolicyServer。

已经有不少先进同学在这方面有研究了。这个PolicyServer是负责向Silverlight发送策略信息的,也就是说,Silverlight的Socket,在连接之前,会默认去连接目标主机的943端口,请求ClientAccessPolicy的认证,只有通过了,则可以继续访问其他的Socket。

 

这个PolicyServer的设计,不是我的原创,但我稍做了修改。请将下面的代码保存为一个独立的文件,放在SocketServer这个项目里面

using System; using System.Configuration; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Text; namespace SocketServerService {
/// /// This is a silverlight socket client access policy file server. /// /// Background: /// When a socket connection open attempt to some server is made in Silverlight 2.0 /// Silverlight automatically makes a request to the server in question on port 943 for a policy file /// The policy file served includes the valid ports and valid clients for the socket server /// /// Outcomes: /// The socket request will result in success if the client access policy file served by the socket /// server permits access to the requested port and the client URI is in the
element /// See ClientAccessPolicy.xml & http://msdn.microsoft.com/en-us/library/cc645032(VS.95).aspx for further details /// /// The socket request will be denied if the client access policy file is not served or if the client / /// port is denied in the client access policy file ///
class SL_SocketPortPolicyListener {
TcpListener _Listener = null; TcpClient _Client = null; const string _PolicyRequestString = "
"; int _ReceivedLength = 0; byte[] _Policy = null; byte[] _ReceiveBuffer = null; EventLog eventLog; /// /// Initializes a new instance of the
class. ///
/// The service event log. public SL_SocketPortPolicyListener(EventLog serviceEventLog) {
eventLog = serviceEventLog; Start(); } /// /// 增加的代码 /// public SL_SocketPortPolicyListener() : this(new EventLog("Application")) {
} /// /// Starts this instance. /// void Start() {
try {
//增加的代码 var policyConfig = "
" + "
" + "
" + "
" + "
" + "
" + "
" + "
" + "
" + "
" + "
" + "
" + "
"; //删除的代码 //string executionLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); //string policyFile = ConfigurationManager.AppSettings["PolicyFilePath"]; //using(FileStream fs = new FileStream(executionLocation + policyFile, FileMode.Open)) //{
// _Policy = new byte[fs.Length]; // fs.Read(_Policy, 0, _Policy.Length); //} //增加的代码 _Policy = Encoding.Default.GetBytes(policyConfig); _ReceiveBuffer = new byte[_PolicyRequestString.Length]; //Using TcpListener which is a wrapper around a Socket //Allowed port is 943 for Silverlight sockets policy data _Listener = new TcpListener(IPAddress.Any, 943); _Listener.Start(); _Listener.BeginAcceptTcpClient(new AsyncCallback(OnBeginAccept), null); } catch(Exception exp) {
LogError(exp); } } /// /// Called when [begin accept]. /// /// The ar. private void OnBeginAccept(IAsyncResult ar) {
_Client = _Listener.EndAcceptTcpClient(ar); _Client.Client.BeginReceive(_ReceiveBuffer, 0, _PolicyRequestString.Length, SocketFlags.None, new AsyncCallback(OnReceiveComplete), null); } /// /// Called when [receive complete]. /// /// The ar. private void OnReceiveComplete(IAsyncResult ar) {
try {
_ReceivedLength += _Client.Client.EndReceive(ar); //See if there's more data that we need to grab if(_ReceivedLength < _PolicyRequestString.Length) {
//Need to grab more data so receive remaining data _Client.Client.BeginReceive(_ReceiveBuffer, _ReceivedLength, _PolicyRequestString.Length - _ReceivedLength, SocketFlags.None, new AsyncCallback(OnReceiveComplete), null); return; } //Check that
was sent from client string request = System.Text.Encoding.UTF8.GetString(_ReceiveBuffer, 0, _ReceivedLength); if(StringComparer.InvariantCultureIgnoreCase.Compare(request, _PolicyRequestString) != 0) {
//Data received isn't valid so close _Client.Client.Close(); return; } //Valid request received....send policy file _Client.Client.BeginSend(_Policy, 0, _Policy.Length, SocketFlags.None, new AsyncCallback(OnSendComplete), _Client.Client); } catch(Exception exp) {
_Client.Client.Close(); LogError(exp); } _ReceivedLength = 0; //listen for the next client _Listener.BeginAcceptTcpClient(new AsyncCallback(OnBeginAccept), null); } /// /// Called when [send complete]. /// /// The ar. private void OnSendComplete(IAsyncResult ar) {
Socket socket = (Socket)ar.AsyncState; try {
socket.EndSend(ar); } catch(Exception exp) {
LogError(exp); } finally {
socket.Close(); } } /// /// Logs the error. /// /// The exp. private void LogError(Exception exp) {
eventLog.WriteEntry(string.Format("Error in PolicySocketServer: {0} \r\n StackTrace: {1}", exp.Message, exp.StackTrace)); } } }

 

然后,在SocketServer的主程序中,加入下面的代码(只需要添加红色这一行即可

 

using System; using System.Collections.Generic; using System.Linq; using System.Text; //额外导入的两个命名空间 using System.Net.Sockets; using System.Net; namespace SocketServer {
class Program {
/// /// Socket Server 演示 /// 作者:陈希章 /// /// static void Main(string[] args) {
var policyServer = new SocketServerService.SL_SocketPortPolicyListener(); //创建一个新的Socket,这里我们使用最常用的基于TCP的Stream Socket(流式套接字) var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //将该socket绑定到主机上面的某个端口 //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.bind.aspx socket.Bind(new IPEndPoint(IPAddress.Any, 4530)); //启动监听,并且设置一个最大的队列长度 //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.listen(v=VS.100).aspx socket.Listen(4); //开始接受客户端连接请求 //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.beginaccept.aspx socket.BeginAccept(new AsyncCallback(ClientAccepted), socket); Console.WriteLine("Server is ready!"); Console.Read(); } public static void ClientAccepted(IAsyncResult ar) {
var socket = ar.AsyncState as Socket; //这就是客户端的Socket实例,我们后续可以将其保存起来 var client = socket.EndAccept(ar); //给客户端发送一个欢迎消息 client.Send(Encoding.Unicode.GetBytes("Hi there, I accept you request at " + DateTime.Now.ToString())); //实现每隔两秒钟给服务器发一个消息 //这里我们使用了一个定时器 var timer = new System.Timers.Timer(); timer.Interval = 2000D; timer.Enabled = true; timer.Elapsed += (o, a) => {
//检测客户端Socket的状态 if(client.Connected) {
try {
client.Send(Encoding.Unicode.GetBytes("Message from server at " + DateTime.Now.ToString())); } catch(SocketException ex) {
Console.WriteLine(ex.Message); } } else {
timer.Stop(); timer.Enabled = false; Console.WriteLine("Client is disconnected, the timer is stop."); } }; timer.Start(); //接收客户端的消息(这个和在客户端实现的方式是一样的) client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), client); //准备接受下一个客户端请求 socket.BeginAccept(new AsyncCallback(ClientAccepted), socket); } static byte[] buffer = new byte[1024]; public static void ReceiveMessage(IAsyncResult ar) {
try {
var socket = ar.AsyncState as Socket; //方法参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socket.endreceive.aspx var length = socket.EndReceive(ar); //读取出来消息内容 var message = Encoding.Unicode.GetString(buffer, 0, length); //显示消息 Console.WriteLine(message); //接收下一个消息(因为这是一个递归的调用,所以这样就可以一直接收消息了) socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), socket); } catch(Exception ex){
Console.WriteLine(ex.Message); } } } }

再次测试,我们就发现Silverlight客户端能够连接到服务器了

 

既然连接上了服务器,那么就让我们来将Silverlight客户端里面其他的一些功能都实现一下吧

 

第五步:实现Silverlight客户端的消息收发

using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; //导入命名空间 using System.Net.Sockets; using System.Text; namespace SocketSilverlightClient {
/// /// 演示如何在Silverlight中使用Socket技术 /// 作者:陈希章 /// public partial class MainPage : UserControl {
public MainPage() {
InitializeComponent(); } //创建一个套接字 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); /// /// 尝试连接到服务器 /// /// /// private void btConnect_Click(object sender, RoutedEventArgs e) {
//准备一个异步参数(这是特有的) var args = new SocketAsyncEventArgs(); //设置远程服务器地址,这里用DnsSafeHost,可以获取到宿主远程服务器的主机名称 args.RemoteEndPoint = new DnsEndPoint(App.Current.Host.Source.DnsSafeHost, 4530); //注册Completed事件处理程序 args.Completed += ConnectCompleted; //发起异步的连接请求 socket.ConnectAsync(args); } /// /// 该事件在连接成功时发生 /// /// /// public void ConnectCompleted(object sender, SocketAsyncEventArgs e) {
if(e.SocketError > 0)//0表示成功,其他的表示有错误 {
//注意,因为Completed方法是在工作线程触发的,所以要对主线程进行访问,必须使用Dispatcher机制 this.Dispatcher.BeginInvoke(() => {
MessageBox.Show("Connect fail:" + e.SocketError.ToString()); }); } else {
this.Dispatcher.BeginInvoke(() => {
//MessageBox.Show("Connect success"); //将连接按钮禁用掉 btConnect.Content = "Connected"; btConnect.IsEnabled = false; var buffer = new byte[1024]; e.SetBuffer(buffer, 0, buffer.Length); e.Completed -= ConnectCompleted; e.Completed += ReceiveCompleted; socket.ReceiveAsync(e); }); } } /// /// 该事件在接收消息时发生 /// /// /// public void ReceiveCompleted(object sender, SocketAsyncEventArgs e) {
//将消息显示在界面上 var result = Encoding.Unicode.GetString(e.Buffer, 0, e.Count); this.Dispatcher.BeginInvoke(() => {
icResult.Items.Add(result); }); //递归继续接收消息 socket.ReceiveAsync(e); } private void btSend_Click(object sender, RoutedEventArgs e) {
var args = new SocketAsyncEventArgs(); //将用户输入的文本转成字节 var buffer = Encoding.Unicode.GetBytes(txtInput.Text); args.SetBuffer(buffer, 0, buffer.Length); //设置远程服务器地址,这里用DnsSafeHost,可以获取到宿主远程服务器的主机名称 args.RemoteEndPoint = new DnsEndPoint(App.Current.Host.Source.DnsSafeHost, 4530); //发送完成的话,将控件清空,激活 args.Completed += (o, a) => {
this.Dispatcher.BeginInvoke(() => {
txtInput.Text = string.Empty; btSend.IsEnabled = true; }); }; //禁用按钮 btSend.IsEnabled = false; //发送消息 socket.SendAsync(args); } } }

运行起来看看吧

 

还不错对吧,这个例子给大家演示了如何在Silverlight中使用Socket,接下来大家可以结合自己的现实工作做一些研究和扩展吧

本文完整代码如下 

转载于:https://www.cnblogs.com/kingdom_0/articles/2277685.html

你可能感兴趣的文章
发布和逸出-构造过程中使this引用逸出
查看>>
Oracle执行计划发生过变化的SQL语句脚本
查看>>
使用SanLock建立简单的HA服务
查看>>
发现一个叫阿尔法城的小站(以后此贴为我记录日常常用网址的帖子了)
查看>>
Subversion使用Redmine帐户验证简单应用、高级应用以及优化
查看>>
Javascript Ajax 异步请求
查看>>
DBCP连接池
查看>>
cannot run programing "db2"
查看>>
mysql做主从relay-log问题
查看>>
Docker镜像与容器命令
查看>>
批量删除oracle中以相同类型字母开头的表
查看>>
Java基础学习总结(4)——对象转型
查看>>
BZOJ3239Discrete Logging——BSGS
查看>>
SpringMVC权限管理
查看>>
spring 整合 redis 配置
查看>>
redhat6.1下chrome的安装
查看>>
cacti分组发飞信模块开发
查看>>
浅析LUA中游戏脚本语言之魔兽世界
查看>>
飞翔的秘密
查看>>
Red Hat 安装源包出错 Package xxx.rpm is not signed
查看>>