Online Game Server programming

Source: Internet
Author: User

1. server network connection

Most online game servers use a non-blocking select structure. Why? Because the network game servers need to process a large number of connections, and most of them will choose to run in Linux/Unix, it is not cost-effective to open a thread for each user, on the one hand, threads in Linux/Unix are simulated by the concept of processes, which consume system resources. Besides I/O, basically, each thread has no redundant tasks that require parallel processing, and online games are highly interactive. Therefore, inter-thread synchronization becomes very troublesome. As a result, blocking is obviously unrealistic for a Single-threaded server that contains a large number of network connections. For network connections, a structure is required for storage, which must contain a buffer for writing messages to the client, and a buffer for reading messages from the client, the specific size depends on the message structure. In addition, for synchronization, it takes some time to proofread The values. Different values are also required to record the current status. The following shows a preliminary connection structure:

Typedef connection_s {

User_t * ob;/* point to the structure for processing the server logic */

Int FD;/* socket connection */

Struct sockaddr_in ADDR;/* connection address information */

Char text [max_text];/* Received message buffer */

Int text_end;/* tail pointer of the received message buffer */

Int text_start;/* Header pointer of the received message buffer */

Int last_time;/* when is the last message received */

Struct timeval latency;/* difference between the local time of the client and the local time of the server */

Struct timeval last_confirm_time;/* time of the last verification */

Short is_confirmed;/* Whether the connection has passed verification */

Int ping_num;/* The Ping value from the client to the server */

Int ping_ticker;/* how many I/O cycles are processed to update the ping value once */

Int message_length;/* length of the buffer message to be sent */

Char message_buf [max_text];/* Sending buffer */

Int iflags;/* The connection status */

} Connection_t;

The server processes all connections cyclically. It is an endless loop. Each loop uses select to check whether a new connection has arrived. Then, it loops through all connections to see which connection can be written or read, read and Write the connection. Since all the processing is non-blocking, All socket Io can be completed using one thread.

Due to the network transmission relationship, each Recv () may contain more than one message, or less than one message. How can this problem be solved? Therefore, two pointers are used for receiving message buffering. Each receipt starts from text_start, because the remaining half of the message received last time may exist, then text_end points to the end of the message buffer. In this way, two pointers can be used to easily handle this situation. In addition, it is worth noting that the process of parsing messages is a cyclical process, it is possible that more than two messages are received in the message buffer at a time. At this time, it should be executed until there is only one message that is not available in the message buffer. The general process is as follows:

While (text_end-text_start> A complete message length)

{

Start processing from text_start;

Text_start + = message length;

}

Memcpy (text, text + text_start, text_end-text_start );

For message processing, you first need to know the total messages in your game and all the messages to design a reasonable message header. Generally, messages can be divided into four parts: main message, scenario message, synchronous message, and interface message. The main message includes all the actions of the role controlled by the client, including walking, running, and fighting. Scenario messages include weather changes, and some things appear in the scenario for a certain period of time. The characteristics of such messages are that all message initiators are servers, broadcast objects are all players in the scenario. The synchronous message is intended for a player who initiates an object and is broadcast to all players who can see the object on the server. The message also includes all actions, different from the main message, this type of message is sent from the server to the client, and the main message is generally sent from the client to the server. Finally, the interface message includes the chat message sent from the server to the client and various attributes and status information.

The following describes the composition of a message. Generally, a message consists of a message header and a message body. The length of the message header remains unchanged, and the length of the message body is variable, the length of the message body to be saved in the message body. To clearly differentiate each message, you need to define a unique identifier of the message header and the type and ID of the message. The structure of the message header is as follows:

Type struct message_s {

Unsigned short message_sign;

Unsigned char message_type;

Unsigned short message_id

Unsigned char message_len

} Message_t;

2 server Broadcast

The focus of server broadcast is how to calculate broadcast objects. Obviously, in a large map, a player's action on the eastmost side should not be visible to a player on the westmost side. So how can we calculate broadcast objects? The simplest way is to divide a map into small blocks and broadcast each time only a few blocks of players around it. So how much is appropriate? In general, the chunks are larger, the memory consumption increases, the chunks are smaller, and the CPU consumption increases (the reason will be mentioned later ). I personally think it is more appropriate to split a small block to the left and right of a screen. Each time nine small pieces of players around the broadcast, due to the frequent broadcast operations, the operations on the nine blocks around the bucket will become quite frequent. Therefore, if the block size is smaller, the range of the bucket will be expanded and the CPU resources will be quickly exhausted.

