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


      在上一篇文章《基於mina框架的GPS設備與服務器之間的交互》中,提到之前一直使用superwebsocket框架做為IIS和APP通信的媒介,經常出現無法通信的問題,必須一天幾次的手動回收程序池,甚至重起服務器,通常周末接到一個陌生電話,就是說客戶端無法登錄了,說多了都是淚。痛定思痛,開始找解決方案,其實superwebsocket以IIS做為宿主,就注定他可能不穩定了,當然,它部署非常方便;為了穩定,我開始嘗試使用SuperSocket,當然,這也注定了后期部署會麻煩些;生活就是這樣哈,魚和熊掌難兼得。學習一個新東西,就如同一個打怪升級做任務的歷程,其中有數不清的陷阱,當然也有絢麗景色。關於服務,WCF等幾乎都是第一次運用,其中肯定有很多不對的地方,還請了解的朋友們指出來,以免誤了別人。對於SuperSocket之前也只是聽說過,本次也只是簡單的應用,如有應用不對,或者說得不對的地方,還請江大漁同學指出。另外,江大牛做的事讓我的開發變得簡單了,在此,對其表示由衷的感謝和敬佩!

消息傳遞流程

  消息傳遞流程如圖1所示,創建一個Windows Service,並啟動superSocket,發布一個WCF,以Windows Service做為宿主,隨服務啟動與關閉。 IIS通過WCF傳遞消息給Windows Service,然后再傳給superSocket,再傳遞給android客戶端;客戶端上傳坐標處理給superSocket,再保存於數據庫。

                (圖1)

 

 

SuperSocket

  以下內容是摘自其官網,大家可以自行查看:SuperSocket 是一個輕量級, 跨平台而且可擴展的 .Net/Mono Socket 服務器程序框架。你無須了解如何使用 Socket, 如何維護 Socket 連接和 Socket 如何工作,但是你卻可以使用 SuperSocket 很容易的開發出一款 Socket 服務器端軟件,例如游戲服務器,GPS 服務器, 工業控制服務和數據采集服務器等等。-- http://www.supersocket.net/

 

實現自己的AppSession,AppServer

   下載最新版源碼,目前最新版應該是1.6.3,好像是馬上要發布1.6.4了吧。解決方案如圖2,目前我只是簡單的應用,源碼就沒細看了,其實也看不懂,哈哈。

    

          (圖2)

      其文檔中如下描述:

         AppSession 代表一個和客戶端的邏輯連接,基於連接的操作應該定於在該類之中。你可以用該類的實例發送數據到客戶端,接收客戶端發送的數據或者關閉連接。

     AppServer 代表了監聽客戶端連接,承載TCP連接的服務器實例。理想情況下,我們可以通過AppServer實例獲取任何你想要的客戶端連接,服務器級別的操作和邏輯應該定義在此類之中。

   所以,通常情況要根據自己的業務來實現自己的AppSession,AppServer。如,我需求在session斷開時,修改app狀態;或者我的AppSession有自己特殊的屬性。
下面是我實現的自己的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>
        /// 數據編號,配送員,或者商家編號等
        /// </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的有所幫助,也希望能共同交流。當然,還有一個原因讓我堅持寫完了,那就是對江大牛的感謝和敬佩,也希望他能繼續完善這個框架。

  

      成為一名優秀的程序員!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM