zoukankan      html  css  js  c++  java
  • 你得学会并且学得会的Socket编程基础知识(续)——Silverlight客户端

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

    http://www.cnblogs.com/chenxizhang/archive/2011/09/10/2172994.html

    本文将在这个案例的基础上,加入一个特殊场景,利用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端口监听。

    本文完整代码如下 https://files.cnblogs.com/chenxizhang/SocketWorkshop(with-silverlight).rar

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

    第一步:创建Silverlight项目

    image

    image

    第二步:设计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>
    

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

    image

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

    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的代码,是因为这里就会遇到连接问题,其他代码先不着急写出来。我们可以运行起来看看

    image

    点击“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客户端能够连接到服务器了

    image

    既然连接上了服务器,那么就让我们来将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);
            }
        }
    }
    

    运行起来看看吧

    image

    image

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

    本文完整代码如下 https://files.cnblogs.com/chenxizhang/SocketWorkshop(with-silverlight).rar

  • 相关阅读:
    flex+spring3.0+blazds 数据推送整理版
    深入理解java虚拟机学习笔记1.
    一个netty序列化引起的思考。
    万年历
    面试理论题
    EF之Database First
    20120902 09:29 SQL、LINQ、Lambda 三种用法
    内容漂浮在图片上(div漂浮)
    生成订单号
    时间对比
  • 原文地址:https://www.cnblogs.com/chenxizhang/p/2173101.html
Copyright © 2011-2022 走看看