網路遊戲程式設計師須知 收包與發包

2021-09-30 08:03:24 字數 4558 閱讀 2679

// platform detection

#define platform_windows  1

#define platform_mac      2

#define platform_unix     3

#include

#elif platform == platform_mac || platform == platform_unix

#include

#include

#include

inline bool initializesockets()

inline void shutdownsockets()

現在我們有了乙個好的開頭,我們的埠層已經與平台無關了,並且不需要初始化埠,這些小事是我們開始的時候必須要認真完成的細節。

建立乙個埠

現在就讓我們來建立乙個udp埠吧, 看**:

int handle = socket( af_inet, sock_dgram, ipproto_udp );

if ( handle <= 0 )

接下來,我們需要給埠繫結乙個埠號(比如:40000)。每個埠都必須有乙個埠號,這樣當你收到包的時候,機器才能知道改發給哪個埠。不用用1024以下那些埠,那些是系統保留埠。

還有一點特殊情況就是,如果你不打算收包,那麼可以給埠繫結0埠。這個操作的意思就是讓作業系統隨機分配乙個埠號給你,反正不用你操心就是了。

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 )

現在我們要用來發包的埠就就緒了。

上面這段**中有乙個比較奇怪的函式「htons」,這個函式是幹嘛的呢?這個函式其實是用來調整低端位元「little-endian」和高階位元「big-endian」的不同的作業系統可能會使用不同的位元模式。如果你想直接給埠賦值16bit的整形變數,那麼你必須使用這個函式。這個函式還有乙個32bit的兄弟函式,用來倒32bit的整數。這篇文章中我們會有不少涉及,留神了。 by rellikt

將埠設為無阻塞模式

預設的埠設定是阻塞模式。這意味著如果你想用「recvfrom」讀乙個包,那麼在這個包可以被完整讀取以前,recvfrom都不會返回。我們的遊戲是實時模擬的,一般會限幀在30fps或者60fps。這種阻塞模式不是我們想要的模式。

我們這裡在建立完埠以後需要把埠調到無阻塞模式。這種模式下,呼叫recvfrom會立即返回,不同的是如果包還不能被讀取,recvfrom會給出乙個返回值告訴我們讀取不成功。下次還要再讀。 by rellikt

下面是設定埠為無阻塞模式的**:

#if platform == platform_mac || platform == platform_unix

int nonblocking = 1;

if ( fcntl( handle, f_setfl, o_nonblock, nonblocking ) == -1 )

#elif platform == platform_windows

dword nonblocking = 1;

if ( ioctlsocket( handle, fionbio, &nonblocking ) != 0 )

#endif 你可以看到windows中沒有「fcntl」函式,所以我們要用「loctlsocket」函式來做代替。

發包udp不是基於連線的傳輸協議,因此你每個包都得包含目標的位址和埠號。你可以在同乙個udp埠上給不同機器的不同埠發包,這完全可以。這裡沒有任何的連線的存在。

下面我展示了怎麼發包:

int sent_bytes = sendto( handle, (const char*)packet_data, packet_size,

0, (sockaddr*)&address, sizeof(sockaddr_in) );

if ( sent_bytes != packet_size )

注意!這裡的返回值是表示這個包是否成功發出的意思。它並不能告訴你這個包是否被成功收到。事實上udp協議不存在這種能讓你知道對方是否收到的方法。

你可以看到在上面的函式中我們輸入了乙個「sockaddr_in」的資料結構,我們是如何給這個資料結構負值的呢?

比如說我們現在要給207.45.186.98:30000發包。

我們可以這麼做:

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 ); 我們這裡做得其實就是給出a,b,c,d四個值,每個都在0到255的範圍內,然後將他們和目標整數的不同位元位關聯起來,然後用htonl或者htons來保證位元位的高階和低端問題。還是挺簡單的吧。

收包當你給你的udp埠繫結了乙個埠號以後,你就可以收包了。事實上那些收到的包會先進入埠的佇列,然後你可以通過乙個迴圈來進行不停的「recvfrom」這個佇列直到recvfrom返回值表示佇列裡已經沒有包可讀。

