Silverlight+WCF 实战-网络象棋最终篇之对战视频-下篇[客户端发送与服务端中转](六)

本篇继上一篇:Silverlight+WCF 实战-网络象棋最终篇之对战视频-上篇[客户端开启视频/注册编号/接收视频](五)

 

 一:对战视频 简单原理

略,内容见上篇。

 

二:对战视频 步骤解析:

略,内容见上篇。

 

三:对战视频 具体实施

1:如何打开视频

略,内容见上篇。

 

2:Silverlight如何使用Socket进行通讯

2.1:与远程建立链接:

2.2:注册编号[这里的规则是“房间号+棋手颜色值”]

2.3:开新线程,等待接收对方视频

2.4:将视频显示出来,需要用主线程来操作

略,以上内容见上篇。作者:路过秋天 博客:http://cyq1162.cnblogs.com/ 秋色园http://www.cyqdata.com/

 

3:图片压缩与视频发送

3.1:图片压缩


我们发送的视频,是通过定时器每秒截5张图发送过去的,每秒钟将产生5张图片,因此,图片压缩变的相当重要。

因此,找一种图片压缩算法,是一种开始:

一开始:是从网上down了个PngEncoder,压缩160*160的截图后,图片大小是40K,看成是4K[因为看字节时是4后面好多0,看少了一个0],兴奋的我~~~

因此一开始在本地测试是正常的,上到网上就oh..no了。

40K*5,即每秒要发送200K的数据,这样就等于把2M/200K带宽给用光了,房东那限制的512K/56K带宽,就更提不上了~~~

最后:还是用上了大伙普通通用的JpgEncoder,压缩160*160的截图后,图片大小是10K,每秒产生10K*5=50K,56K带宽刚好够用了。

 

由于JpgEncoder为第三方插件,因此其代码就不贴了,下面简单介绍下:


1:JpgEncoder下载后内容为:FJ.Core.dll、JpgEncoder.cs两个文件。

2:JpgEncoder.cs有一静态方法,直接可以获取Stream流:

 public static Stream GetStream(WriteableBitmap bitmap)

3:没了~~~

ps:具体FJ.Core.dll、JpgEncoder.cs两个文件可以从下载源码下找到。

 

3.2 视频发送

为了定时发送视频,我们需要开启定时器:


        System.Windows.Threading.DispatcherTimer timer;//全局定义
         public MainPage()
        {
            InitializeComponent();
            timer = new System.Windows.Threading.DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(0.2);//0.2秒一次,每秒5次
            timer.Tick += new EventHandler(timer_Tick);          
        }
        void timer_Tick(object sender, EventArgs e)
        {
           //这里就是发送视频的代码了
        }
        private void btnSend_Click(object sender, RoutedEventArgs e)
        {
            timer.Start();//点击发送视频时,启动定时器即可
        }

在点击发送触发定时器时,发送视频


        byte[] content = new byte[56 * 1024];
        int length;       
        void timer_Tick(object sender, EventArgs e)
        {
            WriteableBitmap img = new WriteableBitmap(canVideo, null);
            Stream stream = JpgEncoder.GetStream(img); //获取压缩后的流
            length = (int)stream.Length;
            stream.Read(content, 0, length);
            stream.Close();

            SocketAsyncEventArgs sendEvent = new SocketAsyncEventArgs();
            sendEvent.SetBuffer(content, 0, length);
            videoSocket.SendAsync(sendEvent);//这里只管发送,发送后的结果不管了。
           
            img = null;
        }

 

至此,客户端的一系列动作就完成了,包括[打开视频/注册编号/发送视频/接收视频],下面到服务端代码上场了。

 

4:控制台服务端Socket中转

4.1:额外的处理事件

第一:服务端需要解决跨域问题,这个看过:Silverlight+WCF 新手实例 象棋 WCF通讯跨域(十五)--就会明白Silverlight客户端和通讯端不在同一站点下通讯时,需要解决跨域问题了。

虽然这里没用WCF,改用Socket方式,一样需要解决跨域问题。

第二:用Socket通讯方式,还需要开启另外的943端口监听。

 

不过这两步,网上都有现成的代码,直接copy就可以了。

步骤如下:

