分类: C/C++
2008-04-23 22:05:16
介绍一种数据库远程备份方案
作者:
功能简述
服务端(发送方)和客户端(接收方)定好数据库结构及内容格式后,由服务端将数据库生成指定XML格式文件后, 通过Socket发给客户端。客户端接收成功后,用收到的XML库格式文件,生成数据库。
用例图:

设计
根据以上需求,采用VC6.0生成两个工程文件:DBClent(客户端及接收方)和DBServer(服务端及发送方)。其中DBClient负责接收XML库格式文件并生成数据库,DBServer负责生成指定格式的XML库文件与发送此文件给DBClient。
总体类图:

实现
1、数据库转换为XML格式设计 规定:数据库用Database,在其节点属性中用name来指定数据库名称。其子节点可能有多个表,表节点名为:Table,其节点属性中name用来指定表名称。表节点下包括表结构描述Struct和内容描述Content。表结构中列名用节点Field表示,名称/类型/长度等在其属性中表示。表内容中用节点Record表示每个记录,详细信息在属性中表示。
示例: DBTest.xml
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 0 );
PreReturnCleanup: // 结束及清理
// 释放内存及关闭打开句柄
delete[] recdData;
if ( bFileIsOpen )
destFile.Close();
sockClient.Close();
return bRet;
}
// 解析XML库并生成数据库
bool CBulidDB::Parse_XML_Document()
{
if( !m_bDone )
{
if( Is_Tag( "" ) )
{
// 获取生成数据库名称
if( Is_Having_Attribute( "name" ) )
m_strDBName = Get_Attribute_Value();
}
if( Is_Tag( "" ) )
{
// 获取库内表名
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( "" ) && Is_Child_of( "" ) )
{
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( "" ) && Is_Child_of( "" ) )
{
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库文件的功能。服务端和客户端没有实现多线程发送/接收等。
阅读(473) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~