在上一篇文章《基于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 = ;
/// <summary>
/// 0表示订单
/// </summary>
public const uint ORDER = ;
} /// <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 > )
{
key = p.Substring(, pos).Trim();
pos += ;
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();
}
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 != && Type == )
{
APPUser user = new APPUser(this.UserName, this.Password, this.SessionID, this.Type);
if (user.app != null)
{
user.app.UpdateLoginState(this.SessionID, );
}
}
} /// <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代码如下:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketBase.Protocol;
using Hangjing.SQLServerDAL.serverinterface; namespace SuperSocket.SocketService
{
/// <summary>
/// 自定义服务器类MyServer,继承AppServer,并传入自定义连接类MySession
/// </summary>
public class NoticeServer : AppServer<NoticeSession>
{
protected override bool Setup(IRootConfig rootConfig, IServerConfig config)
{
return base.Setup(rootConfig, config);
} protected override void OnStarted()
{
base.OnStarted();
} protected override void OnStopped()
{
base.OnStopped();
} /// <summary>
/// 输出新连接信息
/// </summary>
/// <param name="session"></param>
protected override void OnNewSessionConnected(NoticeSession session)
{
base.OnNewSessionConnected(session);
//输出客户端IP地址
//session.Logger.Debug("\r\n NoticeServer.OnNewSessionConnected->" + session.LocalEndPoint.Address.ToString() + ":连接"); } /// <summary>
/// 输出断开连接信息
/// </summary>
/// <param name="session"></param>
/// <param name="reason"></param>
protected override void OnSessionClosed(NoticeSession session, CloseReason reason)
{
//输出客户端IP地址</span>
//session.Logger.Debug("\r\n NoticeServer.OnSessionClosed->" + session.LocalEndPoint.Address.ToString() + ":断开 dataid=" + session.DataID + "&Type=" + session.Type);
//退出
if (session.DataID != )
{
APPUser user = new APPUser(session.UserName, session.Password, session.SessionID, session.Type);
if (user.app != null)
{
user.app.UpdateLoginState(session.SessionID, );
}
}
base.OnSessionClosed(session, reason);
} }
}

实现自己的消息处理机制

  消息都会进到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 == && 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 > )
{
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();
ol.Close();
}
session.DataID = userid;
state = "";
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();
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();//此处模拟创建对象耗时
} 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的有所帮助,也希望能共同交流。当然,还有一个原因让我坚持写完了,那就是对江大牛的感谢和敬佩,也希望他能继续完善这个框架。

成为一名优秀的程序员!

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

  1. 微信java开发之实现微信主动推送消息

    1.拉取access_token2.拉取用户信息3.主动推送消息4.接口貌似要申请权限5.依赖httpclient4.2.3 和jackson 2.2.1 public class WeixinAPI ...

  2. Websocket实现Java后台主动推送消息到前台

    写在前面 需求: 项目测试, 缺少用户登录失败给admin推送消息, 想到这个方式, 当用户登录失败时, admin用户会在页面看到咣咣乱弹的alert. 正文 pom.xml <!-- web ...

  3. 微信公众平台主动推送消息(asp.net)

    /// <summary>        /// MD5 32位加密        /// </summary>        /// <param name=" ...

  4. GoEasy实现websocket 推送消息通知到客户端

    最近在实现一个推送功能,用户扫描二维码签到,后台及时将签到成功信息推送到浏览器端.排除了前端ajax轮询的方式,决定采用websocket及时推送. 于是发现了第三方websocket推送库GoEas ...

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

    推荐一个网站www.itziy.com csdn免积分下载器.pudn免积分下载器.51cto免积分下载器www.verypan.com 百度网盘搜索引擎www.94cto.com 编程相关视频教程. ...

  6. 为美多商城(Django2.0.4)添加基于websocket的实时通信,主动推送,聊天室及客服系统

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_67 websocket是个啥? webSocket是一种在单个TCP连接上进行全双工通信的协议 webSocket使得客户端和服务 ...

  7. HTML5服务器推送消息的各种解决办法

    摘要 在各种BS架构的应用程序中,往往都希望服务端能够主动地向客户端推送各种消息,以达到类似于邮件.消息.待办事项等通知. 往BS架构本身存在的问题就是,服务器一直采用的是一问一答的机制.这就意味着如 ...

  8. Django2.0.4 + websocket 实现实时通信,主动推送,聊天室及客服系统

    webSocket是一种在单个TCP连接上进行全双工通信的协议. webSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在WebSocket API中,浏览器 ...

  9. HTML5服务器推送消息的各种解决办法,html5服务器

    HTML5服务器推送消息的各种解决办法,html5服务器 摘要 在各种BS架构的应用程序中,往往都希望服务端能够主动地向客户端推送各种消息,以达到类似于邮件.消息.待办事项等通知. 往BS架构本身存在 ...

随机推荐

  1. 纯CSS打造好看的按钮样式

    好看的按钮.链接.div样式,效果预览: http://hovertree.com/code/run/css/s8o19792.html 发现今天积分和排名不错: 代码如下: <!DOCTYPE ...

  2. C#操作Excell常用方法

    这是在博客园看的文章,写的很好,所以转一下,方便自己以后用到的时候可以随时查看. range.NumberFormatLocal = "@";     //设置单元格格式为文本 r ...

  3. Android中AsyncTask分析--你所不注意的坑

    AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI ...

  4. 基于Ruby的watir-webdriver自动化测试方案与实施(二)

    接着基于Ruby的watir-webdriver自动化测试方案与实施(一) http://www.cnblogs.com/Javame/p/4159360.html 继续 ... ...   回顾 软 ...

  5. canvas 绘制圆角矩形

    <!DOCTYPE HTML> <head> <meta charset = "utf-8"> <title>canvas</ ...

  6. Windows Phone App Studio发布重要更新-支持Windows 8.1 源代码生成

    自2013年8月Apps Team发布Windows Phone App Studio以来,由于其低入门门槛和较好的易用性,用户和项目数量增长迅速,从Windows Phone Developer B ...

  7. MSDB数据库置疑的解决方法

    1.原因 机房停电,服务器非法关机,导致MSDB数据库被“置疑” 2.Msdb数据库的作用 Msdb 数据库供SQLServer 代理程序调度警报和作业以及记录操作员时使用.比如,我们备份了一个数据库 ...

  8. python基础(七)函数

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 函数最重要的目的是方便我们重复使用相同的一段程序. 将一些操作隶属于一个函数,以后 ...

  9. UEFI+GPT模式下的Windows系统中分区结构和默认分区大小及硬盘整数分区研究

    内容摘要:本文主要讨论和分析在UEFI+GPT模式下的Windows系统(主要是最新的Win10X64)中默认的分区结构和默认的分区大小,硬盘整数分区.4K对齐.起始扇区.恢复分区.ESP分区.MSR ...

  10. Acionbar logo

    问题: 在使用Actionbar时,默认在左上角是会有一个跟软件发布时的LOGO一样的图标,在大多数情况下按照默认图标进行显示已经很好,既使得软件整体统一,也方便省事.但有些情况下,还是希望不同的界面 ...