ET---通过注册登陆请求走一遍简单的消息传输

通过注册登陆请求走一遍简单的消息传输

请大家关注我的微博:@NormanLin_BadPixel坏像素


//创建登录服务器连接
IPEndPoint connetRealmEndPoint = NetworkHelper.ToIPEndPoint(GlobalConfigComponent.Instance.GlobalProto.Address);
session = Hotfix.Scene.ModelScene.GetComponent<NetOuterComponent>().Create(connetRealmEndPoint);

//发送注册请求
prompt.text = "正在注册中....";
R2C_Register_Ack r2C_Register_Ack = await session.Call(new C2R_Register_Req() { Account = account.text, Password = password.text }) as R2C_Register_Ack;

我们看到,客户端的这些消息会发送给Realm服务器,再由Realm服务器转发给其他服务器,我们去服务端代码看看是怎么处理注册信息的。

我们知道,Realm服务器会在NetOuterComponent组件内接收到直接从客户端传来的消息。

我们一步一步来看看是怎样创建连接的。

我们在添加NetOuterComponent组件的时候,就已经创建了一个AService(这里是TService),注意这里我们没有传入IP地址,所以它并没有监听哪个端口。

首先我们会从NetOuterComponent创建一个与Realm服务器的回话消息Session

我们在创建Session之前,得先通过Service创建一个Channel,在创建这个Channel的时候就开始对远端发送连接请求了。不过,对数据的接收会在下一帧开始,因为在Start方法里启动。

之后便会通过这个SessionRealm服务器发送消息或者请求。

这里我们发送了一个注册请求,请求的数据里面包含用户名和密码。

总长度(size) + 是一个请求(flag) + 注册(opcode) + RpcId(递增,唯一的)(可选。等于0表示不是RPC消息) + 用户名和密码(data)。

关于消息的结构,我们之前讲了很多。TChannel学习笔记

扫描二维码关注公众号,回复: 174860 查看本文章

这里的注册是一个请求,是一个Rpc消息。所以我们用Session.Call。

消息发送成功后,我们看看服务端是怎么接收到这个消息的。

我们看到,Realm服务器添加NetOuterComponent组件的时候,把自己的outerConfig.IPEndPoint传进去了,也就是说,它会开始监听这个端口。

整个步骤是这样的。

NetworkComponent
    Awake
        StartAccept
    StartAccept
        await this.Accept();
    Accept
        AChannel channel = await this.Service.AcceptChannel();
TService
    AcceptChannel
        TcpClient tcpClient = await this.acceptor.AcceptTcpClientAsync();

我们最后会在收到客户端传来的请求的时候创建一个跟其通信的Session,这里我们接收到的是注册请求。

我们看这个Session是怎么处理Channel里面的信息的。

Session
    Start
        StartRecv
    StartRecv
        Packet packet;
        packet = await this.channel.Recv();
        this.Run(packet);
    Run

这个Run具体做了什么,大家可以先复习一下Session学习笔记

接下来,这个消息包,会被调到消息分发组件处理。

我们知道,NetOuterComponent的消息调度器是OuterMessageDispatcher

首先,我们得判断一下,我们的注册请求是不是Actor消息。

[Message(HotfixOpcode.C2R_Login_Req)]
[ProtoContract]
public partial class C2R_Login_Req: IRequest

只是简单的请求,所以我们直接交给消息调度组件处理。

而消息调度器会找到注册了操作符的处理器来处理这个消息。

这里,我们的操作符是HotfixOpcode.C2R_Login_Req,我们只要找到对应的处理器就好了。

这里我帮大家找好了。

C2R_Register_ReqHandler

我们看看,会怎么处理这个消息。

我们看到,这里会获取数据库操作对象,但是我们在Program里面启动Realm服务的时候并没有添加数据库操作相关的组件。唯一有添加的是AllServer,所以我猜测可能是Demo是用AllServer启动的,作者懒得在这里添加了,这里大家注意就好了。

之后,就是通过DBProxyComponent对数据库进行操作。如果成功注册,则返回一个默认R2C_Register_Ack对象,否则,返回一个带错误信息的R2C_Register_Ack对象。

按理说,这个注册消息应该是属于ActorRpc消息的,因为它通过Realm服务器向DB服务器发送请求。不过这里,因为Demo仅是供大家学习的,没有独立分出一个DB服务器,默认所有服务都在同一台服务器上。大家不用纠结,当自己写的时候,注意一下不要照抄就好了。

同样的,我们再来看看登陆消息的处理。

C2R_Login_ReqHandler

前面对数据库的操作,大家都明了。

之后有一个将已在线玩家踢下线的操作,不过我们先跳过,最后再讲。

//随机分配网关服务器
StartConfig gateConfig = Game.Scene.GetComponent<RealmGateAddressComponent>().GetAddress();
Session gateSession = Game.Scene.GetComponent<NetInnerComponent>().Get(gateConfig.GetComponent<InnerConfig>().IPEndPoint);

