本篇记叙网关服务器的搭建。
主要用到的框架是江大的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