最近很浮躁,也不知道是不是工作的原因,好长时间都没有静下心来好好学习,我也知道自己技术还是菜鸟级别,烦躁的我又把大学写的很烂的俄罗斯方块,增加一点东西,改成网络版的,也就是增加注册,提交分数,获取高分榜这三个功能。感觉需要把自己的基础打好,尤其是c++。
既然是网络版的,就要有服务器和客户端,客户端已经有了,改下估计就行了。服务就用简单的qtservice吧(我也只会这个),可能要连接多个客户端,就用简单的一个连接开一个线程(双方交互完就立即断开)吧。好比下面这样:
bool TetrisApp::init()
{
ACE_INET_Addr addr(12161);
ACE_SOCK_Acceptor server;
if(server.open(addr)==-1)
{
ACE_DEBUG ((LM_DEBUG,ACE_TEXT ("(%P|%t) %p\n"),ACE_TEXT ("bind failed")));
return false;
}
ACE_SOCK_Stream *pStream = 0;
while((pStream = new ACE_SOCK_Stream()) && server.accept(*pStream)!= -1)
{
ACE_INET_Addr raddr;
pStream->get_remote_addr(raddr);
ACE_DEBUG ((LM_DEBUG,ACE_TEXT ("(%P|%t) connect:%s %d\n"),
raddr.get_host_addr(),raddr.get_port_number()));
ACE_Thread::spawn(worker,pStream);
Sleep(500);
}
return true;
}
DWORD TetrisApp::worker( void *arg )
{
ACE_SOCK_Stream *pStream =(ACE_SOCK_Stream *) arg;
ACE_Time_Value recv_timeout(2);
int len = pStream->recv_n(buffer,sizeof(PacketHead),&recv_timeout);
if(len > 0)
{
int bodyLen = ((PacketHead*)buffer)->bodyLength;
pStream->recv_n(buffer+sizeof(PacketHead),bodyLen,0);
switch (nCode)
{
case Register:
}
}
ACE_INET_Addr raddr;
pStream->get_remote_addr(raddr);
ACE_DEBUG ((LM_DEBUG,ACE_TEXT ("(%P|%t) close:%s %d\n"),raddr.get_host_addr(),raddr.get_port_number()));
pStream->close();
delete pStream;
return 0;
}
既然可以注册,提交,用数据库比较方便,查了下Acess数据库连接字及用法,感觉比较方便适合。交互内容就用Xml吧,tinyxml解析比较方便。
QString iniPath = QCoreApplication::applicationDirPath() + "/Tetrisdb.mdb";
m_strDBPath = QString("DRIVER={Microsoft Access Driver (*.mdb)};FIL={MS Access};DBQ=%1").arg(iniPath);
void TetrisApp::getHighScore( string &strout )
{
QSqlDatabase ldb = QSqlDatabase::addDatabase("QODBC");
ldb.setDatabaseName(m_strDBPath);
//ldb.setDatabaseName("DRIVER={Microsoft Access Driver (*.mdb)};FIL={MS Access};DBQ=D:/Tetrisdb.mdb");
bool ok = ldb.open();
if(ok)
{
QSqlQuery mquery = QSqlQuery::QSqlQuery(ldb);
bool ret = mquery.exec("select * from score order by b");
if(ret)
{
TiXmlDocument *pDoc = new TiXmlDocument;
TiXmlElement *pHeadElem = new TiXmlElement("HSF_XML");
pDoc->LinkEndChild(pHeadElem);
while(mquery.next())
{
QString strName=mquery.value(0).toString();
int nScore=mquery.value(1).toInt();
TiXmlElement *pUserElem = new TiXmlElement("Item");
pUserElem->SetAttribute("User",strName.toStdString().c_str());
pUserElem->SetAttribute("Score",nScore);
pHeadElem->LinkEndChild(pUserElem);
}
TiXmlPrinter xmlPt;
pDoc->Accept(&xmlPt);
strout = xmlPt.CStr();
mquery.clear();
ldb.close();
}
}
else
{
qDebug()<<ldb.lastError().text();
}
}
好了,服务端先这样吧,客户端因为之前用Win32 SDK写的,也只能一边查sdk用法,一边改了(socket简单通讯不想用ACE),获取最高分使用开线程方式,防止界面卡住。
LRESULT CALLBACK CommitScoreDlgProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
{
HWND hConfirmEdit = GetDlgItem(hWnd,IDC_EPWD2);
SendMessage(hConfirmEdit,EM_SETPASSWORDCHAR,0,0);//去除密码属性
SendMessage(hConfirmEdit,EM_SETREADONLY,1,0); ///只读
SetDlgItemText(hWnd,IDC_CONFIRM,(LPCTSTR)(_T("当前战绩")));
SetDlgItemText(hWnd,IDC_EPWD2,(LPCTSTR)(s2ws(string(strScore)).c_str()));
} break;
case WM_COMMAND:
{
int wmId= LOWORD(wParam);
int wmEvent=HIWORD(wParam);
switch (wmId)
{
case IDOK:
{
GetDlgItemText(hWnd,IDC_EUSER,userName,sizeof(userName));
GetDlgItemText(hWnd,IDC_EPWD,password,sizeof(password));
if(_tcslen(userName) <= 0 || _tcslen(password) <= 0)
{
MessageBox(hWnd,_T("用户名或密码不能为空!"),_T("错误"),MB_ICONWARNING);
return FALSE;
}
initSocket();
int ret=commitScore(UnicodeToANSI(userName),UnicodeToANSI(password),score);
switch (ret)
{
case Succeed:
case NotRegister:
case WrongPassword:
}
} break;
case IDCANCEL:
{
EndDialog(hWnd, LOWORD(wParam));
return TRUE;
} break;
}
} break;
}
return FALSE;
}
接收设置超时时间,接收线程如下:
BOOL recvPacketThread(LPVOID *p)//,FILE *fp)
{
SOCKET socket=*(SOCKET*)p;
int iMode = 0;//block
ioctlsocket(socket, FIONBIO, (u_long FAR*) &iMode);
DWORD TimeOut=1000*10; //设置发送超时10秒
if(::setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR)
{
return 0;
}
char buf[2048]={0};
int recvLength = recv(socket,buf,sizeof(buf),MSG_WAITALL);
if(recvLength==SOCKET_ERROR)
{
closesocket(socket);
return -1;
}
HWND hwnd=FindWindow(NULL,TEXT("Tetris高分榜"));
if(listTable.size() > 0)
{
HWND hs=GetDlgItem(hwnd,IDC_LSCORE);
list<scoreTable>::reverse_iterator it = listTable.rbegin();
while(it != listTable.rend())
{
string str = ii+" " + it->name+"("+it->score+")";
SendMessage(hs,LB_ADDSTRING ,0,(LPARAM)(LPCTSTR)(s2ws(str).c_str()));
++it;
}
}
else
{
string strout;
UTF_8ToGB2312(strout, buf, strlen(buf));
MessageBox(hwnd,(LPCTSTR)(s2ws(strout).c_str()),_T("失败"),MB_ICONWARNING);
}
return 0;
}
其中显示utf-8时出现了乱码,不通编码转换很头痛,还好网上都有封装好的函数:
///std::string to wstring
std::wstring s2ws(const std::string& s)
{
int slength = (int)s.length() + 1;
int len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
wchar_t* buf = new wchar_t[len];
MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
std::wstring r(buf);
delete[] buf;
return r.c_str();
}
///UTF-8 to Unicode
void UTF_8ToUnicode(wchar_t* pOut,char *pText)
{
char* uchar = (char *)pOut;
uchar[1] = ((pText[0] & 0x0F) << 4) + ((pText[1] >> 2) & 0x0F);
uchar[0] = ((pText[1] & 0x03) << 6) + (pText[2] & 0x3F);
return;
}
///Unicode to GB2312
void UnicodeToGB2312(char* pOut,wchar_t uData)
{
WideCharToMultiByte(CP_ACP,NULL,&uData,1,pOut,sizeof(wchar_t),NULL,NULL);
return;
}
///utf-8 to GB2312
void UTF_8ToGB2312(string &pOut, char *pText, int pLen)
{
char * newBuf = new char[pLen];
char Ctemp[4];
memset(Ctemp,0,4);
int i =0;
int j = 0;
while(i < pLen)
{
if(pText[i] > 0)
{
newBuf[j++] = pText[i++];
}
else
{
WCHAR Wtemp;
UTF_8ToUnicode(&Wtemp,pText + i);
UnicodeToGB2312(Ctemp,Wtemp);
newBuf[j] = Ctemp[0];
newBuf[j + 1] = Ctemp[1];
i += 3;
j += 2;
}
}
newBuf[j] = '\0';
pOut = newBuf;
delete []newBuf;
return;
}
///wstring to ansi(string)
string UnicodeToANSI( const wstring& str )
{
// wide char to multi char
int iTextLen = WideCharToMultiByte( CP_ACP,
0,str.c_str(),-1,NULL,0,NULL,NULL );
char *pElementText = new char[iTextLen + 1];
memset( ( void* )pElementText, 0, sizeof( char ) * ( iTextLen + 1 ) );
::WideCharToMultiByte( CP_ACP,0,str.c_str(),-1,pElementText,iTextLen,NULL,NULL );
string strText = pElementText;
delete[] pElementText;
return strText;
}
客户端基本完成,但是只能在我本机连接,自己电脑又不是公网IP,别人还是连接不上,那就申请个花生壳(或nat123)账户,需要将他们提供的域名解析成IP地址,网上找了一个函数还可以用哈。
BOOL DomainToIP(char *pDomain,char *pIPBuff)
{
unsigned long lgIP = inet_addr(pDomain);
//输入的IP字符串
if(lgIP != INADDR_NONE)
{
memcpy(pIPBuff,pDomain,strlen(pDomain));
return TRUE;
}
HOSTENT *host_entry;
host_entry=gethostbyname(pDomain);
if(host_entry!=0)
{
sprintf(pIPBuff,"%d.%d.%d.%d",
(host_entry->h_addr_list[0][0]&0xff),
(host_entry->h_addr_list[0][1]&0xff),
(host_entry->h_addr_list[0][2]&0xff),
(host_entry->h_addr_list[0][3]&0xff));
}
else
{
return FALSE;
}
return TRUE;
}
游戏代码太lan就不提了。
应该算基本完成,虽然游戏不是很好玩,基本实现所想,应该学一些实用的东西,应该把c++再好好看看。baidu网盘下载地址:
http://pan。baidu.com/s/1gdeOjDp