游戏网络开发二之数据的发送与接收

原文

原文出处

Introduction

Hi, I’m Glenn Fiedler and welcome to Networking for Game Programmers.


In the previous article we discussed options for sending data between computers and decided to use UDP instead of TCP for time critical data.


In this article I am going to show you how to send and receive UDP packets.


BSD sockets


For most modern platforms you have some sort of basic socket layer available based on BSD sockets.


BSD sockets are manipulated using simple functions like “socket”, “bind”, “sendto” and “recvfrom”. You can of course work directly with these functions if you wish, but it becomes difficult to keep your code platform independent because each platform is slightly different.


So although I will first show you BSD socket example code to demonstrate basic socket usage, we won’t be using BSD sockets directly for long. Once we’ve covered all basic socket functionality we’ll abstract everything away into a set of classes, making it easy to you to write platform independent socket code.


Platform specifics


First let’s setup a define so we can detect what our current platform is and handle the slight differences in sockets from one platform to another:


    // 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

Now let’s include the appropriate headers for sockets. Since the header files are platform specific, we’ll use the platform #define to include different sets of files depending on the platform:


    #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

Sockets are built in to the standard system libraries on unix-based platforms so we don’t have to link to any additonal libraries. However, on Windows we need to link to the winsock library to get socket functionality.


Here is a simple trick to do this without having to change your project or makefile:


    #if PLATFORM == PLATFORM_WINDOWS
#pragma comment( lib, "wsock32.lib" )
#endif

I like this trick because I’m super lazy. You can always link from your project or makefile if you wish.


Initializing the socket layer


Most unix-like platforms (including macosx) don’t require any specific steps to initialize the sockets layer, however Windows requires that you jump through some hoops to get your socket code working.


You must call “WSAStartup” to initialize the sockets layer before you call any socket functions, and “WSACleanup” to shutdown when you are done.


Let’s add two new functions:


    bool InitializeSockets()
{
#if PLATFORM == PLATFORM_WINDOWS
WSADATA WsaData;
return WSAStartup( MAKEWORD(2,2),
&WsaData )
== NO_ERROR;
#else
return true;
#endif
}

void ShutdownSockets()
{
#if PLATFORM == PLATFORM_WINDOWS
WSACleanup();
#endif
}

Now we have a platform independent way to initialize the socket layer.


Creating a socket


It’s time to create a UDP socket, here’s how to do it:


    int handle = socket( AF_INET,
SOCK_DGRAM,
IPPROTO_UDP );

if ( handle <= 0 )
{
printf( "failed to create socket\n" );
return false;
}

Next we bind the UDP socket to a port number (eg. 30000). Each socket must be bound to a unique port, because when a packet arrives the port number determines which socket to deliver to. Don’t use ports lower than 1024 because they are reserved for the system. Also try to avoid using ports above 50000 because they used when dynamically assigning ports.


Special case: if you don’t care what port your socket gets bound to just pass in “0” as your port, and the system will select a free port for you.


    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 the socket is ready to send and receive packets.


But what is this mysterious call to “htons” in the code above? This is just a helper function that converts a 16 bit integer value from host byte order (little or big-endian) to network byte order (big-endian). This is required whenever you directly set integer members in socket structures.


You’ll see “htons” (host to network short) and its 32 bit integer sized cousin “htonl” (host to network long) used several times throughout this article, so keep an eye out, and you’ll know what is going on.


Setting the socket as non-blocking


By default sockets are set in what is called “blocking mode”.


This means that if you try to read a packet using “recvfrom”, the function will not return until a packet is available to read. This is not at all suitable for our purposes. Video games are realtime programs that simulate at 30 or 60 frames per second, they can’t just sit there waiting for a packet to arrive!


The solution is to flip your sockets into “non-blocking mode” after you create them. Once this is done, the “recvfrom” function returns immediately when no packets are available to read, with a return value indicating that you should try to read packets again later.


Here’s how put a socket in 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\n" );
return false;
}

#elif PLATFORM == PLATFORM_WINDOWS

