Unity教程之-创建你自己的游戏服务器(三)

 

前两篇文章《Unity教程之-创建你自己的游戏服务器(一)》 《Unity教程之-创建你自己的游戏服务器(二)》我们学习了如何搭建服务器,本篇unity3d教程我呢么继续来学习下,创建自己的服务器–网关服务器的搭建,下面开始

一、引入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();
}

 

 

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

好了,本篇unity3d教程到此结束,下篇我们再会!