Step By Step创建你自己的游戏服务器(三)网关服务器

本篇记叙网关服务器的搭建。

主要用到的框架是江大的supersocket。文档地址:http://docs.supersocket.net/

一、引入SS

首先,下载最新版的ss,或者通过NuGet安装。我是下载源码的,当前版本是1.6.4。之后运行build脚本,生成之后,到bin目录下拷贝相应.net版本的dll到项目中。

我在原来的解决方案新建了一个GateServer项目,同样是Console应用。新建文件夹Reference,将刚才复制的dll粘贴到目录中。将ss需要用的log4net的配置文件放到项目根目录下的Config目录中。添加引用。

最后的结构大概如下图:

目录结构

二、启动SS

接着创建自己的PlayerSession和GatewayServer。

public class GatewayServer : AppServer<PlayerSession>
{
}

public class PlayerSession : AppSession<PlayerSession>
{
}

在App.Config添加相关的配置, 最后大概如下:

<configuration>
  <configSections>
    <section name="superSocket"
             type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig, SuperSocket.SocketEngine" />
  </configSections>
  <superSocket xmlns="http://schema.supersocket.net/supersocket"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://schema.supersocket.net/supersocket http://schema.supersocket.net/v1-6/supersocket.xsd">
    <servers>
      <server name="网关服务器"
              serverTypeName="GatewayServer"
              ip="Any"
              port="2020">
      </server>
    </servers>
    <serverTypes>
      <add name="GatewayServer"
           type="RoyNet.GateServer.GatewayServer, RoyNet.GateServer"/>
    </serverTypes>
  </superSocket>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
</configuration>

在Main函数中,使用SS的Bootstrap通过配置启动服务器

        var bootstrap = BootstrapFactory.CreateBootstrap();
        if (!bootstrap.Initialize())
        {
            Console.WriteLine("Failed to initialize!");
            Console.ReadLine();
            return;
        }
        var result = bootstrap.Start();
        Console.WriteLine("Start result: {0}!", result);
        if (result == StartResult.Failed)
        {
            Console.ReadLine();
            return;
        }
        Console.WriteLine("Press key 'q' to stop it!");
        while (Console.ReadKey().KeyChar != 'q')
        {
            Console.WriteLine();
        }
        Console.WriteLine();

        //Stop the appServer
        bootstrap.Stop();
        Console.WriteLine("The server was stopped!");
        Console.ReadLine();

F5运行,如果你开启了异常中断,可能会遇到一个SocketException,这似乎是SS用来平台检测的代码抛出的异常,不用担心已经被catch了,略过即可。 看到这句Start result: Success!,恭喜,SS启动成功。

三、个性化,实现网关

网关要做的事情有两个:验证登录和给登录的玩家传递交互数据。

那就是有两个Command:LoginCommand和InteractCommand

所以协议需要区别两个Command。这里我使用了固定头协议,并且这样约定:每个报文都有6个字节的头

  • 2个字节的神马备用
  • 1个字节标识指向的服务器
  • 1个字节标识command
  • 2个字节的body长度

那么继承FixedHeaderReceiveFilter,根据刚才约定的协议实现自己的GatewayReceiveFilter

    class GatewayReceiveFilter : FixedHeaderReceiveFilter<BinaryRequestInfo>
{
    public GatewayReceiveFilter()
        : base(6)
    {

    }

    protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
    {
        //byte salt1 = header[offset];    //备用
        //byte salt2 = header[offset + 1];//备用
        //byte dest = header[offset + 2]; //目标
        //byte cmd  = header[offset + 3]; //Command Name
        int len = header[offset + 4] * 256 + header[offset + 5];//最后两字节表示长度
        return len;
    }

    protected override BinaryRequestInfo ResolveRequestInfo(ArraySegment<byte> header, byte[] bodyBuffer, int offset, int length)
    {
        byte[] bodydata = new byte[length];
        Array.Copy(bodyBuffer, offset, bodydata, 0, length);
        return new BinaryRequestInfo(header.Array[header.Offset + 3].ToString("X"), bodydata);
    }
}