我們知道udp其實是沒有連線這個概念的,所以說即使是同乙個udp埠佇列,裡面也可能會包含不同機器發來的udp包,這些包裡都會有傳送者的ip和埠號,你可以在讀這些包的時候得到這些資訊。 by rellikt

下面還是看**:

while ( true )

佇列裡那些比你的緩衝區更大的包會被預設丟棄。所以謹記你必須開乙個足夠大的緩衝區,不然那些超過你緩衝區大小的包,你就不

用想了。我們這裡是用udp實現我們自己的網路傳輸協議,所以不需要有太多限制,記住開足夠大的緩衝區就可以了。 

銷毀埠

大多數的類unix系統上,埠和檔案控制代碼一樣,你只需要用close像關檔案控制代碼一樣消耗埠一樣就可以了。切記要消耗,不然導致的控制代碼溢位是很麻煩的。windows系統上我們要做的稍微要麻煩點。大致我們可以如下來完成這個工作:

#if platform == platform_mac || platform == platform_unix

close( socket );

#elif platform == platform_windows

closesocket( socket );

#endif埠類

我們這裡已經介紹了所有的基本操作:開端口,繫結埠號,設定無阻塞模式,收包,發包,銷毀埠。

但是你是否覺得我們用到的那些平台相關的**有點讓人覺得不好處理呢?靠一堆#define什麼的來編碼顯得很不好用,另外關於sockaddr_in這個資料結果的賦值也是很容易出錯的,這裡我們這裡就做乙個抽象的封裝socket類來完成需要的那些功能:

class socket

;接下來是我們的address類:

class address

;最後是你可以怎麼用這些類來完成收包和發包:

// create socket

const int port = 30000;

socket socket;

if ( !socket.open( port ) )

// send a packet

const char data = "hello world!";

socket.send( address(127,0,0,1,port), data, sizeof( data ) );

// receive packets

while ( true )

有了這個抽象類我們的生活會變得簡單不少,另外我們這裡用到的**就已經和平台無關了。我們可以在底層處理那些麻煩的api。結論

我們至此已經有了乙個平台無關的可以用來收發udp包的基本架構了。 udp是連線無關的,我這裡寫了乙個示例**來說明這個問題。這段**從乙個txt檔案中讀取一些ip位址,然後每隔一秒鐘會對這些ip位址發乙個包。每次本機收到包的時候則會打出收到包的**和包的大小。你可以修改一下引數,然後啟動多個示例來做實驗,如果你這樣做的話,其實你已經在實現一種簡單的p2p的網路架構了。 by rellikt

這個示例**是在mac上做的,不過在其他平台上編譯通過應該也不會有問題。

這篇的內容就到此為止了,如果你沒有任何問題我想我們可以進行下一章的內容了。下一章我會告訴你如何做乙個可以實際應用的udp協議。就這樣吧

網路遊戲程式設計師須知 UDP vs TCP

網路遊戲程式設計師須知 udp vs tcp 這篇教程讓我們就從最基本的網路資料收發開始談起吧。其實這部分才是網路程式設計師應該做的最基礎最簡單的部分,但是這部分如果想要做好相對來說還是很有技巧和困難的。而且如果這部分你沒做好,在多人對戰類遊戲中它帶來的影響是極其惡劣的。你可能聽說過埠這個概念,也可...

程式設計師和網路遊戲

我發現程式設計師生涯和網遊非常像。網遊中的級別高低,完全取決於其經驗值的多少。程式設計師也是一樣。程式設計師的水平高低,不在於會用多少框架,不在於學過多少教程。為什麼程式設計師這行對學歷特別的不看重?程式設計師到底看重什麼?經驗。但是這個經驗不是按時間計算的。同樣幹了幾年的程式設計師,水平為什麼有高...

乙個網路遊戲程式設計師的棋牌開發之路

我從中部某省偏僻的小山村來到上海這個燈紅酒綠的國際大都市,找到第乙份工作,從事大型網路遊戲開發,剛開始負責底層驅動開發,因為技術過硬,基礎知識功底紮實,很快從程式開發人員中脫穎而出,公司經理提公升我為專案小組負責人,這樣我過了兩年時間,開始全面負責一些工作,為以後創業打下了牢固基礎。工作之餘,我開始...