當前位置:才華齋>範例>校園>

網路遊戲製作技術培訓考試題

校園 閱讀(2.37W)

網路遊戲的程式開發從某種意義上來看,最重要的應該在於遊戲伺服器端的設計和製作。對於伺服器端的製作。將分為以下幾個模組進行:

網路遊戲製作技術培訓考試題

1.網路通訊模組

2.協議模組

3.執行緒池模組

4.記憶體管理模組

5.遊戲規則處理模組

6.後臺遊戲模擬世界模組。

現在就網路中的通訊模組處理談一下自己的看法!!

在網路遊戲客戶端和伺服器端進行互動的雙向I/O模型中分別有以下幾種模型:

1. Select模型

2. 事件驅動模型

3. 訊息驅動模型

4. 重疊模型

5. 完成埠重疊模型。

在這樣的幾種模型中,能夠通過硬體效能的提高而提高軟體效能,並且能夠同時處理成千上百個I/O請求的模型。伺服器端應該採用的最佳模型是:完成埠 模型。然而在眾多的模型之中完成埠的處理是最複雜的,而它的複雜之處就在於多伺服器工作執行緒並行處理客戶端的I/O請求和理解完成埠的請求處理過程。

對於伺服器端完成埠的處理過程總結以下一些步驟:

1. 建立伺服器端SOCKET套接字描述符,這一點比較簡單。

例如:

SOCKET server_socket;

Server_socket = socket(AF_INET,SOCK_STREAM,0);

2.繫結套接字server_socket。

Const int SERV_TCP_PORT = 5555;

struct sockaddr_in server_address.

memset(&server_address, 0, sizeof(struct sockaddr_in));

server__family = AF_INET;

server__addr.s_addr = htonl(INADDR_ANY);

server__port = htons(SERV_TCP_PORT);

//繫結

Bind(serve_socket,( struct sockaddr *)&server_address, sizeof(server_address));

2. 對於建立的伺服器套接字描述符偵聽。

Listen(server_socket ,5);

3. 初始化我們的完成埠,開始的時候是產生一個新的完成埠。

HANDLE hCompletionPort;

HCompletionPort = CreateIoCompletionPort(NULL,NULL,NULL,0);

4. 在我們已經產生出來新的完成埠之後,我們就需要進行系統的偵測來得到系統的'硬體資訊。從而來定出我們的伺服器完成埠工作執行緒的數量。

SYSTEM_INFO system_info;

GetSystemInfo(&system_info);

在我們知道我們系統的資訊之後,我們就需要做這樣的一個決定,那就是我們的伺服器系統該有多少個執行緒進行工作,我一般會選擇當前處理器的2倍來生成我 們的工作執行緒數量(原因考慮執行緒的阻塞,所以就必須有後備的執行緒來佔有處理器進行執行,這樣就可以充分的提高處理器的利用率)。

程式碼:

WORD threadNum = system_info. DwNumberOfProcessors*2+2;

