在上一篇文章《基於mina框架的GPS設備與服務器之間的交互》中,提到以前一直使用superwebsocket框架作爲IIS和APP通訊的媒介,常常出現沒法通訊的問題,必須一天幾回的手動回收程序池,甚至重起服務器,一般週末接到一個陌生電話,就是說客戶端沒法登陸了,說多了都是淚。痛定思痛,開始找解決方案,其實superwebsocket以IIS作爲宿主,就註定他可能不穩定了,固然,它部署很是方便;爲了穩定,我開始嘗試使用SuperSocket,固然,這也註定了後期部署會麻煩些;生活就是這樣哈,魚和熊掌難兼得。學習一個新東西,就如同一個打怪升級作任務的歷程,其中有數不清的陷阱,固然也有絢麗景色。關於服務,WCF等幾乎都是第一次運用,其中確定有不少不對的地方,還請了解的朋友們指出來,以避免誤了別人。對於SuperSocket以前也只是據說過,本次也只是簡單的應用,若有應用不對,或者說得不對的地方,還請江大漁同窗指出。另外,江大牛作的事讓個人開發變得簡單了,在此,對其表示由衷的感謝和敬佩!html
消息傳遞流程如圖1所示,建立一個Windows Service,並啓動superSocket,發佈一個WCF,以Windows Service作爲宿主,隨服務啓動與關閉。 IIS經過WCF傳遞消息給Windows Service,而後再傳給superSocket,再傳遞給android客戶端;客戶端上傳座標處理給superSocket,再保存於數據庫。
android
(圖1)程序員
如下內容是摘自其官網,你們能夠自行查看:SuperSocket 是一個輕量級, 跨平臺並且可擴展的 .Net/Mono Socket 服務器程序框架。你無須瞭解如何使用 Socket, 如何維護 Socket 鏈接和 Socket 如何工做,可是你卻可使用 SuperSocket 很容易的開發出一款 Socket 服務器端軟件,例如遊戲服務器,GPS 服務器, 工業控制服務和數據採集服務器等等。-- http://www.supersocket.net/web
下載最新版源碼,目前最新版應該是1.6.3,好像是立刻要發佈1.6.4了吧。解決方案如圖2,目前我只是簡單的應用,源碼就沒細看了,其實也看不懂,哈哈。數據庫
(圖2)編程
其文檔中以下描述:json
AppSession 表明一個和客戶端的邏輯鏈接,基於鏈接的操做應該定於在該類之中。你能夠用該類的實例發送數據到客戶端,接收客戶端發送的數據或者關閉鏈接。安全
AppServer 表明了監聽客戶端鏈接,承載TCP鏈接的服務器實例。理想狀況下,咱們能夠經過AppServer實例獲取任何你想要的客戶端鏈接,服務器級別的操做和邏輯應該定義在此類之中。服務器
因此,一般狀況要根據本身的業務來實現本身的AppSession,AppServer。如,我需求在session斷開時,修改app狀態;或者個人AppSession有本身特殊的屬性。
下面是我實現的本身的AppSession(NoticeSession),AppServer(NoticeServer),有興趣能夠瞥下。websocket
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> /// 數據編號,配送員,或者商家編號等 /// </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> /// 根據登陸的參數,保存cookie ,並設置屬性 /// </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(); else value = 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> /// 向客戶端發送消息(0 表示訂單 ,1表示消息) /// </summary> /// <param name="type">(0 表示訂單 ,1表示消息)</param> /// <param name="message">消息內容(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退出,對應騎士下線 /// </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> /// 根據編號爲類型獲取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; } } } }
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 /// 自定義服務器類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 }
消息都會進到MainService.NewRequestReceived 方法中,因此我在這裏處理本身的消息。默認消息機制裏,會把消息序列化爲 StringRequestInfo,這個對像包含Key和Body,默認是用空格分隔的。我主要實現app登陸(創建連接),和app上傳座標等兩個消息,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登陸發送的數據 { 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; } }
LoginThread 主要實現驗證用戶名,密碼後,返回用戶相關信息,具體代碼以下:
/// <summary> /// 登陸函數 /// </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(); }
考慮到可能會有騎士,商家,取餐員等對像同時存在,爲了保證服務程序的通用性,抽象出每一個對像的相同操做。面向接口進行編程,以下圖
通過,以上簡單步驟,運行InstallService.bat,便可建立服務,監聽指定端口了。可用TCP&UDP測試工具,簡單測試下,看效果,以下圖:
android客戶端方面,是我同事基於mina實現的,這裏我就不介紹了,其實我也不太懂,我只是簡單的把他原來以websocket協議實現的,修改爲了純數據的了。
當時在考慮若是把消息(如把訂單調度給某個配送員了)傳給Windows Service時,考慮了多個方法:想過用數據庫,想過用消息隊列;可是都以爲不太好,當WCF飄過腦海時,一會兒以爲這個可行,其實在此以前,我也只是聽過說而已,也許就是由於不熟悉,以爲神奇,才讓我以爲稀奇吧。說幹就幹,看了幾篇文章,實現了一個簡單的WCF。UserNoticeService.cs實現代碼以下,只有一個簡單的方法
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); } } }
當UserNoticeService.AddMessage 接收到消息後,如何傳遞給 Windows Service時,也糾結了很久,直到就快放棄思考,準備用消息隊列來實現時,纔想到委託。這個東西吧,一直以爲不少神奇,以前也花了不少時間去理解,一直以爲似懂非懂的感受,原來是沒有真正的應用。代碼部分就比較簡單了,如下是NoticeManager.cs相關代碼,在UserNoticeService.AddMessage中執行添加的方法。
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() //類型構造器,確保線程安全 { instance = new NoticeManager(); } private NoticeManager() //構造方法爲private,這就堵死了外界利用new建立此類型實例的可能 { Thread.Sleep(50);//此處模擬建立對象耗時 } 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); }
在MainService中註冊委託
NoticeManager nm = NoticeManager.GetInstance();
nm.AddEvent += nm_AddEvent;
網站中引用WCF,比較方便,VS 中網站右鍵,添加-》服務引用,以下圖,
調用也很是簡單,兩行代碼:
wcfnotice.UserNoticeServiceClient unsc = new wcfnotice.UserNoticeServiceClient();
///發訂單
unsc.AddMessage(id, se, type, msg);
這篇文章,寫到一半時,特別糾結,以爲本身作的事件,好像沒有什麼技術含量,只是基於superSocket框架,作了簡單的應用,一度想放棄這篇文章,但轉念一想,我用這個程序替換原來的 SuperWebSocket後,確實穩定了,app任什麼時候間均可以登陸了,也許能對那些正在和咱們同樣用SuperWebSocket的有所幫助,也但願能共同交流。固然,還有一個緣由讓我堅持寫完了,那就是對江大牛的感謝和敬佩,也但願他能繼續完善這個框架。
成爲一名優秀的程序員!