在上一篇文章《基于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的有所帮助,也但愿能共同交流。固然,还有一个缘由让我坚持写完了,那就是对江大牛的感谢和敬佩,也但愿他能继续完善这个框架。
成为一名优秀的程序员!