After cutting blocks, how can players move around between blocks? Let's think about what to do when switching a block. First, you need to figure out which of the nine players around the next block are not available in the current block, and broadcast your information to those players, at the same time, it is also necessary to determine which items in the nine blocks around the next block are not available now, and broadcast the information of those items to yourself, then, we can broadcast the information about the disappearance of the nine objects in the next block to ourselves, at the same time, it broadcasts the lost messages to those objects. This operation is not only cumbersome but also consumes a lot of CPU resources. How can we quickly calculate these items? Compare them one by one? Obviously, it seems that this is not a good solution. Here we can refer to some ideas of the Two-dimensional matrix collision detection to take nine blocks around ourselves as one matrix, and nine blocks around the target block as another matrix, check whether the two matrices collide. If the two matrices intersect, how can we calculate the blocks that do not overlap. Here, we can convert the coordinates of the intersection blocks into internal coordinates and then perform operations.

There is another solution for broadcasting. The implementation is not as simple as chunking. This method requires the client to assist in the computation. First, add a broadcast object queue in the connection structure of the server. When the client logs on to the server, the queue is sent to the client by the server, and the client maintains the queue by itself, when someone leaves the client's field of view, the client requests the server to send a message to the object that disappears. However, it is troublesome for someone to gain a general view.

First, each time the client sends an update position message to the server, the server calculates a field of view for the connection, and then loops the players on the entire map when the broadcast is required, find players whose coordinates are within their field of view. The advantage of using this method is that a large amount of messages need to be broadcast at one time when there is no conversion block. The disadvantage is that players on the whole map need to traverse when calculating the broadcast object, if there are too many players on a map, this operation will be slow.

3. Server Synchronization

Synchronization is very important in online games. It ensures that every player can see the same things on the screen. In fact, the simplest way to solve the synchronization problem is to broadcast the movements of each player to other players. There are actually two problems: 1. Broadcast to which players, which messages are broadcast. 2. What if network latency occurs. In fact, the first question is a very simple one. However, the reason why I raised this question is to remind everyone to consider this factor when designing their own message structure. The second problem is a very troublesome one. Let's take a look at this example:

For example, Player A sends a command to the server, saying that I am going to P2. The command is sent at T0, the server receives the command at T1, and then broadcasts the message to the players around it, the message content is "Player A from P1 to p2", there is a player B near a. The time for receiving this broadcast message from the server is T2. then, draw a picture on the client, A starts from P1 to P2. At this time there is a problem of non-synchronization, Players A and B on the screen shows a different picture of the T2-T1 time. What should we do at this time?

There is a solution. I name it "prediction pull". Although it is a little weird, basically everyone can understand it literally. To solve this problem, we must first define a value: prediction error. Then, you need to add an attribute in the connection class of each player on the server, called latency. Then, when the player logs in, the client time and server time are compared, the difference value is stored in latency. In the above example, when the server broadcasts a message, it calculates the currenttime of a client based on the latency of the object to be broadcast, and then contains the currenttime in the message header, then broadcast. In addition, a queue is created locally on the client of Player A to save the message. The message is deleted only in the message queue that has never been verified after server verification. If verification fails, it will be pulled back to Point P1. Then, when Player B receives the message "Player A from P1 to p2" sent from the server, it checks the server sending time in the message and compares it with the local time, if it is greater than the defined prediction error, even at the time of T2, Player A's screen goes to the location P3, and then the Player A on Player B's screen is pulled directly to P3, continue, so that synchronization can be ensured. Further, in order to ensure that the client runs more smooth, I do not recommend pulling the player directly, but calculate a bit of P4 after P3, and then use (P4-P1)/t (P4-P3) to calculate a very fast speed S, and then let player a quickly move to P4 with the speed S, this method is more reasonable, the prototype of this solution is known internationally as full plesiochronous. Of course, this prototype has been tampered with by me a lot to adapt to the synchronization of online games, so it becomes called prediction pull.
 
In another solution, I name it "verify synchronization" and I know the synchronization name. The general meaning is that each command is executed after it passes the server verification. The specific idea is as follows: first define a latency in each player connection type, and then the client will not move forward while responding to the player's mouse, instead, send a walking command to the server and wait for verification. After receiving the message, the server verifies the logic layer and calculates the range to be broadcast, including Player A. Different message headers are generated based on the latency of each client, start broadcasting. At this time, the gamer's walking information is completely synchronized. The advantage of this method is that it can ensure absolute synchronization between clients. The disadvantage is that when the network latency is large, the player's client behavior will become less smooth, it makes players feel uncomfortable. The prototype of this solution is known internationally as hierarchical master-slave synchronization. It has been widely used in various network fields since 1980s.

