[转贴]Silverlight Socket 实现收发信息

原文:http://blog.csdn.net/banmuhuangci/archive/2009/05/16/4192031.aspx 

刚接触Silverlight的时候,除了其异步应用WCF、流媒体、动画效果等方面外,Socket是最另我兴奋的功能。
在Web上实现Socket虽然不是什么新鲜事了,Activex,flash等都可以实现这样的效果,但是Silverlight这样方便的运用Socket让服务器与客户端通信确是我之前没有体验过的。

用它可以做什么?可以连线式的让服务器与客户端交互,而且,是在Web上,那么Web开发游戏,语音,视频聊天等都可以基于Socket功能实现,另外,服务器端是独立出来的,不依赖IIS进程,这样让数据之间的交互更自由。

废话不说,下面来看看如何实现

首先,在进行数据交换之前,我们必须明白Silverlight Socket的一些规矩和原则。

Silverlight客户端的Socket都是异步的,这点很容易明白,另外就是,考虑到Silverlight是应用到Web上的,而Silverlight的Socket自然就有一些安全限制。

每一个请求到服务器端的新的Socket连接会话Silverlight都会先悄悄的用另一个Socket去请求策略文件,这是很多刚接触Silverlight Socket的人感到郁闷的地方,请求策略时,Silverlight会自己发送一个字符串<policy-file-request/>到服务器的943端口,然后你必须在服务器程序里接收该请求,分析是否是策略请求后,发送一个策略文件的字符串给客户端,客户端接收到策略文件后自己分析完后再发送程序员自己写的数据请求。

客户端的策略请求是自动发送的,策略文件的接收和分析也是自动的,是Silverlight自发工作的,不需要程序员手工写代码进行发送接收和分析。

但是,服务器端接收策略请求需要手工完成,程序员必须创建一个Socket监听943端口(该端口是固定的,客户端策略请求固定发送到该端口),然后分析请求过来的数据是否是策略请求,如果是的,那么就读取策略文件,再将该策略文件发送到客户端就可以了。

另外一个限制,Silverlight Socket 数据交换端口必须在4502-4534范围,也就是说,整个Socket将用到两个端口,一个是943用于策略请求,另一个是4502-4534范围的你指定的数据交换端口。

不管你的Socket代码是如何工作,第一次在连接之前,Silverlight都会发送策略请求,只有成功接收到服务器返回的策略文件后,你的Socket代码才能进行工作,所以在第一次连接的时候,实际上Silverlight是进行了两次Socket,第一次请求策略,成功才进行你的Socket,因此,服务器端必要监听两个端口,但是两个监听可以分开在两个线程上工作(两个线程,不是两个进程)。每个会话请求一次策略后,之后的请求就不会再请求策略了,所以他们不能是线性的工作,而是两个独立的监听,否则会阻塞。

我的服务器端的策略监听和数据监听是用的两个子线程运行,而MS的示例是用的异步方法,都是为了不相互阻塞,用MS的方式也许更有效率些,而我是为了让代码更容易理解。

客户端实现了将文本框的内容发送到服务器端,然后服务器收到后显示出来,然后发回一句字符串,关闭连接,客户端收到服务器端的信息后也关闭连接。就这么简单

好后,具体看看示例,说明很详细。(yjmyzz注:周飞原文中的代码有些问题,主要是用户发送的内容长度超过指定缓冲区时,服务端/客户端接收均不完整,我这里做了些改进)

Silverlight客户端:

Xaml部分:

MainPage.Xaml

<UserControl x:Class="SocketDemo.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:DesignWidth="640" d:DesignHeight="480">
    <Grid x:Name="LayoutRoot" Background="White" ShowGridLines="False">
        <Grid.RowDefinitions >
            <RowDefinition Height="*"  />
            <RowDefinition Height="25"  />
            <RowDefinition Height="200"  />
        </Grid.RowDefinitions>
        <ScrollViewer Margin="5" Grid.Row="0">
            <TextBox x:Name="txtToSend" TextWrapping="Wrap"   />
        </ScrollViewer>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Row="1">
            <Button  Click="OnSend" Content="Send" Width="80" Height="20"/>
            <Button Width="80" Height="20" Margin="5,0,0,0" Click="ClearResult" Content="清除结果区"></Button>
        </StackPanel>
        
        <ScrollViewer Margin="5" Grid.Row="2">
            <TextBlock x:Name="txtResult" Text="就绪"  TextWrapping="Wrap"/>
        </ScrollViewer>
    </Grid>
