效果
先看看效果再说,基本逻辑是两个人通过Silverlight端,借助TCP协议分别向服务器不断传输视频,服务器接收到视频后,会检测这些视频是发给谁的,然后回调某个客户端来接收并显示这些视频。
实现
双工的服务契约定义:
[ServiceContract(CallbackContract
=
typeof
(IChatServiceCallBack))]
public interface IChatService
{
[OperationContract]
void SendVideo(UserVideo userVideo);
}
[ServiceContract]
public interface IChatServiceCallBack
{
[OperationContract(IsOneWay = true )]
void GetVideos(List < UserVideo > listVideos);
}
public interface IChatService
{
[OperationContract]
void SendVideo(UserVideo userVideo);
}
[ServiceContract]
public interface IChatServiceCallBack
{
[OperationContract(IsOneWay = true )]
void GetVideos(List < UserVideo > listVideos);
}
数据契约,由三部分组成,发送者,接受者和视频流,方便服务器进行判断,选择接收的回调句柄。
遗留问题
1、由于客户端是定时上传视频流,而非长连接方式,需要不停的调用服务器来上传视频,有些耗资源,并且有时会出现下面的异常,猜想是由于不停的连接导致。
[DataContract]
public class UserVideo
{
[DataMember]
public string UserName { get ; set ; }
[DataMember]
public string PartnerName { set ; get ; }
[DataMember]
public byte [] VideoByte { set ; get ; }
}
public class UserVideo
{
[DataMember]
public string UserName { get ; set ; }
[DataMember]
public string PartnerName { set ; get ; }
[DataMember]
public byte [] VideoByte { set ; get ; }
}
既然是双工的,当然我们还需要定义一个客户端的回调句柄类,包括两个属性,一个是客户端名称,一个是回调句柄:
public
class
ClientHandler {
public string Name { set ; get ; }
public IChatServiceCallBack Client { set ; get ; } }
public string Name { set ; get ; }
public IChatServiceCallBack Client { set ; get ; } }
服务实现,这里没有采用定时检测视频集合,而是在每次有客户端上传视频时进行检测并回调客户端来接收,这样做的好处是事件驱动,比定时检测更具有准确性。
public
class
ChatService : IChatService
{
static List < ClientHandler > listOfClientHandler = new List < ClientHandler > ();
private static List < UserVideo > listVideos = new List < UserVideo > ();
IChatServiceCallBack client;
public void SendVideo(UserVideo userVideo)
{
Console.WriteLine( " receiving... " );
listVideos.Add(userVideo);
client = OperationContext.Current.GetCallbackChannel < IChatServiceCallBack > ();
if (listOfClientHandler.Where(m => m.Name == userVideo.UserName).Count() == 0 )
{
listOfClientHandler.Add( new ClientHandler() { Name = userVideo.UserName, Client = client });
}
foreach (var item in listOfClientHandler)
{
if (listVideos.Where(m => m.PartnerName == item.Name).Count() > 0 )
{
var videos = listVideos.Where(m => m.PartnerName == item.Name).ToList();
item.Client.GetVideos(videos);
Console.WriteLine( " sending... " );
listVideos.RemoveAll(m => m.PartnerName == item.Name); // 处理一个视频后直接从服务器上删除此视频
}
}
}
}
{
static List < ClientHandler > listOfClientHandler = new List < ClientHandler > ();
private static List < UserVideo > listVideos = new List < UserVideo > ();
IChatServiceCallBack client;
public void SendVideo(UserVideo userVideo)
{
Console.WriteLine( " receiving... " );
listVideos.Add(userVideo);
client = OperationContext.Current.GetCallbackChannel < IChatServiceCallBack > ();
if (listOfClientHandler.Where(m => m.Name == userVideo.UserName).Count() == 0 )
{
listOfClientHandler.Add( new ClientHandler() { Name = userVideo.UserName, Client = client });
}
foreach (var item in listOfClientHandler)
{
if (listVideos.Where(m => m.PartnerName == item.Name).Count() > 0 )
{
var videos = listVideos.Where(m => m.PartnerName == item.Name).ToList();
item.Client.GetVideos(videos);
Console.WriteLine( " sending... " );
listVideos.RemoveAll(m => m.PartnerName == item.Name); // 处理一个视频后直接从服务器上删除此视频
}
}
}
}
客户端,基本原理是先发送视频,然后定义回调函数来处理服务器的回调:
void
btnSendVideo_Click(
object
sender, RoutedEventArgs e)
{
System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
timer.Interval = new TimeSpan( 0 , 0 , 0 , 0 , 200 );
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick( object sender, EventArgs e)
{
proxy = new ChatServiceClient();
proxy.GetVideosReceived += new EventHandler < GetVideosReceivedEventArgs > (proxy_GetVideosReceived);
WriteableBitmap bmp = new WriteableBitmap( this .rectangleUser, null );
MemoryStream ms = new MemoryStream();
EncodeJpeg(bmp, ms);
UserVideo userVideo = new UserVideo();
userVideo.PartnerName = this .Partner;
userVideo.UserName = this .User;
userVideo.VideoByte = ms.GetBuffer();
proxy.SendVideoCompleted += (se,ev) => { };
proxy.SendVideoAsync(userVideo);
}
void proxy_GetVideosReceived( object sender, GetVideosReceivedEventArgs e)
{
foreach (ChatService.UserVideo video in e.listVideos)
{
MemoryStream ms = new MemoryStream(video.VideoByte);
BitmapImage bitmap = new BitmapImage();
bitmap.SetSource(ms);
imagePartner.Source = bitmap;
ms.Close();
}
}
{
System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
timer.Interval = new TimeSpan( 0 , 0 , 0 , 0 , 200 );
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick( object sender, EventArgs e)
{
proxy = new ChatServiceClient();
proxy.GetVideosReceived += new EventHandler < GetVideosReceivedEventArgs > (proxy_GetVideosReceived);
WriteableBitmap bmp = new WriteableBitmap( this .rectangleUser, null );
MemoryStream ms = new MemoryStream();
EncodeJpeg(bmp, ms);
UserVideo userVideo = new UserVideo();
userVideo.PartnerName = this .Partner;
userVideo.UserName = this .User;
userVideo.VideoByte = ms.GetBuffer();
proxy.SendVideoCompleted += (se,ev) => { };
proxy.SendVideoAsync(userVideo);
}
void proxy_GetVideosReceived( object sender, GetVideosReceivedEventArgs e)
{
foreach (ChatService.UserVideo video in e.listVideos)
{
MemoryStream ms = new MemoryStream(video.VideoByte);
BitmapImage bitmap = new BitmapImage();
bitmap.SetSource(ms);
imagePartner.Source = bitmap;
ms.Close();
}
}
app.config配置:
<
system.serviceModel
>
< bindings >
< netTcpBinding >
< binding name ="netTcpBindConfig"
closeTimeout ="00:01:00"
openTimeout ="00:01:00"
receiveTimeout ="00:10:00"
sendTimeout ="00:01:00"
transactionFlow ="false"
transferMode ="Buffered"
transactionProtocol ="OleTransactions"
hostNameComparisonMode ="StrongWildcard"
listenBacklog ="10"
maxBufferPoolSize ="2147483647 "
maxBufferSize ="2147483647 "
maxConnections ="10"
maxReceivedMessageSize ="2147483647 " >
< readerQuotas maxDepth ="32"
maxStringContentLength ="2147483647 "
maxArrayLength ="2147483647 "
maxBytesPerRead ="4096"
maxNameTableCharCount ="16384" />
< reliableSession ordered ="true"
inactivityTimeout ="00:10:00"
enabled ="false" />
< security mode ="None" >
</ security >
</ binding >
</ netTcpBinding >
</ bindings >
< services >
< service behaviorConfiguration ="Server.ChatServiceBehavior" name ="Server.ChatService" >
< host >
< baseAddresses >
< add baseAddress ="net.tcp://localhost:4503/ChatService" />
</ baseAddresses >
</ host >
< endpoint address ="" binding ="netTcpBinding" contract ="Server.IChatService" bindingConfiguration ="netTcpBindConfig" ></ endpoint >
< endpoint address ="mex" binding ="mexTcpBinding" contract ="IMetadataExchange" ></ endpoint >
</ service >
</ services >
< behaviors >
< serviceBehaviors >
< behavior name ="Server.ChatServiceBehavior" >
< serviceMetadata />
< serviceDebug includeExceptionDetailInFaults ="false" />
</ behavior >
</ serviceBehaviors >
</ behaviors >
</ system.serviceModel >
< bindings >
< netTcpBinding >
< binding name ="netTcpBindConfig"
closeTimeout ="00:01:00"
openTimeout ="00:01:00"
receiveTimeout ="00:10:00"
sendTimeout ="00:01:00"
transactionFlow ="false"
transferMode ="Buffered"
transactionProtocol ="OleTransactions"
hostNameComparisonMode ="StrongWildcard"
listenBacklog ="10"
maxBufferPoolSize ="2147483647 "
maxBufferSize ="2147483647 "
maxConnections ="10"
maxReceivedMessageSize ="2147483647 " >
< readerQuotas maxDepth ="32"
maxStringContentLength ="2147483647 "
maxArrayLength ="2147483647 "
maxBytesPerRead ="4096"
maxNameTableCharCount ="16384" />
< reliableSession ordered ="true"
inactivityTimeout ="00:10:00"
enabled ="false" />
< security mode ="None" >
</ security >
</ binding >
</ netTcpBinding >
</ bindings >
< services >
< service behaviorConfiguration ="Server.ChatServiceBehavior" name ="Server.ChatService" >
< host >
< baseAddresses >
< add baseAddress ="net.tcp://localhost:4503/ChatService" />
</ baseAddresses >
</ host >
< endpoint address ="" binding ="netTcpBinding" contract ="Server.IChatService" bindingConfiguration ="netTcpBindConfig" ></ endpoint >
< endpoint address ="mex" binding ="mexTcpBinding" contract ="IMetadataExchange" ></ endpoint >
</ service >
</ services >
< behaviors >
< serviceBehaviors >
< behavior name ="Server.ChatServiceBehavior" >
< serviceMetadata />
< serviceDebug includeExceptionDetailInFaults ="false" />
</ behavior >
</ serviceBehaviors >
</ behaviors >
</ system.serviceModel >
1、由于客户端是定时上传视频流,而非长连接方式,需要不停的调用服务器来上传视频,有些耗资源,并且有时会出现下面的异常,猜想是由于不停的连接导致。
2、wcf传输方式配置的是transferMode="Buffered",这种方式并不适合流式传输。实时性上仍有待改进。