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

  • 相关阅读:
    Java.io.outputstream.PrintStream:打印流
    Codeforces 732F. Tourist Reform (Tarjan缩点)
    退役了
    POJ 3281 Dining (最大流)
    Light oj 1233
    Light oj 1125
    HDU 5521 Meeting (最短路)
    Light oj 1095
    Light oj 1044
    HDU 3549 Flow Problem (dinic模版 && isap模版)
  • 原文地址:https://www.cnblogs.com/chenxizhang/p/2173101.html
Copyright © 2011-2022 走看看