你得学会并且学得会的Socket编程基础知识(续)——Silverlight客户端
本文将在这个案例的基础上,加入一个特殊场景,利用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端口监听。
本文完整代码如下 http://files.cnblogs.com/chenxizhang/SocketWorkshop(with-silverlight).rar
那么,我们就来通过例子学习一下在Silverlight中如何使用Socket技术与服务端通讯吧
第一步:创建Silverlight项目
第二步:设计Silverlight界面
<UserControl
x:Class="SocketSilverlightClient.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"> <Grid
x:Name="LayoutRoot"
Background="White"
Margin="20"> <Grid.Resources>
<Style
TargetType="Button">
<Setter
Property="Width"
Value="100"></Setter>
<Setter
Property="HorizontalAlignment"
Value="Left"></Setter>
<Setter
Property="Margin"
Value="5"></Setter>
</Style> <Style
TargetType="TextBlock">
<Setter
Property="Margin"
Value="5"></Setter>
<Setter
Property="HorizontalAlignment"
Value="Left"></Setter>
<Setter
Property="TextWrapping"
Value="Wrap"></Setter>
</Style>
</Grid.Resources> <StackPanel>
<Button
Content="Connect"
x:Name="btConnect"
Click="btConnect_Click"></Button> <TextBlock
Text="Type your message"></TextBlock> <StackPanel
Margin="5"
Orientation="Horizontal">
<TextBox
x:Name="txtInput"
Width="200"></TextBox>
<Button
Content="Send"
x:Name="btSend"
Click="btSend_Click"></Button>
</StackPanel> <TextBlock
Text="Messages from server"></TextBlock> <ItemsControl
Margin="5"
x:Name="icResult">
</ItemsControl>
</StackPanel> </Grid>
</UserControl>
这个界面看起来像下面这样
第三步:编写基本的客户端代码
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
{
/// <summary>
/// 演示如何在Silverlight中使用Socket技术
/// 作者:陈希章
/// </summary>
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
} /// <summary>
/// 尝试连接到服务器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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的设置。
这里有一篇详细的介绍 http://msdn.microsoft.com/zh-cn/library/cc197955(VS.95).aspx
第四步:为服务器添加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
{
/// <summary>
/// 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 <allow-from> 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
/// </summary>
class SL_SocketPortPolicyListener
{
TcpListener _Listener = null;
TcpClient _Client = null;
const string _PolicyRequestString = "<policy-file-request/>";
int _ReceivedLength = 0;
byte[] _Policy = null;
byte[] _ReceiveBuffer = null;
EventLog eventLog; /// <summary>
/// Initializes a new instance of the <see cref="SL_SocketPortPolicyListener"/> class.
/// </summary>
/// <param name="serviceEventLog">The service event log.</param>
public SL_SocketPortPolicyListener(EventLog serviceEventLog)
{
eventLog = serviceEventLog;
Start();
}
/// <summary>
/// 增加的代码
/// </summary>
public SL_SocketPortPolicyListener()
: this(new EventLog("Application"))
{ } /// <summary>
/// Starts this instance.
/// </summary>
void Start()
{
try
{
//增加的代码
var policyConfig =
"<?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-4530\" protocol=\"tcp\" />" +
"</grant-to>" +
"</policy>" +
"</cross-domain-access>" +
"</access-policy>"; //删除的代码
//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);
}
} /// <summary>
/// Called when [begin accept].
/// </summary>
/// <param name="ar">The ar.</param>
private void OnBeginAccept(IAsyncResult ar)
{
_Client = _Listener.EndAcceptTcpClient(ar);
_Client.Client.BeginReceive(_ReceiveBuffer, 0, _PolicyRequestString.Length, SocketFlags.None,
new AsyncCallback(OnReceiveComplete), null);
} /// <summary>
/// Called when [receive complete].
/// </summary>
/// <param name="ar">The ar.</param>
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 <policy-file-request/> 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);
} /// <summary>
/// Called when [send complete].
/// </summary>
/// <param name="ar">The ar.</param>
private void OnSendComplete(IAsyncResult ar)
{
Socket socket = (Socket)ar.AsyncState;
try
{
socket.EndSend(ar);
}
catch(Exception exp)
{
LogError(exp);
}
finally
{
socket.Close();
}
} /// <summary>
/// Logs the error.
/// </summary>
/// <param name="exp">The exp.</param>
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
{
/// <summary>
/// Socket Server 演示
/// 作者:陈希章
/// </summary>
/// <param name="args"></param>
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
{
/// <summary>
/// 演示如何在Silverlight中使用Socket技术
/// 作者:陈希章
/// </summary>
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
} //创建一个套接字
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); /// <summary>
/// 尝试连接到服务器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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);
} /// <summary>
/// 该事件在连接成功时发生
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
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);
});
}
} /// <summary>
/// 该事件在接收消息时发生
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
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,接下来大家可以结合自己的现实工作做一些研究和扩展吧
本文完整代码如下 http://files.cnblogs.com/chenxizhang/SocketWorkshop(with-silverlight).rar
转自:http://www.cnblogs.com/chenxizhang/archive/2011/09/10/2173101.html
你得学会并且学得会的Socket编程基础知识(续)——Silverlight客户端的更多相关文章
- 你得学会并且学得会的Socket编程基础知识
这一篇文章,我将图文并茂地介绍Socket编程的基础知识,我相信,如果你按照步骤做完实验,一定可以对Socket编程有更好地理解. 本文源代码,可以通过这里下载 http://files.cnblog ...
- Java从零开始学四十五(Socket编程基础)
一.网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可 ...
- 预学第二天:计算机的基础知识+python安装+pycharm安装+思维导图
目录 计算机的基础知识 计算机是由什么组成的 什么是操作系统
- 学golang之前都需要哪些前置知识?
我学golang,感觉前面基础语法部分都很快能学会,但是到了goroutine,channel等后面的部分就看不懂了,是不是我学这个之前还得学习其他什么知识啊?(我有C语言基础,对于C语言里面的指针, ...
- 想入门Web安全,这些基础知识都学会了吗?
毕业季已经正式告一段落,这届毕业生都找到心仪的工作了吗? 正在实习期或者试用期的职场新人,是否在岗位上做的风生水起? 工作了一两年,从未升职加薪的菜鸟,还愿意继续原地踏步吗? 在校学生.IT从业者.毕 ...
- Java基础:三步学会Java Socket编程
Java基础:三步学会Java Socket编程 http://tech.163.com 2006-04-10 09:17:18 来源: java-cn 网友评论11 条 论坛 第一步 ...
- 快看Sample代码,速学Swift语言(2)-基础介绍 快看Sample代码,速学Swift语言(1)-语法速览
快看Sample代码,速学Swift语言(2)-基础介绍 Swift语言是一个新的编程语言,用于iOS, macOS, watchOS, 和 tvOS的开发,不过Swift很多部分内容,我们可以从C或 ...
- 使用Code First建模自引用关系笔记 asp.net core上使用redis探索(1) asp.net mvc控制器激活全分析 语言入门必学的基础知识你还记得么? 反射
使用Code First建模自引用关系笔记 原文链接 一.Has方法: A.HasRequired(a => a.B); HasOptional:前者包含后者一个实例或者为null HasR ...
- Java Socket编程 深入讲解?你之前真的学懂了吗
很多人学习Socket往往会碰到这样哪样的问题,可以看看下面,加深理解.能看懂多少看懂多少. Socket是Java网络编程的基础,了解还是有好处的,这篇文章主要讲解Socket的基础编程.Socke ...
随机推荐
- Python练手之爬虫
很久没更新博客了,最近自学Python,写个在百度上爬算法题题解的爬虫,第一次写爬虫..纯当练手 慢慢来.. #coding:utf-8 ''' Created on 2016年11月22日 @aut ...
- SonarQube的安装、配置与使用(windows)
onarQube是管理代码质量一个开放平台,可以快速的定位代码中潜在的或者明显的错误,下面将会介绍一下这个工具的安装.配置以及使用. 准备工作: 1.jdk(不再介绍) 2.sonarqube:htt ...
- 学习Java你必须了解的知识
1.什么是Java虚拟机?为什么Java被称作是平台无关的编程语言? Java虚拟机是一个可以执行Java字节码的虚拟机进程.Java源文件被编译成能被Java虚拟机执行的字节码文件.Java应用程序 ...
- JAVA类课后练习
1.Pg168--2 package com.hanqi; import java.util.Scanner; public class Rectangle { Rectangle() { //完成初 ...
- 结合Python代码介绍音符起始点检测 (onset detection)
本文由 meelo 原创,请务必以链接形式注明 本文地址 音符起始点检测介绍 音符起始点检测(onset detection)是音乐信号处理中非常重要的一个算法.节拍和速度(tempo)的检测都会基于 ...
- Python3语法详解
一.下载安装 1.1Python下载 Python官网:https://www.python.org/ 1.2Python安装 1.2.1 Linux 平台安装 以下为在Unix & Linu ...
- 【Codechef】Random Number Generator(多项式除法)
题解 前置技能 1.多项式求逆 求\(f(x)\*g(x) \equiv 1 \pmod {x^{t}}\) 我们在t == 1时,有\(f[0] = frac{1}{g[0]}\) 之后呢,我们倍增 ...
- Python全栈开发之15、DOM
文档对象模型(Document Object Model,DOM)是一种用于HTML和XML文档的编程接口.它给文档提供了一种结构化的表示方法,可以改变文档的内容和呈现方式.我们最为关心的是,DOM把 ...
- Scala入门4(_的用法)
从网上找了一篇博客,详细讲解了Scala下划线的用法,这里做保留 博客链接
- php开启memcache扩展
1.下载memcache.dll(php7)https://github.com/nono303/PHP7-memcahe-dll/tree/master 2.将dll文件放到php7/ext目录下 ...