//请求登录Gate服务器密匙
G2R_GetLoginKey_Ack getLoginKey_Ack = await gateSession.Call(new R2G_GetLoginKey_Req() { UserID = account.Id }) as G2R_GetLoginKey_Ack;

经过之前的学习,我想大家理解这几段代码都很简单吧。不了解的可以去Component列表里面学习。

我们现在来看看,Gate服务是怎么处理这个消息的。

protected override void Run(Session session, R2G_GetLoginKey_Req message, Action<G2R_GetLoginKey_Ack> reply)
{
    G2R_GetLoginKey_Ack response = new G2R_GetLoginKey_Ack();
    try
    {
        long key = RandomHelper.RandInt64();
        Game.Scene.GetComponent<LandlordsGateSessionKeyComponent>().Add(key, message.UserID);
        response.Key = key;
        reply(response);
    }
    catch (Exception e)
    {
        ReplyError(response, e, reply);
    }
}

我们看到,这里往LandlordsGateSessionKeyComponent储存了一个随机Key跟UserID,并把Key通过G2R_GetLoginKey_Ack返回给Rleam服务。Rleam服务会把这个key跟自己随机分配给这个用户的Gate服务器地址通过G2R_GetLoginKey_Ack返回给客户端。现在还不知道这个key有什么作用。

好,我们看看客户端接收到登陆请求的回复后会做什么。

登陆失败的就不说了。

如果成功的话,则会跟分配给自己的Gate服务器进行连接。

//创建Gate服务器连接
IPEndPoint connetGateEndPoint = NetworkHelper.ToIPEndPoint(r2C_Login_Ack.Address);
Session gateSession = Hotfix.Scene.ModelScene.GetComponent<NetOuterComponent>().Create(connetGateEndPoint);

r2C_Login_Ack.Address是分配给自己的Gate服务器地址。

建立连接后,会尝试登陆Gate服务器

//登录Gate服务器
G2C_LoginGate_Ack g2C_LoginGate_Ack = await gateSession.Call(new C2G_LoginGate_Req() { Key = r2C_Login_Ack.Key }) as G2C_LoginGate_Ack;

我们看看Gate服务器收到登陆请求的反应。在C2G_LoginGate_ReqHandler里面处理。

首先会先对LandlordsGateSessionKeyComponent进行查询,看登陆Key是否存在,如果不存在,则直接返回登陆失败的答复。

这里,我们能知道,当用户通过Realm服务请求分配Gate服务器的时候,会在Gate服务器上生成一个登陆Key,这个key是有时限的,在时限内用户通过这个key才能创建客户端与Gate服务器的会话连接。

如果登陆成功了,则会移除这个key,并且创建User对象。

User user = UserFactory.Create(userId, session.Id);
await user.AddComponent<ActorComponent>().AddLocation();
//添加User对象关联到Session上
session.AddComponent<SessionUserComponent>().User = user;
//添加消息转发组件
await session.AddComponent<ActorComponent, IEntityActorHandler>(new GateSessionEntityActorHandler()).AddLocation();

UserFactory.Create内,会添加UnitGateComponent并储存SessionId,并且可以通过UnitGateComponent请求到Session代理。

public static User Create(long userId, long sessionId)
{
    User user = ComponentFactory.Create<User, long>(userId);
    user.AddComponent<UnitGateComponent, long>(sessionId);
    Game.Scene.GetComponent<UserComponent>().Add(user);
    return user;
}

大家可以理解为这里User对象和其跟Gate服务器的会话Session互相绑定的过程。

并且,User对象跟Session对象都会添加ActorComponent添加到Gate服务器的ActorProxyComponent内。而且,这里gate session收到的消息会通过GateSessionEntityActorHandler处理。

ActorComponent学习笔记

在这里调用ActorComponent的AddLocation方法,就是把User对象和Session对象挂在Gate服务器名下,并且注册到地址服务器。之后其他服务器通过地址服务器查询这些对象时,就能找到Gate服务器了。

Gate服务器对消息处理完后,还会向登录服务器发送玩家上线消息。

StartConfigComponent config = Game.Scene.GetComponent<StartConfigComponent>();
IPEndPoint realmIPEndPoint = config.RealmConfig.GetComponent<InnerConfig>().IPEndPoint;
Session realmSession = Game.Scene.GetComponent<NetInnerComponent>().Get(realmIPEndPoint);
realmSession.Send(new G2R_PlayerOnline_Ntt() { UserID = userId, GateAppID = config.StartConfig.AppId });

我们看到,这个只是一个消息,而不是请求,不需要登陆服务器回复,所以我们用Session.Send方法来发送消息。

最后,Gate服务器会向发起登陆到Gate服务器请求的请求发起者回复结果,结果包括在Gate服务器上创建的User实例的一些消息。

response.PlayerID = user.Id;
response.UserID = user.UserID;
reply(response);

Gate服务器上的处理完了,我们去看看登陆服务器收到玩家上线后的处理吧。

