Unity教程之-创建你自己的游戏服务器(四):服务器间通信

上篇文章《Unity教程之-创建你自己的游戏服务器(三):网关服务器》,本篇unity3d教程我们来实现服务器的通信,下面开始!

本回要引入的是:NetMQ NetMQ是开源消息队列ZeroMQ的.NET版本,也是基于socket,所以可以在不同主机进行通讯。基于安全性的考虑,服务器内部的通讯必须是可信的,所以可以使用NetMQ。NetMQ的使用非常简单,如下几步走:

  • CreateContext
  • CreateSocket
  • Connect/Bind
  • Send/Receive
  • Dispose

这里我使用Push->Pull模型 代码差不多是这个样子


using (var context = NetMQContext.Create())
{
using (var socket = context.CreatePushSocket())
{
socket.Connect(address);//address="127.0.0.1:8081"
while (true)
{
socket.Send(msg);//msg就是要发送的数据
}
}
}

如果是Pull端的话,把Connect()换成Bind(), Send换成Receive即可。 不过在参数address上,发送方因为是connect,所以必须是明确的地址127.0.0.1,接收方可以是*LS到Gate的通信是单向的,所以Push->Pull就可以了,LS只负责Push。PullPush也支持多对多,所以(应该,仅简单测试)不需要担心并发的问题。

然后修改GS的代码,在GS中加入一根单独的线程来Pull从LS来的数据。然后还需要一个线程安全的字典可以查询当前的token。

等等,发现少了点什么!假如客户端收到token并转发给GS的时候GS还没有pull到那个token,晚了一步导致验证失败,怎么救?那么看来这里使用PushPull是不明智的,改成RequestResponse吧。

LS 2 Gate

那么修改之前的LS的代码,并加上注册的外部接口


public class LoginModule : NancyModule
{
public LoginModule()
{
Get["/login/{uname}/{pwd}"] = p =>
{
using (var conn = ConnectionProvider.Connection)
{
string uid = conn.Query<string>("select uid from Account where username=@username and password=md5(@password)", new
{
username = p.uname,
password = p.pwd
}).FirstOrDefault();
if (uid != null)
{
string token = Guid.NewGuid().ToString();
SendToGate(uid, token);
return Response.AsJson(new { Result = "OK", Token = token });
}
else
{
return Response.AsJson(new { Result = "Failed" });
}
}
};

Get["/reg/{uname}/{pwd}"] = p =>
{
using (var conn = ConnectionProvider.Connection)
{
var account = new
{
username = p.uname,
password = p.pwd
};
int ret = conn.Execute("insert into Account(<code>username</code>,<code>password</code>) values(@username,@password)", account);
if (ret == 1)
{
string uid = conn.Query&lt;string&gt;("select uid from Account where username=@username", account).First();
string token = Guid.NewGuid().ToString();
SendToGate(uid, token);
return Response.AsJson(new { Result = "OK", Token = token });
}
else
{
return Response.AsJson(new { Result = "Failed" });
}
}
};
}

void SendToGate(string uid, string token)
{
using (var context = NetMQContext.Create())
{
using (var socket = context.CreateRequestSocket())
{
socket.Connect(Config.GateAddress);
string msg = string.Format("{0},{1}", uid, token);
socket.Send(msg);
socket.Receive();//just wait
}
}
}
}

OK,那么接着去修改GS,在GatewayServer类中增加一根线程接收LS传来的数据。

先给GS增加几个字段


private readonly ResponseSocket _socket2LoginServer;
private CancellationTokenSource _cancelToken2LoginServer;
private Thread _receThread2LoginServer;

再在重载的Start方法内启动Thread


_receThread2LoginServer = new Thread(ReceiveLoginServer);
_cancelToken2LoginServer = new CancellationTokenSource();
_receThread2LoginServer.Start();

Thread的处理函数如下


void ReceiveLoginServer()
{
while (!_cancelToken2LoginServer.IsCancellationRequested)
{
Thread.Sleep(1);
if (!_socket2LoginServer.HasIn)
continue;
try
{
string[] data = _socket2LoginServer.ReceiveString(Encoding.UTF8)
.Split(separator, StringSplitOptions.RemoveEmptyEntries);
string uid = data[0];
string token = data[1];
//Console.WriteLine("{0},{1}",uid, token);
_socket2LoginServer.Send("ok");
}
catch (Exception ex)
{
//log
}
}
}

同步

Gate需要将刚才接收到的信息保存到一个字典中,用于登录验证。因为是在不同线程中,所以需要一个线程安全的容器。这里我使用了ConcurrentDictionary

把刚才写的 //Console.WriteLine("{0},{1}",uid, token);替换成UIDTokens[uid] = token;

测试

启动两个Server,还是使用浏览器来进行测试,访问地址http://127.0.0.1:8080/reg/uname/password断点或者Log输出检查GS是否收到。

好,那么下篇是游戏服务器的搭建。好了,本篇unity3d教程到此结束,下篇我们继续!