ET6.0 server framework learning notes (II. A login protocol)
The last main record is ET6 0 server startup function, this article mainly records ET6 0 is a complete protocol, from configuration to generating protocol data, to sending from the client to the server, and then back to the client
1, Login protocol process
1. Protocol configuration
ET6.0 client communicates with the server. The serialization method is protobuf and the library is protobuf net.
To use protobuf, you need to define the structure entry of protocol communication data. Corresponding path:
Because the login protocol is the communication between the client and the server and does not belong to the internal protocol of the server, open outermessage Proto, which stores the protocol data defined by the communication between the client and the server.
For example, the login protocol is defined as follows:
Note:
1. Because login is a request response type protocol (that is, send a piece of data and expect to return a piece of data), note that the corresponding C2R_Login protocol is marked with "/ / ResponseType R2C_Login". When generating the protocol, it is used to mark that the response type corresponding to the C2R_Login request is R2C_Login
2. Because the request is sent directly to the realm server, it is a common IRequest type protocol marked IRequest
3.R2C_ The login reply message structure is an ordinary IResponse because it is sent to the client by the real server
4. Note that both protocol classes have rpcid, which is mainly used to send their own rpcid when sending request response messages, and bring back this value when returning. It is used to find out which request protocol is returned when the sender receives the return protocol.
2. Protocol data generation
Running Proto2CS program will automatically generate InnerMessage protocol into serverMessagePath directory and OuterMessage protocol into serverMessagePath and clientMessagePath directories respectively.
You can see that the following protocol data classes will be generated (in order to see the whole, the code is folded, and the generated by the server and the client are the same)
3. Sending protocol
Find the loginhelp class of the client. This is an example of sending login DEMO written by ET. As follows:
public static async ETTask Login(Scene zoneScene, string address, string account, string password) { try { // Create a Session of ETModel layer R2C_Login r2CLogin; using (Session session = zoneScene.GetComponent<NetKcpComponent>().Create(NetworkHelper.ToIPEndPoint(address))) { r2CLogin = (R2C_Login) await session.Call(new C2R_Login() { Account = account, Password = password }); } // Create a gate Session and save it to the SessionComponent Session gateSession = zoneScene.GetComponent<NetKcpComponent>().Create(NetworkHelper.ToIPEndPoint(r2CLogin.Address)); gateSession.AddComponent<PingComponent>(); zoneScene.AddComponent<SessionComponent>().Session = gateSession; G2C_LoginGate g2CLoginGate = (G2C_LoginGate)await gateSession.Call( new C2G_LoginGate() { Key = r2CLogin.Key, GateId = r2CLogin.GateId}); Log.Debug("land gate success!"); await Game.EventSystem.Publish(new EventType.LoginFinish() {ZoneScene = zoneScene}); } catch (Exception e) { Log.Error(e); } }
Core code:
// Create a Session of ETModel layer R2C_Login r2CLogin; using (Session session = zoneScene.GetComponent<NetKcpComponent>().Create(NetworkHelper.ToIPEndPoint(address))) { r2CLogin = (R2C_Login) await session.Call(new C2R_Login() { Account = account, Password = password }); }
- First, create or obtain an existing connection between a corresponding IP address and a port from the NetKcpComponent of the current Scene.
- Use the await method and wait for the protocol data to be returned. Call session Call, the ettask < iresponse > with the expected result is returned.
- Create a new C2R_Login structure, fill in the corresponding data and send it directly.
4. Processing protocol
View C2R on server side_ Loginhandler class, which is the general processing protocol sent by the client.
[MessageHandler] public class C2R_LoginHandler : AMRpcHandler<C2R_Login, R2C_Login> { protected override async ETTask Run(Session session, C2R_Login request, R2C_Login response, Action reply) { // Randomly assign a Gate StartSceneConfig config = RealmGateAddressHelper.GetGate(session.DomainZone()); //Log.Debug($"gate address: {MongoHelper.ToJson(config)}"); // Request a key from the gate, and the client can connect to the gate with this key G2R_GetLoginKey g2RGetLoginKey = (G2R_GetLoginKey) await ActorMessageSenderComponent.Instance.Call( config.InstanceId, new R2G_GetLoginKey() {Account = request.Account}); response.Address = config.OuterIPPort.ToString(); response.Key = g2RGetLoginKey.Key; response.GateId = g2RGetLoginKey.GateId; reply(); } }
Note:
- [MessageHandler] attribute tag. The method marked as this will be distributed and processed by the messagedispatcher component, that is, it will be directly processed by the message dispatcher of the current process server (it also means that the processing protocol is an ordinary protocol)
- Derived from AMRpcHandler < C2R_Login, R2C_Login > to accept a C2R_Login type protocol, return an r2c_ In the login protocol, the AMRpcHandler class encapsulates the unified processing of reply messages, that is, the incoming Action reply.
- Specific server C2R_Login processing: obtain the relevant configuration of a random Gate, and real sends an actor request to the Gate service to get an authentication key used by the client to log in to the Gate. Actor request, do not pay attention here, wait for the next detailed explanation, here is to get a certified key directly, and fill in the reply instance, call reply to send the reply protocol back to the client.
5. Accept return agreement
Look back at the loginhelp class of the client, and the C2R is received on the server_ Login protocol, return r2c_ After login, the client receives the message. After a series of processing (the intermediate process will be explained in detail in the code analysis later in this article), r2login = (R2C_Login) await session Call(new C2R_Login() { Account = account, Password = password }); Return to this line of code and continue execution. Because await and ETTask are used for asynchronous processing, the code after await will be returned when the protocol is received, that is, r2c will be obtained directly_ Login class instance.
This direct request response protocol only requires the client to conduct await session Call, and then the server processes and fills in the data and returns the data. The whole process has run through.
2, Common protocol processing related code analysis
1. External NetKcpComponent component
The server refers to the complete set of NetKcpComponent components of the client, including its extension method.
The definition of NetKcpComponent class is relatively simple: an instance of AService class providing server listening function and an IMessageDispatcher dispatcher instance for distributing protocol data.
The real logical functions are in the extension method. File location:
Initialization related:
public override void Awake(NetKcpComponent self, IPEndPoint address) { self.MessageDispatcher = new OuterMessageDispatcher(); self.Service = new TService(NetThreadComponent.Instance.ThreadSynchronizationContext, address, ServiceType.Outer); self.Service.ErrorCallback += self.OnError; self.Service.ReadCallback += self.OnRead; self.Service.AcceptCallback += self.OnAccept; NetThreadComponent.Instance.Add(self.Service); }
- Initialize the message dispatcher to an external network dispatcher: OuterMessageDispatcher
- Create a web server TService
- Initialize several actions of the network server, which are used to react the network server message to the NetKcpComponent extension method.
- Add the created network server to the NetThreadComponent component to drive the network server.
The methods that need attention are: OnRead and OnAccept methods.
OnAccept method is used to create a new external connection Socket. Execute this method to create an associated Session instance.
The OnRead method is used to receive MemoryStream stream data when receiving data from an Internet connection, find the associated Session class and the corresponding stream data, and distribute the data through the message dispatcher.
2.TService class
It is used to provide the main types of network services and manage the network connection TChannel class instances established by this network service.
Types related to network services: Socket and SocketAsyncEventArgs. The specific combined usage of these two classes can be understood by referring to the official Microsoft documentation and in combination with the use of Tservice class.
public TService(ThreadSynchronizationContext threadSynchronizationContext, IPEndPoint ipEndPoint, ServiceType serviceType) { this.ServiceType = serviceType; this.ThreadSynchronizationContext = threadSynchronizationContext; this.acceptor = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); this.acceptor.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); this.innArgs.Completed += this.OnComplete; this.acceptor.Bind(ipEndPoint); this.acceptor.Listen(1000); this.ThreadSynchronizationContext.PostNext(this.AcceptAsync); }
The core code is as above, and the main process is as follows:
- Set synchronization context and service type.
- Create a new socket and enable the listening mode to listen for connections from other sockets to provide network service functions.
- Set the asynchronous completion callback of SocketAsyncEventArgs class instance innArgs.
- Throw the AcceptAsync method to the start of the main thread and start listening
- If other connections are detected, the branch is divided into asynchronous completion (asynchronous completion, which is handled by the OnComplete method of the innArgs instance and thrown to the main thread to execute the OnAcceptComplete method; non asynchronous completion, directly call the OnAcceptComplete method to start the next round of connection detection.
- OnAcceptComplete processing also includes creating a new TChannel class instance, binding socket s from other connections, and calling OnAccept method (that is, returning to the acceptance method of NetKcpComponent component)
3.TChannel class
It is mainly used to bind a socket connection and manage the sending and receiving of socket messages. There are two SocketAsyncEventArgs class instances innargs and outargs, and the corresponding one has two ET6 0 encapsulated CircularBuffer class instances recvBuffer and sendBuffer are used to process the conversion of steam stream from socket to byte stream.
The usage of "SocketAsyncEventArgs" is the same as that of Tservice. The main idea of the CircularBuffer class is to recycle the byte array to process the steam stream, so as to achieve 0GC.
There are many details about byte processing. Related classes: PacketParser parses packet data from the CircularBuffer (the beginning of the protocol data contains the message data size). CircularBuffer is used to receive socket messages circularly. It can store data in segments (multiple byte arrays), and read and take out the previously stored data from these segments.
4.Session class
At ET6 0 can represent a connected Entity class, which is used to abstract the underlying connection TChannel. It also has the function of Entity class. It has InstanceID, can be used to send Actor messages, and can also attach various components.
Core method of sending with return protocol:
public async ETTask<IResponse> Call(IRequest request) { int rpcId = ++RpcId; RpcInfo rpcInfo = new RpcInfo(request); this.requestCallbacks[rpcId] = rpcInfo; request.RpcId = rpcId; this.Send(request); return await rpcInfo.Tcs; }
rpcId is marked in auto increment mode and directly encapsulated into the sending protocol. There is no need for developers to worry. At the same time, ETTask is used for asynchronous processing.
Specific sending method:
public void Send(IMessage message) { switch (this.AService.ServiceType) { case ServiceType.Inner: { (ushort opcode, MemoryStream stream) = MessageSerializeHelper.MessageToStream(0, message); OpcodeHelper.LogMsg(this.DomainZone(), opcode, message); this.Send(0, stream); break; } case ServiceType.Outer: { (ushort opcode, MemoryStream stream) = MessageSerializeHelper.MessageToStream(message); OpcodeHelper.LogMsg(this.DomainZone(), opcode, message); this.Send(0, stream); break; } } } public void Send(long actorId, MemoryStream memoryStream) { this.LastSendTime = TimeHelper.ClientNow(); this.AService.SendStream(this.Id, actorId, memoryStream); }
Call the sending method to distinguish between internal and external networks. The MessageSerializeHelper class is used to serialize objects to the stream stream. It also includes the function of deserializing stream to objects, and encapsulates the protocol number into the stream. Then call the corresponding Service to send, transfer to the corresponding TChannel, and send the underlying socket. Note that when the TChannel sends, it will actively join the protocol size stream.
Specific acceptance reply message processing:
When the underlying protocol of the TChannel is received, it will jump layer by layer. Finally, when it is distributed to the OuterMessageDispatcher, it is determined that it is a reply protocol and directly go to the OnRead processing of the corresponding Session.
OnRead method of the corresponding Session:
public void OnRead(ushort opcode, IResponse response) { OpcodeHelper.LogMsg(this.DomainZone(), opcode, response); if (!this.requestCallbacks.TryGetValue(response.RpcId, out var action)) { return; } this.requestCallbacks.Remove(response.RpcId); if (ErrorCode.IsRpcNeedThrowException(response.Error)) { action.Tcs.SetException(new Exception($"Rpc error, request: {action.Request} response: {response}")); return; } action.Tcs.SetResult(response); }
Through the returned rpcID, we can find the requestCallbacks message that was cached before calling Call, and inside is the SetResult method calling the corresponding Task, so that the asynchronous method before calling Call can continue. So as to complete the process from await sending to message processing to sending back messages, and call setresult method to resume asynchronous execution.
The whole process adopts ETTask, which changes the complex callback into a logic similar to synchronization without blocking the main thread. It writes very smoothly. At the same time, it encapsulates the sending, listening, callback, protocol serialization and deserialization, RpcID,Reply method encapsulation, data filling and other methods of the whole bottom layer. Writing the whole communication logic becomes very simple. The only thing to pay attention to is the writing of proto files, the feature labels of protocol processing classes, and inherited processing classes.
summary
This article records how the first login protocol works. Points to note:
1. Common internet protocol, in outermessage The data structure is defined in the proto file. At the same time, note the need for tag comments to generate correct protocol data classes and tags
2. After the protocol data is generated, use the corresponding Session to directly send the corresponding class instance. For the message with reply, remember to use await for asynchronous waiting processing.
3. The protocol processing class that receives ordinary messages needs to inherit AMRpcHandler class, which encapsulates a series of processing methods and reply methods of ordinary messages.
4. To send a message, you can define an instance of canceling the etcancelationtoken class to facilitate the recall of some message sending.
The next article is going to record the processing of ordinary Actor class messages.