</UserControl>

CS部分:

MainPage.Xaml.cs

using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Browser;

namespace SocketDemo
{
    //socket通讯 silverlight客户端示例(最终修改:菩提树下的杨过 2009-11-28 http://yjmyzz.cnblogs.com/)
    public partial class MainPage : UserControl
    {
        //定义一个可在全局使用的Socket
        Socket socket;

        string splitChar = "^";

        List<byte> _listReceive = new List<byte>(1024);

        public MainPage()
        {
            InitializeComponent();

            //事先生成一些字符串,省得打字 :) 可去掉
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 10; i++)
            {
                for (int j = 0; j < 1024; j++)
                {
                    sb.Append(i);
                }
            }

            txtToSend.Text = sb.ToString();
        }

      

        //发送信息按钮的单击事件
        void OnSend(object sender, EventArgs eventArgs)
        {
            if (txtToSend.Text.Trim().Length == 0) 
            {
                HtmlPage.Window.Alert("请输入发送内容!");
                txtToSend.Focus();
                return;
            }

            //定义一个字节数组,并将文本框的的类容转换为字节数组后存入
            byte[] bytes = Encoding.UTF8.GetBytes(splitChar + txtToSend.Text.Trim().Replace(splitChar,"") + splitChar);             

            //为socket创建示例,并设置相关属性。
            socket = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //定义并实例一个Socket参数
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();

            //设置到远程终节点属性(实际使用中,请将下面的Ip地址换成scoket服务器所在的IP)
            args.RemoteEndPoint = new DnsEndPoint("127.0.0.1", 4502);

            //设置好当Socket任何一个动作完成时的回调函数。
            args.Completed += new EventHandler<SocketAsyncEventArgs>(ConnectComplete);

            //Socket参数的用户标识,实际上就是一个可以传递的OBJECT参数。
            args.UserToken = bytes;

            //执行连接。
            socket.ConnectAsync(args);
        }

        /// <summary>
        /// 连接完成的回调函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ConnectComplete(object sender, SocketAsyncEventArgs e) 
        {
            //当连接成功后,获取Socket参数 e传递过来的用户标识(也就是本示例中用户输入的字符串转换的Byte字节数组)
            byte[] bytes = (byte[])e.UserToken;

            //同步一下上下文,显示一下当前的状态信息。               
            GetText("连接状态:" + e.SocketError.ToString() + ",操作:" + e.LastOperation.ToString());

            if (e.SocketError != SocketError.Success) 
            {                
                return;
            }
                      
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.RemoteEndPoint = e.RemoteEndPoint;          

            //设置Socket参数的缓冲区参数,将我们的字节数组设置为Socket的缓冲区。
            args.SetBuffer(bytes, 0, bytes.Length);
            args.Completed += new EventHandler<SocketAsyncEventArgs>(SendComplete);

            //发送数据
            socket.SendAsync(args);

           
        }

        /// <summary>
        /// 发送完成的回调函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void SendComplete(object sender, SocketAsyncEventArgs e)  
        {
            GetText("发送状态:" + e.SocketError.ToString() + ",操作:" + e.LastOperation.ToString());
            //执行异步接收
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.RemoteEndPoint = e.RemoteEndPoint;
            byte[] buffer = new byte[1024];
            args.SetBuffer(buffer, 0, buffer.Length);
            args.Completed += new EventHandler<SocketAsyncEventArgs>(ReceiveComplate);            
            socket.ReceiveAsync(args);
        }

       

        /// <summary>
        /// 接收完成的回调函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ReceiveComplate(object sender, SocketAsyncEventArgs e) 
        {
            GetText("接收状态:" + e.SocketError.ToString() + ",操作:" + e.LastOperation.ToString());

            char _splitChar = splitChar.ToCharArray()[0];

            bool _IsReceiveEnd = false;

            if (e.Buffer[e.Offset] == _splitChar && e.BytesTransferred > 0) //验证开头是否以分隔符开始
            {
                for (int i = e.Offset + 1; i < e.BytesTransferred; i++)
                {
                    if (e.Buffer[i] == _splitChar) //遇到结束符号
                    {
                        _IsReceiveEnd = true;
                        break;
                    }
                    _listReceive.Add(e.Buffer[i]);
                }
            }
            else 
            {

                _IsReceiveEnd = true;
            }

            if (_IsReceiveEnd)
            {
                string _Content = UTF8Encoding.UTF8.GetString(_listReceive.ToArray(),0,_listReceive.Count);
                _listReceive.Clear();
                _listReceive = new List<byte>(1024);
                GetText(_Content);               
                socket.Close();
                socket = null;
                GetText("本次socket通讯完成,socket对象已关闭!");
                this.Dispatcher.BeginInvoke(() => { this.txtToSend.Text = ""; });
            }
            else 
            {
                //继续接收
                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                args.RemoteEndPoint = e.RemoteEndPoint;
                byte[] buffer = new byte[1024];
                args.SetBuffer(buffer, 0, buffer.Length);
                args.Completed += new EventHandler<SocketAsyncEventArgs>(ReceiveNext);
                socket.ReceiveAsync(args);
            }           
        }

