转自 posted on 2015-05-18 11:50 LitDev https://www.cnblogs.com/New-world/p/4511543.html
我的第一个Socket程序-SuperSocket使用入门(一)
第一次使用Socket,遇到过坑,也涨过姿势,网上关于SuperSocket的教程基本都停留在官方给的简单demo上,实际使用还是会碰到一些问题,所以准备写两篇博客,分别来介绍SuperSocket以及实际的案例应用,应用中我还遇到一些问题,还没解决,还请有经验的指出问题
--------------------------------------------------------------------------------begin-------------------------------------------------------------------------------
先奉上官方网址:http://www.supersocket.net/ 源码:http://supersocket.codeplex.com/releases/view/161987
官方的源码目前就不推荐看了,毕竟也是新手,不一定看得懂,使用前先看下官方的文档:http://docs.supersocket.net/ 官方源码目录中有个QuickStart文件夹,顾名思义就是快速开始,官方的Demo,这里很想吐槽,网上的很多Demo都是官方Demo都是这个源码中最简单的那个,操,那有个毛用!这一篇就不扯其他的了,只讲SuperSocket的使用
本人书读的比较少,文笔比较差,所以我尽量来写清楚SuperSocket的使用逻辑
一个SuperSocket的程序,可以包含多个Socket服务(称为AppServer),一个Socket服务中有多个客户端连接对象(称这个连接对象为AppSession),一个客户端与Socket服务通讯命令都在AppSession中进行(称这个命令为Commands),每一个命令在被执行前我们可以来控制这个命令是否给予执行,类似与MVC中的 Action Filter(称为CommandFilterAttribute),还有一些其他的例如命令行协议的,用默认的就可以了,复杂的Socket程序可能需要自定义协议,这里我们不予深究(其实也简单,搞懂上面的,这个就好搞了)
接下来我拿我项目的代码分别对上面列出的概念来说明
我用的是最新的SuperSocket1.6.4.0,VS2013,需要使用三个官方提供的类库:SuperSocket.Common.dll,SuperSocket.SocketBase.dll,SuperSocket.SocketEngine.dll,上面忘了说了,SuperSocket集成了日志插件:log4net,所以这里我们也要引用,注意这个1.6.4.0对应的log4net版本为:1.2.13.0,一定要使用官方Demo包中的dll,避免版本引用不一致的问题,项目结构(控制台程序):
结构实际是参照官方的,如图:,对应文件夹为:
这是一个将Socket程序的宿主使用Windows服务的形式运行,为什么要使用WindowsService就不解释了,后面再会写篇来介绍使用服务。我曾建个普通的windows服务项目,把SuperSocket的代码集成进去,无奈服务启动就特么停止,不清楚为啥,所以使用官方提供的Demo,官方的代码我就不解释了(其实是我也没看懂...),我们要通过配置文件来启动Socket程序,所以先看下App.config
<configuration> <configSections> <section name="log4net" type="System.Configuration.IgnoreSectionHandler"/><!-- 注册log4net --> <section name="superSocket" type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig, SuperSocket.SocketEngine"/><!-- 注册SuperSocket --> </configSections> <appSettings> <add key="ServiceName" value="SupperSocketService"/> <!-- 服务名称 --> <add key="ServiceDescription" value="试镜成Socket程序"/> <!-- 服务说明 --> </appSettings> <superSocket> <servers> <server name="WeChatSocket" textEncoding="gb2312" serverType="LXGlass.SocketService.WeChatServer, LXGlass.SocketService" ip="Any" port="2020" maxConnectionNumber="100"> </server> <!-- 一个socket服务(AppServer),当然可以配置多个 --> </servers> </superSocket> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> </startup> <runtime> <gcServer enabled="true" /> </runtime> </configuration>
上面说了,每个命令都在AppSession中,所以先来建个AppSession:
/// <summary> /// 微信Session /// </summary> public class WeChatSession:AppSession<WeChatSession> { /// <summary> /// 是否登录 /// </summary> public bool isLogin { get; set; } /// <summary> /// 机器编码 /// </summary> public string SN { get; set; } protected override void OnSessionStarted() { //this.Send("Welcome to SuperSocket WeChat Server\r\n"); } protected override void OnInit() { //this.Charset = Encoding.GetEncoding("gb2312"); base.OnInit(); } protected override void HandleUnknownRequest(StringRequestInfo requestInfo) { LogHelper.WriteLog("收到命令:" + requestInfo.Key.ToString()); this.Send("不知道如何处理 " + requestInfo.Key.ToString() +" 命令\r\n"); } /// <summary> /// 异常捕捉 /// </summary> /// <param name="e"></param> protected override void HandleException(Exception e) { this.Send("\n\r异常信息:{0}", e.Message); //base.HandleException(e); } /// <summary> /// 连接关闭 /// </summary> /// <param name="reason"></param> protected override void OnSessionClosed(CloseReason reason) { base.OnSessionClosed(reason); } }
在这个Session中我额外定义了两个属性:isLogin、SN,Session也就类似与asp.net中的Session
接着把AppSession注册到服务(AppServer)中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
/// <summary> /// 微信服务 /// </summary> //[WeChatCheckCommandFilter] public class WeChatServer:AppServer<WeChatSession> { protected override bool Setup(IRootConfig rootConfig, IServerConfig config) { return base .Setup(rootConfig, config); } protected override void OnStarted() { LogHelper.WriteLog( "WeChat服务启动" ); base .OnStarted(); } protected override void OnStopped() { LogHelper.WriteLog( "WeChat服务停止" ); base .OnStopped(); } /// <summary> /// 新的连接 /// </summary> /// <param name="session"></param> protected override void OnNewSessionConnected(WeChatSession session) { //LogHelper.WriteLog("WeChat服务新加入的连接:" + session.LocalEndPoint.Address.ToString()); base .OnNewSessionConnected(session); } } |
注意这两个类的继承关系,代码很简单,我把我的业务代码删掉了,先搞懂supersocket
再接着就是关键了(其实这些都是关键....),命令!先建一个CHECK的命令:
public class CHECK : CommandBase<WeChatSession, StringRequestInfo> { public override void ExecuteCommand(WeChatSession session, StringRequestInfo requestInfo) { if (requestInfo.Parameters.Count() != 1) { session.Send("The wrong format\r\n"); } else { string sn = requestInfo.Parameters[0].ToString(); if (string.IsNullOrWhiteSpace(sn)) session.Send("The wrong sn\r\n"); else { //已用此SN注册的连接会替换Sesion var session_client = session.AppServer.GetAllSessions().Where(c => c.SN == sn); if (session_client != null) { foreach (var item in session_client) { item.Send("new check,To close the connection for you\r\n"); item.Close(); } } session.isLogin = true; session.SN = sn; session.Send("success\r\n"); } } } }
这个命令继承自:CommandBase<WeChatSession, StringRequestInfo>,WeChatSession为我们刚才写的Session,StringRequestInfo为当前请求命令中的信息
当客户端发送:CHECK 1\r\n 就会收到success的返回,关于这个格式,就是命令行协议了,我这里使用默认的,想要自定义,去参照官方文档,这个默认的命令行协议的规则:每次请求和响应的数据结尾都有\r\n也就是换行符,通过\r\n来判断命令的结尾,CHECK为命令key,requestInfo.Key可以获得,上面的requestInfo.Parameters[0]是这个key后面以空格进行分割得到数组,所以requestInfo.Parameters[0]就可以取到CHECK 1 \r\n中的1,需要更多的数据就继续空格加
我代码中有段:已用此SN注册的连接会替换Sesion这里解释一下,因为每连上一个客户端,我们都要给他一个标识(这也是CHECK命令存在的意义),当一个客户端已用这个CHECK 1注册了,另外一个客户端也要用CHECK 1来注册,那程序就有两个sn=1的session了,为了保证唯一性,所以这里要把之前已注册CHECK=1的给踢掉,请无视item.send中的英文
需要其他命令的再添加类,继承CommandBase<WeChatSession, StringRequestInfo>
推荐个Socket客户端调试工具:SocketTool,下载地址:http://pan.baidu.com/s/1dDcZCfJ
基础就到这里了,下一篇说说实际的应用
我的第一个Socket程序-SuperSocket使用入门(二)
操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操操
辛辛苦苦写那么久的博客,最后手贱点了全屏富文本编辑器找上传附件的按钮,结果整个页面都卡死了,只能关掉再打开看自动保存的,尼玛的,就一个div显示内容,都找不到按钮来还原,麻痹麻痹麻痹麻痹麻痹,这恶心的IE,没心情写了,后面的是手动F12从dom中拷出来的代码,最后直接发个百度网盘的连接吧,这个富文本编辑器都没看到上传附件的按钮
接下来看下线程里的操作:
/// <summary> /// 异步线程发送信息 /// </summary> public class ThreadSendMsg { public void DoWork() { while (true) { try { List<MsgEntity> list = GlobalWeChatMsgList.GetList(); for (int i = 0; i < list.Count; i++) { var session = list[i].session; if (session != null) { var session_client = session.AppServer.GetAllSessions(); var client = session_client.Where(c => c.SN == list[i].sn); if (client != null) { foreach (var item in client) { item.Send(list[i].msg + " " + list[i].guid.ToString() + " " + list[i].open_id + "$"); } } } } } catch (Exception ex) { LogHelper.WriteLog("线程循环出错:" + ex.Message + "------------" + DateTime.Now.ToString()); } //线程睡眠1秒 System.Threading.Thread.Sleep(2000); } } }
上面有一句:if (client != null) ,这里从所有的session查找sn=列表中的那个,并判断这个连接是否还在线,如果在则发送,不在则消息还存在待发送的列表中 上面我们向安卓客户端发出去了消息,什么时候我们来把这条消息从待发送列表中删掉呢?不然安卓客户端就会不停的收到这条消息,这就需要安卓客户端主动告诉我们已经收到了这条消息我们新加一个命令:
/// <summary> /// 客户端来这里确认收到消息 /// </summary> public class IGET : CommandBase<WeChatSession, StringRequestInfo> { public override void ExecuteCommand(WeChatSession session, StringRequestInfo requestInfo) { if (requestInfo.Parameters.Count() != 1) { session.Send("error parameters\r\n"); return; } string key = requestInfo.Parameters[0]; if (string.IsNullOrWhiteSpace(key)) { session.Send("guid is null\r\n"); return; } if (key.Substring(key.Length - 1, 1) != "$") { session.Send("error guid\r\n"); return; } try { Guid guid = new Guid(key.Substring(0, key.Length - 1)); GlobalWeChatMsgList.RemoveMsg(guid); session.Send("success\r\n"); } catch { session.Send("not validate guid\r\n"); } } }
基本功能这里都已经好了,还有一个是客户端连接的心跳,客户端怎么知道当前与socket服务器连接正常?所以再加一个命令处理类:
public class XT : CommandBase<WeChatSession, StringRequestInfo> { public override void ExecuteCommand(WeChatSession session, StringRequestInfo requestInfo) { if (requestInfo.Parameters.Count() == 1) { if (requestInfo.Parameters[0] == "&") { if (!session.isLogin|| string.IsNullOrWhiteSpace(session.SN)) session.Send("no check\r\n"); else session.Send("$\r\n"); } } } }
这个心跳的消息通讯很简单,他发一个&字符,服务器响应一个$符号,这里再重申一下,服务器与客户端的通讯信息结尾都是以\r\n结束。
---------------------------------------------------------------------------------- End ---------------------------------------------------------------------------------------------------------
源码:http://pan.baidu.com/s/1bntsF3x