The last solution is an idealized solution, known internationally as mutual synchronization. It is a well-predicted solution for the future of the network. The reason for this solution is not to say that we have fully implemented this solution, but to apply some ideas of this solution to some aspects of the online game field. I named this solution semi-server synchronization. The general design concept is as follows:

First, the client needs to create many broadcast lists when logging on to the world. These lists are not synchronized in a timely manner on the backend of the client and the server. The reason for this is to create multiple lists, it is because there are more than one broadcast type, such as local message, remote message, and global message, these lists must be created based on the messages sent from the server during client login. When creating a list, you also need to obtain the latency of the broadcast object in each list, and maintain a complete user status list in the background, which is not synchronized with the server in time, according to the local user status table, some decisions can be decided by the client itself. When the client sends this decision, the final decision is directly sent to the client in each broadcast list, and the time is proofread to ensure that each client proofreads the received message based on the local time. Then, the method that uses the advance computing volume mentioned in the prediction pull to increase the speed of walking will make the synchronization very smooth. The advantage of this solution is that the synchronization between clients is not implemented through the server, which greatly reduces the errors caused by network latency, and most decisions can be made by the client, it also greatly reduces server resources. The drawback is that because both the message and decision-making power are placed on the client, the plug-in provides a great opportunity.

4. NPC Problems
Next, I want to talk about the NPC design and NPC intelligence on the server. First, we need to know what the NPC is and what the NPC needs to do. The full name of NPC is (non-player character). Obviously, it is a character but not a player. From this point, we can know that some NPC behaviors are similar to those of players, he can walk, fight, and breathe (This point is mentioned in the later NPC intelligence). What's different from the player's object is that, the NPC can be resumed (that is, the NPC can be re-generated within a certain period of time after being killed ). In fact, the most important thing is that all the decisions on player objects are made by players, while the decisions on NPC are made by computers, therefore, when making a decision on the NPC, we need the so-called NPC intelligence to make a decision.

Next I will talk about the NPC in two parts: the first is the NPC intelligence, and the second is how the server organizes the NPC. The reason for talking about NPC intelligence is that we can design servers to organize the NPC only when we understand what we need to do.

NPC smart

There are two types of NPC intelligence: passive events and active events. For passively triggered events, the processing is relatively simple. The event itself can call functions on the NPC, such as the death of the NPC, in fact, when the HP of the NPC is smaller than a certain value, it actively calls the ondie () function on the NPC. This kind of NPC intelligence triggered by events is called passive triggering. There are usually two types of triggers:

One is the change of the attributes of the NPC caused by other objects, and the change of attributes will also lead to some behaviors of the NPC. As a result, the NPC object contains at least the following functions:

Class NPC {

Public:

// Who is causing my attributes to change.

Onchangeattribute (object_t * Who, int which, int how, int where );

PRIVATE:

Ondie ();

Onescape ();

Onfollow ();

Onsleep ();

// A series of events.

}

This is a basic NPC structure. This passive event that triggers the NPC, I call it the reflection of the NPC. However, such a structure can only allow the NPC to passively receive some information for decision-making. Such an NPC is stupid. So how can an NPC make some decisions? There is a way to breathe. So how can we let the NPC breathe?

A very simple method is to use a timer to regularly trigger the breathing of all the NPCs so that a single NPCs can breathe. In this case, there will be a problem. When there are too many NPCs, the last NPC breathing has not been completed, and the next breathing has come again. How can this problem be solved. There is a way for the NPC to breathe asynchronously, that is, the breathing cycle of each NPC is determined based on the birth time of the NPC, at this time, the timer needs to perform a period of time to check which NPCs should breathe at the time to trigger the breathing of these NPCs.

As mentioned above, how does the system trigger the breathing rate of the NPC itself? This is like a real person. The breathing frequency is different between sleeping and intense exercise. Similarly, the breathing frequency of the NPC is different from that of the normal one during combat. A breath_ticker is required to set the current breathing frequency of the NPC.

In the NPC breathing event, how do we set the NPC intelligence? It can be summarized into two parts: checking the environment and making decisions. First, we need to make statistics on the current environment, such as whether there are several enemies in the battle, how much HP is left in the battle, and whether there are any enemies nearby. The statistical data is passed into the decision-making module. The decision-making module makes some decisions based on the NPC's own personality orientation. For example, the attention-type NPC will continue to soar when the number of HP is relatively small, for example, a smart NPC will choose to escape when there are few HP nodes. And so on.

Now, the structure of a breathing and reflecting NPC has basically been made up. Let's talk about how the system organizes an NPC to appear in the world.
 