1:新建控制台项目—》起名:TCPService

2:新建类文件:PolicyServer.cs,完整代码如下,大伙直接使用就可以了:

PolicyServer类与跨域xml文件

3:控制台启动首行代码

 static void Main(string[] args)
 {
    PolicyServer ps = new PolicyServer(SocketPolicy.Policy);//Silverlight跨域访问与开启943端口
  }

 

至此,我们添加了个额外的处理类来解决943端口和跨域问题[注意上面代码中xml的端口号配置范围哦],下面开始自己的服务端处理流程

 

4.2:服务端处理流程

4.2.1:开启监听


namespace TCPService
{
    class Program
    {
        public static Dictionary<int, ThreadProxy> soketList;//房号+颜色值
         static void Main(string[] args)
        {
            PolicyServer ps = new PolicyServer(SocketPolicy.Policy);//Silverlight跨域访问及943端口
            //主线程监听
            soketList = new Dictionary<int, ThreadProxy>();
            Console.WriteLine("TCPService正在启动运行");
            IPEndPoint ip = new IPEndPoint(IPAddress.Any, 4505);//本地任意IP及4505端口
            Socket mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            mainSocket.Bind(ip);
            mainSocket.Listen(-1);
            while (true)
            {
                Socket socket = mainSocket.Accept();
                new ThreadProxy(socket).Run();//收到消息即时处理。

            }
        }
        public static void WriteLine(string msg)
        {
            Console.WriteLine(msg);
        }
    }
    class ThreadProxy
    {
        public Socket socket;
        public ThreadProxy(Socket newSocket)
        {
            socket = newSocket;
        }
        public void Run()
        {
            Thread thread = new Thread(new ThreadStart(Action));
            thread.Start();
        }
        public void Action()
        {
            Program.WriteLine("有人来了----");
            //下面开启处理逻辑
        }
   }
}

 

说明:

这里要注意的是监听的端口号必须要跨域文件配置的范围内。同时用一字典泛型soketList保存了所以注册的用户通讯socket,这样可以方便查找对方的socket进行中转。

 

4.2.2 定义下全局变量


        public Socket socket;//我方的Socket
        ThreadProxy youThreadProxy;//对方

        int num;//注册的编号

        byte[] buffer = new byte[30 * 1024];//缓冲字节30K,简单说就是用户10K发送3次,这里收到满30K才转发一次
        bool firstConn = true;//是否第一次建立链接,首次链接都是注册编号,不发送视频的;

 

4.2.3 处理编号注册、移除、查找对方

编号注册:


        private void RegSocket(string key)
        {
            firstConn = false;//注册完后,设置下标识
            if (key.Length < 10)//字节太多就是图片流了
            {
                if (int.TryParse(key, out num))
                {
                    if (Program.soketList.ContainsKey(num))//之前都有人在了
                       {
                        Program.soketList[num].socket.Close();
                        Program.soketList[num].socket.Dispose();
                        Program.soketList.Remove(num);
                    }
                    Program.soketList.Add(num, this);
                    Program.WriteLine("用户注册:" + key);
                    FindYouSocket();
                    return;
                }
            }
        }

线程错误,编号移除:


       private void OnError(ThreadProxy errorProxy,string errorMsg)
        {
            if (errorProxy.socket != null)
            {
                errorProxy.socket.Close();
            }
            Console.WriteLine("删除用户:" + errorProxy.num +"错误信息:"+ errorMsg);
            Program.soketList.Remove(errorProxy.num);
            
        }

查询对方:


       private void FindYouSocket()
       {
            int youNum = num % 2 == 0 ? num - 1 : num + 1;
            if (Program.soketList.ContainsKey(youNum))
            {
                youThreadProxy = Program.soketList[youNum];
            }
         }

 

