基于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;
            }

        }
    }
}

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协议实现的,修改成了纯数据的了。

创建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);
        }
    }
}

使用委托及时传递消息

  当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;

IIS通过WCF发送消息

  网站中引用WCF,比较方便,VS 中网站右键,添加-》服务引用,如下图,

调用也非常简单,两行代码:

wcfnotice.UserNoticeServiceClient unsc = new wcfnotice.UserNoticeServiceClient();
        ///发订单
        unsc.AddMessage(id, se, type, msg);

感谢

  这篇文章,写到一半时,特别纠结,觉得自己做的事件,好像没有什么技术含量,只是基于superSocket框架,做了简单的应用,一度想放弃这篇文章,但转念一想,我用这个程序替换原来的 SuperWebSocket后,确实稳定了,app任何时间都可以登录了,也许能对那些正在和我们一样用SuperWebSocket的有所帮助,也希望能共同交流。当然,还有一个原因让我坚持写完了,那就是对江大牛的感谢和敬佩,也希望他能继续完善这个框架。

成为一名优秀的程序员!

时间: 2024-08-24 19:03:12

基于SuperSocket的IIS主动推送消息给android客户端的相关文章

(转)移动端主动推送消息原理

转:https://www.zhihu.com/question/19628406/answer/77205019 一.服务端主动推送消息到客户端过程 作者:谢泽帆   李琰链接:https://www.zhihu.com/question/24938934/answer/85359794来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 服务端主动推送到客户端是怎么一个过程 目前服务端给客户端推送,普遍做法是客户端与服务端维持一个长连接,客户端定时向服务端发送心跳以

springboot 项目==基于websocket的服务端推送消息。

1.创建springboot项目,首先我们还是先引入依赖 <!-- webSocket begin--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- webSocket end--> 2.创建配置类 

php简陋版实现微信公众号主动推送消息

推荐一个网站www.itziy.com csdn免积分下载器.pudn免积分下载器.51cto免积分下载器www.verypan.com 百度网盘搜索引擎www.94cto.com 编程相关视频教程.电子书.源码.开发工具.文档手册模拟登录微信公共平台,实现主动信息发送: 突破订阅号一天只能发送一条信息的限制. 使用编码UTF-8代码地址:https://github.com/itziy/wechat使用方法: $arr = array( 'account' => '公众平台帐号', 'pass

ios如何实现远程推送消息

远程推送可以解决 当用户关闭程序的时候 不能收到推送消息.当客户端使用了远程推送的功能,不管用户有没有关闭程序,都能收到 服务器推送的消息. 苹果实现远程推送 是基于长连接的, 默认情况下 苹果的所有设备都是和 苹果的apns服务器建立长连接的, 只要设备已经联网. 长连接的好处: 苹果服务器可以主动向 客户端发送消息, 也可以找回我的设备(当设备丢失时). 远程推送的步骤: 客户端向苹果服务器获取deviceToken步骤:客户端将 BundleId 和 设备UDID 发送给苹果Apns服务器

iOS不使用第三方平台,发送推送消息

iOS不使用第三方平台,发送推送消息 先看看客户端: 需要关注两个点:一是代码部分的DeviceToken获取,且看代码 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //消息推送支持的类型 UIRemoteNotificationType types = (UIRemoteNotificationTypeBadge |U

微信主动推送文本消息C#

1. 登陆,根据用户名和密码登陆到微信公众平台管理页面,获取token,模拟登陆请求地址:http://mp.weixin.qq.com/cgi-bin/login?lang=zh_CN,2. 登陆后,获取用户所有的信息,地址:https://mp.weixin.qq.com/cgi-bin/contactmanage?t=user/index&token=,根据前面的token.3. 发送消息,地址:https://mp.weixin.qq.com/cgi-bin/singlesend?t=a

WebSocket 实现服务端给客户端推送消息

目录 代码发布 应用场景 ajax 操作 队列 递归 如何实现服务端主动给客户端推送消息的效果 长轮询(兼容性好) websocker(主流浏览器都支持) 代码验证(了解) 代码发布 服务端主动给客户端推送消息 截至目前为止,我们所写的 web 项目基本都是基于 HTTP 协议的 HTTP 协议有四大特性:无链接 基于 HTTP 协议实现服务端主动给客户端推送消息好像有点麻烦--- 我们都经历过,浏览器打开一个网站不动,网站过一会儿自动弹出消息 再比如网页版本的微信和 qq,我们所有人创建一个群

推送消息

APNS(Apple Push Notification Services)苹果专门的推送服务器 接收我们自己应用服务器需要被推送的消息 然后推送到我们的手机 手机通知我们的应用程序 注册的大概流程: 1 设备需要向APNS服务器注册 2 注册成功后返回device_token值 3 将这个token值发送给我们自己的服务器 4 有需要推送的消息时  自己的服务器将消息按一定的格式打包 结合token值发送给APNS服务器 5 由于App与APNS维持一个基于TCP的长连接 APNS将消息推送到

RabbitMQ消息队列安装和配置以及推送消息

好久没有写了,最近项目用到RabbitMQ,找了一些资料试验,最后终于成功了,把安装配置的步骤分享给大家. 一.Erlang安装具体过程: 1.双击otp_win32_R16801.exe(不同版本可能命名字不一样),选择next 2.默认安装在C盘,建议程序安装在非系统盘比如D盘(如果安装在C盘可能会出现一些权限问题),修改好安装路径后,选next: 3.进入安装程序,选择install,即可完成安装. 配置环境变量:在系统变量下添加 变量名:ERLANG_HOME,变量值:C:\Progra