        /// <summary>
        /// 继续接受
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ReceiveNext(object sender, SocketAsyncEventArgs e)
        {
            GetText("接收状态:" + e.SocketError.ToString() + ",操作:" + e.LastOperation.ToString());

            char _splitChar = splitChar.ToCharArray()[0];

            bool _IsReceiveEnd = false;

           
            for (int i = e.Offset ; i < e.BytesTransferred; i++)
            {
                if (e.Buffer[i] == _splitChar) //遇到结束符号
                {
                    _IsReceiveEnd = true;
                    break;
                }
                _listReceive.Add(e.Buffer[i]);
            }

            if (_IsReceiveEnd)
            {
                string _Content = UTF8Encoding.UTF8.GetString(_listReceive.ToArray(), 0, _listReceive.Count);
                _listReceive.Clear();
                _listReceive = new List<byte>(1024);
                GetText(_Content);
                socket.Close();
                socket = null;
                GetText("本次socket通讯完成,socket对象已关闭!");
                this.Dispatcher.BeginInvoke(() => { this.txtToSend.Text = ""; });
            }
            else
            {
                //继续接收
                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                args.RemoteEndPoint = e.RemoteEndPoint;
                byte[] buffer = new byte[1024];
                args.SetBuffer(buffer, 0, buffer.Length);
                args.Completed += new EventHandler<SocketAsyncEventArgs>(ReceiveNext);
                socket.ReceiveAsync(args);
            }           
            
        }

        //同步上下文调用的方法。
        void GetText(object str)
        {
            //异步操作中,无法直接用txtResult.Text="xxx"来赋值,会报异常
            this.Dispatcher.BeginInvoke(() => { this.txtResult.Text =  str.ToString() + "\r\n" + this.txtResult.Text; });

        }

        /// <summary>
        /// 清空结果区
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ClearResult(object sender, RoutedEventArgs e)
        {
            this.Dispatcher.BeginInvoke(() => { this.txtResult.Text = ""; });
        }

        
    }
}

Console服务端:

