游戏中基于消息的异步非阻塞WinSock模型
在游戏中,服务器端或者客户端发送数据都是主动的,而接收数据则是在消息被动驱动下主动接收,这样就实现了非阻塞。我对服务器端以及客户端的WinSock都进行了简单的封装,毕竟TCP的验证和重发机制让我比较放心,在封装的时候只是设计了方便的调用方法,而在数据通信机制上没有任何的干涉。 服务器端的初始化如下:
| BOOL CServer::InitAndListen(HWND hwnd,UINT port) { m_uPort=port; m_hWnd=hwnd; if(m_hSocket != NULL) m_hSocket = socket(AF_INET, SOCK_STREAM,0); m_addr.sin_family = AF_INET; int ret = 0; if(ret == SOCKET_ERROR) return TRUE; |
服务器的作用就是在某个端口上一直监听,当收到某个客户端的数据后,经过一些处理,最后反馈给包括发送数据的客户端和其他的所有客户端。所以在以上初始化成功后,要对服务器端的Socket进行异步模式设置:
| if(WSAAsyncSelect(m_hSocket, m_hWnd, SER_MESSAGE, FD_ACCEPT|FD_READ|FD_WRITE|FD_CLOSE)>0) AfxMessageBox(”error select”); |
其中SER_MESSAGE自定义消息,定义在头文件里:
| #define SER_MESSAGE WM_USER + 100 |
另外,还要定义该消息的处理函数:
| afx_msg LRESULT OnServerMessage(WPARAM wParam, LPARAM lParam);
ON_MESSAGE(SER_MESSAGE,OnServerMessage) |
这样一来,当客户端给服务器端发送数据的时候,服务器端便会立即执行该函数,所以非阻塞的优势便在于此,使得服务器端不必一直等待数据。 当服务器端被消息触发开始执行消息处理函数的时候,如何知道发来的消息是什么类型以及是哪个游戏客户端发来的呢?利用WPARAM wParam, LPARAM lParam这两个参数就可以了。
| switch(lParam) { case FD_ACCEPT: socket= accept(server.m_hSocket,NULL,NULL); case FD_READ: |
到现在为止,服务器端的框架就出来了,客户端跟服务器很相似,唯一不同的是,客户端不需要监听,但是却要连接服务器,连接成功后,也需要异步模式设置,然后是自定义消息和消息处理函数。 在这之后,就是如何定义一种合理的数据格式,在服务器端和客户端之进行互相传送。这个数据格式的设计原则我觉得应该是尽量的短小精悍,但是也不吝啬长度,因为如果通信数据短了,那么客户端的负担就重了,与增加的负担相比,增加几个字节的通信数据也无伤大雅,关键是一个平衡度的把握。我觉得应该把客户端不容易计算或者计算需要很多辅助条件的数据直接通过通信传输获得,这样也可以很好的协调各个客户端的数据同步。尤其是在扑克游戏中,每个玩家都可以看到对方的剩余牌数,这个数据如果在每个客户端自主计算的话,确实也不是什么难事,但是如果在获得服务器数据的同时,附加每个玩家的牌数,就可以更加方便的得到数据,而且不会因为某个客户端的计算错误,导致几个玩家看到的对方牌数不一样。
另一个问题是,某个玩家执行出牌操作后,只是给服务器端发送数据告诉服务器自己要出什么牌,然后服务器将出牌的一些数据,包括出牌面值、出牌的方式、出牌的格式和出牌的玩家socket,告诉所有的玩家,也包括出牌人,这时候,包括出牌人在内的所有玩家收到服务器发来的出牌数据后,更新自己的桌面,显示出牌画面。这是一种方式,另一种方式是,自己出牌后,首先更新自己的桌面,显示出牌,同时发送给服务器,服务器只要将出牌数据发送给其余的玩家。我没有采用后者,因为后者相当于将自己特殊化了,同样的代码要给自己用一次,其他玩家再用一次。而事实上对于服务器端来说每个玩家都是平等的,在客户端必然存在一种方式可以让每个玩家都执行相同的操作,只是界面上给玩家的感觉是有一方属于自己。
还有对于出牌的数据设计,这是个最重要的问题,如何通过简单的字符串描述出牌的信息,从而让对方玩家能够不需要过多的分析就可以进行计算。还有出牌的验证,这些都是客户端的任务。这些会在单独的文章中介绍。 再次感谢试玩游戏和支持我的朋友们!







最近评论