Introduction
Hello everyone, today we will talk about the second article about how to learn about online game programmers: Package receiving and sending.
In the previous article, we compared two Protocols: UDP and TCP, the final conclusion is that we must use the network transmission protocol that is more convenient to customize for games so that our games can have better real-time performance, so as not to cause unnecessary trouble due to packet loss and other problems.
Now let me write some actual code to illustrate it.
BSD socket
Most modern platform systems support BSD-based port protocols.
BSD Protocol ports generally have APIs similar to "socket", "bind", "sendto", and "recvfrom". Of course, you can directly use these APIs to create relevant modules for your game, however, if you really want to write it like this, your code portability will be poor, because different platforms tend to be slightly different on the API.
Next, although I will use the BSD API to write some sample code to demonstrate the functions we need, we will not always use those APIs. I mean, when we understand all the basic things, we need to abstract them appropriately and continue to develop at the abstract level. With this technique, our code will be irrelevant to the platform. By rellikt
Platform
First, we need to define some macros. With these macros, we can identify the current platform so that our code can use different APIs for different platforms.
// Platform detection
# Define PLATFORM_WINDOWS 1
# Define PLATFORM_MAC 2
# Define PLATFORM_UNIX 3
# If defined (_ WIN32)
# Define PLATFORM PLATFORM_WINDOWS
# Elif defined (_ APPLE __)
# Define PLATFORM PLATFORM_MAC
# Else
# Define PLATFORM PLATFORM_UNIX
# Endif next we will add the header file. # If PLATFORM = PLATFORM_WINDOWS
# Include <winsock2.h>
# Elif PLATFORM = PLATFORM_MAC | PLATFORM = PLATFORM_UNIX
# Include <sys/socket. h>
# Include <netinet/in. h>
# Include <fcntl. h>
# Endif
The port API is the system library of the operating system. No additional library links are required. You only need to include the header file. However, on the Windows platform, we need to specify additional libraries for the link. The following is the sample code: # if PLATFORM = PLATFORM_WINDOWS
# Pragma comment (lib, "wsock32.lib ")
# Endif
I must like to add the Library to the link of the project, so I am lazy. Of course, you can also add more formal project files. That's all you need. By rellikt
Initialize the port Layer
Most operating systems (including Apple's macosx) do not need to initialize the port layer, but Windows is an exception. If you want to use Windows ports, you must do the necessary
Initialization. You must call "WSAStartup" to initialize the port before calling the port function. After you finish what you want to do, you must call "WSACleanup ".
Aftercare.
Here we are going to use the two functions of the new home to complete this job.
Inline bool InitializeSockets ()
{
# If PLATFORM = PLATFORM_WINDOWS
WSADATA WsaData;
Return WSAStartup (MAKEWORD (2, 2), & WsaData) = NO_ERROR;
# Else
Return true;
# Endif
}
Inline void ShutdownSockets ()
{
# If PLATFORM = PLATFORM_WINDOWS
WSACleanup ();
# Endif
} Now we have a good start. Our port layer has nothing to do with the platform, and we do not need to initialize the port. These things are the details that must be carefully completed at the beginning.
Create a port
Now let's create a UDP port. Check the Code:
Int handle = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP );
If (handle <= 0)
{
Printf ("failed to create socket \ n ");
Return false;
} Next, we need to bind a port number (for example, 40000) to the port ). Each port must have a port number, so that when you receive the package, the machine can know which port to send. Do not use ports lower than 1024, which are reserved by the system.
Another special case is that if you do not want to collect packets, You can bind port 0 to the port. This operation means that the operating system will randomly allocate a port number to you. You don't have to worry about it.
Sockaddr_in address;
Address. sin_family = AF_INET;
Address. sin_addr.s_addr = INADDR_ANY;
Address. sin_port = htons (unsigned short) port );
If (bind (handle, (const sockaddr *) & address, sizeof (sockaddr_in) <0)
{
Printf ("failed to bind socket \ n ");
Return false;
} Now we have ready the port for sending packets.
In the above Code, there is a strange function "htons". What does this function do? This function is actually used to adjust the low-end bit "little-endian" and the high-end bit "big-endian" different operating systems may use different bit modes. If you want to assign a 16-bit integer variable to the port, you must use this function. This function also has a 32bit sibling function that is used to reverse the 32bit integer. In this article, we will have a lot to do. By rellikt
Set the port to non-blocking mode
The default port is the blocking mode. This means that if you want to use "recvfrom" to read a package, recvfrom will not return until the package can be fully read. Our games are simulated in real time. Generally, frames are limited to 30 FPS or 60 FPS. This blocking mode is not what we want.
After creating a port, we need to adjust the port to the non-blocking mode. In this mode, calling recvfrom will return immediately. The difference is that if the package cannot be read, recvfrom will return a returned value to indicate that the read is unsuccessful. Read again next time. By rellikt
The following code sets the port to non-blocking mode:
# If PLATFORM = PLATFORM_MAC | PLATFORM = PLATFORM_UNIX
Int nonBlocking = 1;
If (fcntl (handle, F_SETFL, O_NONBLOCK, nonBlocking) =-1)
{
Printf ("failed to set non-blocking socket \ n ");
Return false;
}
# Elif PLATFORM = PLATFORM_WINDOWS
DWORD nonBlocking = 1;
If (ioctlsocket (handle, FIONBIO, & nonBlocking )! = 0)
{
Printf ("failed to set non-blocking socket \ n ");
Return false;
}
# Endif you can see that there is no "fcntl" function in Windows, so we should use the "loctlsocket" function instead.
Packet sending
UDP is not a connection-based transmission protocol. Therefore, each package must contain the destination address and port number. You can send packets to different ports on the same UDP port. No connection exists.
The following shows how to send a package:
Int sent_bytes = sendto (handle, (const char *) packet_data, packet_size,
0, (sockaddr *) & address, sizeof (sockaddr_in ));
If (sent_bytes! = Packet_size)
{
Printf ("failed to send packet: return value = % d \ n", sent_bytes );
Return false;
} Note! The returned value indicates whether the package is successfully issued. It does not tell you whether the package is successfully received. In fact, the UDP protocol does not have this method that allows you to know whether the other party has received it.
You can see that in the above function we have input a "sockaddr_in" data structure. How do we give this data structure a negative value?
For example, we want to send a packet to 207.45.186.98: 30000.
We can do this:
Unsigned int a = 207;
Unsigned int B = 45;
Unsigned int c = 186;
Unsigned int d = 98;
Unsigned short port = 30000;
Unsigned int destination_address = (a <24) | (B <16) | (c <8) | d;
Unsigned short destination_port = port;
Sockaddr_in address;
Address. sin_family = AF_INET;
Address. sin_addr.s_addr = htonl (destination_address );
Address. sin_port = htons (destination_port); here we actually give four values a, B, c, and d, each of which is in the range of 0 to 255, then they are associated with different bitwise of the target integer, and htonl or htons is used to ensure the high-end and low-end problems of bit. It's quite simple.
It is particularly important that the IP address of the local localhost is 127.0.0.1, which is the reserved IP address. If you want to perform a standalone test, you can use this IP address.
Collect packets
When you bind a port number to your UDP port, you can collect packets. In fact, the packets received will first enter the port queue, and then you can keep the "recvfrom" queue through a loop until the return value of recvfrom indicates that no packets in the queue are readable.
We know that UDP is actually not connected, so even the same UDP port queue may contain UDP packets sent from different machines, all these packages contain the sender's IP address and port number. You can obtain the information when reading these packages. By rellikt
The Code is as follows:
While (true)
{
Unsigned char packet_data [256];
Unsigned int maximum_packet_size = sizeof (packet_data );
# If PLATFORM = PLATFORM_WINDOWS
Typedef int socklen_t;
# Endif
Sockaddr_in from;
Socklen_t fromLength = sizeof (from );
Int received_bytes = recvfrom (socket, (char *) packet_data, maximum_packet_size,
0, (sockaddr *) & from, & fromLength );
If (received_bytes <= 0)
Break;
Unsigned int from_address = ntohl (from. sin_addr.s_addr );
Unsigned int from_port = ntohs (from. sin_port );
// Process completed ed packet
} Packets in the queue that are larger than your buffer will be discarded by default. So remember that you must open a buffer that is large enough. Otherwise, you will not
Think about it. We use UDP to implement our own network transmission protocol, so there is no need to have too many restrictions. Remember to open enough buffer.
Destroy Port
In most Unix-like systems, the port is the same as the file handle. You only need to use close to consume the port like the file handle. Remember to consume, otherwise it will be very troublesome to cause handle overflow. On Windows, we have to do a little trouble. Generally, we can complete this job as follows:
# If PLATFORM = PLATFORM_MAC | PLATFORM = PLATFORM_UNIX
Close (socket );
# Elif PLATFORM = PLATFORM_WINDOWS
Closesocket (socket );
# Endif Port Class
Here we have introduced all the basic operations: open the port, bind the port number, set the non-blocking mode, collect packets, send packets, and destroy the port.
But do you think the platform-related code we use is a bit difficult to handle? Coding by a bunch of # define or something is not easy to use. In addition, the assignment of the result of sockaddr_in is also very error-prone, here we will make an abstract encapsulation Socket class to complete the required functions:
Class Socket
{
Public:
Socket ();
~ Socket ();
Bool Open (unsigned short port );
Void Close ();
Bool IsOpen () const;
Bool Send (const Address & destination, const void * data, int size );
Int Receive (Address & sender, void * data, int size );
Private:
Int handle;
}; Next is our Address class:
Class Address
{
Public:
Address ();
Address (unsigned char a, unsigned char B, unsigned char c, unsigned char d, unsigned short port );
Address (unsigned int address, unsigned short port );
Unsigned int GetAddress () const;
Unsigned char GetA () const;
Unsigned char GetB () const;
Unsigned char GetC () const;
Unsigned char GetD () const;
Unsigned short GetPort () const;
Bool operator = (const Address & other) const;
Bool operator! = (Const Address & other) const;
Private:
Unsigned int address;
Unsigned short port;
}; Finally, how can you use these classes to complete packet collection and sending:
// Create socket
Const int port = 30000;
Socket socket;
If (! Socket. Open (port ))
{
Printf ("failed to create socket! \ N ");
Return false;
}
// Send a packet
Const char data [] = "hello world! ";
Socket. Send (Address (, port), data, sizeof (data ));
// Receive packets
While (true)
{
Address sender;
Unsigned char buffer [256];
Int bytes_read = socket. Receive (sender, buffer, sizeof (buffer ));
If (! Bytes_read)
Break;
// Process packet
} With this abstract class, our life will become much simpler. In addition, the code we use here has nothing to do with the platform. We can handle troublesome APIs at the underlying layer. Conclusion
Now we have a basic platform-independent architecture that can be used to send and receive UDP packets. UDP is unrelated to the connection. I have written an example code to illustrate this problem. This Code reads some IP addresses from a txt file and sends a packet to these IP addresses every second. Each time the local machine receives the package, the source and package size of the received package are displayed. You can modify the parameters and start multiple examples to do the experiment. If you do this, you are actually implementing a simple p2p network architecture. By rellikt
This sample code is made on mac, but compilation on other platforms should not be a problem.
The content of this article is so far. If you have no problem, I think we can proceed to the next chapter. In the next chapter, I will show you how to implement a UDP protocol that can be applied in practice. That's it.
This article from the CSDN blog, reproduced please indicate the source: http://blog.csdn.net/rellikt/archive/2010/08/23/5833233.aspx