基于SuperSocket的IIS主动推送消息给android客户端
????? 在上一篇文章《基于mina框架的GPS設(shè)備與服務(wù)器之間的交互》中,提到之前一直使用superwebsocket框架做為IIS和APP通信的媒介,經(jīng)常出現(xiàn)無法通信的問題,必須一天幾次的手動回收程序池,甚至重起服務(wù)器,通常周末接到一個陌生電話,就是說客戶端無法登錄了,說多了都是淚。痛定思痛,開始找解決方案,其實superwebsocket以IIS做為宿主,就注定他可能不穩(wěn)定了,當(dāng)然,它部署非常方便;為了穩(wěn)定,我開始嘗試使用SuperSocket,當(dāng)然,這也注定了后期部署會麻煩些;生活就是這樣哈,魚和熊掌難兼得。學(xué)習(xí)一個新東西,就如同一個打怪升級做任務(wù)的歷程,其中有數(shù)不清的陷阱,當(dāng)然也有絢麗景色。關(guān)于服務(wù),WCF等幾乎都是第一次運用,其中肯定有很多不對的地方,還請了解的朋友們指出來,以免誤了別人。對于SuperSocket之前也只是聽說過,本次也只是簡單的應(yīng)用,如有應(yīng)用不對,或者說得不對的地方,還請江大漁同學(xué)指出。另外,江大牛做的事讓我的開發(fā)變得簡單了,在此,對其表示由衷的感謝和敬佩!
消息傳遞流程
消息傳遞流程如圖1所示,創(chuàng)建一個Windows Service,并啟動superSocket,發(fā)布一個WCF,以Windows Service做為宿主,隨服務(wù)啟動與關(guān)閉。 IIS通過WCF傳遞消息給Windows Service,然后再傳給superSocket,再傳遞給android客戶端;客戶端上傳坐標(biāo)處理給superSocket,再保存于數(shù)據(jù)庫。
(圖1)
?
?
SuperSocket
以下內(nèi)容是摘自其官網(wǎng),大家可以自行查看:SuperSocket 是一個輕量級, 跨平臺而且可擴展的 .Net/Mono Socket 服務(wù)器程序框架。你無須了解如何使用 Socket, 如何維護(hù) Socket 連接和 Socket 如何工作,但是你卻可以使用 SuperSocket 很容易的開發(fā)出一款 Socket 服務(wù)器端軟件,例如游戲服務(wù)器,GPS 服務(wù)器, 工業(yè)控制服務(wù)和數(shù)據(jù)采集服務(wù)器等等。-- http://www.supersocket.net/
?
實現(xiàn)自己的AppSession,AppServer
下載最新版源碼,目前最新版應(yīng)該是1.6.3,好像是馬上要發(fā)布1.6.4了吧。解決方案如圖2,目前我只是簡單的應(yīng)用,源碼就沒細(xì)看了,其實也看不懂,哈哈。
(圖2)
????? 其文檔中如下描述:
???????? AppSession 代表一個和客戶端的邏輯連接,基于連接的操作應(yīng)該定于在該類之中。你可以用該類的實例發(fā)送數(shù)據(jù)到客戶端,接收客戶端發(fā)送的數(shù)據(jù)或者關(guān)閉連接。
? AppServer 代表了監(jiān)聽客戶端連接,承載TCP連接的服務(wù)器實例。理想情況下,我們可以通過AppServer實例獲取任何你想要的客戶端連接,服務(wù)器級別的操作和邏輯應(yīng)該定義在此類之中。
所以,通常情況要根據(jù)自己的業(yè)務(wù)來實現(xiàn)自己的AppSession,AppServer。如,我需求在session斷開時,修改app狀態(tài);或者我的AppSession有自己特殊的屬性。
下面是我實現(xiàn)的自己的AppSession(NoticeSession),AppServer(NoticeServer),有興趣可以瞥下。
NoticeSession代碼如下:
using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Text;using SuperSocket.Common; using SuperSocket.SocketBase; using SuperSocket.SocketBase.Protocol; using System.Threading; using Hangjing.SQLServerDAL.serverinterface;namespace SuperSocket.SocketService {public class MESSAGETYPE{/// <summary>/// 1表示消息/// </summary>public const uint MSG = 1;/// <summary>/// 0表示訂單/// </summary>public const uint ORDER = 0;}/// <summary>/// 自定義連接類MySession,繼承AppSession,并傳入到AppSession /// </summary>public class NoticeSession : AppSession<NoticeSession>{bool isSendMessage = false;public StringDictionary Cookies { get; private set; }/// <summary>/// 數(shù)據(jù)編號,配送員,或者商家編號等/// </summary>public int DataID{get;set;}/// <summary>/// 類型:1表示騎士,2表示商家/// </summary>public int Type{set;get;}/// <summary>/// 用戶名;/// </summary>public String UserName{get;set;}/// <summary>/// 密碼/// </summary>public String Password{get;set;}protected override void OnSessionStarted(){}protected override void HandleUnknownRequest(StringRequestInfo requestInfo){//Logger.Debug("NoticeSession.OnSessionStarted:Unknow request"); }protected override void HandleException(Exception e){//Logger.Debug("NoticeSession.OnSessionStarted:Unknow request"); }protected override void OnSessionClosed(CloseReason reason){Logout();base.OnSessionClosed(reason);}/// <summary>/// 根據(jù)登錄的參數(shù),保存cookie ,并設(shè)置屬性/// </summary>public void SetCookie(string cookieValue){var cookies = new StringDictionary();if (!string.IsNullOrEmpty(cookieValue)){string[] pairs = cookieValue.Split(';');int pos;string key, value;foreach (var p in pairs){pos = p.IndexOf('=');if (pos > 0){key = p.Substring(0, pos).Trim();pos += 1;if (pos < p.Length)value = p.Substring(pos).Trim();elsevalue = string.Empty;cookies[key] = Uri.UnescapeDataString(value);}}}this.Cookies = cookies;this.UserName = Cookies["name"];this.Password = Cookies["password"];this.Type = Convert.ToInt32(Cookies["type"]);}/// <summary>/// 向客戶端發(fā)送消息(0 表示訂單 ,1表示消息)/// </summary>/// <param name="type">(0 表示訂單 ,1表示消息)</param>/// <param name="message">消息內(nèi)容(json)</param>public void SendMessage(uint type, String message){while (isSendMessage){Thread.Sleep(1);}isSendMessage = true;String value = "";switch (type){case MESSAGETYPE.ORDER:value = "ORDER::" + message;break;case MESSAGETYPE.MSG:value = "MSG::" + message;break;}this.Send(value);isSendMessage = false;}/// <summary>/// session退出,對應(yīng)騎士下線/// </summary>public void Logout(){if (DataID != 0 && Type == 1){APPUser user = new APPUser(this.UserName, this.Password, this.SessionID, this.Type);if (user.app != null){user.app.UpdateLoginState(this.SessionID, 0);}}}/// <summary>/// 根據(jù)編號為類型獲取session/// </summary>/// <param name="id"></param>/// <param name="type"></param>/// <returns></returns>public NoticeSession GetSession(int id, int type){NoticeSession session = this.AppServer.GetAllSessions().Where(a => a.DataID == id && a.Type == type).FirstOrDefault();if (session != null){return session;}else{return null;}}} } View Code?
NoticeServer代碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using SuperSocket.SocketBase; 6 using SuperSocket.SocketBase.Config; 7 using SuperSocket.SocketBase.Protocol; 8 using Hangjing.SQLServerDAL.serverinterface; 9 10 namespace SuperSocket.SocketService 11 { 12 /// <summary> 13 /// 自定義服務(wù)器類MyServer,繼承AppServer,并傳入自定義連接類MySession 14 /// </summary> 15 public class NoticeServer : AppServer<NoticeSession> 16 { 17 protected override bool Setup(IRootConfig rootConfig, IServerConfig config) 18 { 19 return base.Setup(rootConfig, config); 20 } 21 22 protected override void OnStarted() 23 { 24 base.OnStarted(); 25 } 26 27 protected override void OnStopped() 28 { 29 base.OnStopped(); 30 } 31 32 /// <summary> 33 /// 輸出新連接信息 34 /// </summary> 35 /// <param name="session"></param> 36 protected override void OnNewSessionConnected(NoticeSession session) 37 { 38 base.OnNewSessionConnected(session); 39 //輸出客戶端IP地址 40 //session.Logger.Debug("\r\n NoticeServer.OnNewSessionConnected->" + session.LocalEndPoint.Address.ToString() + ":連接"); 41 42 } 43 44 /// <summary> 45 /// 輸出斷開連接信息 46 /// </summary> 47 /// <param name="session"></param> 48 /// <param name="reason"></param> 49 protected override void OnSessionClosed(NoticeSession session, CloseReason reason) 50 { 51 //輸出客戶端IP地址</span> 52 //session.Logger.Debug("\r\n NoticeServer.OnSessionClosed->" + session.LocalEndPoint.Address.ToString() + ":斷開 dataid=" + session.DataID + "&Type=" + session.Type); 53 //退出 54 if (session.DataID != 0) 55 { 56 APPUser user = new APPUser(session.UserName, session.Password, session.SessionID, session.Type); 57 if (user.app != null) 58 { 59 user.app.UpdateLoginState(session.SessionID, 0); 60 } 61 } 62 base.OnSessionClosed(session, reason); 63 } 64 65 } 66 } View Code?
實現(xiàn)自己的消息處理機制
? 消息都會進(jìn)到MainService.NewRequestReceived 方法中,所以我在這里處理自己的消息。默認(rèn)消息機制里,會把消息序列化為?StringRequestInfo,這個對像包含Key和Body,默認(rèn)是用空格分隔的。我主要實現(xiàn)app登錄(建立鏈接),和app上傳坐標(biāo)等兩個消息,NewRequestReceived 方法代碼如下
/// <summary>/// 收到新的消息/// </summary>/// <param name="session"></param>/// <param name="requestInfo"></param>void NewRequestReceived(NoticeSession session, StringRequestInfo requestInfo){//session.Logger.Debug("Key=" + requestInfo.Key + "|body=" + requestInfo.Body);switch (requestInfo.Key){case "Cookie:"://這里為了兼容原來的app登錄發(fā)送的數(shù)據(jù) {session.SetCookie(requestInfo.Body);User user = new User(session);Thread thdProcess = new Thread(user.LoginThread);thdProcess.Start();}break;case "GPS":{string json = requestInfo.Body;if (session.DataID == 0 && json == ""){return;}User user = new User(session,json);Thread thdProcess = new Thread(user.UploadGPS);thdProcess.Start();}break;}} View Code
?
LoginThread 主要實現(xiàn)驗證用戶名,密碼后,返回用戶相關(guān)信息,具體代碼如下:
/// <summary>/// 登錄函數(shù)/// </summary>public void LoginThread(){String state = "";String message = "";APPUser user = new APPUser(session.UserName, session.Password, session.SessionID, session.Type);if (user.app == null){session.Logger.Debug("登錄:" + session.UserName + " type=" + session.Type+" 對像為空");return;}int userid = user.app.APPLogin(session.UserName, session.Password, session.SessionID);if (userid > 0){NoticeSession ol = session.GetSession(userid, session.Type);if (ol != null){state = "-2";message = "Login::{\"userid\":\"" + session.DataID.ToString() + "\",\"state\":\"" + state + "\"}";ol.Send(message);Thread.Sleep(2);ol.Close();}session.DataID = userid;state = "1";message = user.app.getLoginJSON(userid,state);message = Utils.ToUTF8(message);session.Send(message);return;}else{state = "-1";message = "Login::{\"userid\":\"" + session.DataID.ToString() + "\",\"state\":\"" + state + "\"}";}session.Send(message);Thread.Sleep(2);session.Close();} View Code? 考慮到可能會有騎士,商家,取餐員等對像同時存在,為了保證服務(wù)程序的通用性,抽象出每個對像的相同操作。面向接口進(jìn)行編程,如下圖
?
經(jīng)過,以上簡單步驟,運行InstallService.bat,即可創(chuàng)建服務(wù),監(jiān)聽指定端口了。可用TCP&UDP測試工具,簡單測試下,看效果,如下圖:
android客戶端方面,是我同事基于mina實現(xiàn)的,這里我就不介紹了,其實我也不太懂,我只是簡單的把他原來以websocket協(xié)議實現(xiàn)的,修改成了純數(shù)據(jù)的了。
創(chuàng)建WCF服務(wù)庫
當(dāng)時在考慮如果把消息(如把訂單調(diào)度給某個配送員了)傳給Windows Service時,考慮了多個方法:想過用數(shù)據(jù)庫,想過用消息隊列;但是都覺得不太好,當(dāng)WCF飄過腦海時,一下子覺得這個可行,其實在此之前,我也只是聽過說而已,也許就是因為不熟悉,覺得神奇,才讓我覺得稀奇吧。說干就干,看了幾篇文章,實現(xiàn)了一個簡單的WCF。UserNoticeService.cs實現(xiàn)代碼如下,只有一個簡單的方法
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text;namespace Hangjing.WCFService {[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]public class UserNoticeService : IUserNoticeService{/// <summary>/// 添加消息/// </summary>/// <param name="userid">用戶編號</param>/// <param name="usertype">用戶類型 1表示騎士,2表示商家</param>/// <param name="messagetype">消息類型 消息類型:0表示訂單,1表示純消息。</param>/// <param name="message">消息json</param>public void AddMessage(int userid, int usertype, int messagetype, string message){NoticeInfo model = new NoticeInfo();model.UserId = userid;model.UserType = usertype;model.MessageType = messagetype;model.Message = message;NoticeManager nm = NoticeManager.GetInstance();nm.Add(model);}} } View Code?
使用委托及時傳遞消息
當(dāng)UserNoticeService.AddMessage 接收到消息后,如何傳遞給?Windows Service時,也糾結(jié)了好久,直到就快放棄思考,準(zhǔn)備用消息隊列來實現(xiàn)時,才想到委托。這個東西吧,一直覺得很多神奇,之前也花了很多時間去理解,一直覺得似懂非懂的感覺,原來是沒有真正的應(yīng)用。代碼部分就比較簡單了,以下是NoticeManager.cs相關(guān)代碼,在UserNoticeService.AddMessage中執(zhí)行添加的方法。
???
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading;namespace Hangjing.WCFService {/// <summary>/// 對消息的管理/// </summary>public class NoticeManager{public static List<NoticeInfo> NoticeList = new List<NoticeInfo>();public static object m_SessionSyncRoot = new object();public event AddHandler AddEvent = null;private static NoticeManager instance;static NoticeManager() //類型構(gòu)造器,確保線程安全 {instance = new NoticeManager();}private NoticeManager() //構(gòu)造方法為private,這就堵死了外界利用new創(chuàng)建此類型實例的可能 {Thread.Sleep(50);//此處模擬創(chuàng)建對象耗時 }public static NoticeManager GetInstance() //次方法是獲得本類實例的唯一全局訪問點 {return instance;}/// <summary>/// 添加方法/// </summary>/// <param name="notice"></param>public void Add(NoticeInfo model){//后期再考慮消息的存儲//foreach (var item in NoticeManager.NoticeList)//{// if (item.UserId == model.UserId && item.UserType == model.UserType)// {// lock (NoticeManager.m_SessionSyncRoot)// {// NoticeManager.NoticeList.Remove(item);// }// }//}//lock (NoticeManager.m_SessionSyncRoot)//{// NoticeManager.NoticeList.Add(model);//}if (this.AddEvent != null){this.AddEvent(model);}}}public delegate void AddHandler(NoticeInfo notice);} View Code?
在MainService中注冊委托
??? NoticeManager nm = NoticeManager.GetInstance();
??? nm.AddEvent += nm_AddEvent;
IIS通過WCF發(fā)送消息
網(wǎng)站中引用WCF,比較方便,VS?中網(wǎng)站右鍵,添加-》服務(wù)引用,如下圖,
??
??? 調(diào)用也非常簡單,兩行代碼:
??? ??? wcfnotice.UserNoticeServiceClient unsc = new wcfnotice.UserNoticeServiceClient();
??????? ///發(fā)訂單
??????? unsc.AddMessage(id, se, type, msg);
?
感謝
這篇文章,寫到一半時,特別糾結(jié),覺得自己做的事件,好像沒有什么技術(shù)含量,只是基于superSocket框架,做了簡單的應(yīng)用,一度想放棄這篇文章,但轉(zhuǎn)念一想,我用這個程序替換原來的 SuperWebSocket后,確實穩(wěn)定了,app任何時間都可以登錄了,也許能對那些正在和我們一樣用SuperWebSocket的有所幫助,也希望能共同交流。當(dāng)然,還有一個原因讓我堅持寫完了,那就是對江大牛的感謝和敬佩,也希望他能繼續(xù)完善這個框架。
??
????? 成為一名優(yōu)秀的程序員!
轉(zhuǎn)載于:https://www.cnblogs.com/jijunjian/p/4049203.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的基于SuperSocket的IIS主动推送消息给android客户端的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python学习系列:PyCharm C
- 下一篇: 【Spring Cloud笔记】 Eur