基於SuperSocket的IIS主動推送消息給android客戶端

      在上一篇文章《基於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

  如下內容是摘自其官網,你們能夠自行查看:SuperSocket 是一個輕量級, 跨平臺並且可擴展的 .Net/Mono Socket 服務器程序框架。你無須瞭解如何使用 Socket, 如何維護 Socket 鏈接和 Socket 如何工做,可是你卻可使用 SuperSocket 很容易的開發出一款 Socket 服務器端軟件,例如遊戲服務器,GPS 服務器, 工業控制服務和數據採集服務器等等。-- http://www.supersocket.net/web

 

實現本身的AppSession,AppServer

   下載最新版源碼,目前最新版應該是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;
            }

        }
    }
}
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     /// 自定義服務器類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

 

實現本身的消息處理機制

   消息都會進到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;
            }

        }
View Code

 

 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();

        }
View Code

  考慮到可能會有騎士,商家,取餐員等對像同時存在,爲了保證服務程序的通用性,抽象出每一個對像的相同操做。面向接口進行編程,以下圖

 

 

  通過,以上簡單步驟,運行InstallService.bat,便可建立服務,監聽指定端口了。可用TCP&UDP測試工具,簡單測試下,看效果,以下圖:

  

  

  android客戶端方面,是我同事基於mina實現的,這裏我就不介紹了,其實我也不太懂,我只是簡單的把他原來以websocket協議實現的,修改爲了純數據的了。

建立WCF服務庫

  當時在考慮若是把消息(如把訂單調度給某個配送員了)傳給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);
        }
    }
}
View Code

 

使用委託及時傳遞消息

  當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);


}
View Code

 

在MainService中註冊委託

    NoticeManager nm = NoticeManager.GetInstance();
    nm.AddEvent += nm_AddEvent;

IIS經過WCF發送消息

  網站中引用WCF,比較方便,VS 中網站右鍵,添加-》服務引用,以下圖,

  

    調用也很是簡單,兩行代碼:

        wcfnotice.UserNoticeServiceClient unsc = new wcfnotice.UserNoticeServiceClient();
        ///發訂單
        unsc.AddMessage(id, se, type, msg);

 

感謝

  這篇文章,寫到一半時,特別糾結,以爲本身作的事件,好像沒有什麼技術含量,只是基於superSocket框架,作了簡單的應用,一度想放棄這篇文章,但轉念一想,我用這個程序替換原來的 SuperWebSocket後,確實穩定了,app任什麼時候間均可以登陸了,也許能對那些正在和咱們同樣用SuperWebSocket的有所幫助,也但願能共同交流。固然,還有一個緣由讓我堅持寫完了,那就是對江大牛的感謝和敬佩,也但願他能繼續完善這個框架。

  

      成爲一名優秀的程序員!

相關文章
相關標籤/搜索