[MessageHandler(AppType.Realm)]
public class G2R_PlayerOnline_NttHandler : AMHandler<G2R_PlayerOnline_Ntt>
{
    protected override async void Run(Session session, G2R_PlayerOnline_Ntt message)
    {
        OnlineComponent onlineComponent = Game.Scene.GetComponent<OnlineComponent>();
        //将已在线玩家踢下线
        await RealmHelper.KickOutPlayer(message.UserID);
        //玩家上线
        onlineComponent.Add(message.UserID, message.GateAppID);
        Log.Info($"玩家{message.UserID}上线");
    }
}

作者注释的很详细了。我们之前在发起登陆的时候就已经调用了一次将已在线玩家踢下线,这里又处理了一次,避免额外情况。

public static async Task KickOutPlayer(long userId)
{
    //验证账号是否在线,在线则踢下线
    int gateAppId = Game.Scene.GetComponent<OnlineComponent>().Get(userId);
    if (gateAppId != 0)
    {
        StartConfig userGateConfig = Game.Scene.GetComponent<StartConfigComponent>().Get(gateAppId);
        IPEndPoint userGateIPEndPoint = userGateConfig.GetComponent<InnerConfig>().IPEndPoint;
        Session userGateSession = Game.Scene.GetComponent<NetInnerComponent>().Get(userGateIPEndPoint);
        await userGateSession.Call(new R2G_PlayerKickOut_Req() { UserID = userId });

        Log.Info($"玩家{userId}已被踢下线");
    }
}

我们看到,如果之前就已经成功踢下线了,这里就不会再向玩家所在的Gate服务器发送请求了。

我们去看看Gate服务器又进行了什么处理。

G2R_PlayerKickOut_Ack response = new G2R_PlayerKickOut_Ack();
try
{
    User user = Game.Scene.GetComponent<UserComponent>().Get(message.UserID);
    long userSessionId = user.GetComponent<UnitGateComponent>().GateSessionId;
    Session userSession = Game.Scene.GetComponent<NetOuterComponent>().Get(userSessionId);
    userSession.Send(new G2C_PlayerDisconnect_Ntt());
    await Game.Scene.GetComponent<TimerComponent>().WaitAsync(1000);
    Log.Info($"将玩家{message.UserID}连接断开");
    userSession.Dispose();
    reply(response);
}
catch (Exception e)
{
    ReplyError(response, e, reply);
}

我们知道,挂在自己服务器名下的User对象跟自己Gate服务器和客户端之间的会话Session都分别保存在本地的UserComponent组件和NetOuterComponent组件里面,所以这里可以直接获取到跟客户端的会话Session。通过这个Session,我们可以向客户端发送消息,这里,我们发送了玩家下线的消息给客户端。等待一秒后,将玩家连接断开,这里我们在是在Dispose里面进行断开连接的处理。

我们先看看客户端收到下线消息的处理吧。

//移除连接组件
Game.Scene.RemoveComponent<SessionComponent>();
//释放本地玩家对象
ClientComponent clientComponent = Game.Scene.GetComponent<ClientComponent>();
clientComponent.LocalPlayer.Dispose();
clientComponent.LocalPlayer = null;

UIComponent uiComponent = Hotfix.Scene.GetComponent<UIComponent>();

UI uiLogin = uiComponent.Create(UIType.LandlordsLogin);
uiLogin.GetComponent<LandlordsLoginComponent>().SetPrompt("连接断开");

if (uiComponent.Get(UIType.LandlordsLobby) != null)
{
    uiComponent.Remove(UIType.LandlordsLobby);
}
else if(uiComponent.Get(UIType.LandlordsRoom) != null)
{
    uiComponent.Remove(UIType.LandlordsRoom);
}

没有其他消息的发送了,仅是对客户端进行一些操作,因为Demo基本上是由UI组成的,所以这里大部分是对UI进行操作。如果我们自己做游戏的话,得加自己的逻辑。

好,我们继续回到Gate服务器怎么断开连接的。

...
base.Dispose();
...
this.Network.Remove(id);
...

我们主要看这两段代码。首先,它会调用原生的Dispose,我们知道,Session是继承Entity的,EntityDispose方法里面,会将添加在其下面的组件也一并Disopse掉。Entity学习笔记

所以,它会在将自己与其身上的组件Dispose之后,再调用NetworkComponent.Remove方法从NetworkComponent移除这个Session的记录。

那这个Session上面有什么组件呢?大家可否还记得,在我们创建这个Session的时候,让它与User对象互相绑定。那个时候我们为其添加了SessionUserComponentActorComponent组件,我们来看看这些组件被移除时做的处理。

SessionUserComponent学习笔记

ActorComponent组件被移除时,会把Actor信息从ActorManagerComponent移除。

Game.Scene.GetComponent<ActorManagerComponent>().Remove(actorId);

差不多,在服务上对登陆请求的一些处理我们都过了一遍,我们来看看客户端接收到登陆成功后的处理吧。

这些处理我们补充到LandlordsLoginComponent学习笔记里。因为之前没有服务端方面的知识,所以只讲了一半。

猜你喜欢

转载自blog.csdn.net/norman_lin/article/details/79992271