介绍
五子棋服务器主要完成两个功能:
- 保存玩家请求战斗的信息,并为玩家匹配;
- 匹配好了后直接为两玩家转发坐标数据即可。
五子棋网络对战功能:
- 指定对手名称;
- 由服务器随选择对手;
两种数据结构
// 开始游戏后传输的坐标数据
struct PositionData
{
Point pos;
};
采用四个线程来处理
主线程专门负责监听连接,将玩家的信息大包,再根据相应的游戏类型保存到相应的队列中:
// 存放指定对手的玩家 map<string,PlayerInfo> NamedGame; // 存放随机对手的玩家 list<PlayerInfo> RandomGame;
- 保存的玩家信息数据结构为
// 保存玩家信息与套接字 struct PlayerInfo { CertifyData info; SOCKET sock; };
- 线程
DWORD WINAPI ProgressMap(LPVOID lpParm);
专门处理指定对手的玩家- 线程
DWORD WINAPI ProgressList(LPVOID lpParm);
专门处理随机对战的玩家- 线程
DWORD WINAPI GameRunThread(LPVOID lpParm);
专门负责为这种对战的玩家转发消息,每一对玩家开辟一个线程(效率低,但简单实用);传递的参数的数据结构保持了两位玩家的信息和SOCKET号
// 传递给玩家开始游戏后的线程数据结构 struct GameRunParm { PlayerInfo PlayerA; PlayerInfo PlayerB; };
临界区
需要创建两个临界区,为对连个玩家队列的互斥访问
// 访问NameGame的临街区 CRITICAL_SECTION cri_namedgame; // 访问RandomGame的临界区 CRITICAL_SECTION cri_randomgame;
初始化资源
winsock资源的初始化
WORD wVersionRequested; WSADATA wsaData; ZeroMemory(&wsaData,sizeof WSADATA); wVersionRequested = MAKEWORD(2,0); int re = WSAStartup(wVersionRequested,&wsaData); if(re != 0) return;
临界区的初始化
// 临界区初始化 InitializeCriticalSection(&cri_namedgame); InitializeCriticalSection(&cri_randomgame);
主循环
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
ZeroMemory(&wsaData,sizeof WSADATA);
wVersionRequested = MAKEWORD(2,0);
int re = WSAStartup(wVersionRequested,&wsaData);
if(re != 0)
return;
// 临界区初始化
InitializeCriticalSection(&cri_namedgame);
InitializeCriticalSection(&cri_randomgame);
SOCKET svrScok = socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addSvr;
addSvr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addSvr.sin_family = AF_INET;
addSvr.sin_port = htons(SERVER_PORT);
bind(svrScok,(SOCKADDR*)&addSvr,sizeof(SOCKADDR));
re = listen(svrScok,MAX_CON);
assert(re != SOCKET_ERROR);
// 创建对map与list玩家列表处理的线程
MapProThread = CreateThread(NULL,0,ProgressMap,NULL,NULL);
ListProThread = CreateThread(NULL,ProgressList,NULL);
// 接收玩家连接
while(1)
{
cout<<"监听中..."<<endl;
SOCKADDR_IN cliAddr;
int len = sizeof(SOCKADDR_IN);
ZeroMemory(&cliAddr,len);
CertifyData revData;
memset(&revData,'\0',CrtDataSize);
// 阻塞接收连接
SOCKET cliSock = accept(svrScok,(SOCKADDR*)&cliAddr,&len);
assert(cliSock != INVALID_SOCKET);
// 接收玩家信息
recv(cliSock,(char*)&revData,CrtDataSize,0);
cout<<"玩家"<<revData.myname<<"连接建立成功"<<endl;
// 将玩家放入相应的列表里
PlayerInfo plyerInfo = {revData,cliSock};
string str = revData.myname;
switch(revData.GameType)
{
case Name_Named:
EnterCriticalSection(&cri_namedgame);
NamedGame.insert(make_pair(str,plyerInfo));
LeaveCriticalSection(&cri_namedgame);
break;
case Name_Random:
EnterCriticalSection(&cri_randomgame);
RandomGame.push_back(plyerInfo);
LeaveCriticalSection(&cri_randomgame);
break;
}
}
}
子线程
指定对手
DWORD WINAPI ProgressMap(LPVOID lpParm) { typedef map<string,PlayerInfo>::iterator mapItr; do { // 每隔200ms处理一次 Sleep(200); EnterCriticalSection(&cri_namedgame); // 遍历map,查找指定对手 for (mapItr itr = NamedGame.begin();itr != NamedGame.end();) { string str = itr->second.info.opponame; mapItr OppoItr = NamedGame.find(str); // 如果找到对手 if (OppoItr != NamedGame.end()) { // 大包玩家信息 GameRunParm runparm = {itr->second,OppoItr->second}; // 创建游戏线程 CreateThread(NULL,GameRunThread,&runparm,NULL); // 从玩家列表中将两者删除 NamedGame.erase(itr++); if(itr == OppoItr) ++itr; NamedGame.erase(OppoItr++); if(itr == OppoItr && OppoItr != NamedGame.end()) ++ OppoItr; } else ++itr; } } while (1); LeaveCriticalSection(&cri_namedgame); }
随机对手
DWORD WINAPI ProgressList(LPVOID lpParm) { typedef list<PlayerInfo>::iterator listItr; do { Sleep(200); EnterCriticalSection(&cri_randomgame); listItr plyA = RandomGame.end(); if (plyA != RandomGame.end()) { listItr plyB = plyA; ++plyB; // 当两者都存在时 while(plyA != RandomGame.end() && plyB != RandomGame.end()) { // 打包玩家信息 GameRunParm runparm = {*plyA,*plyB}; // 创建游戏线程 CreateThread(NULL,NULL); // 删除对象 RandomGame.erase(plyA++); RandomGame.erase(plyB++); plyA = plyB; if(plyB != RandomGame.end()) ++plyB; } } LeaveCriticalSection(&cri_randomgame); } while (1); }
正式游戏线程
DWORD WINAPI GameRunThread(LPVOID lpParm) { GameRunParm *plyInfo = (GameRunParm*)lpParm; SOCKET SockA = plyInfo->PlayerA.sock; SOCKET SockB = plyInfo->PlayerB.sock; // 首先返回确认信息给两位玩家 PlayerInfo recvData; memset(&recvData,CrtDataSize); // 玩家A recvData = plyInfo->PlayerA; recvData.info.ISFIRST = true; int re = send(SockA,(char*)&recvData,0); cout<<"server told to "<<recvData.info.myname<<" game begin,and you play first hand!"<<endl; // 玩家B recvData = plyInfo->PlayerB; recvData.info.ISFIRST = false; re = send(SockB,and you play second hand!"<<endl; // 进入循环开始游戏 int PSize = sizeof(Point); while(1) { Point point = {-1,-1}; re = send(SockA,(char*)&point,PSize,0); if (re == SOCKET_ERROR) { cout<<"game over"<<endl; break; } send(SockB,0); cout<<"player "<<plyInfo->PlayerA.info.myname<<" send to player " <<plyInfo->PlayerB.info.myname<<" position:( "<<point.x <<","<<point.y<<" )"<<endl; re = recv(SockB,0); if (re == SOCKET_ERROR) { cout<<"game over"<<endl; break; } send(SockA,0); cout<<"player "<<plyInfo->PlayerB.info.myname<<" send to player " <<plyInfo->PlayerA.info.myname<<" position:( "<<point.x <<","<<point.y<<" )"<<endl; } // 关闭资源,线程负责关闭自己的接收socket re = shutdown(SockA,SD_SEND); re = shutdown(SockB,SD_SEND); Point data; while(send(SockA,(char*)&data,0) > 0); while(send(SockB,0) > 0); closesocket(SockA); closesocket(SockB); cout<<"connection "<<plyInfo->PlayerA.info.myname<<" exit."<<endl; cout<<"connection "<<plyInfo->PlayerB.info.myname<<" exit."<<endl; return 0; }