4.2.4 主业务处理中转流程


       public ThreadProxy(Socket newSocket)
        {
            socket = newSocket;
            socket.SendBufferSize = buffer.Length;
            socket.ReceiveBufferSize = buffer.Length;
        }
        public void Run()
        {
            Thread thread = new Thread(new ThreadStart(Action));
            thread.Start();
        }
        public void Action()
        {
            Program.WriteLine("有人来了----");
            try
            {
                while (true)
                {
                    if (socket.Connected)
                    {
                        int length = 0, count = 0;
                        do
                        {
                            System.Threading.Thread.Sleep(20);//关键点,请求太快数据接收不全
                            length = socket.Receive(buffer, count, socket.Available, 0);
                            count = count + length;

                        }
                        while (socket.Available > 0);

                        if (count > 1)
                        {

                            if (count < 4)//小字节,命令字符
                            {
                                if (firstConn)//首次登陆,需要注册ID
                                {
                                    string key = ASCIIEncoding.ASCII.GetString(buffer, 0, count);
                                    RegSocket(key);
                                }
                            }
                            else if (youThreadProxy == null)
                            {
                                Program.WriteLine("没人接收。。。");
                                FindYouSocket();
                            }
                            else if (youThreadProxy.canReceive)//对方允许接收图片发送
                                {
                                Program.WriteLine("图片来了:" + count);
                                if (youThreadProxy.socket.Connected)
                                {
                                    Program.WriteLine("图片转发:" + buffer.Length);
                                    try
                                    {
                                        youThreadProxy.socket.Send(buffer, count, 0);
                                    }
                                    catch(Exception err)
                                    {
                                        OnError(youThreadProxy, err.Message);
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        OnError(this,"socket链接已关闭");
                        break;
                    }
                }
            }
            catch(Exception err)
            {
                OnError(this,err.Message);
            }

        }

处理流程也很简单,根据请求的字节大小来调用是“注册”还是“中转”。

至此,整个完整的视频传输篇完成了,完成的图片和上一节一样了:

 

 

最后是大家期待已久的示例源码下载:点击下载 [别忘了留下言推荐下哦^-^]

说明:视频源码中的内容会多一些,包括一开始我写的一些其它杂七杂八的代码,不过不影响整个的运行。

 

最后:谢谢大家对本系列的喜欢,谢谢支持~

PS:传说点一下推荐会有10个园豆,喜欢麻烦点一下“推荐”,thank you very much!!

版权声明:本文原创发表于博客园,作者为路过秋天,原文链接:

http://www.cnblogs.com/cyq1162/archive/2010/12/03/1895177.html

时间: 2024-09-22 10:54:11

Silverlight+WCF 实战-网络象棋最终篇之对战视频-下篇[客户端发送与服务端中转](六)的相关文章

Silverlight+WCF 实战-网络象棋最终篇之对战视频-上篇[客户端开启视频/注册编号/接收视频](五)

前言: 近期在忙点"秋色园"的事情,所以网络象棋这一块文章就写的相对慢,而且刚好接上篇:Silverlight+WCF 实战-网络象棋最终篇之非线程阻塞倒计时窗口(四)  之后, 是一些代码修改,会比较枯燥,所以没接着写,不过有昨天有网页表示对象棋在线演示中的 对战视频 感兴趣,希望可以提前看到代码,所以本次就提前写里面的对战视频这一块. 由于对战视频采用控制台程序,并没有在服务器运行,所以在线演示版本里一进入显示是显示"未链接"的提示. 作者:路过秋天 博客:ht

Silverlight+WCF 实战-网络象棋最终篇之房间装修-Silverlight端[带第九阶段源码](三)

在线演示地址:Silverlight+WCF 新手实例 象棋 在线演示 上一系列四十篇索引:Silverlight+WCF 新手实例 象棋 专题索引     本篇紧接着上一篇:Silverlight+WCF 实战-网络象棋最终篇之房间装修-WCF端(二) 继续为房间进行如下的装修:   代码实现[Silverlight端] 说明: 由于更换背景引入图片,房间的属性发生了较大的变化,由此引发了客户端房间类较大的改动.     1:Silverlight端:GameRoom类大调整[被注释的是原来的

Silverlight+WCF 实战-网络象棋最终篇之解决重复的消息提示-状态重置(九)

上节留下的问题: 在上一节:Silverlight+WCF 网络象棋 终极篇 解决重复的消息提示(八) 中,我们解决了重复登陆时产生的多次消息的重复提示. 不过由此优化产生的另一个问题:全局只有一个实例,在来回的切换房间或进出时,需要重置状态,我们这节来解决这个问题.     在上节的,我留下了几行这样的注释代码: //loginObj.Reset();//roomObj.Reset();//indexObj.Reset();   本节就顺路把这三个注册的方法给实现了: 1:loginObje.

Silverlight+WCF 实战-网络象棋最终篇之房间装修-WCF端(二)

在线演示地址:Silverlight+WCF 新手实例 象棋 在线演示 上一系列四十篇索引:Silverlight+WCF 新手实例 象棋 专题索引   佛靠金装,人要衣裳,房间也要加金砖.本篇我们来把房间装修下,让它看起来专业一点!   一:效果预览,先上图   这是之前的房间图片: 今天我们要装修成的房间图片: 再上一张游戏中的效果图:   二:实现说明   1:新增加图片 为了实现装修,我这里新增加了3张图片: 1:房间图片 2:房间游戏中状态的图片 3:QQ用户头像 图片是从QQ象棋游戏

Silverlight+WCF 实战-网络象棋最终篇之十字轨迹(一)

前言 继之前Silverlight+WCF 新手实例 象棋系列四十篇之后,一个多月的时间都在写CYQ.Data框架系列[CYQ.Data 轻量数据层之路 框架开源系列 索引], 让各位对该Silverlight+WCF 象棋系列有兴趣的网友久候了,上一系列详见:[Silverlight+WCF 新手实例 象棋 专题索引] 今天开始就在之前四十篇续上,直到把 [Silverlight+WCF 新手实例 象棋 在线演示] 上的最新代码写完,谢谢支持!   乱七杂八说两句: 一个多月没碰VS2010了

Silverlight+WCF 实战-网络象棋最终篇之非线程阻塞倒计时窗口(四)

前言: 在前面的系列中,我们虽然完成了其大部分功能,但是,离正真运行,还是有一大段距离 当你F5运行时,在弹出对话框之后,如果你不即时点确定,或者上个WC回来之后,你会发现已经提示出错了 这节开始,我们将对其进行一小步一小步的优化,来避免一些明显容易引发的错误.   感知一下最原始的消息弹出框如下图:     一:传统消息框,容易引发命案   1:原始的消息框,是线程阻塞类型的,很容易引发超时问题 线程阻塞?怎么理解? 简单的说就是,WCF服务端给客户端发送了消息提示之后,一直进入等待状态,直到

Silverlight+WCF 实战-网络象棋最终篇之解决重复的消息提示(八)

前言: 最近有网友经常会问,在跟着做象棋对战的通讯中,在重复退出进入的时候,消息会重复出现,本节就这问题进行解说与优化.   一:分析问题产生的原因?   1:首先看App.xaml,里面定义了一个全局客户端回调: public static GameService.ServiceClient client;//回调的客户端   并且这个回调我们全局只实例化一次,并且默认加载时定位到登陆页面: private void Application_Startup(object sender, Sta

Silverlight+WCF 新手实例 象棋 主界面-事件区-游戏开始(二十七)

本专题出产简单原由: 一开始的初衷,只是想写个简单的单机BS人机对战版的,开始还下了点AI算法看看的: 但是写到最后,都写成了通讯版本的对战了,只因中间不小心看到了WCF的相关内容,顺便加了进来; 最后就定局了,反正新手实例,能加多点内容就加多点了. 关于原始初衷,后期再补上了.       好了,先上几个附加索引: 1:Silverlight+WCF 新手实例 象棋 在线演示 2:Silverlight+WCF 简单部署问题集 3:Silverlight4 ListBox bug 4:Silv

Silverlight+WCF 新手实例 象棋 主界面-事件区-求和认输(三十二)

在线演示地址:Silverlight+WCF 新手实例 象棋 在线演示   事隔几篇,我们又回到事件区,继续其它两个按钮事件,来张图吧: 在Silverlight+WCF 新手实例 象棋 主界面-事件区-游戏开始(二十七) 和之后的几篇,我们实现了游戏开始, 在这篇之前,基本上双方已可以对战了,看似主体功能已完成.只是,大伙都知道,细节的东西,才是花时间的,漫长的路还在后面....... 如标题所示,这节实现"求和+认输"两个事件.   每次开始,我们都习惯的先写WCF服务端代码,再回