socket server

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace SocketServer
{
    /// <summary>
    /// socket服务端示例(最终修改:菩提树下的杨过 2009-11-28 http://yjmyzz.cnblogs.com/)
    /// </summary>
    class Program
    {
        static char splitChar = '^';

        static void Main(string[] args)
        {
            Console.WriteLine("================Socket服务开启======================");

            Thread tPolicy = new Thread(PolicyListen);//943策略监听线程
            tPolicy.Start();

            Thread tMsg = new Thread(MessageListen);
            tMsg.Start();
        }

        //监听策略请求和发送策略请求方法
        static void PolicyListen()
        {

            //创建一个Socket用来监听943(固定的)端口的策略请求   
            Socket policy = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            policy.Bind(new IPEndPoint(IPAddress.Any, 943));
            policy.Listen(10);

            //无限循环监听
            while (true)
            {
                if (policy.Blocking)//如果Socket是阻止模式的(这个东西实际上可以用不)
                {
                    //创建Socket,用来获取监听Socket的第一个Socket链接
                    Socket _policy = policy.Accept();

                    //定义一个字符串,该字符串与Silverlight发送过来的请求字符串一样。
                    string policyRequestString = "<policy-file-request/>";

                    //定义一个字节数组
                    byte[] b = new byte[policyRequestString.Length];

                    //将客户端发送过来,服务器接收到的字节数组存入b中
                    _policy.Receive(b);

                    //将接收到的字节数组转换成字符串
                    string requeststring = System.Text.Encoding.UTF8.GetString(b, 0, b.Length);

                    //显示客户端发送的字符串
                    Console.WriteLine(requeststring);

                    //比对客户端发送过来的字符串是否和之前定义的额定好的策略请求字符串相同,如果相同,说明该请求是一个策略请求。
                    if (requeststring == policyRequestString)
                    {
                        //如果客户端发送的是一个策略请求,服务器发送策略文件到客户端
                        SendPolicy(_policy);
                        Console.WriteLine("Policy File have sended");
                        //关闭当前连接Socket
                        _policy.Close();

                    }
                    else// 否则,显示错误
                    {
                        Console.WriteLine("not a sure request string!");
                    }
                }
            }

        }

        //监听信息请求和发送信息方法
        static void MessageListen()
        {
            //创建一个Socket用于监听4502端口,获取接收客户端发送过来的信息
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Bind(new IPEndPoint(IPAddress.Any, 4502));
            socket.Listen(10);

            //无线循环监听
            while (true)
            {
                //创建Socket,用来获取监听Socket的第一个Socket链接
                Socket _client = socket.Accept();

                #region 接受数据
                List<byte> listReceive = new List<byte>(_client.ReceiveBufferSize);

                byte[] b1 = new byte[32];
                _client.Receive(b1);

                if (b1[0] == splitChar)//如果是以分隔符开头
                {
                    for (int i = 1; i < b1.Length; i++)
                    {
                        if (b1[i] == splitChar) //遇到结束符
                        {
                            goto ok;
                        }
                        else 
                        {
                            listReceive.Add(b1[i]);
                        }                        
                    }

                    while (_client.Available > 0)
                    {
                        byte[] b = new byte[32];

                        _client.Receive(b);

                        for (int j = 0; j < b.Length; j++)
                        {
                            if (b[j] == splitChar) //遇到结束符号则退出
                            {
                                goto ok;
                            }
                            else
                            {
                                listReceive.Add(b[j]);
                            }
                        }
                    }
                }
                #endregion

            ok: string _Content = UTF32Encoding.UTF8.GetString(listReceive.ToArray(), 0, listReceive.Count);

                Console.WriteLine("收到" + _client.RemoteEndPoint + ":" + listReceive.Count + " 字节 " + DateTime.Now.ToString() + "\n" + _Content);

                //发回一个信息给客户端,该信息是字节数组,所以我们将信息字符串转换成字节数组
                byte[] _sendData = UTF32Encoding.UTF8.GetBytes(splitChar.ToString() +  "本次Scoket通讯完成,共收到" + _client.RemoteEndPoint + "发来" + listReceive.Count + "字节的内容!(来自服务端)\r\n" + _Content.Replace(splitChar.ToString(),"")  + splitChar.ToString());
                _client.Send(_sendData);
                //关闭当前Socket连接
                _client.Close();

                System.Threading.Thread.Sleep(5);
            }
        }

        //发送策略文件的方法
        //参数是传递进来的Socket连接
        static void SendPolicy(Socket socket)
        {
            //创建一个文件流,该文件留指定代开一个策略文件,至于策略文件的格式,MS的Silverlight有详细说明和配置方法
            string _policyFilePath = Application.StartupPath + "\\PolicyFile.xml";
            FileStream fs = new FileStream(_policyFilePath, FileMode.Open);
            int length = (int)fs.Length;
            byte[] bytes = new byte[length];
            //将策略文件流读到上面定义的字节数组中
            fs.Read(bytes, 0, length);
            //关闭文件流
            fs.Close();
            //其策略文件的字节数组发送给客户端
            socket.Send(bytes, length, SocketFlags.None);
        }

    }
}

服务端策略文件:

PolicyFile.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-4506" protocol="tcp" />
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

源代码下载:

http://files.cnblogs.com/yjmyzz/SocketDemo.rar

时间: 2024-11-02 14:42:29

[转贴]Silverlight Socket 实现收发信息的相关文章

Silverlight Socket 实现收发信息

原文 http://www.cnblogs.com/ZetaChow/archive/2009/05/16/2237347.html 刚接触Silverlight的时候,除了其异步应用WCF.流媒体.动画效果等方面外,Socket是最另我兴奋的功能.   在Web上实现Socket虽然不是什么新鲜事了,Activex,flash等都可以实现这样的效果,但是Silverlight这样方便的运用Socket让服务器与客户端通信确是我之前没有体验过的.   用它可以做什么?可以连线式的让服务器与客户端

socket通信收发错位的问题

