糯米文學吧

位置:首頁 > 範文 > 校園

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

校園2.34W

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

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

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模型。