基于WindowsService的WebSocket编程Demo
一、什么是WebSocket
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。说了半天也就是说有了它你再也不需要定时去向服务端发送请求请求信息,服务器可以自己给你推送。
二、源码实现
1)新建WinService服务,导入WebSocket编程所需要的应用,该项目引用不在.net驱动包中,需要自己手动加载,我将其放在根目录下Lib文件下。
2)还是修改Program.cs,制定服务入口MyWebSocketService,在MyWebSocketService中实现服务所做的事情。
3)实例一个SuperWebSocket对象server;
4)新用户连接时创建会话连接(多连接);
4.1)拿着连接直接发送。
5)关闭会话时-----客户端关闭,发送到考场;考场关闭发送到客户端
6)接收信息
6.1)接收信息,发送出去
6.2)如果没有连接则构建连接。
using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.ServiceProcess; using SuperWebSocket; namespace SuperWebSocketWindowsServiceDemo { public partial class MyWebSocketService : ServiceBase { public MyWebSocketService() { InitializeComponent(); } WebSocketServer server; protected override void OnStart(string[] args) { var ip = ConfigurationManager.AppSettings["APWebSocketIP"]; var port = ConfigurationManager.AppSettings["APWebSocketPort"]; //WebSocket服务器端启动 server = new WebSocketServer(); if (!server.Setup(ip, int.Parse(port))) { //Debug.Write("WebSocket服务器端启动失败"); //处理启动失败消息 return; } //新的会话连接时 server.NewSessionConnected += server_NewSessionConnected; //会话关闭 server.SessionClosed += server_SessionClosed; //接收到新的消息时 server.NewMessageReceived += server_NewMessageReceived; if (!server.Start()) { //Debug.Write(string.Format("开启WebSocket服务侦听失败:{0}:{1}", server.Config.Ip, server.Config.Port)); //处理监听失败消息 return; } } protected override void OnStop() { } string KSessionId; string VSessionId; Dictionary<string, List<string>> msgDictionary = new Dictionary<string, List<string>>(); private void server_NewMessageReceived(WebSocketSession session, string value) { Debug.WriteLine("接收到新的消息:{0} 来自:{1} 时间:{2:HH:MM:ss}", value, session.RemoteEndPoint, DateTime.Now); if (value.StartsWith("K")) { KSessionId = session.SessionID; //页面已链接 if (!String.IsNullOrEmpty(VSessionId)) SendMsgToRemotePoint(VSessionId, string.Format("考场发来消息:{0}", value)); //页面未链接 else { AddMsgToSessionId(VSessionId); } } else if (value.StartsWith("S")) { VSessionId = session.SessionID; //考场已链接 if (!String.IsNullOrEmpty(KSessionId)) SendMsgToRemotePoint(KSessionId, string.Format("学生A发来消息:{0}", value)); //考场已断开 else { AddMsgToSessionId(KSessionId); } } } /// <summary> /// 添加会话消息 /// </summary> /// <param name="value"></param> private void AddMsgToSessionId(string value) { if (value != null) { //消息列表包含页面会话ID if (msgDictionary.ContainsKey(value)) { msgDictionary[value].Add(value); } //消息列表不包含页面会话ID else msgDictionary.Add(value, new List<string>() { value }); } } /// <summary> /// 会话关闭 /// </summary> /// <param name="session"></param> /// <param name="value"></param> private void server_SessionClosed(WebSocketSession session, SuperSocket.SocketBase.CloseReason value) { Debug.WriteLine("会话关闭,关闭原因:{0} 来自:{1} 时间:{2:HH:MM:ss}", value, session.RemoteEndPoint, DateTime.Now); if (session.SessionID == KSessionId) SendMsgToRemotePoint(VSessionId, "考场已断开"); else if (session.SessionID == VSessionId) SendMsgToRemotePoint(KSessionId, "学生A已断开"); } /// <summary> /// 新的会话链接 /// </summary> /// <param name="session"></param> private void server_NewSessionConnected(WebSocketSession session) { Debug.WriteLine("新的会话连接 来自:{0} SessionID:{1} 时间:{2:HH:MM:ss}", session.RemoteEndPoint, session.SessionID, DateTime.Now); //判断是否键存在,如果存在则锁定该键,赋值 if (msgDictionary.ContainsKey(session.SessionID)) msgDictionary[session.SessionID].ForEach(item => session.Send(item)); } /// <summary> /// 发送消息到 /// </summary> /// <param name="sessionId"></param> /// <param name="msg"></param> private void SendMsgToRemotePoint(string sessionId, string msg) { var allSession = server.GetAppSessionByID(sessionId); if (allSession != null) allSession.Send(msg); } } }
7)配置app.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <!-- ==============自定义==================--> <appSettings> <add key="APWebSocketIP" value="192.168.1.199"/> <add key="APWebSocketPort" value="8200"/> </appSettings> <!-- ===================================== --> </configuration>
8) 配置日志信息
<?xml version="1.0" encoding="utf-8" ?> <log4net> <appender name="errorAppender" type="log4net.Appender.RollingFileAppender"> <filter type="log4net.Filter.LevelMatchFilter"> <levelToMatch value="ERROR" /> </filter> <filter type="log4net.Filter.DenyAllFilter" /> <File value="Logs\err.log" /> <appendToFile value="true" /> <rollingStyle value="Date" /> <datePattern value="yyyyMMdd" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> </layout> </appender> <appender name="infoAppender" type="log4net.Appender.RollingFileAppender"> <filter type="log4net.Filter.LevelMatchFilter"> <levelToMatch value="INFO" /> </filter> <filter type="log4net.Filter.DenyAllFilter" /> <File value="Logs\info.log" /> <appendToFile value="true" /> <rollingStyle value="Date" /> <datePattern value="yyyyMMdd" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> </layout> </appender> <appender name="debugAppender" type="log4net.Appender.RollingFileAppender"> <filter type="log4net.Filter.LevelMatchFilter"> <levelToMatch value="DEBUG" /> </filter> <filter type="log4net.Filter.DenyAllFilter" /> <File value="Logs\debug.log" /> <appendToFile value="true" /> <rollingStyle value="Date" /> <datePattern value="yyyyMMdd" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> </layout> </appender> <appender name="perfAppender" type="log4net.Appender.RollingFileAppender"> <filter type="log4net.Filter.LevelMatchFilter"> <levelToMatch value="INFO" /> </filter> <filter type="log4net.Filter.DenyAllFilter" /> <File value="Logs\perf.log" /> <appendToFile value="true" /> <rollingStyle value="Date" /> <datePattern value="yyyyMMdd" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date %logger - %message%newline" /> </layout> </appender> <root> <level value="ALL" /> <appender-ref ref="errorAppender" /> <appender-ref ref="infoAppender" /> <appender-ref ref="debugAppender" /> </root> <logger name="Performance" additivity="false"> <level value="ALL" /> <appender-ref ref="perfAppender" /> </logger> </log4net>
9)安装该服务
10)添加web空项目,添加全局服务类Global.asax。添加界面。需要注意的就服务端每建立一次连接,就会生成一个session,然后在服务的方法中直接加上参数WebSocketSession session就可以了
三、总结:
1)个人感觉WebSocket就是一个分装好的服务,大家都可以通过固定IP、端口来访问该服务。接收时候服务可以直接推送过来,发送直接发送给服务就可以了。在服务中通过会话Id做下信息的交互就可以了。
2)这个项目中的学生只有最后一个在线是因为KSession的值会被最后一个覆盖,所以只能有一个学生在线,在代码中注释的部分是为了能狗找出所有连接的session然后直接服务端推送消息。
3)源码地址:http://pan.baidu.com/s/1c19kJwW