Silverlight虽然提供了socket功能,但仅仅数据接收和发送的基础功能,你必须还要息制定协议和处理烦脑的粘包问题才能更好地进行信息处理.这里介绍如何通过Beetle.SL这个基于Silverlight socket实现的开源组件如何方便地解决这些问题.下面通过组件在Silverlight下实现一个聊天室程序.聊天室的功能主要是登陆,获取当前其他用户和信息转发.
首先Beetle.SL是支持通过对象来描述tcp通讯协议,只需简单地实现IMessage接收就可以了.以下是制定信息
public class Register:MsgBase { public string Name; public override void Load(Beetle.BufferReader reader) { base.Load(reader); Name = reader.ReadString(); } public override void Save(Beetle.BufferWriter writer) { base.Save(writer); writer.Write(Name); } }
这是用户注册到聊天室信息,还有更多的信息描述最终可以下载项目得到就不一一介绍了.
消息制定后就要处理tcp连接和接收问题,Beetle.SL封装了一个TcpChannel对象,提供简单的方法事件,如消息接收,消息发送,连接成功和连接断开等.
Beetle.ChannelController.RegisterHandler(this); mChannel = new Beetle.TcpChannel(new HeadSizePackage()); new Beetle.ChannelController(mChannel); mChannel.Connected += OnConnected; mChannel.Disposed += OnDisposed; mChannel.Error += OnError;
ChannelController是一个消息控制器,它的主要功能是自动把接收的消息路由到有相关消息类型的方法上,可以让不需要判断消息类型执行不同方法的过程.在创建一个TcpChannel的时候指定一个分析器即可,这里是一个消息头4个字节描述大小的分析方式;组件还提供基于结束符的分析器详细可以查看Beetle.SL的代码.信道创建后绑定相关事件,由于ChannelController接管了信道所以不需要绑定消息接收事件.
连接创完成了,现在就看下消息是怎处理的
public void _ReceiveSay(Beetle.TcpChannel channel, Say e) { this.Dispatcher.BeginInvoke(() => { addSay(e.Lines, e.User); }); } public void _OtherUnRegister(Beetle.TcpChannel channel, UnRegister e) { this.Dispatcher.BeginInvoke(() => { ListUsers list = new ListUsers(); mChannel.Send(list); }); } public void _OthreRegister(Beetle.TcpChannel channel, OnRegister e) { this.Dispatcher.BeginInvoke(() => { if (!lstNames.Items.Contains(e.User)) { lstNames.Items.Add(e.User); } }); } public void _OnLogin(Beetle.TcpChannel channel, RegisterResponse e) { ListUsers list = new ListUsers(); mChannel.Send(list); this.Dispatcher.BeginInvoke(() => { cmdSend.IsEnabled = true; cmdConnect.IsEnabled = false; }); } public void _OnList(Beetle.TcpChannel channel, ListUsersResponse e) { this.Dispatcher.BeginInvoke(() => { lstNames.Items.Clear(); foreach (UserInfo item in e.Users) { lstNames.Items.Add(item); } }); }
看上去代码是不是很少,消息处理基本就是这些了.你不需要数据包分析,不需消息判断处理.这些就是Beetle.SL所做的事情.
通讯和输出的代码虽然已经完成,但还有些重要的事情.讲解之前先看下效果图
用过Silverlight的RichTextBox控件的朋友应该知道RichTextBox获取xaml属性有些元素是无法获取的,所以不得不自己封装一下.针对这些信息的传输制定一个转换对象.
public class Line:Beetle.IMessage { public IList<Element> Elements = new List<Element>(); public void Load(Beetle.BufferReader reader) { Elements = reader.ReadObjects<Element>(); } public void Save(Beetle.BufferWriter writer) { writer.Write(Elements); } } public class Element:Beetle.IMessage { public const int TEXT = 1; public const int IMAGE = 2; public const int None = 0; public string Text; public string URI; public byte[] Data; public int EType; public void Load(Beetle.BufferReader reader) { EType = reader.ReadInt32(); Text = reader.ReadString(); URI = reader.ReadString(); Data = reader.ReadByteArray(); } public void Save(Beetle.BufferWriter writer) { writer.Write(EType); writer.Write(Text); writer.Write(URI); writer.Write(Data); } }
以上制定了两种对象来处理,一个是描述行,一个是描述行里面的元素.元素这里并没有所有情况都处理,只是简单地处理TEXT和IMAGE两个元素.在一个简单的聊天室来说够用了.如果实现需要估计要重新设计和定义了.如果要传本机图象那就要多写些代码了,原理很简单imagesource和byte[]转换 http://writeablebitmapex.codeplex.com/
传输的结构制定了就是针对RichTextBox的内容实现一个简单转换方法.
public static IList<Line> GetLines(BlockCollection bc) { List<Line> result = new List<Line>(); Element el; Line line; foreach (Paragraph item in bc) { line = new Line(); result.Add(line); foreach(Inline il in item.Inlines) { if (il is Run) { el = new Element(); el.EType = Element.TEXT; el.Text = ((Run)il).Text; line.Elements.Add(el); } else if( il is InlineUIContainer) { InlineUIContainer iui =(InlineUIContainer)il; if (iui.Child != null && iui.Child is Image) { Image img = (Image)iui.Child; if (img.Source is BitmapImage) { el = new Element(); el.EType = Element.IMAGE; el.URI = ((BitmapImage)img.Source).UriSource.ToString(); line.Elements.Add(el); } } } else{ } } } return result; } public static void SetLines(BlockCollection bc, IList<Line> lines) { foreach (Line item in lines) { Paragraph p = new Paragraph(); bc.Add(p); Run sp = new Run(); sp.Text = "\t"; p.Inlines.Add(sp); foreach (Element e in item.Elements) { if (e.EType == Element.TEXT) { Run run = new Run(); run.Text = e.Text; p.Inlines.Add(run); } else if (e.EType == Element.IMAGE) { InlineUIContainer iui = new InlineUIContainer(); Image img = new Image(); iui.Child = img; img.Height = 24; img.Width = 24; var uri = new Uri(new Uri( string.Format("{0}://{1}:{2}/" ,App.Current.Host.Source.Scheme,App.Current.Host.Source.Host,App.Current.Host.Source.Port)), e.URI); img.Source = new BitmapImage(uri); p.Inlines.Add(iui); } else { } } } }
到这里这个Silverlight的简单聊天室就完成了.想更详细地了解实现过程到https://beetlesl.codeplex.com/获取Beetle.SL源和项目代码.