• Silverlight Socket通信学习笔记


          之前因为项目的关系,涉及到与服务器实时通信,比如通过GPRS将GPS的位置信息等信息发送到服务器,然后再转发给Silverlight应用程序,最后在地图上标示出实时的地理位置,查了查相关的资料,网上给出的比较好的方法就是利用Socket与服务器通信。于是这两天看了看Silverlight下的Socket通信,在此将学习的心得和实现过程作一个记录,以供相互学习和交流。

          园子里关于这方面的内容已经有很多大神写过了,这里小小的推荐一下:

          http://www.cnblogs.com/webabcd/archive/2008/12/22/1359551.html

          因此本文的重点知识说一下具体实现的过程,细节和原理性的东西不会太多,因为本人也是新手,所以就不卖弄了。之前说到和地图结合,所以本文的后续工作将会把Silverlight的Socket通信与ArcGIS 的地图结合,来实现一个小小的功能,而本篇则主要关于Socket的实现过程。下面就进入正题吧。

    一.Silverlight的Socket通信和控制台、WinForm下的Socket通信有很大的区别。

    对于后两者的Socket通信,其过程就是开启端口,绑定端口,监听端口,连接,接收数据,发送数据。

    而在Silverlight中则不太一样,在Silverlight中,首先是Silverlight客户端自动向943端口的服务器端发送一个“<policy-file-request/>”的语句请求,然后服务器端向客户端发送策略文件:

    clientaccesspolicy.xml,例如:

    <?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>

    发送之后,才允许和服务器进行Socket通信,之后的过程则都是一样。

    二、服务器端

    2.1、Silverligh中发送策略文件服务

    上面说到,Silverlight中,服务器端会向客户端发送策略文件,然后才能开始Socket通信,下面给出服务器端的发送策略文件服务的代码,该代码是在网上找的,是别人已经写好的一个类,所以在编写Silverlight 的Socket通信程序时,只要添加这个类就好了,代码如下:

    using System;
    using System.Net.Sockets;
    using System.Net;
    using System.Threading;
    using System.IO;
    using System.Windows.Forms;
    
    namespace WindowsServer
    {
        class PolicySocketServer
        {
            TcpListener _Listener = null;
            TcpClient _Client = null;
            static ManualResetEvent _TcpClientConnected = new ManualResetEvent(false);
            const string _PolicyRequestString = "<policy-file-request/>";
            int _ReceivedLength = 0;
            byte[] _Policy = null;
            byte[] _ReceiveBuffer = null;
    
            private void InitializeData()
            {
                string policyFile = Path.Combine(Application.StartupPath, "clientaccesspolicy.xml");
                using (FileStream fs = new FileStream(policyFile, FileMode.Open))
                {
                    _Policy = new byte[fs.Length];
                    fs.Read(_Policy, 0, _Policy.Length);
                }
                _ReceiveBuffer = new byte[_PolicyRequestString.Length];
            }
            public void StartSocketServer()
            {
                InitializeData();
    
                try
                {
                    _Listener = new TcpListener(IPAddress.Any, 943);
                    _Listener.Start();
                    while (true)
                    {
                        _TcpClientConnected.Reset();
                        _Listener.BeginAcceptTcpClient(new AsyncCallback(OnBeginAccept), null);
                        _TcpClientConnected.WaitOne();
                    }
                }
                catch (Exception)
                {
                }
            }
    
            private void OnBeginAccept(IAsyncResult ar)
            {
                _Client = _Listener.EndAcceptTcpClient(ar);
                _Client.Client.BeginReceive(_ReceiveBuffer, 0, _PolicyRequestString.Length, SocketFlags.None,
                    new AsyncCallback(OnReceiveComplete), null);
            }
    
            private void OnReceiveComplete(IAsyncResult ar)
            {
                try
                {
                    _ReceivedLength += _Client.Client.EndReceive(ar);
                    if (_ReceivedLength < _PolicyRequestString.Length)
                    {
                        _Client.Client.BeginReceive(_ReceiveBuffer, _ReceivedLength,
                            _PolicyRequestString.Length - _ReceivedLength,
                            SocketFlags.None, new AsyncCallback(OnReceiveComplete), null);
                        return;
                    }
                    string request = System.Text.Encoding.UTF8.GetString(_ReceiveBuffer, 0, _ReceivedLength);
                    if (StringComparer.InvariantCultureIgnoreCase.Compare(request, _PolicyRequestString) != 0)
                    {
                        _Client.Client.Close();
                        return;
                    }
                    _Client.Client.BeginSend(_Policy, 0, _Policy.Length, SocketFlags.None,
                        new AsyncCallback(OnSendComplete), null);
                }
                catch (Exception)
                {
                    _Client.Client.Close();
                }
                _ReceivedLength = 0;
                _TcpClientConnected.Set(); //Allow waiting thread to proceed 
            }
    
            private void OnSendComplete(IAsyncResult ar)
            {
                try
                {
                    _Client.Client.EndSendFile(ar);
                }
                catch (Exception)
                {
                }
                finally
                {
                    _Client.Client.Close();
                }
            } 
        }
    }

    2.2、启动策略文件服务,声明Socket,监听端口,接收数据,发送数据。

    启动策略文件服务

    #region Start The Policy Server 验证策略文件
                PolicySocketServer StartPolicyServer = new PolicySocketServer();
                Thread th = new Thread(new ThreadStart(StartPolicyServer.StartSocketServer));
                th.IsBackground = true;
                th.Start();
                #endregion 

    声明Socket,绑定端口,开始监听

     private void StartButton_Click(object sender, EventArgs e)
            {//创建Socket
                listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //获取主机信息
                IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
    
                //把IP和端口转换化为IPEndPoint实例,端口号取4530
                //Win7 中开启了IPV6的地址,因此0,1对应的是IPV6的地址,2,3对应IPV4地址,3对应本机的IP地址
                //XP中没有开启IPV6
                HostIPTextBox.Text = ipHostInfo.AddressList[3].ToString();
    
                if (!string.IsNullOrEmpty(PortTextBox.Text))
                {
                    //获取端口号
                    int port = Convert.ToInt32(PortTextBox.Text.Trim());
                    //获得本机的IP地址
                    localEP = new IPEndPoint(ipHostInfo.AddressList[3], port);
                }
                else
                {
                   //默认4530端口
                    ipAddress = IPAddress.Parse("127.0.0.1");
                    localEP = new IPEndPoint(ipHostInfo.AddressList[3], 4530);
                }
    
                try
                {
                    //绑定指定的终结点
                    listener.Bind(localEP);
                    //开始监听
                    listener.Listen(10);
                    //一直循环接收客户端的消息,开启监听端口线程
                    ThreadStart threadwatchStart = new ThreadStart(WatchConnecting);
                    threadWatch = new Thread(threadwatchStart);
                    threadWatch.IsBackground = true;
                    threadWatch.Start();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Data.ToString());
                }
            }

    连接端口,接收数据

       private void WatchConnecting()
            {
                ChangeStatue("等待Silverlight客户端连接.....");
                while (true)  //持续不断监听客户端发来的请求  
                {
                    listener.BeginAccept(AcceptCallBack, listener);
                    _flipFlop.WaitOne();
                }
            }
     private  void AcceptCallBack(IAsyncResult asyresult)
            {
                Socket listener = (Socket)asyresult.AsyncState;
                Socket socket = listener.EndAccept(asyresult);
                ChangeStatue("连接到Silverlight客户端....");
                _flipFlop.Set();
                var state = new StateObject();
                state.Socket = socket;
                socket.BeginReceive(state.Buffer, 0, StateObject.BufferSize, 0, ReciverCallBack, state);
            }
      private void ReciverCallBack(IAsyncResult asyResult)
            {
                StateObject state = (StateObject)asyResult.AsyncState;
                Socket socket = state.Socket;
                int read = socket.EndReceive(asyResult);
                if (read > 0)
                {
                    string chunk = Encoding.UTF8.GetString(state.Buffer, 0, read);
                    state.StringBuilder.Append(chunk);
                    if (state.StringBuilder.Length > 0)
                    {
                        string result = state.StringBuilder.ToString();
                        ChangeStatue("成功接收到消息:"+result);
                        ChangeReciveText(result);
                        Send(socket, SendTextBox.Text);
                        AddListItems("接收消息:"+result+"\n");
                        AddListItems("发送消息:" + SendTextBox.Text + "\n");
                    }
                }
            }

    发送数据

     private void Send(Socket handler, String data)
            {
                byte[] byteData = Encoding.UTF8.GetBytes(data);
                handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallBack), handler);
            }
    
            private void SendCallBack(IAsyncResult asyResult)
            {
                try
                {
                    Socket handler = (Socket)asyResult.AsyncState;
                    int byteSent = handler.EndSend(asyResult);
                    if (byteSent > 0)
                    {
                        ChangeStatue("发送数据成功!");
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Data.ToString());
                }
            }

    StateObject类:

     public class StateObject
        {
            public Socket Socket;
            public StringBuilder StringBuilder = new StringBuilder();
            public const int BufferSize = 1024;
            public byte[] Buffer = new byte[BufferSize];
            public int TotalSize;
        }

    客户端:

     和服务器端类似,客户端的操作包括:声明Socket,指定服务器地址和端口,连接到指定的服务器端口,发送数据,接收数据。

    下面是具体的实现代码:

    声明Socket

    private Socket socket;

    指定服务器地址和端口,开始连接

      private void SendButton_Click(object sender, RoutedEventArgs e)
            {
                if(string.IsNullOrEmpty(IPTextBox.Text)||string.IsNullOrEmpty(PortTextBox.Text))
                {
                    MessageBox.Show ("请输入主机IP地址和端口号!");
                    return;
                }
                //ip地址
                string host=IPTextBox.Text.Trim();
                //端口号
                int port=Convert.ToInt32(PortTextBox.Text.Trim());
                //建立终结点对象
                DnsEndPoint hostEntry=new DnsEndPoint(host,port);
                //创建一个Socket对象
                socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
                //创建Socket异步事件参数
                SocketAsyncEventArgs socketEventArg=new SocketAsyncEventArgs ();
                //将消息转化为发送的byte[]格式
                byte[]buffer=Encoding.UTF8.GetBytes(MessageTextBox.Text);
                //注册Socket完成事件
                socketEventArg.Completed+=new EventHandler<SocketAsyncEventArgs>(socketEventArg_Completed);
                //设置Socket异步事件远程终结点
                socketEventArg.RemoteEndPoint=hostEntry;
                //将定义好的Socket对象赋值给Socket异步事件参数的运行实例属性
                socketEventArg.UserToken = buffer;
                try
                {
                    socket.ConnectAsync(socketEventArg);
                }
                catch(SocketException ex)
                {
                    throw new SocketException((int)ex.ErrorCode);
                }
            }

    向服务器发送数据,并接受服务器回复的消息。

     private void socketEventArg_Completed(object sender, SocketAsyncEventArgs e)
            {
               //检查是否发送出错
                if (e.SocketError != SocketError.Success)
                {
                    if (e.SocketError == SocketError.ConnectionAborted)
                    {
                        Dispatcher.BeginInvoke(() => MessageBox.Show("连接超时....请重试!"));
                    }
                    else if (e.SocketError == SocketError.ConnectionRefused)
                    {
                        Dispatcher.BeginInvoke(() => MessageBox.Show("无法连接到服务器端:"+e.SocketError));
                    }else
                    {
                        Dispatcher.BeginInvoke(() => MessageBox.Show("出错了!"+e.SocketError));
                    }
                    return;
                }
               //如果连接上,则发送数据
                if (e.LastOperation == SocketAsyncOperation.Connect)
                {
                        byte[] userbytes = (byte[])e.UserToken;
                        e.SetBuffer(userbytes, 0, userbytes.Length);
                        socket.SendAsync(e);
                        
                }//如果已发送数据,则开始接收服务器回复的消息
                else if (e.LastOperation == SocketAsyncOperation.Send)
                {
                    Dispatcher.BeginInvoke(() =>
                    {
                        listBox1.Items.Add("客户端在" + DateTime.Now.ToShortTimeString() + ",发送消息:" + MessageTextBox.Text);
                    });
                    byte[] userbytes = new byte[1024];
                    e.SetBuffer(userbytes, 0, userbytes.Length);
                    socket.ReceiveAsync(e);
                }//接收服务器数据
                else if (e.LastOperation == SocketAsyncOperation.Receive)
                {
                    string RecevieStr = Encoding.UTF8.GetString(e.Buffer, 0, e.Buffer.Length).Replace("\0", "");
                    Dispatcher.BeginInvoke(() =>
                    {
                        listBox1.Items.Add("服务器在" + DateTime.Now.ToShortTimeString() + ",回复消息:" + RecevieStr);
                    });
                    socket.Close();
                }
            }
       

    xaml代码:

    <UserControl
        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"
        xmlns:esri="http://schemas.esri.com/arcgis/client/2009" x:Class="SilverlightSocket.MainPage"
        mc:Ignorable="d"
        d:DesignHeight="417" d:DesignWidth="530">
    
        <Grid x:Name="LayoutRoot" Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.868*"/>
                <ColumnDefinition Width="0.135*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <esri:Map Background="White" WrapAround="True" Grid.ColumnSpan="2">
                <esri:ArcGISTiledMapServiceLayer Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"/>
            </esri:Map>
            <StackPanel Grid.Column="1" Background="#7F094870">
                <StackPanel.Effect>
                    <DropShadowEffect/>
                </StackPanel.Effect>
                <TextBlock x:Name="textBlock1" Text="主机IP" Grid.Column="1" Margin="5,5,0,0" Foreground="#FFE7D4E3" FontWeight="Bold" />
                <TextBox x:Name="IPTextBox" Text="169.254.57.67" Grid.Column="1" d:LayoutOverrides="Width" Margin="5,5,0,0" HorizontalAlignment="Left"/>
                <TextBlock x:Name="textBlock2" Text="端口号" Grid.Column="1" Margin="5,5,0,0" Foreground="#FFE7D4E3" FontWeight="Bold" />
                <TextBox x:Name="PortTextBox" Width="51" Text="4530" Grid.Column="1" Margin="5,5,0,0" HorizontalAlignment="Left"/>
                <TextBlock  x:Name="textBlock4" Text="消息记录:" Height="23" Grid.Column="1" Margin="5,5,0,0" Foreground="#FFE7D4E3" FontWeight="Bold" />
                <ListBox  x:Name="listBox1" Grid.Column="1" Margin="5,5,0,0" Height="150" />
                <TextBlock x:Name="textBlock3" Text="发送信息内容" Height="16" Grid.Column="1" d:LayoutOverrides="Width" Margin="5,5,0,0" Foreground="#FFE7D4E3" FontWeight="Bold" />
                <TextBox x:Name="MessageTextBox" Grid.Column="1" Height="50" Margin="5,5,0,0" />
                <Button Content="发送" Height="23" x:Name="SendButton" Grid.Column="1" Margin="5,5,0,0" />
                <Button Content="清空" Height="23" x:Name="ClearButton" Grid.Column="1" Margin="5,5,0,0" />
            </StackPanel>
        </Grid>
    </UserControl>

    最后效果示意图:

    服务器端:

    Silverlight客户端:

    后续工作中将结合地图来实现模拟实时位置的显示功能。。。。

    (版权所有,转载请标明出处)

  • 相关阅读:
    Java中的集合类
    Java中的包装类
    Java中的多线程总结(转)
    Java开发中的23种设计模式详解 (转)
    Java中异常处理和设计
    Jmeter的ForEach控制器
    Jmeter事务控制器
    Jmeter下线程顺序启动
    Jmeter跨线程调用参数
    Jmeter使用Python
  • 原文地址:https://www.cnblogs.com/potential/p/2873035.html
Copyright © 2020-2023  润新知