其實構建一個Web
多房間聊天室也並不是什么困難的技術,借助於websocket
就可以輕松實現多用戶在線實時通訊交互;在這里主要介紹一下在BeetleX
和BeetleXjs
的支持下如何讓這個功能實現的更簡單和高效。接下來通過使用BeetleX
來一步步講解Web多房間聊天室的具體實現。
信息邏輯
既然是多房間聊天室那它具備兩個主要元素,分別是用戶和房間;下面通過類來描述這兩個元素:
用戶
public class User { public string Name { get; set; } public string Address { get; set; } [JsonIgnore] public ISession Session { get; set; } [JsonIgnore] public Room Room { get; set; } public void Send(BeetleX.FastHttpApi.WebSockets.DataFrame frame) { frame.Send(Session); } public void Exit() { Room?.Exit(this); } }
信息描述比較簡單主要包括信息用:名稱,會話和房間;涉及的行為有發送信息和退出房間。
房間
public class Room { public string Name { get; set; } public List<User> Users { get; private set; } = new List<User>(); public HttpApiServer HttpServer { get; set; } public void Send(Command cmd) { cmd.Room = Name; var frame = HttpServer.CreateDataFrame(cmd); lock (Users) { foreach (var item in Users) item.Send(frame); } } public User[] GetOnlines() { lock (Users) return Users.ToArray(); } public void Enter(User user) { if (user == null) return; if (user.Room != this) { user.Room?.Exit(user); lock (Users) Users.Add(user); user.Room = this; Command quit = new Command { Type = "enter",Message=$"enter room", User = user }; Send(quit); } } public void Exit(User user) { if (user == null) return; lock (Users) Users.Remove(user); user.Room = null; Command quit = new Command { Type = "quit", Message = $"exit room", User = user }; Send(quit); } }
房間信息主要包括名稱和用戶信息,具體行為有進房間,出房間和向房間發送信息。
服務邏輯
有了邏輯信息那就需要把這個信息通過接口的服務方式提供給外部訪問操作,接下來定義一個簡單的控制器類來描述相關接口服務行為
[BeetleX.FastHttpApi.Controller] public class Home : BeetleX.FastHttpApi.IController { [BeetleX.FastHttpApi.NotAction] public void Init(HttpApiServer server, string path) { for (int i = 0; i < 10; i++) { string key = $"{i:00}"; mRooms[key] = new Room { Name = key, HttpServer = server }; } server.HttpDisconnect += (o, e) => { GetUser(e.Session)?.Exit(); }; } private ConcurrentDictionary<string, Room> mRooms = new ConcurrentDictionary<string, Room>(StringComparer.OrdinalIgnoreCase); public object Rooms() { return from a in mRooms.Values orderby a.Name select new {a.Name}; } public void Enter(string room, IHttpContext context) { User user = GetUser(context.Session); mRooms.TryGetValue(room, out Room result); result?.Enter(user); } public void Talk(string message, IHttpContext context) { if (!string.IsNullOrEmpty(message)) { var user = GetUser(context.Session); Command cmd = new Command { Type = "talk", Message = message, User = user }; user?.Room?.Send(cmd); } } public void Login(string name, IHttpContext context) { User user = new User(); user.Name = name; user.Session = context.Session; user.Address = context.Request.RemoteIPAddress; SetUser(context.Session, user); } private User GetUser(ISession session) { return (User)session["__user"]; } private void SetUser(ISession session, User user) { session["__user"] = user; } }
Init方法
用於初始化房間信息,並綁定連接斷開事件,如果用戶斷開了則執行用戶退出房間。
Login方法
登陸到用戶中
Rooms方法
獲取所有房間信息
Enter方法
用戶進入房間
Talk
用戶向房間內發送一條消息
啟動服務
當功能邏輯寫好后,接下來的工作就是讓這些接口部署到websocket
服務中,部署的代碼比較簡單:
class Program { static void Main(string[] args) { var builder = new HostBuilder() .ConfigureServices((hostContext, services) => { services.UseBeetlexHttp(o => { o.LogToConsole = true; o.ManageApiEnabled = false; o.Port = 80; o.SetDebug(); o.LogLevel = BeetleX.EventArgs.LogType.Warring; }, typeof(Program).Assembly); }); builder.Build().Run(); } }
當服務部署后就可以專心去做前端實現的工作。
Web前端
為了更方便地和Beetlex
服務整合,因此也單獨針對性地封裝了相應的javascript
組件;除了自有封裝的javascript
還涉及到vuejs
的使用。通過以上組件整合前端的代碼相比服務端來說就更簡單了,詳細代碼如下:
<body> <div id="page"> <page-header> </page-header> <div class="container" style="margin-top:110px;"> <div class="row"> <ul style="list-style:none;"> <li v-for="item in messages" class="message"> <h4> <span class="label label-success">[{{item.Room}}]</span> <span class="label label-info">{{item.User.Name}}</span> <span class="label label-default">{{new Date()}}</span> </h4> <div style="padding-left:20px;"> {{item.Message}} </div> </li> </ul> </div> </div> <page-footer :status="loginStatus" @login="onLogin($event)" @talk="onTalk($event)" @select="onSelectRoom($event)" :rooms="getRooms.result"> </page-footer> </div> <script> beetlex.websocket.receive = function (r) { page.messages.push(r); }; beetlex.websocket.disconnect = function () { page.loginStatus = false; }; beetlex.useWebsocket(); var login = new beetlexAction("/Login"); var getRooms = new beetlexAction('/Rooms', null, []); var enterRoom = new beetlexAction('/Enter'); var talk = new beetlexAction('/Talk'); login.requested = function (r) { page.loginStatus = true; }; var model = { getRooms: getRooms, loginStatus: false, login: login, talk: talk, enterRoom: enterRoom, messages: [], }; var page = new Vue({ el: '#page', data: model, methods: { onSelectRoom: function (r) { // alert(r); this.enterRoom.post({ room: r }); }, onLogin: function (r) { this.login.post({ name: r }); }, onTalk: function (msg) { talk.post({ message: msg }); }, }, }); getRooms.get(); </script> </body>
beetlex
是針對http
和websocket
封裝的功能類,它自動兼容這兩種請求;在默認情況是http
請求,調用useWebsocket
后所有請求都優先使用websocket
;當websocket
不可用的情況組會自動切回到http
.
beetlexAction
用於描述一個請求,分別提供了post
和get
方法;當在websocket
下這兩個方法的處理方式一樣。
運行效果
演示地址
代碼地址
https://github.com/IKende/BeetleX-Samples/tree/master/WebSocket.Chat