for(int i=0;I

{

HANDLE hThread;

DWORD dwthreadId;

hThread = _beginthreadex(NULL,ServerWorkThrea, (LPVOID)hCompletePort,0,&dwthreadId);

CloseHandle(hThread);

}

CloseHandle(hThread)在程式程式碼中的作用是在工作執行緒在結束後,能夠自動銷燬物件作用。

6. 產生伺服器檢測客戶端連線並且處理執行緒。

HANDLE hAcceptThread;

DWORD dwThreadId;

hAcceptThread= _beginthreadex(NULL,AcceptWorkThread,NULL, &dwThreadId);

CloseHandle(hAcceptThread);

7.連線處理執行緒的處理,線上程處理之前我們必須定義一些屬於自己的資料結構體來進行網路I/O互動過程中的資料記錄和儲存。

首先我要將如下幾個函式來向大家進行解析:

1.

HANDLE CreateIoCompletionPort (

HANDLE FileHandle, // handle to file

HANDLE ExistingCompletionPort, // handle to I/O completion port

ULONG_PTR CompletionKey, // completion key

DWORD NumberOfConcurrentThreads // number of threads to execute concurrently

);

引數1:

可以用來和完成埠聯絡的各種控制代碼,在這其中可以包括如下一些:

套接字,檔案等。

引數2:

已經存在的完成埠的控制代碼,也就是在第三步我們初始化的完成埠的控制代碼就可以了。

引數3:

這個引數對於我們來說將非常有用途。這就要具體看設計者的想法了, ULONG_PTR對於完成埠而言是一個單控制代碼資料,同時也是它的完成鍵值。同時我們在進行

這樣的GetQueuedCompletionStatus(….)(以下解釋)函式時我們可以完全得到我們在此聯絡函式中的完成鍵,簡單的說也就是我們 在CreateIoCompletionPort(…..)申請的記憶體塊,在GetQueuedCompletionStatus(……)中可以完封不動 的得到這個記憶體塊,並且使用它。這樣就給我們帶來了一個便利。也就是我們可以定義任意資料結構來儲存我們的資訊。在使用的時候只要進行強制轉化就可以了。

引數4:

引用MSDN上的解釋

[in] Maximum number of threads that the operating system allows to concurrently process I/O completion packets for the I/O completion port. If this parameter is zero, the system allows as many concurrently running threads as there are processors in the system.

這個引數我們在使用中只需要將它初始化為0就可以了。上面的意思我想大家應該也是瞭解的了!嘿嘿!!

我要向大家介紹的第二個函式也就是

2.

BOOL GetQueuedCompletionStatus(

HANDLE CompletionPort, // handle to completion port

LPDWORD lpNumberOfBytes, // bytes transferred

PULONG_PTR lpCompletionKey, // file completion key

LPOVERLAPPED *lpOverlapped, // buffer

DWORD dwMilliseconds // optional timeout value

);

引數1:

我們已經在前面產生的完成埠控制代碼,同時它對於客戶端而言,也是和客戶端SOCKET連線的那個埠。

引數2:

一次完成請求被交換的位元組數。(重疊請求以下解釋)

引數3:

完成埠的單控制代碼資料指標,這個指標將可以得到我們在CreateIoCompletionPort(………)中申請那片記憶體。

借用MSDN的解釋:

[out] Pointer to a variable that receives the completion key value associated with the file handle whose I/O operation has completed. A completion key is a per-file key that is specified in a call to CreateIoCompletionPort.

所以在使用這個函式的時候只需要將此處填一相應資料結構的空指標就可以了。上面的解釋只有大家自己擺平了。

引數4:

重疊I/O請求結構,這個結構同樣是指向我們在重疊請求時所申請的記憶體塊,同時和lpCompletionKey,一樣我們也可以利用這個記憶體塊來儲存我們要儲存的任意資料。以便於我們來進行適當的伺服器程式開發。

[out] Pointer to a variable that receives the address of the OVERLAPPED structure that was specified when the completed I/O operation was started.(MSDN)

3.

int WSARecv(

SOCKET s,

LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPDWORD lpNumberOfBytesRecvd,

LPDWORD lpFlags,

LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

);

這個函式也就是我們在進行完成埠請求時所使用的請求接受函式,同樣這個函式可以用ReadFile(………)來代替,但不建議使用這個函式。

引數1:

已經和Listen套接字建立連線的客戶端的套接字。

引數2:

用於接受請求資料的緩衝區。

[in/out] Pointer to an array of WSABUF structures. Each WSABUF structure contains a pointer to a buffer and the length of the buffer.(MSDN)。

引數3:

引數2所指向的WSABUF結構的數量。

[in] Number of WSABUF structures in the lpBuffers array.(MSDN)

引數4:

[out] Pointer to the number of bytes received by this call if the receive operation completes immediately. (MSDN)

引數5:

[in/out] Pointer to flags.(MSDN)

引數6:

這個引數對於我們來說是比較有作用的,當它不為空的時候我們就是提出我們的重疊請求。同時我們申請的這樣的一塊記憶體塊可以在完成請求後直接得到,因此我們同樣可以通過它來為我們儲存客戶端和伺服器的I/O資訊。

引數7:

[in] Pointer to the completion routine called when the receive operation has been completed (ignored for nonoverlapped sockets).(MSDN)

4.

int WSASend(

SOCKET s,

LPWSABUF lpBuffers,

DWORD dwBufferCount,

LPDWORD lpNumberOfBytesSent,

DWORD dwFlags,

LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

);

引數解釋可以參考上面或者MSDN。在這裡就不再多說了。

下面就關client端使用者連線(connect(……..))請求的處理方式進行

舉例如下:

const int BUFFER_SIZE = 1024;

typedef struct IO_CS_DATA

{

SOCKET clisnt_s; //客戶端SOCKET

WSABUF wsaBuf;

Char inBuffer[BUFFET_SIZE];

Char outBuffer[BUFFER_SIZE];

Int recvLen;

Int sendLen;

SYSTEM_TIME start_time;

SYSTEM_TIME start_time;

}IO_CS_DATA;

UINT WINAPI ServerAcceptThread(LPVOID param)

{

SOCKET client_s;

HANDLE hCompltPort = (HANDLE) param;

struct sockaddr_in client_addr;

int addr_Len = sizeof(client_addr);

LPHANDLE_DATA hand_Data = NULL;

while(true)

{

If((client_s=accept(server_socket,NULL,NULL)) == SOCKET_ERROR)

{

printf("Accept() Error: %d",GetLastError());

return 0;

}

hand_Data = (LPHANDLE_DATA)malloc(sizeof(HANDLE_DATA));

hand_Data->socket = client_s;

if(CreateIoCompletionPort((HANDLE)client_s,hCompltPort,(DWORD)hand_Data,0)==NULL)

{

printf("CreateIoCompletionPort()Error: %d", GetLastError());

}

else

{

game_Server->RecvDataRequest(client_s);

}

}

return 0;

}

在這個例子中,我們要闡述的是使用我們已經產生的接受連線執行緒來完成我們響應Client端的connect請求。關於這個執行緒我們同樣可以用我們執行緒池的方式來進行生成多個執行緒來進行處理,其他具體的函式解釋已經在上面解釋過了,希望不懂的自己琢磨。

關於game_Sever object的定義處理將在下面進行介紹。

class CServerSocket : public CBaseSocket

{

public:

CServerSocket();

virtual ~CServerSocket();

bool StartUpServer(); //啟動伺服器

void StopServer(); //關閉伺服器

//傳送或者接受資料(重疊請求)

bool RecvDataRequest(SOCKET client_s);

bool SendDataRequest(SOCKET client_s,char *buf,int b_len);

void ControlRecvData(SOCKET client_s,char *buf,int b_len);

void CloseClient(SOCKET client_s);

private:

friend UINT WINAPI GameServerThread(LPVOID completionPortID); //遊戲伺服器通訊工作執行緒

private:

void Init();

void Release();

bool InitComplePort();

bool InitServer();

bool CheckOsVersion();

bool StartupWorkThread();

bool StartupAcceptThread();

private:

enum { SERVER_PORT = 10006};

UINT cpu_Num; //處理器數量

CEvent g_ServerStop; //伺服器停止事件

CEvent g_ServerWatch; //伺服器監視事件

public:

HANDLE hCompletionPort; //完成埠控制代碼

};

在上面的類中,是我們用來處理客戶端使用者請求的伺服器端socket模型。