DWORD nonBlocking = 1;
if ( ioctlsocket( handle,
FIONBIO,
&nonBlocking ) != 0 )
{
printf( "failed to set non-blocking\n" );
return false;
}

#endif

Windows does not provide the “fcntl” function, so we use the “ioctlsocket” function instead.


Sending packets


UDP is a connectionless protocol, so each time you send a packet you must specify the destination address. This means you can use one UDP socket to send packets to any number of different IP addresses, there’s no single computer at the other end of your UDP socket that you are connected to.


Here’s how to send a packet to a specific address:


    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\n" );
return false;
}

Important! The return value from “sendto” only indicates if the packet was successfully sent from the local computer. It does not tell you whether or not the packet was received by the destination computer. UDP has no way of knowing whether or not the the packet arrived at its destination!


In the code above we pass a “sockaddr_in” structure as the destination address. How do we setup one of these structures?


Let’s say we want to send to the address 207.45.186.98:30000


Starting with our address in this form:


    unsigned int a = 207;
unsigned int b = 45;
unsigned int c = 186;
unsigned int d = 98;
unsigned short port = 30000;

We have a bit of work to do to get it in the form required by “sendto”:


    unsigned int address = ( a << 24 ) |
( b << 16 ) |
( c << 8 ) |
d;

sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl( address );
addr.sin_port = htons( port );

As you can see, we first combine the a,b,c,d values in range [0,255] into a single unsigned integer, with each byte of the integer now corresponding to the input values. We then initialize a “sockaddr_in” structure with the integer address and port, making sure to convert our integer address and port values from host byte order to network byte order using “htonl” and “htons”.


Special case: if you want to send a packet to yourself, there’s no need to query the IP address of your own machine, just pass in the loopback address 127.0.0.1 and the packet will be sent to your local machine.


Receiving packets


Once you have a UDP socket bound to a port, any UDP packets sent to your sockets IP address and port are placed in a queue. To receive packets just loop and call “recvfrom” until it fails with EWOULDBLOCK indicating there are no more packets to receive.


Since UDP is connectionless, packets may arrive from any number of different computers. Each time you receive a packet “recvfrom” gives you the IP address and port of the sender, so you know where the packet came from.


Here’s how to loop and receive all incoming packets:


    while ( true )
{
unsigned char packet_data[256];

unsigned int max_packet_size =
sizeof( packet_data );

#if PLATFORM == PLATFORM_WINDOWS
typedef int socklen_t;
#endif

sockaddr_in from;
socklen_t fromLength = sizeof( from );

int bytes = recvfrom( socket,
(char)packet_data,
max_packet_size,
0,
(sockaddr)&from,
&fromLength );

if ( bytes <= 0 )
break;

unsigned int from_address =
ntohl( from.sin_addr.s_addr );

unsigned int from_port =
ntohs( from.sin_port );

// process received packet
}

Any packets in the queue larger than your receive buffer will be silently discarded. So if you have a 256 byte buffer to receive packets like the code above, and somebody sends you a 300 byte packet, the 300 byte packet will be dropped. You will not receive just the first 256 bytes of the 300 byte packet.


Since you are writing your own game network protocol, this is no problem at all in practice, just make sure your receive buffer is big enough to receive the largest packet your code could possibly send.


Destroying a socket


On most unix-like platforms, sockets are file handles so you use the standard file “close” function to clean up sockets once you are finished with them. However, Windows likes to be a little bit different, so we have to use “closesocket” instead:


#if PLATFORM == PLATFORM_MAC ||
PLATFORM == PLATFORM_UNIX
close( socket );
#elif PLATFORM == PLATFORM_WINDOWS
closesocket( socket );
#endif

Hooray windows.


Socket class


So we’ve covered all the basic operations: creating a socket, binding it to a port, setting it to non-blocking, sending and receiving packets, and destroying the socket.


But you’ll notice most of these operations are slightly platform dependent, and it’s pretty annoying to have to remember to #ifdef and do platform specifics each time you want to perform socket operations.


We’re going to solve this by wrapping all our socket functionality up into a “Socket” class. While we’re at it, we’ll add an “Address” class to make it easier to specify internet addresses. This avoids having to manually encode or decode a “sockaddr_in” structure each time we send or receive packets.


