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

相关文章
|
1月前
|
网络协议 Java API
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
61 2
|
1月前
|
存储 网络协议 Java
【网络】UDP回显服务器和客户端的构造,以及连接流程
【网络】UDP回显服务器和客户端的构造,以及连接流程
51 2
|
30天前
|
安全 区块链 数据库
|
2月前
|
存储 机器人 Linux
Netty(二)-服务端网络编程常见网络IO模型讲解
Netty(二)-服务端网络编程常见网络IO模型讲解
|
2月前
|
网络协议 C语言
C语言 网络编程(十三)并发的TCP服务端-以进程完成功能
这段代码实现了一个基于TCP协议的多进程并发服务端和客户端程序。服务端通过创建子进程来处理多个客户端连接,解决了粘包问题,并支持不定长数据传输。客户端则循环发送数据并接收服务端回传的信息,同样处理了粘包问题。程序通过自定义的数据长度前缀确保了数据的完整性和准确性。
|
2月前
|
网络协议 C语言
C语言 网络编程(十一)TCP通信创建流程---服务端
在服务器流程中,新增了绑定IP地址与端口号、建立监听队列及接受连接并创建新文件描述符等步骤。`bind`函数用于绑定IP地址与端口,`listen`函数建立监听队列并设置监听状态,`accept`函数则接受连接请求并创建新的文件描述符用于数据传输。套接字状态包括关闭(CLOSED)、同步发送(SYN-SENT)、同步接收(SYN-RECEIVE)和已建立连接(ESTABLISHED)。示例代码展示了TCP服务端程序如何初始化socket、绑定地址、监听连接请求以及接收和发送数据。
|
2月前
|
网络协议 C语言
C语言 网络编程(十四)并发的TCP服务端-以线程完成功能
这段代码实现了一个基于TCP协议的多线程服务器和客户端程序,服务器端通过为每个客户端创建独立的线程来处理并发请求,解决了粘包问题并支持不定长数据传输。服务器监听在IP地址`172.17.140.183`的`8080`端口上,接收客户端发来的数据,并将接收到的消息添加“-回传”后返回给客户端。客户端则可以循环输入并发送数据,同时接收服务器回传的信息。当输入“exit”时,客户端会结束与服务器的通信并关闭连接。
|
2月前
|
C语言
C语言 网络编程(八)并发的UDP服务端 以进程完成功能
这段代码展示了如何使用多进程处理 UDP 客户端和服务端通信。客户端通过发送登录请求与服务端建立连接,并与服务端新建的子进程进行数据交换。服务端则负责接收请求,验证登录信息,并创建子进程处理客户端的具体请求。子进程会创建一个新的套接字与客户端通信,实现数据收发功能。此方案有效利用了多进程的优势,提高了系统的并发处理能力。
|
2月前
|
网络协议 C语言
C语言 网络编程(十)TCP通信创建流程---客户端
在TCP通信中,客户端需通过一系列步骤与服务器建立连接并进行数据传输。首先使用 `socket()` 函数创建一个流式套接字,然后通过 `connect()` 函数连接服务器。连接成功后,可以使用 `send()` 和 `recv()` 函数进行数据发送和接收。最后展示了一个完整的客户端示例代码,实现了与服务器的通信过程。
|
2月前
|
C语言
C语言 网络编程(九)并发的UDP服务端 以线程完成功能
这是一个基于UDP协议的客户端和服务端程序,其中服务端采用多线程并发处理客户端请求。客户端通过UDP向服务端发送登录请求,并根据登录结果与服务端的新子线程进行后续交互。服务端在主线程中接收客户端请求并创建新线程处理登录验证及后续通信,子线程创建新的套接字并与客户端进行数据交换。该程序展示了如何利用线程和UDP实现简单的并发服务器架构。

热门文章

最新文章