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:圖片壓縮

複製代碼
ExpandedBlockStart.gif
我們發送的視頻,是通過定時器每秒截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爲第三方插件,因此其代碼就不貼了,下面簡單介紹下:

複製代碼
ExpandedBlockStart.gif
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 視頻發送

爲了定時發送視頻,我們需要開啓定時器:

複製代碼
ExpandedBlockStart.gif
        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();
// 點擊發送視頻時,啓動定時器即可
        }
複製代碼

在點擊發送觸發定時器時,發送視頻

複製代碼
ExpandedBlockStart.gif
         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:開啓監聽

複製代碼
ExpandedBlockStart.gif
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 定義下全局變量

複製代碼
ExpandedBlockStart.gif
         public  Socket socket; // 我方的Socket
        ThreadProxy youThreadProxy; // 對方
        int num;//註冊的編號
         byte [] buffer  =   new   byte [ 30   *   1024 ]; // 緩衝字節30K,簡單說就是用戶10K發送3次,這裏收到滿30K才轉發一次
         bool  firstConn  =   true ; // 是否第一次建立鏈接,首次鏈接都是註冊編號,不發送視頻的;
複製代碼

 

4.2.3 處理編號註冊、移除、查找對方

編號註冊:

複製代碼
ExpandedBlockStart.gif
         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 ;
                }
            }
        }
複製代碼

線程錯誤,編號移除:

複製代碼
ExpandedBlockStart.gif
        private   void  OnError(ThreadProxy errorProxy, string  errorMsg)
        {
            
if  (errorProxy.socket  !=   null )
            {
                errorProxy.socket.Close();
            }
            Console.WriteLine(
" 刪除用戶: "   +  errorProxy.num  + " 錯誤信息: " +  errorMsg);
            Program.soketList.Remove(errorProxy.num);
            
        }
複製代碼

查詢對方:

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

 

4.2.4 主業務處理中轉流程

複製代碼
ExpandedBlockStart.gif
        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