2013年3月6日

SuperSocket的研究(1)—— Telnet实例

前几天终于把开题报告写好了,这玩意儿着实令人厌烦。你不写吧,交不了差。但写了又没多大用处,无非是抄点前人的老话、套话——你还得小心别抄袭了,不过总算是完成了。我要做的是基于WebSocekt的一个监控系统,JAVA已经有不少像Tomcat、JBoss等服务器级的支持,而.NET这边却很少。IIS8支持WebSocket,但需要WIN8,我懒得折腾系统,就找了一个开源的WebSocekt Server的实现:WebSocket4Net,是SuperWebSocket为了方便.NET程序员进行WebSocket开发而衍生的一个版本。而SuperWebSocket又是基于SuperSocket
我曾询问过作者(该项目是国人开发的)SuperSocket的性能如何,得到的答复是在一台较主流的笔记本上可以达到每秒1万次的并发。我虽然不敢妄加评论这个性能是好是坏,但至少比之前使用的Comet及其实现(AspNetComet)那据说是只有200的并发要好得多。况且这并不是一个商业项目,作为本科毕业设计,做出来的东西本来就是实验、探究性质,对性能的要求可以适当降低。下面是我研究SuperSocket 1.5的开发者文档,而做的笔记和测试。根据我的需求,文档中最后3个或4个我可能不会去研究。
虽然说是学习和研究,其实主要是跟着文档中的操作自己再做一遍,并把英文的文档翻译一下。一些我遇到了而原文档中没有详细说明的地方我也会提出来。
但当我写到这篇文章的末尾我发现SuperSocket并不能直接应用于我的项目,我要用的是WebSocket4Net。当然研究一下根儿也是不错的,只是我可能不会那么用心了。
这一节讲述了用Super Socket搭建一个telnet服务器并使用windows的telnet客户端进行测试的步骤。同时还说明了Super Socket的依赖关系、核心组件以及初步的使用方法。
Super Socket的核心组件有3个,分别是:SuperSocket.Common.dll、SuperSocket.SocketBase.dll、SuperSocket.SocketEngine.dll。另外,Super Socket使用了log4net作为其默认的日志系统,所以在项目中使用时需要添加引用这4个dll文件(包括log4net.dll)。

创建一个控制台应用程序并添加Super Socket的相关引用

  1. 下载Super Socket的程序集。源码或已编译的dll均可,注意Super Socket编译的.NET版本要跟自己的项目一致。然后创建一个控制台应用程序,需要注意的是编译的平台选择.NET 3.5或4.0这样的完全版,而不要选择Client Profile。项目建立后仍可以在该项目的“属性”中查看\更改这一属性。
  2. 复制上面提到的4个dll到项目的bin目录,并添加这些引用。同时,如果没有项目没有引用System.Configuration,也需要一并添加,Super Socket需要用到这一程序集。
  3. 将Super Socket中的对应.NET版本目录下的Config文件夹复制到自己项目的根目录下。

编写启动/停止服务器代码

原文中只给出了Main方法里的代码,且仅限于服务器的启停,没有后文中各种测试的支持。虽然这对开发者来说应该不会造成太大的障碍,但我仍然在这里贴出完整的,经我测试可以运行的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;

namespace SuperSocket_0100_TalnetExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press any key to start the server!");
            Console.ReadKey();
            Console.WriteLine();
            AppServer appSrv = new AppServer();

            //启动服务器
            if (!appSrv.Setup(2012))//启动服务器并监听2012端口
            {
                Console.WriteLine("Failed to setup");
                Console.ReadKey();
                return;
            }

            Console.WriteLine();

            //Try to start the appServer
            if (!appSrv.Start())
            {
                Console.WriteLine("Failed to start!");
                Console.ReadKey();
                return;
            }

            //添加会话连接委托事件
            appSrv.NewSessionConnected += new SessionHandler(appSrv_NewSessionConnected);
            //添加收到请求后的委托事件
            appSrv.NewRequestReceived += new RequestHandler(appSrv_NewRequestReceived);

            Console.WriteLine("The server started successfully, press key \"q\" to stop it. ");
            while (Console.ReadKey().KeyChar != 'q')
            {
                Console.WriteLine();
                continue;
            }

            //Stop the appServer
            appSrv.Stop();
            Console.WriteLine("The server was stopped");
            Console.ReadKey();

        }
        //向客户端发送欢迎信息
        static void appSrv_NewSessionConnected(AppSession session)
        {
            session.Send("Welcome to SuperSocket Telnet Server!");
        }
        //处理客户端请求,这里定义了ECHO,ADD,MULT 3种操作
        static void appSrv_NewRequestReceived(AppSession session, StringRequestInfo requestInfo)
        {
            switch (requestInfo.Key.ToUpper())
            {
                case("ECHO"):
                    session.Send(requestInfo.Body);
                    break;
                case("ADD"):
                    session.Send(requestInfo.Parameters.Select(p=>Convert.ToInt32(p)).Sum().ToString());
                    break;
                case ("MULT"):
                    int result = 1;
                    foreach (int factor in requestInfo.Parameters.Select(p => Convert.ToInt32(p)))
                    {
                        result *= factor;
                    }
                    session.Send(result.ToString());
                    break;
            }
        }

    }
}
需要说明的是,注意引用必要的命名空间。在CodePlex上有人问到过这样的问题:StringRequestInfo这个类找不到。这个类在SuperSocket.SocketBase.Protocol命名空间下,需要在代码中添加using语句才可使用。

使用Telnet客户端进行测试

WIN7默认没有安装Telnet的服务端和客户端。需要用户手动开启,在“控制面板”——“打开或关闭Windows功能”中勾选Telnet服务端(可选)和Telnet客户端,然后需要在服务中启动Telnet服务才能完成下面的测试。

  1. 启动服务器程序,按任意键启动服务器。
  2. 打开telnet客户端,这里我使用windows自带的客户端。
  3. 输入“telnet localhost 2012”,回车
  4. 首先会得到欢迎信息,随后可输入ADD 2 3或ECHO Hello这样的命令。前者会返回两数之和,后者会原样输出字符串,如图。

命令行的用法(以下代码我并未测试)

前面这种switch-case的方式在处理复杂业务逻辑时显得十分臃肿,也不符合面向对象设计(OOD)的要求。Super Socket提供这样一个框架,允许定义独立的类来处理不同的请求。我们只需要继承CommandBase这个类,并覆盖ExecuteCommand方法即可。
例如,可以定义个名为ADD的类,来处理客户端发送过来的ADD请求(大小写敏感的)。
public class ADD : CommandBase<AppSession, StringRequestInfo>
{
    public override void ExecuteCommand(AppSession session, StringRequestInfo requestInfo)
    {
        session.Send(requestInfo.Parameters.Select(p => Convert.ToInt32(p)).Sum().ToString());
    }
}
同理,MULT类,用来处理名为MULT的请求
public class MULT : CommandBase<AppSession, StringRequestInfo>
{
    public override void ExecuteCommand(AppSession session, StringRequestInfo requestInfo)
    {
        var result = 1;

        foreach (var factor in requestInfo.Parameters.Select(p => Convert.ToInt32(p)))
        {
            result *= factor;
        }

        session.Send(result.ToString());
    }
}
上面这种方式与添加委托事件的方式会产生冲突,需要移除前面第一段代码中的高亮部分。(委托事件的方法也可一并移除或注释掉)

没有评论:

发表评论