So let’s add a socket class:


    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;
};

and an 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;

private:

unsigned int address;
unsigned short port;
};

Here’s how to to send and receive packets with these classes:


    // 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(127,0,0,1,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
}

As you can see it’s much simpler than using BSD sockets directly.


As an added bonus the code is the same on all platforms because everything platform specific is handled inside the socket and address classes.


Conclusion


You now have a platform independent way to send and receive packets. Enjoy :)

译文

译文出处





因译文很多地方均有疏漏, 本文已经对部分疏漏做了修正.

翻译:杨嘉鑫(矫情到死的仓鼠君,)审校:赵菁菁(轩语轩缘)


$hhd$1>序言

大家好,我是Glenn Fiedler,欢迎阅读《针对游戏程序员的网络知识》系列教程的第二篇文章。

在前面的文章中我们讨论了在不同计算机之间发送数据的方法,并决定使用用户数据报协议(UDP而非传输控制协议(TCP。我们之所以使用用户数据报协议(UDP,是因为它能够使数据在不等待重发包而造成数据聚集的情况下按时被送达。

现在我将要告诉各位如何使用用户数据报协议(UDP发送和接收数据包。


$hhd$1>伯克利套接字 BSD socket

对于大多数现代的平台来说你都可以找到建立在伯克利套接字上的sockets。伯克利套接字主要通过“socket”,“bind”, “sendto” and “recvfrom”几个简单函数进行控制。如果你愿意的话你当然可以直接对这几个函数进行调用,但是由于每个平台之间有细微差别,保持代码平台的独立性将会变得有些困难。因此,尽管我将先给各位介绍伯克利套接字的示例代码用以说明它的基本使用功能,我们也不会大量的直接使用伯克利套接字。所以当我们掌握了所有基础socket 功能后,我们将会把所有内容汇总到一个系列的课中,以便你可以轻松地编写代码。


$hhd$1>平台的特殊性

首先 我们先建立一个“define”程序用来测试我们现有的平台是什么,这样我们就可以发现不同平台间间各个socket里的细微差别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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

接下来我们为sockets写入适当的标头,由于头文件具有平台的特殊性所以我们将使用“#define”来根据不同的平台引用不同的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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</fcntl.h></netinet></sys></winsock2.h>


如果sockets是建立在unix平台上,我们就不需要任何其他多余的连接,若它是建立在windows系统里,为了确保socket正常使用我们就需要连接到“winsock”库内。

以下是一个简单的技巧,它可以在不改变已有项目或生成文件的前提下完成上述工作。

1
2
3
4
5
#if PLATFORM == PLATFORM_WINDOWS
#pragma comment( lib, “wsock32.lib” )
#endif


我之所以非常喜欢这个小技巧是因为我太懒了~当然啦,如果你愿意每次都进行项目链接或生成文件也未尝不可。


$hhd$1>socket层的初始化

大多数“unix-like”的平台 (包括macosx) 是不需要任何特殊的步骤去初始化socket层的。但是Windows需要进行一些特殊设置来确保你的sockets代码正常工作。在你使用其他任何sockets功能前你必须先调用 “WSAStartup” 来初始化它们,在你的程序段结束时你也必须使用 “WSACleanup”来结束。

下面让我们来添加以上两个新功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool InitializeSockets()
{
#if PLATFORM == PLATFORM_WINDOWS
WSADATA WsaData;
return WSAStartup( MAKEWORD(2,2), &WsaData ) == NO_ERROR;
#else
return true;
#endif
}
void ShutdownSockets()
{
#if PLATFORM == PLATFORM_WINDOWS
WSACleanup();
#endif
}


这样我们就得到了一个初始化socket层的方法。对于那些不需要socket初始化的平台来说这些功能可以忽略不计。


$hhd$1>建立一个socket

现在是时候来建立一个基于用户数据报协议(UDP)的socket了,下面是实施的方法:

1
2
3
4
5
6
7
8
int handle = socket( AF_INET, SOCK_DGRAM,IPPROTO_UDP );
if ( handle <= 0 )
{
printf( “failed to create socket\n” );
return false;
}

接下来我们把用户数据报协议(UDP)的socket对应到一个端口上(比如30000这个端口)。每一个socket都必须对应到一个独一无二的端口上。这么做的原因是端口号决定了每个数据包发送到的位置。不要使用1024以下的端口,因为这是为系统调用所预留的。

有一种特殊情况,如果你不在乎socket指定到哪个端口上,你就可以输入“0”,这样系统将会自动为你选择一个闲置的端口。

1
2
3
4
5
6
7
8
9
10
11
12
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;
}

这样我们的socket已经准备就绪并可以发送和接收包了。

那么上面提到的“htons”是起什么作用呢?这是一个辅助功能,它将一个16位整数的值由主机字节序列(小端或大端)转换成网络字节序列(大端)。这就要求你在任何时候都直接在socket结构里设置整数数字。

你会看到“htons”(主机到网络短字节)及其32位整数大小的表兄妹”htonl”(主机到网络长字节)这在这篇文章中被多次使用,你留意了以后你在下文中再次遇到就会明白。


$hhd$1>socket设置为非阻塞形式

默认情况下,socket是被设置在阻塞模式的状态下。这意味着,如果你想使用“recvfrom”功能读一个包,在一个数据包被读取前该函数值将不能被返回。这与我们的目标完全不符。视频游戏是拟态在3060帧每秒实时的程序,他们不能只是坐在那里等待数据包的到达!

解决方案是你将socket转换成以非阻塞模式后再创建他们。一旦做到这一点,当没有包可供阅读时,“recvfrom”函数就可以立即返回,返回值显示你应该稍后再尝试读取包。

下面是如何将socket设置为非阻塞模式的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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\n” );
return false;
}
#elif PLATFORM == PLATFORM_WINDOWS
DWORD nonBlocking = 1;
if ( ioctlsocket( handle, FIONBIO, &nonBlocking ) != 0 )
{
printf( “failed to set non-blocking\n” );
return false;
}
#endif

从上面的程序我们可以发现,Windows本身并不提供框架的功能,所以我们使用“ioctlsocket”功能来实现。


$hhd$1>发送数据包

用户数据报协议(UDP是一种无连接协议,所以每次你发送一个数据包前都要指定一个目的地址。你可以使用一个用户数据报协议(UDP发送数据包到任意数量的不同的IP地址,而在你用户数据报协议(UDP socket的另一端并没有连接某一台计算机。

下面是如何发送一个数据包到一个特定的地址方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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\n” );
return false;
}

很重要的一点!“sendto”的返回值只是表明数据包是否被成功地从本地计算机发送,它并不能表明目标计算机是否成功接收到你的数据包!用户数据报协议(UDP)没有办法知道数据包是否能到达目的地。

上面的代码中,我们通“sockaddr_in”结构为目的地址。

那么我们如何设置这些结构呢?

现在让我们以发送到207.45.186.98:30000 这个地址为例

我们从以下这个程序开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned int a = 207;
unsigned int b = 45;
unsigned int c = 186;
unsigned int d = 98;
unsigned short port = 30000;
我们还要在形式上进行设置从而符合“sendto”的要求:
unsigned int address = ( a << 24 ) |
( b << 16 ) |
( c << 8 ) | d;
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl( address );
addr.sin_port = htons( port );

正如您所看到的,我们首先将ABCD值在范围[ 0, 255 ]内的值转化为一个单一的无符号整数,从而使这个整数的每个字节对应输入值。然后以整数地址和端口来初始化一个“sockaddr_in”结构,这样就确保使用“htonl” “htons”来将整型地址和端口值从主机字节序列转换为为网络字节序列。

一种特殊情况:如果你想给自己发送一个数据包,不需要查询自己机器的IP地址,在回送地址127.0.0.1中数据包就将被发送到你的本地机器。


$hhd$1>接收数据包

一旦你将一个用户数据报协议(UDP套接字绑定到一个端口,任何发送到您scoket IP地址和端口的用户数据报协议(UDP数据包都将放在一个队列里。接收数据包的话, 只需要循环调用 “recvfrom”函数直到他失败并返回”EWOULDBLOCK”,这就意味着队列里有没有留下其他的数据包了。由于用户数据报协议(UDP是无连接性的,数据包可以到达许多不同的计算机。每当你收到一个数据包,“recvfrom”都会给你发送者的IP地址和端口以便你知道这是来自哪里的数据包。

下面是如何进行循环接收传入的数据包的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
while ( true )
{
unsigned char packet_data[256];
unsigned int max_packet_size = sizeof( packet_data );
#if PLATFORM == PLATFORM_WINDOWS
typedef int socklen_t;
#endif
sockaddr_in from;
socklen_t fromLength = sizeof( from );
int bytes = recvfrom( socket,
(char)packet_data,
max_packet_size,
0,
(sockaddr)&from,
&fromLength );
if ( bytes <= 0 )
break;
unsigned int from_address = ntohl( from.sin_addr.s_addr );
unsigned int from_port = ntohs( from.sin_port );
// process received packet
}

在队列中,数据包一旦大于你接收缓冲区的范围,他们都会被系统悄悄舍弃。因此,如果你有一个256字节的缓冲区用来接收数据包,有人给你一个发送300字节的数据包,300字节的数据包都将被删除。您将不会接收到300字节数据包的前256个字节。

因为您正在编写自己的游戏网络协议,以上这些操作这是没有什么影响的。

在实践中您就要确保您的接收缓冲区足够大,以接收最大的数据包。


$hhd$1>关闭一个socket

在大多数Unix平台,一旦你完成了自己所需的程序后,在socket文件中只要使用标准的文件“close”函数来清理即可。然而,在Windows系统中以上情形会有点不同,我们要用“closesocket”函数来操作:

1
2
3
4
5
6
7
8
9
#if PLATFORM == PLATFORM_MAC || PLATFORM == PLATFORM_UNIX
close( socket );
#elif PLATFORM == PLATFORM_WINDOWS
closesocket( socket );
#endif

$hhd$1>Socket class

现在,我们已经完成了所有的基本操作:创建一个socket,将他绑定到一个端口并设置为非阻塞,发送和接收数据包,清除socket

但是你会发现以上这些操作中多多少少都是依赖于平台的,在每一次你想执行socket操作时,你不得不记住“# ifdef”指令和针对不同平台的各种细节,这些繁琐的操作是很令人抓狂的。

为了解决这个问题,我们可以将所有的socket功能封装成一个“socket class‘’。当我们在使用它的时候,我们将添加一个“Address class‘’,这样使它更容易指定互联网地址。这避免了我们每次发送或接收数据包时进行手动编码或解码“sockaddr_in”结构。

下面是“socket class‘’的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
};


下面是“address class”的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
private:
unsigned int address;
unsigned short port;
};

下面是这些class如何接收和发送数据包的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 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(127,0,0,1,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
}


$hhd$1>结论

我们现在有了一种不限平台的方法来发送和接收用户数据报协议(UDP)的数据包。

用户数据报协议(UDP是无连接性的,因此我编写了一个简单的示例程序,它可以从文本文件中读取IP地址,并能够每秒向这些地址发送一个数据包。每当这个程序接收到一个数据包时,它就会告诉你它们来自哪个机器,以及接收到的数据包的大小。

您可以很容易地设置它,然后你就拥有了一系列在本地机器上互相发送数据包的节点。这样你就可以利用以下程序通过不同的端口,进入不同的应用程序:

> Node30000

> Node 30001

> Node 30002

etc…

然后每个节点都将尝试发送数据包到每个其他节点,它的工作原理就像一个小型的“peer-to-peer”设置。

我是在MacOSX系统中开发的这个程序,但我想你应该能够轻松地在任何Unix系统或Windows上对他进行编译。如果你有任何应用在其他不同机器上的兼容性补丁,也非常欢迎您与我取得联系。


【版权声明】

原文作者未做权利声明,视为共享知识产权进入公共领域,自动获得授权。


源码下载

因 Gaffer On Games 的源码原下载地址失效, 所以特地补上.

请点击