Improve Communication Process
Blocking and Non-Blocking
大家知道,游戏需要不断循环处理游戏中的逻辑并进行游戏世界的绘制,上面所介绍的Winsock处理方式均是以阻塞方式进行,这样就违背了游戏的执行本质,可以想象,在客户端连接到服务器的过程中,你的游戏不能得到控制,这时如果玩家想取消连接或者做其他处理,甚至显示一个最基本的动态连接提示都不行。
所以我们需要用其他方式来处理网络通讯,使其不会与游戏主线相冲突,可能大家都会想到:创建一个网络线程来处理不就可以了?没错,我们可以创建一个专门用于网络通讯的子线程来解决这个问题。当然,我们游戏中多了一个线程,我们就需要做更多的考虑,让我们来看看如何创建网络通讯线程。
在Windows系统中,我们可以通过CreateThread()函数来进行线程的创建,看看下面的代码片段:
DWORD dwThreadID;
HANDLE hThread = CreateThread( NULL, 0, NetThread/*网络线程函式*/, sSocket, 0, &dwThreadID );
if( hThread == NULL )
{
Failed( "WinSocket Thread Create Error!");
}
这里我们创建了一个线程,同时将我们的Socket传入线程函数:
DWORD WINAPINetThread(LPVOID lParam)
{
SOCKET sSocket (SOCKET)lParam;
...
return 0;
}
NetThread就是我们将来用于处理网络通讯的网络线程。那么,我们又如何把Socket的处理引入线程中?
看看下面的代码片段:
HANDLE hEvent;
hEvent = CreateEvent(NULL,0,0,0);
// 设置异步通讯
if( WSAEventSelect( sSocket, hEvent,
FD_ACCEPT|FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE ) ==SOCKET_ERROR )
{
Failed( "WinSocket EventSelect Error!");
}
通过上面的设置之后,WinSock API函数均会以非阻塞方式运行,也就是函数执行后会立即返回,这时网络通讯会以事件方式存储于hEvent,而不会停顿整支程式。
完成了上面的步骤之后,我们需要对事件进行响应与处理,让我们看看如何在网络线程中获得网络通讯所产生的事件消息:
WSAEnumNetworkEvents( sSocket, hEvent, &SocketEvents );
if( SocketEvents.lNetworkEvents != 0 )
{
switch( SocketEvents.lNetworkEvents )
{
case FD_ACCEPT:
WSANETWORKEVENTS SocketEvents;
break;
case FD_CONNECT:
{
if( SocketEvents.iErrorCode[FD_CONNECT_BIT] == 0)
// 连接成功
{
// 连接成功后通知主线程(游戏线程)进行处理
}
}
break;
case FD_READ:
// 获取网络数据
{
if( recv( sSocket, pBuffer, lLength, 0) == SOCKET_ERROR )
{
Failed( "WinSocket Recv Error!");
}
}
break;
case FD_WRITE:
break;
case FD_CLOSE:
// 通知主线程(游戏线程),网络已经断开
break;
default:
break;
}
}
这里仅对网络连接(FD_CONNECT) 和读取数据(FD_READ) 进行了简单模拟操作,但实际中网络线程接收到事件消息后,会对数据进行组织整理,然后再将数据回传给我们的游戏主线程使用,游戏主线程再将处理过的数据发送出去,这样一个往返就构成了我们网络游戏中的数据通讯,是让网络游戏动起来的最基本要素。
Communication Data Packet
最后,我们来谈谈关于网络数据包(数据封包)的组织,网络游戏的数据包是游戏数据通讯的最基本单位,网络游戏一般不会用字节流的方式来进行数据传输,一个数据封包也可以看作是一条消息指令,在游戏进行中,服务器和客户端会不停的发送和接收这些消息包,然后将消息包解析转换为真正所要表达的指令意义并执行。