问题描述 socket通信收发错位的问题 本人正在写一个带有简单文件和文件夹传输功能的小程序,文件的传输一切正常,但是在传文件夹的时候,有一定几率会有某个文件的收发出现错位的现象,导致传输错误,代码大致如下:(缓冲区中的值我直接用含义代替了,实际前后还有很多代码,数据我是放到char数组中的) 服务端:(s为socket,buff为缓冲区) 发送文件夹(路径) { 查找文件的循环,使用CFileFind,这里直接从找到某个文件开始,每个文件都会进入到下面的代码 if(path为目录) { sen

环信 聊天室如果收发信息哦 有代码吗?大哥求救啊

问题描述 如题 解决方案 就是环信如何收发信息  聊天室功能的解决方案二:发送消息单聊群聊和聊天室没什么区别,只是发消息的时候给消息的类型赋值不同.好像是一个messageType的枚举.具体你看看EMMessage头文件.收消息他们官网文档上写的挺清楚的,你去看看.解决方案三:/*! @method @brief 收到消息时的回调 @param message 消息对象 @discussion 当EMConversation对象的enableReceiveMessage属性为YES时, 会触发

asp.net使用Socket.Send发送信息及Socket.SendFile传输文件的方法_实用技巧

本文实例讲述了asp.net使用Socket.Send发送信息及Socket.SendFile传输文件的方法.分享给大家供大家参考,具体如下: // Displays sending with a connected socket // using the overload that takes a buffer. public static int SendReceiveTest1(Socket server) { byte[] msg = Encoding.UTF8.GetBytes("Th

微信突然挂了?多名用户反映无法登陆或收发信息

网友微博截图10月20日消息,今日下午,部分用户反映微信无法收发消息.经测试发现,在网络良好的情况下,微信确实出现了短时内无法传输信息的情况,不管是文字还是图片均无法发送和接收,几分钟后功能恢复,但马上又出现了同样的问题,且时好时坏.经调查身边的用户发现,安卓系统用户最先出现了这种情况,随后苹果系统用户也出现了无法收发信息的问题.查看微博后发现,微博上也有很多用户反映微信无法收发消息的问题,甚至在用户退出或卸载重装以后,微信无法登陆.初步估计,这次影响的人数还比较多.在排除网络原因之后,基本可以

世纪佳缘网每月约有100万名用户付费收发信息

摘要: 在中国,互联网用户常常希望享受免费服务,而有一家交友网站的用户每天收发的几十条信息却要按每条人民币2元的价格付费. 世纪佳缘网每月约有100万名用户付费收发信息.由此可 在中国,互联网用户常常希望享受免费服务,而有一家交友网站的用户每天收发的几十条信息却要按每条人民币2元的价格付费. 世纪佳缘网每月约有100万名用户付费收发信息.由此可以看出这样一种趋势:中国一些互联网公司开始通过向用户收取使用费和会员费创收,而不是通过竞争激烈的传统广告业务赚钱. 世纪佳缘主要是通过向用户收取信息收发费

微信出现短时故障,部分用户无法收发信息

网易科技讯 10月20日消息,今日下午,部分用户反映微信无法收发消息.经测试发现,在网络良好的情况下,微信确实出现了短时内无法传输信息的情况,不管是文字还是图片均无法发送和接收,几分钟后功能恢复,但马上又出现了同样的问题,且时好时坏.经调查身边的用户发现,http://www.aliyun.com/zixun/aggregation/12650.html">安卓系统手机最先出现了这种情况,随后苹果系统用户也出现了无法收发信息的问题. 查看微博后发现,微博上也有很多用户反映微信无法收发消息的

Silverlight获取WebHost配置信息--WebClient和XmlSerializer模拟

  在我们的silverlight项目中,是被打包为xap zip文件下载到客户端,所以silverlight中的app配置文件我们不能直接修改,而在其宿主web host中的web.config在服务端我们也不能直接访问.在我们的项目中遇见了这个问题所以我就有了此博客.    先说明解决这个问题的方案有: 1:调用wcf,webservice,Asp.net页面等服务端数据源,异步显示在我们的UI. 2:利用silverlight项目的宿主页面 object,传入初始化参数,在silverli

php socket服务器收发数据实现代码

 代码如下 复制代码   /*socket收发数据 @host(string) socket服务器IP @post(int) 端口 @str(string) 要发送的数据 @back 1|0 socket端是否有数据返回 返回true|false|服务端数据 */ function sendSocketMsg($host,$port,$str,$back=0){     $socket = socket_create(AF_INET,SOCK_STREAM,0);     if ($socket