之前已经讲解了Beetle简单地构建网络通讯程序,那程序紧紧是讲述了如何发送和接收数据;这一章将更深入的使用Beetle的功能,主要包括消息制定,协议分析包括消息接管处理等常用的功能。为了更好的描述所以通过创建一个聊天室程序来体现以上功能的易用性。
在实现功能之前先想好通讯上的协议需要什么功能,总结一下有:登陆,登陆成功返回,登陆和退出通过,获取现有其他用户和发送聊天信息等。需要的基础功能已经明确那就制定消息了.
通过Beetle处理消息对象必须实现IMessage接口,主要目的是由组件更好地管理buffer,避免重复的byte[]构建开销.
public interface IMessage { void Save(BufferWriter writer); void Load(BufferReader reader); }
注册和返回
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); } } public class RegisterResponse : MsgBase { }
注册和断开通知
public class OnRegister : MsgBase { public OnRegister() { User = new UserInfo(); } public UserInfo User; public override void Load(Beetle.BufferReader reader) { base.Load(reader); User = reader.ReadObject<UserInfo>(); } public override void Save(Beetle.BufferWriter writer) { base.Save(writer); writer.Write(User); } } public class UnRegister :OnRegister { }
获取其他用户
public class ListUsers:MsgBase { } public class ListUsersResponse:MsgBase { public ListUsersResponse() { Users = new List<UserInfo>(); } public override void Load(Beetle.BufferReader reader) { base.Load(reader); Users = reader.ReadObjects<UserInfo>(); } public override void Save(Beetle.BufferWriter writer) { base.Save(writer); writer.Write(Users); } public IList<UserInfo> Users; }
发送了聊天信息
public class Say:MsgBase { public Say() { User = new UserInfo(); } public override void Load(Beetle.BufferReader reader) { base.Load(reader); User = reader.ReadObject<UserInfo>(); Body = reader.ReadString(); } public override void Save(Beetle.BufferWriter writer) { base.Save(writer); writer.Write(User); writer.Write(Body); } public UserInfo User; public string Body; }
协议制订完成后就进入服务端的编写了,之前已经讲述了如何构建一个socket tcp服务这里的不重复了,主要描述一下在连接事件中如何对连接进行ChannelAdapter实例化实现自动分析协议和消息分发处理。
static void OnConnect(object sender, ChannelEventArgs e) { e.Channel.ChannelError += OnError; ChannelAdapter adapter = new ChannelAdapter(e.Channel, new HeadSizePackage(Logic.MsgBase.GetMessage)); adapter.RegisterHandler(new Program()); e.Channel.BeginReceive(); Console.WriteLine("{0} Connected!", e.Channel.EndPoint); }
当产生连接的时候只需要针对构建一个ChannelAdapter对象即可实现协议分析,以上代码是采用头描述大小来分析协议包,组件还提供基于自定义结束符的分包方式。HeadSizePackage提供了默认的封包方式,不过可以承继它重写相关方法实现更细的封包和解包(在后面再一一讲述)。
构建了Adapter的就要注册消息处理对象,通过RegisterHandler方法进行注册,对象必须实现
public interface IMessageHandler { void ProcessMessage(ChannelAdapter adapter, MessageHandlerArgs message); }
接下来我们来实现服务端处理的代码
public void _Register(Beetle.ChannelAdapter adapter, Logic.Register e) { adapter.Channel.Name = e.Name; Logic.RegisterResponse response = new Logic.RegisterResponse(); adapter.Send(response); Logic.OnRegister onreg = new Logic.OnRegister(); onreg.User = new Logic.UserInfo { Name= e.Name, IP= adapter.Channel.EndPoint.ToString() }; foreach (TcpChannel channel in mServer.GetOnlines()) { if (channel != adapter.Channel) channel.Adapter.Send(onreg); } Console.WriteLine("{0} login from {1}", e.Name, adapter.Channel.EndPoint); } public void _Say(Beetle.ChannelAdapter adapter, Logic.Say e) { e.User.Name = adapter.Channel.Name; e.User.IP = adapter.Channel.EndPoint.ToString(); foreach (TcpChannel channel in mServer.GetOnlines()) { if (channel != adapter.Channel) channel.Adapter.Send(e); } Console.WriteLine("{0} say", e.User.Name); } public void _List(Beetle.ChannelAdapter adapter, Logic.ListUsers e) { Logic.ListUsersResponse response = new Logic.ListUsersResponse(); foreach (TcpChannel channel in mServer.GetOnlines()) { if (channel != adapter.Channel) { response.Users.Add(new Logic.UserInfo { Name=channel.Name,IP= channel.EndPoint.ToString() }); } } adapter.Send(response); } public void ProcessMessage(ChannelAdapter adapter, MessageHandlerArgs message) { }
从上面的实现估计有同学感觉奇怪,为什么ProcessMessage什么都没有做。的确这个方法可以什么都不需要做,不过它可以做很多事情所有消息都经过这个方法,通过message参数的一个属性确定是否分发到具体方法中;如果不改变那个值组件会放发到具体的消息方法中。对于方法的对应关系是根据Message的类型来确定,还有方法的定义必须是public否则无法处理.
到这里服务端的工作已经完成,代码并不复杂简单的几句就完成了。接下来就是客户端的工作,相对服务端来的客户也是一样简单。为了省时间创建连接和绑定Adapter部分就不说了,其代码和服务端基本一致。
public void _ReceiveSay(Beetle.ChannelAdapter adapter, Logic.Say e) { string message = string.Format(@"\viewkind4\uc1\pard\sa200\sl276\slmult1\lang2052\f0\cf1\fs22 {0} \cf0 {2} IP:{1} \cf0\line {3}", e.User.Name, e.User.IP, DateTime.Now, e.Body); Invoke(new Action<string>(msg => { addSay(msg); }), message); } public void _OtherUnRegister(Beetle.ChannelAdapter adapter, Logic.UnRegister e) { Invoke(new Action<Logic.UnRegister>(o => { lstUsers.Items.Remove(o.User); }), e); } public void _OthreRegister(Beetle.ChannelAdapter adapter, Logic.OnRegister e) { Invoke(new Action<Logic.OnRegister>(o => { if (!lstUsers.Items.Contains(o.User)) lstUsers.Items.Add(o.User); }), e); } public void _OnLogin(Beetle.ChannelAdapter adapter, Logic.RegisterResponse e) { Logic.ListUsers list = new Logic.ListUsers(); adapter.Send(list); Invoke(new Action<object>(o => { toolStrip2.Enabled = false; groupBox2.Enabled = true; }), new object()); } public void _OnList(Beetle.ChannelAdapter adapter, Logic.ListUsersResponse e) { Invoke(new Action<Logic.ListUsersResponse>(o => { lstUsers.Items.Clear(); foreach (Logic.UserInfo item in o.Users) { lstUsers.Items.Add(item); } }), e); }
客户端的代码主要是接收后更新UI,下面看来这个聊天室程序的效果,为了能显示图片采用了richTextBox控件,直接发送rft格式对方接收后添加到对应的richTextBox即可.
详细可以下载代码了解。
下载代码
测试服务器:109.169.59.115