功能簡述
服務端(發送方)和客戶端(接收方)定好數據庫結構及內容格式後,由服務端將數據庫生成指定XML格式文件後, 通過Socket發給客戶端。客戶端接收成功後,用收到的XML庫格式文件,生成數據庫。
用例圖:
設計
根據以上需求,采用VC6.0生成兩個工程文件:DBClent(客戶端及接收方)和DBServer(服務端及發送方)。其中DBClient負責接收XML庫格式文件並生成數據庫,DBServer負責生成指定格式的XML庫文件與發送此文件給DBClient。
總體類圖:
實現
1、數據庫轉換為XML格式設計規定:數據庫用Database,在其節點屬性中用name來指定數據庫名稱。其子節點可能有多個表,表節點名為:Table,其節點屬性中name用來指定表名稱。表節點下包括表結構描述Struct和內容描述Content。表結構中列名用節點Field表示,名稱/類型/長度等在其屬性中表示。表內容中用節點Record表示每個記錄,詳細信息在屬性中表示。
示例: DBTest.xml
<?xml version="1.0" encoding="GB2312"?>
<Database xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" name="dbTest.mdb">
<Table name="dbTest">
<Struct>
<Field fieldName="Name" fieldType="VARCHAR" fieldLength="20"/>
<Field fieldName="Age" fieldType="VARCHAR" fieldLength="3"/>
</Struct>
<Content>
<Record name="徐景周" age="29" />
<Record name="趙振華" age="25" />
<Record name="田子葉" age="29" />
<Record name="張軍旗" age="29" />
<Record name="黃輝" age="29" />
<Record name="趙顔鋒" age="29" />
<Record name="魏小明" age="29" />
<Record name="趙銳" age="29" />
</Content>
</Table>
</Database>
2、服務端(發送方)實現 采用Socket套接字在指定端口發送生成的XML庫格式文件。生成程序界面效果如下:
示例代碼如下:
// 發送指定的XML文件 #define PRE_AGREED_PORT 8686 // 端口號 #define SEND_BUFFER_SIZE 4096 // 緩沖區大小 BOOL CDBServerDlg::SendFileToRemoteRecipient(CString fName) { AfxSocketInit( NULL ); CSocket sockSrvr; sockSrvr.Create( PRE_AGREED_PORT ); // 指定端口創建socket sockSrvr.Listen(); // 偵聽端口上的客戶端 CSocket sockConnection; sockSrvr.Accept( sockConnection ); // 用另一個socket接收連接 // 是否成功 BOOL bRet = TRUE; int fileLength, cbLeftToSend; // 文件長度及發送進度 BYTE* sendData = NULL; // 發送數據緩沖指針 CFile sourceFile; CFileException fe; BOOL bFileIsOpen = FALSE; if( !( bFileIsOpen = sourceFile.Open( fName, CFile::modeRead | CFile::typeBinary, &fe ) ) ) { TCHAR strCause[256]; fe.GetErrorMessage( strCause, 255 ); TRACE( "SendFileToRemoteRecipient encountered an error while opening the local file\n" "\tFile name = %s\n\tCause = %s\n\tm_cause = %d\n\tm_IOsError = %d\n", fe.m_strFileName, strCause, fe.m_cause, fe.m_lOsError ); bRet = FALSE; goto PreReturnCleanup; } // 首先,發送文件長度 fileLength = sourceFile.GetLength(); fileLength = htonl( fileLength ); cbLeftToSend = sizeof( fileLength ); do { int cbBytesSent; BYTE* bp = (BYTE*)(&fileLength) + sizeof(fileLength) - cbLeftToSend; cbBytesSent = sockConnection.Send( bp, cbLeftToSend ); // 是否出錯 if( cbBytesSent == SOCKET_ERROR ) { int iErr = ::GetLastError(); TRACE( "SendFileToRemoteRecipient returned a socket error while sending file length\n" "\tNumber of Bytes sent = %d\n" "\tGetLastError = %d\n", cbBytesSent, iErr ); bRet = FALSE; goto PreReturnCleanup; } // 發送成功後, 剩余發送總數= 總長度-已發送長度 cbLeftToSend -= cbBytesSent; } while( cbLeftToSend > 0 ); // 然後,發送文件數據 sendData = new BYTE[SEND_BUFFER_SIZE]; cbLeftToSend = sourceFile.GetLength(); do { // 從文件中讀取指定緩沖字節 int sendThisTime, doneSoFar, buffOffset; sendThisTime = sourceFile.Read( sendData, SEND_BUFFER_SIZE ); buffOffset = 0; do { doneSoFar = sockConnection.Send( sendData + buffOffset, sendThisTime ); // 是否出錯 if( doneSoFar == SOCKET_ERROR ) { int iErr = ::GetLastError(); TRACE( "SendFileToRemoteRecipient returned a socket error while sending chunked file data\n" "\tNumber of Bytes sent = %d\n" "\tGetLastError = %d\n", doneSoFar, iErr ); bRet = FALSE; goto PreReturnCleanup; } // 發送成功後, 剩余發送字節及偏移 buffOffset += doneSoFar; sendThisTime -= doneSoFar; cbLeftToSend -= doneSoFar; } while ( sendThisTime > 0 ); } while( cbLeftToSend > 0 ); PreReturnCleanup: // 結束及清理 // 釋放內存及關閉打開句柄 delete[] sendData; if( bFileIsOpen ) sourceFile.Close(); sockConnection.Close(); return bRet; }
3、客戶端(接收方)實現 接收XML庫格式文件,並生成數據庫。程序界面效果如下:
示例代碼如下:
// 接收指定的XML文件 #define PRE_AGREED_PORT 8686 // 指定端口號 #define RECV_BUFFER_SIZE 4096 // 緩沖區大小 BOOL CDBClientDlg::GetFileFromRemoteSender(CString strIP, CString fName) { // 創建客戶端socket AfxSocketInit( NULL ); // 初始化socket CSocket sockClient; sockClient.Create(); // 連接到指定IP和端口號 sockClient.Connect( strIP, PRE_AGREED_PORT ); // PRE_AGREED_PORT 端口號被指定為 8686 // 傳送是否成功 BOOL bRet = TRUE; int dataLength, cbBytesRet, cbLeftToReceive; // 接收數據長度及進度 BYTE* recdData = NULL; // 接收數據緩沖 CFile destFile; CFileException fe; BOOL bFileIsOpen = FALSE; // 打開或創建指定文件,用來接收數據 if( !( bFileIsOpen = destFile.Open( fName, CFile::modeCreate | CFile::modeWrite | CFile::typeBinary, &fe ) ) ) { TCHAR strCause[256]; fe.GetErrorMessage( strCause, 255 ); TRACE( "GetFileFromRemoteSender encountered an error while opening the local file\n" "\tFile name = %s\n\tCause = %s\n\tm_cause = %d\n\tm_IOsError = %d\n", fe.m_strFileName, strCause, fe.m_cause, fe.m_lOsError ); bRet = FALSE; goto PreReturnCleanup; } // 首先,獲取文件長度 cbLeftToReceive = sizeof( dataLength ); do { BYTE* bp = (BYTE*)(&dataLength) + sizeof(dataLength) - cbLeftToReceive; cbBytesRet = sockClient.Receive( bp, cbLeftToReceive ); // 是否出錯 if( cbBytesRet == SOCKET_ERROR || cbBytesRet == 0 ) { int iErr = ::GetLastError(); TRACE( "GetFileFromRemoteSite returned a socket error while getting file length\n" "\tNumber of Bytes received (zero means connection was closed) = %d\n" "\tGetLastError = %d\n", cbBytesRet, iErr ); bRet = FALSE; goto PreReturnCleanup; } // 接收成功,剩余長度 cbLeftToReceive -= cbBytesRet; } while( cbLeftToReceive > 0 ); dataLength = ntohl( dataLength ); // 然後,每次接收指定緩沖的數據 recdData = new byte[RECV_BUFFER_SIZE]; cbLeftToReceive = dataLength; do { int iiGet, iiRecd; iiGet = (cbLeftToReceive<RECV_BUFFER_SIZE) ? cbLeftToReceive : RECV_BUFFER_SIZE ; iiRecd = sockClient.Receive( recdData, iiGet ); // 是否錯誤 if ( iiRecd == SOCKET_ERROR || iiRecd == 0 ) { int iErr = ::GetLastError(); TRACE( "GetFileFromRemoteSite returned a socket error while getting chunked file data\n" "\tNumber of Bytes received (zero means connection was closed) = %d\n" "\tGetLastError = %d\n", iiRecd, iErr ); bRet = FALSE; goto PreReturnCleanup; } // 接收成功, 剩余字節數 destFile.Write( recdData, iiRecd ); // 寫入文件 cbLeftToReceive -= iiRecd; } while( cbLeftToReceive > 0 ); PreReturnCleanup: // 結束及清理 // 釋放內存及關閉打開句柄 delete[] recdData; if ( bFileIsOpen ) destFile.Close(); sockClient.Close(); return bRet; } // 解析XML庫並生成數據庫 bool CBulidDB::Parse_XML_Document() { if( !m_bDone ) { if( Is_Tag( "<Database>" ) ) { // 獲取生成數據庫名稱 if( Is_Having_Attribute( "name" ) ) m_strDBName = Get_Attribute_Value(); } if( Is_Tag( "<Table>" ) ) { // 獲取庫內表名 if( Is_Having_Attribute( "name" ) ) m_strTableName = Get_Attribute_Value(); } // 庫名或表名為空退出 if( "" == m_strDBName || "" == m_strTableName ) return false; // 獲取主程序所在路徑,存在sPath中 CString sPath; GetModuleFileName( NULL, sPath.GetBufferSetLength( MAX_PATH + 1 ), MAX_PATH ); sPath.ReleaseBuffer(); int nPos; nPos = sPath.ReverseFind( ''\\'' ); sPath = sPath.Left( nPos ); CString lpszFile = sPath + "\\" + m_strDBName; CFileFind fFind; BOOL bSuccess; bSuccess = fFind.FindFile( lpszFile ); fFind.Close (); CDaoDatabase db; // 數據庫 CDaoRecordset RecSet( &db ); // 記錄集 // 是否已有創建好的庫文件,沒有則創建它 if( !bSuccess ) { // 創建Mdb庫 db.Create( lpszFile ); // 移動節點到指定位置 Go_to_Parent("Table"); Go_to_Child("Struct"); Go_to_Child( "Field" ); // 獲取庫結構 CString sqlCmd = "CREATE TABLE " + m_strTableName + "("; while( Is_Tag( "<Field>" ) && Is_Child_of( "<Database><Table><Struct>" ) ) { CString strField = ""; // 列名 if( Is_Having_Attribute( "fieldName" ) ) strField = Get_Attribute_Value(); sqlCmd += strField + " "; // 列類型 if( Is_Having_Attribute( "fieldType" ) ) strField = Get_Attribute_Value(); sqlCmd += strField + "("; // 列長度 if( Is_Having_Attribute( "fieldLength" ) ) strField = Get_Attribute_Value(); sqlCmd += strField + "),"; // 同一級下一節點 if( !Go_Forward() ) break; } // 刪除尾部多余逗號 if( -1 != sqlCmd.ReverseFind( '','' ) ) sqlCmd.Delete( sqlCmd.GetLength() - 1, 1 ); sqlCmd += ");"; // 創建庫結構 db.Execute( sqlCmd ); CString strQuery = "SELECT * FROM " + m_strTableName; // 打開已創建的數據表 RecSet.Open( AFX_DAO_USE_DEFAULT_TYPE, strQuery, 0 ); // 移動節點到指定位置 Go_to_Parent("Table"); Go_to_Child("Content"); Go_to_Child( "Record" ); // 創建庫內容記錄 while( Is_Tag( "<Record>" ) && Is_Child_of( "<Database><Table><Content>" ) ) { CString sqlRecordCmd = "INSERT INTO " + m_strTableName + "(Name,Age) VALUES("; CString strRecord = ""; // 名字 if( Is_Having_Attribute( "name" ) ) strRecord = Get_Attribute_Value(); sqlRecordCmd += "''" + strRecord + "'', "; // 年齡 if( Is_Having_Attribute( "age" ) ) strRecord = Get_Attribute_Value(); sqlRecordCmd += strRecord + ")"; // 插入記錄 db.Execute( sqlRecordCmd ); // 同一級下一節點 if( !Go_Forward() ) break; } // 關閉記錄集及庫 RecSet.Close(); db.Close(); // 完成 m_bDone = true; AfxMessageBox( lpszFile + "Access庫成功創建!" ); return true; } else { AfxMessageBox( lpszFile + "Access庫已經存在!" ); } } return false;
}
小結
數據庫遠程備份基本功能已實現,還有很多待完善的地方,如服務端沒有實現直接讀取數據庫生成指定XML庫文件的功能。服務端和客戶端沒有實現多線程發送/接收等。
本文配套源碼