本篇繼上一篇:Silverlight+WCF 實戰-網絡象棋最終篇之對戰視頻-上篇[客戶端開啓視頻/註冊編號/接收視頻](五)
一:對戰視頻 簡單原理
二:對戰視頻 步驟解析:
三:對戰視頻 具體實施
1:如何打開視頻
2:Silverlight如何使用Socket進行通訊
2.1:與遠程建立鏈接:
2.2:註冊編號[這裏的規則是「房間號+棋手顏色值」]
2.3:開新線程,等待接收對方視頻
2.4:將視頻顯示出來,需要用主線程來操作
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:額外的處理事件
不過這兩步,網上都有現成的代碼,直接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