然后还需要一个Factory来创建Filter

    public class GatewayReceiveFilterFactory : IReceiveFilterFactory<BinaryRequestInfo>
{
    public IReceiveFilter<BinaryRequestInfo> CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint)
    {
        return new GatewayReceiveFilter();
    }
}

改动之前的GatewayServer,把FilterFactory作为参数传进去

    public class GatewayServer : AppServer<PlayerSession, BinaryRequestInfo>
{
    public GatewayServer(): base(new GatewayReceiveFilterFactory()) // 7 parts but 8 separators
    {

    }

    protected override void OnNewSessionConnected(PlayerSession session)
    {
        base.OnNewSessionConnected(session);//此处可下断点测试连接
    }
}

你可能也要改动下Session

    public class PlayerSession : AppSession<PlayerSession, BinaryRequestInfo>
{
    protected override void OnSessionStarted()
    {
        base.OnSessionStarted();//此处可下断点测试连接
    }
}

我顺便override两个方法,只是方便测试,你可以了解其调用过程,在此做一些其他的逻辑。

下面实现Command,Command需要继承ICommand接口,实现后的代码大致如下:

    //登录
    public class LoginCommand : ICommand<PlayerSession, BinaryRequestInfo>
{
    public string Name
    {
        get { return 0x01.ToString("X"); }
    }

    public void ExecuteCommand(PlayerSession session, BinaryRequestInfo requestInfo)
    {
        //todo: 下面是假象,完成登录验证
        var bs = Guid.NewGuid().ToByteArray();
        bool same = true;
        if (requestInfo.Body.Length == bs.Length)
        {
            if (bs.Where((t, i) => t != requestInfo.Body[i]).Any())
            {
                same = false;
            }
        }
        else
        {
            same = false;
        }
        if (same)
        {
            session.IsLogin = true;
        }
        //Console.WriteLine("收到报文{0},执行结果{1}", Name, same);
    }
}

这里还需要和登录服交互以验证token,IP,Port等。验证成功之后,与游戏服务器交互,拉取玩家角色列表,转发给客户端。(本篇主要让网关跑起来,服务器之间的交互下篇再见。)

还有个游戏交互操作的报文要处理:

/// <summary>
/// 游戏中的互动操作
/// </summary>
public class InteractCommand : ICommand<PlayerSession, BinaryRequestInfo>
{
    public string Name
    {
        get { return 0x02.ToString("X"); }
    }

    public void ExecuteCommand(PlayerSession session, BinaryRequestInfo requestInfo)
    {
        if (!session.IsLogin)
            return;//未登录的过滤
        //todo: 转发给游戏服
        Console.WriteLine("收到报文{0},转发游戏服", Name);
    }
}

SS默认是通过反射加载Command的,所以请public,如果Command和最终执行程序不是同一程序集,你还需要增加下配置。你也可以通过实现ICommandLoader来自定义命令的加载。更多的详情还是自己去参阅SS的文档站吧。

四、简单测试

之前的登录服通讯可以通过浏览器测试,这次因为骚骚自定义了下协议,所以,等写点代码了。 BTW,其实我也有写webrequest测试过登录服,发现u3d的mono的webrequest和.net的完全不能同日而语,无奈还是得用WWW啊。

这里因为是TCP长连接,客户端可以使用TcpClient类。

代码差不多是这样:

        TcpClient client = new TcpClient();
        client.Connect("127.0.0.1", 2020);
        var stream = client.GetStream();
        while (stream.CanWrite)
        {
            byte[] data = new byte[6+2];
            data[3] = 0x01;//cmd name
            data[4] = 0;
            data[5] = 0x02;//body length
            stream.Write(data, 0 , 8);
            Console.WriteLine("发了一行");
            Console.ReadLine();
        }

加几个断点瞧瞧,成功通讯。

BTW,在解析byte数组的时候你可能还需要注意一点大小端的问题



from: http://123.56.119.97/2015/04/step-by-step-3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值