This article is fromInspoy miscellaneous collection | food chicken inspoy learning record
preface
This is really the last basic framework article! Write here already before the seventh article orz actually very boring, is all some basic aspects of things, and can’t see anything interesting May be I to think things too complicated, want to do everything ability within the scope of the best, especially the underlying framework level But these things are really important, small game may not be obvious, One of the great things about Unity is that you can prototype your game very quickly. I’ve only had one TestView for a long time and there are only two buttons in it! 233 I have considered many of the problems encountered in the production environment, not to say the best, I am still learning XD, as soon as the network framework is put together, we can start writing the game related logic
Network communication
Our game is a multiplayer online real-time game, before the pit is the network part to collapse, to redesign the network part using native TCP Socket communication, custom protocol. Here mainly introduces the client first, first set down the protocol, so that the later introduction of the server will not have too much coupling with the client. Of course, at the beginning, we will build the simplest server. This project plans to use Node.js for development
The simplest server
Create a TCP server on a port, receive a message from a client, concatenate a string and return it. The code is as follows:
const net = require("net");
net.createServer(function(socket) {
console.log("There is a new connection :" + socket.remoteAddress);
socket.on("data".function(data) {
console.log("request: " + data);
socket.write('{"pid":1,"retCode":0}');
});
socket.on("end".function(data) {
console.log("socket end");
});
socket.on("close".function(data) {
console.log("Connection disconnected");
});
socket.write("Hello!");
}).listen(19621);Copy the code
Client design
With two classes, a relatively low-level SFTcpClient, the user does not use this class directly, but is encapsulated by SFNetworkManager, which is a singleton class that can access the network at any time while the game is running, and in order to possibly use not just one SFTcpClient, Encapsulation enables more elegant management of multiple TCP clients.
SFNetworkManager
SFNetworkManager manages several sFTCPClients through the following interfaces of the latter: Method | | | | — – | — – | | void init (string, int, SFClientCallback, SFSocketStateCallback) | according to the specified IP address, Port and related initialization callback | | void uninit () | close TCP client | | void sendData publishes the event (string) | | to the server sends data | bool isReady readiness | | server
The first is what you should have as a singleton class: private constructor, unique instance, method to get instance:
private SFNetworkManager()
{}
private static sm_instance = null;
public static SFNetworkManager getInstance()
{
if (null == sm_instance)
{
sm_instance = new SFNetworkManager();
}
return sm_instance;
}Copy the code
Then there is connection initialization
public void init()
{
m_client = new SFTcpClient();
m_client.init("127.0.0.1".19621, onRecvMsg, ret =>
{
dispatcher.dispatchEvent(SFEvent.EVENT_NETWORK_READY, new SFSimpleEventData(ret));
});
// Pass up the event that the connection is down
m_client.dispatcher.addEventListener(SFEvent.EVENT_NETWORK_INTERRUPTED, e =>
{
dispatcher.dispatchEvent(e);
});
}Copy the code
Void onRecvMsg(string) is a callback function that handles messages pushed by the server
void onRecvMsg(string msg)
{
SFUtils.log("Yes." + msg);
}Copy the code
Now the problem is that since the message callback function is called in the Socket child thread, Unity does not allow you to modify objects in the scene in the child thread, so you need to modify the message to be handled in the main thread. Use a queue where all messages received by child threads are added to the queue and stored in memory. The main thread then periodically checks the queue for any unprocessed messages via the update function and removes them from the queue.
void onRecvMsg(string msg)
{
m_recvQueue.Enqueue(msg);
}
void update()
{
while (m_recvQueue.Count > 0)
{
string data = m_recvQueue.Dequeue();
SFUtils.log("Yes."+ data); }}Copy the code
SFTcpClient
C# TCP Socket asynchronous implementation. The subthreads for sending and receiving data are managed by the system. All methods have a pair of BeginXX and EndXX. Take receiving data as an example:
try
{
if(! m_socket.Connected) {throw new Exception("Socket is not connected");
}
byte[] data = new byte[1024]; // Receive data in 1024 bytes
m_socket.BeginReceive(data, 0, data.Length, SocketFlags.None, result =>
{
int length = m_socket.EndReceive(result); // length indicates the length of the received data
if (length > 0)
{
m_callback(Encoding.UTF8.GetString(data)); // Convert to a string and call the callback
}
else
{
// Length 0 indicates that the network is disconnected
m_socket.close();
SFUtils.logWarning("Network connection is down"); dispatcher.dispatchEvent(SFEvent.EVENT_NETWORK_INTERRUPTED); }},null);
}
catch (Exception e)
{
SFUtils.logWarning("Network connection is down:" + e.Message);
}Copy the code
Others, such as connections, are much the same. For the complete code, see the complete code link at the end of the article.
Since the set protocol
Of course, there are many pits, such as because I use the native TCP Socket to transmit data packets, when the data is large, it will inevitably produce sticky packets, so I have to manually subcontract, this will be discussed later, and the following content is not relevant, SendData () and socketRecv() in SFTcpClient. JSON string is used for data transmission in the network. When sending and receiving data, the client side and server side respectively serialize and deserialize the data. Unity provides a JsonUtility class that makes it easy to serialize and deserialize objects and JSON. Two main methods are used: | method name function | | | — – | — – | | string JsonUtility. ToJson (object) | convert an object to a JSON string | | T JsonUtility. FromJson < T > (string) | convert a JSON string to specify the type of object, if an exception is | error
request
Request types all inherit from the base class SFBaseRequestMessage. For example:
/ / the base class
public class SFBaseRequestMessage
{
public int pid; / / agreement
public string uid; // Unique ID of the user
};
// The user logs in and out
[Serializable]
public class SFRequestMsgUnitLogin : SFBaseRequestMessage
{
public SFRequestMsgUnitLogin() { pid = 1; }
public int loginOrOut;
};Copy the code
The response
The response type is similar in that each request type must correspond to a response type, but the reverse is not necessarily true, that is, a protocol having a request type is a necessary but not sufficient condition to have a response type. The same protocol for logging in as above:
/ / the base class
public class SFBaseResponseMessage : ISFEventData // To make it easy to pass the response as event data as well
{
public int pid; / / agreement
public int retCode; // Error code, 0 indicates success
};
// The user logs in and out
[Serializable]
public class SFResponseMsgUnitLogin : SFBaseResponseMessage
{
public const string pName = "socket_1"; // pName is the event name of SFEvent
public SFReponseMsgUnitLogin() { pid = 1; }};Copy the code
Sending and receiving
Equestmessage () : SFBaseRequestMessage. Serialize the request and throw it directly to the TCP Client.
public void sendMessage(SFBaseRequestMessage req)
{
string data = JsonUtility.ToJson(req);
m_client.sendData(data);
}Copy the code
Receiving is a little more complicated and consists of two steps: first, convert the original string to SFBaseResponseMessage, get its protocol number PID, and then convert to the specific response type depending on the PID.
SFBaseResponse obj = null;
obj = JsonUtility.FromJson<SFBaseResponse>(data);
if (obj == null)
{
SFUtils.logWarning("Unparsed message format :\n" + data);
}
else
{
int pid = obj.pid;
string pName = string.Format("socket_{0}", pid);
if (pid == 1)
{
obj = JsonUtility.FromJson<SFResponseMsgUnitLogin>(data)
}
// else if more protocols
else
{
SFUtils.logWarning("Unrecognized protocol number: {0}".0, pid);
obj = null;
}
if(obj ! =null) { dispatcher.dispatchEvent(pName, obj); }}Copy the code
Then add the corresponding protocol listener elsewhere:
SFNetworkManager.getInstance().dispatcher.addEventListener(SFResponseMsgUnitLogin.pName, onRecvMsg);Copy the code
The callback function must be called in the main thread, so the game scene can be modified without fear.
The test program
Create a UI that looks like this
m_mgr = SFNetworkManager.getInstance();
m_mgr.init();
m_mgr.dispatcher.addEventListener(SFEvent.EVENT_NETWORK_READY, result =>
{
SFSimpleEventData retCode = result.data as SFSimpleEventData;
if (retCode.intVal == 0)
{
m_infoMsg = "Server connection successful";
}
else
{
m_infoMsg = "Server connection failed"; }}); m_mgr.dispatcher.addEventListener(SFEvent.EVENT_NETWORK_INTERRUPTED, onInterrupt); m_mgr.dispatcher.addEventListener(SFResponseMsgUnitLogin.pName, onRecvMsg);Copy the code
If you start the server program before then, you will successfully connect to the server.
"Hello!"
$node./ started has a new connection :: FFFF:127.0. 01.
request: {"pid":1."uid":"abc"."loginOrOut":1}Copy the code
RetCode (‘ pid ‘:1,’ retCode ‘:0}’); retCode (‘ retCode’ :0);
Then press Ctrl+C to forcibly shut down the server program process, the network is interrupted, and the client also has corresponding processing
The complete code
The code snippet posted above only contains key parts due to space limitations, the full code can be found on my Github