之前已經講解了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