程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 介紹一種數據庫遠程備份方案

介紹一種數據庫遠程備份方案

編輯:關於VC++

功能簡述

服務端(發送方)和客戶端(接收方)定好數據庫結構及內容格式後,由服務端將數據庫生成指定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庫文件的功能。服務端和客戶端沒有實現多線程發送/接收等。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
欄目導航
Copyright © 程式師世界 All Rights Reserved