所以我们需要用其他方式来处理网络通讯,使其不会与游戏主线相冲突,可能大家都会想到: 创建一个网络线程来处理不就可以了?没错,我们可以创建一个专门用于网络通讯的子线程来解决这个问题。当然,我们游戏中多了一个线程,我们就需要做更多的考虑,让我们来看看如何创建网络通讯线程。
在Windows系统中,我们可以通过CreateThread()函数来进行线程的创建,看看下面的代码片段:
DWORD dwThreadID; |
这里我们创建了一个线程,同时将我们的Socket传入线程函数:
DWORD WINAPINetThread(LPVOID lParam) { |
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) 进行了简单模拟操作,但实际中网络线程接收到事件消息后,会对数据进行组织整理,然后再将数据回传给我们的游戏主线程使用,游戏主线程再将处理过的数据发送出去,这样一个往返就构成了我们网络游戏中的数据通讯,是让网络游戏动起来的最基本要素。
最后,我们来谈谈关于网络数据包(数据封包)的组织,网络游戏的数据包是游戏数据通讯的最基本单位,网络游戏一般不会用字节流的方式来进行数据传输,一个数据封包也可以看作是一条消息指令,在游戏进行中,服务器和客户端会不停的发送和接收这些消息包,然后将消息包解析转换为真正所要表达的指令意义并执行。