NPC Organization

There are two schemes to choose from. One is to save the location information of the NPC in the scenario and load the NPC when loading the scenario. Second, the location information of the NPC is stored on the NPC. There are special events for all NPC login scenarios. What are the differences between the two methods? What are their differences?

The advantage of the previous method is that when the scenario is loaded at the same time, the scenario can manage the NPC without additional processing, the disadvantage is that the refresh is synchronized during the refresh process, that is, the NPC in a scenario may be extended within the same time. However, the design of the second method may be a little effort-consuming. A unified mechanism is required to allow the NPC to log on to the scenario, and some troublesome designs are required, however, this scheme can achieve asynchronous refresh of the NPC, which is currently widely used in online games. Next we will focus on the implementation of this method:

First, we need to introduce the concept of a "soul", that is, after an NPC dies, what disappears is his physical body, and his soul still exists in the world without breathing, floating near death, waiting for the time to reincarnation, when the reincarnation, all the previous attributes are cleared, re-on the scene to build its physical body. So how can we design such a structure? First, create a ing table for the NPC that will appear in a scenario, and give each NPC a unique identifier. After loading the scene, load the NPC that belongs to the scenario according to the graph scale. In the ondie () event of the NPC, instead of directly dropping the object destroy, the NPC's breathing is disabled, a rebirth timer is opened, and the object is set to invisable. This design can achieve asynchronous refresh of the NPC, saving server resources while making players feel more real.

In addition, we will talk about the application of heuristic searching in NPC intelligence.

The main idea is to filter all the nodes in the next layer through an inspiration function while giving priority to search in breadth, so as to narrow the search scope within a certain range. As we all know, pathfinding A * algorithm is a typical heuristic search application. Its principle is to design a judge (point_t * point) function at the beginning to get the price of point, then, during each search, all the vertices that may arrive at the next step are evaluated by the judge () function to get two or three low-cost points and continue the search, those points that have not been selected won't be searched any more. The consequence is that they may not be the optimal path, this is also why the * algorithm goes before the obstacle while seeking the path, instead of going through the diagonal lines in advance to bypass the obstacle. If you want to find the optimal path, you cannot use the * algorithm. Instead, you must use the dynamic planning method, which consumes much more than.

In addition to finding the path, what other aspects can be applied to heuristic search? In fact, a bigger point is that any decision-making of the NPC can be done through heuristic search, for example, escape. If it is a 2D online game, there are eight directions. Which direction does the NPC choose to escape? You can set a judge (INT direction) to give the price of each vertex, calculate the enemy strength of the vertex in the judge, or how agile the enemy is, finally, choose the least expensive place to escape. Next, let's talk about the design of several smart heuristic search methods common to NPC:

Target select (select target ):

First, obtain the list of enemies near the NPC on the map. Design the judge () function to calculate the cost based on the enemy's strength and distance. Then, select the enemy with the minimum cost to initiate an active attack.

Escape (escape ):

Check your hp in the breathing event. If HP is lower than a certain value, or if you are a remote soldier and the enemy is near, the escape function is triggered, in the escape function, it also organizes a list of all the enemies around you, then designs the judge () function, first selects the biggest enemy that threatens you, the judge () the function needs to determine the speed and combat strength of the enemy, and finally obtain a major enemy. Then, it designs the path's judge () function for the main enemy, the search range may only be in the opposite direction of the main enemy, and then calculate the price based on the strength of the enemy in these directions to make the final choice.

Random Walk (Random Walk ):

I do not recommend the * Algorithm for this, because once the number of NPCS increases, the CPU consumption is terrible, and most NPCs do not need long-distance routing, you only need to walk around, so randomly give a few points nearby, and then let the NPC walk over. If it encounters an obstacle, it will stop, so there is almost no burden.

Follow target (follow the target ):

There are two methods here. One is that the NPC looks stupid, and the other is that the NPC looks smarter. The first method is to let the NPC follow the path of the target, almost no resource consumption. The other is to let the NPC determine the current position of the other party in the breathing event while following, and then go straight, and then bypass a * when encountering an obstacle, this design will consume a certain amount of system resources, so it is not recommended that many NPCs follow the goal. If a large number of NPCS follow the goal, there is a relatively simple method: let the NPC and the Target move synchronously, that is, let them speed up and move the same path. Of course, this design is only suitable for the relationship between the NPC and the target, not the target, it's just a move with the players.

This article from the csdn blog, reproduced please indicate the source: http://blog.csdn.net/leishiwei/archive/2009/11/12/4803869.aspx

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.