IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [原]Oracle调用接口(OCI)源码剖析(1):创建数据库连接

    zhouzxi发表于 2016-09-08 21:35:39
    love 0

    概述
    在笔者所开发过的产品中,有很多都需要与Oracle数据库打交道。为了实现C代码与Oracle数据库的消息交互,Oracle公司为广大的开发者们提供了一个统一的调用接口OCI(Oracle Call Interface)。只要按照规范来调用OCI中的函数,就能够实现C代码与Oracle数据库的交互。

    具体而言,OCI的C语言API包括了两个文件:db_ora_oci_ux.h和db_ora_oci_ux.c。db_ora_oci_ux.h是头文件,而所有与数据库的交互操作的实现都是在db_ora_oci_ux.c中完成的。

    本文对OCI的创建数据库连接操作的源码进行简单的剖析。

    OCI中建立数据库连接的源码剖析
    在OCI中,建立数据库连接的操作是由CDbCreateDb函数实现的,其代码如下:

    void *CDbCreateDb(INT8 *pDbType, INT8*pServer, INT8 *pDbName, INT8 *pUser, INT8 *pPwd)
    {
       CDbRecordset *pcolbuf = NULL;
       OCIHDBC       hdbc    = NULL;
       CDb          *hDb     = NULL;
    
       if (NULL == pServer)
       {
           WriteLog("CDbCreateDb: CDbCreateDb[0] failed", NULL, NULL);
           return NULL;
       }
    
       /* 申请句柄指针空间 */
       hDb = (CDb *)OsGetUB(sizeof(CDb));
       if (NULL == hDb)
       {
           WriteLog("CDbCreateDb: CDbCreateDb[1] failed", NULL, NULL);
           return NULL;
       }
       hDb->hdbc = NULL;
       hDb->hRec = NULL;
    
       pthread_mutex_lock(&s_dbmutex);
       /* 初始化数据库连接 */
       hdbc = DoDbInit((text *)pServer, (text *)pUser, (text *)pPwd);
       if (NULL == hdbc)
       {
           pthread_mutex_unlock(&s_dbmutex);
           OsRetUB((UINT8*)hDb);
           return NULL;
       }
    
       /* 创建结果集 */
       pcolbuf = DoRecInit();
       if (NULL == pcolbuf)
       {
           pthread_mutex_unlock(&s_dbmutex);
           OsRetUB((UINT8*)hDb);
           DoDbFree(hdbc);
           return NULL;
       }
       hDb->hdbc = hdbc;
       hDb->hRec = pcolbuf;
       hDb->iDbType = CDB_TYPE_ORACLE;
    
       pthread_mutex_unlock(&s_dbmutex);
    
       returnhDb;
    }

    从该函数的代码实现中,我们可以看到:
    1)建立数据库连接包括这几步操作:第一步,申请句柄指针空间;第二步,初始化数据库连接;第三步,创建结果集。

    2)申请句柄指针空间操作是由OsGetUB函数实现的,初始化数据库连接操作是由DoDbInit函数实现的,创建结果集操作是由DoRecInit函数实现的。

    3)为了防止在多个流程中同时调用该函数,在初始化数据库连接之前采用了加锁操作,这保证了每一个创建数据库的操作所返回的句柄是唯一的。

    4)如果初始化数据库连接操作函数DoDbInit执行失败了,程序就会执行OsRetUB函数来释放句柄指针空间(该操作与之前的申请句柄指针空间操作对应起来)。

    5)如果创建结果集操作函数DoRecInit执行失败了,程序除了执行OsRetUB函数来释放句柄指针空间之外,还会执行DoDbFree函数来释放数据库连接(该操作与之前的初始化数据库连接操作对应起来)。

    初始化数据库连接操作函数DoDbInit的代码如下:

    static void *DoDbInit(text *dblink, text*uid, text *pwd)
    {
       OCIHDBC hdbc = NULL;
       sword   rc   = (sword)0;
       char errBuf[200];
       sb4  errcode;
    
       /* 申请所有句柄指针保存空间 */
       hdbc = (OCIHDBC)OsGetUB(sizeof(t_envctx));
       if (NULL == hdbc)
       {
           WriteLog("DoDbInit: OsGetUB failed", NULL, NULL);
           return NULL;
       }
    
       /* 创建OCI环境 */
       if (OCIInitialize((ub4)OCI_THREADED|OCI_OBJECT, (dvoid *)0
                    , (dvoid * (*)(dvoid *,size_t))0
                    , (dvoid * (*)(dvoid *, dvoid*, size_t))0
                    , (void (*)(dvoid *, dvoid*))0))
       {
           WriteLog("DoDbInit: OCIInitialize fail", NULL, NULL);
           return NULL;
       }
    
       if(OCIEnvInit((OCIEnv **)&hdbc->envhp, (ub4)OCI_DEFAULT
                , (size_t)0, (dvoid **)0))
       {
           WriteLog("DoDbInit: OCIEnvInit fail", NULL, NULL);
           return NULL;
       }
    
       /*申请错误句柄 */
       if(OCIHandleAlloc((dvoid *)hdbc->envhp, (dvoid **)&hdbc->errhp
                     , (ub4)OCI_HTYPE_ERROR,(size_t)0, (dvoid **)0))
       {
           WriteLog("DoDbInit: OCIHandleAlloc allocate errhp fail", NULL,NULL);
           return NULL;
       }
       /* 申请服务器句柄 */
       if(OCIHandleAlloc((dvoid *)hdbc->envhp, (dvoid **)&hdbc->srvhp
                     , (ub4)OCI_HTYPE_SERVER,(size_t)0, (dvoid **)0))
       {
           OCIErrorGet((dvoid *)hdbc->errhp, 1, NULL, &errcode,(text*)errBuf, (ub4)sizeof(errBuf), (ub4)OCI_HTYPE_ERROR);
           OCIHandleFree((dvoid *)hdbc->errhp, (ub4) OCI_HTYPE_ERROR);
           WriteLog("DoDbInit: OCIHandleAlloc allocate srvhp fail", NULL,NULL);
           WriteLog(errBuf,  NULL, NULL);
           return NULL;
       }
       /* 申请服务环境句柄 */
       if(OCIHandleAlloc((dvoid *)hdbc->envhp, (dvoid **)&hdbc->svchp
                     , (ub4)OCI_HTYPE_SVCCTX,(size_t)0, (dvoid * *)0))
       {
           OCIErrorGet((dvoid *)hdbc->errhp, 1, NULL, &errcode,(text*)errBuf, (ub4)sizeof(errBuf), (ub4)OCI_HTYPE_ERROR);
           OCIHandleFree((dvoid *)hdbc->srvhp, (ub4) OCI_HTYPE_SERVER);
           OCIHandleFree((dvoid *)hdbc->errhp, (ub4) OCI_HTYPE_ERROR);
           WriteLog("DoDbInit: OCIHandleAlloc allocate svchp fail", NULL,NULL);
           WriteLog(errBuf,  NULL, NULL);
           return NULL;
       }
       /* 连接数据库 */
       if (OCIServerAttach(hdbc->srvhp, hdbc->errhp, dblink
                          , (sb4)strlen((char*)dblink), (ub4)OCI_DEFAULT))
       {
           /* 释放环境句柄,系统自动释放在其下所分配的所有其它句柄 */
           OCIErrorGet((dvoid *)hdbc->errhp, 1, NULL, &errcode,(text*)errBuf, (ub4)sizeof(errBuf), (ub4)OCI_HTYPE_ERROR);
           OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
           OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
           OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
           OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
           OsRetUB((UINT8*)hdbc);
           WriteLog("DoDbInit: OCIServerAttach fail", NULL, NULL);
           WriteLog(errBuf,  NULL, NULL);
           return NULL;
       }
       /* 设置服务环境的服务器属性 */
       OCIAttrSet((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX
                , (dvoid *)hdbc->srvhp, (ub4)0
                , (ub4)OCI_ATTR_SERVER, hdbc->errhp);
    
       /* 申请用户会话句柄 */
       OCIHandleAlloc((dvoid *)hdbc->envhp, (dvoid **)&hdbc->authp
                     , (ub4)OCI_HTYPE_SESSION,(size_t)0, (dvoid **)0);
       /* 设置会话所使用的用户帐户和密码 */
       if (OCIAttrSet((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION
                     , (dvoid *)uid,(ub4)strlen((char *)uid)
                     , (ub4)OCI_ATTR_USERNAME,hdbc->errhp))
       {
           OCIServerDetach(hdbc->srvhp, hdbc->errhp, (ub4)OCI_DEFAULT);
           OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
           OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
           OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
           OCIHandleFree((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION);
           OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
           OsRetUB((UINT8*)hdbc);
           WriteLog("DoDbInit: OCIAttrSet[OCI_ATTR_USERNAME] fail", NULL,NULL);
           return NULL;
       }
       if (OCIAttrSet((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION
                     , (dvoid *)pwd,(ub4)strlen((char *)pwd)
                     , (ub4)OCI_ATTR_PASSWORD,hdbc->errhp))
       {
           OCIServerDetach(hdbc->srvhp, hdbc->errhp, (ub4)OCI_DEFAULT);
           OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
           OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
           OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
           OCIHandleFree((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION);
           OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
           OsRetUB((UINT8*)hdbc);
           WriteLog("DoDbInit: OCIAttrSet[OCI_ATTR_PASSWORD] fail", NULL,NULL);
           return NULL;
       }
       /* 建立数据库操作会话 */
       if ( (rc = OCISessionBegin(hdbc->svchp, hdbc->errhp,hdbc->authp
                          , (ub4)OCI_CRED_RDBMS,(ub4)OCI_DEFAULT)))
       {
           DoDbErrProc(hdbc->errhp, rc, "OCISessionBegin");
           OCIServerDetach(hdbc->srvhp, hdbc->errhp, (ub4)OCI_DEFAULT);
           OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
           OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
           OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
           OCIHandleFree((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION);
           OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
           OsRetUB((UINT8*)hdbc);
           WriteLog("DoDbInit: OCISessionBegin fail", NULL, NULL);
           return NULL;
       }
    
       /* 设置会话服务环境 */
       if (OCIAttrSet((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX
                     , (dvoid *)hdbc->authp,(ub4)0
                     , (ub4)OCI_ATTR_SESSION,hdbc->errhp))
       {
           OCISessionEnd(hdbc->svchp, hdbc->errhp, hdbc->authp, (ub4)0);
           OCIServerDetach(hdbc->srvhp, hdbc->errhp, (ub4)OCI_DEFAULT);
           OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
           OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
           OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
           OCIHandleFree((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION);
           OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
           OsRetUB((UINT8*)hdbc);
           WriteLog("DoDbInit:OCIAttrSet[OCI_ATTR_SESSION] fail", NULL, NULL);
           return NULL;
       }
       hdbc->stmthp = NULL;
       return hdbc;
    }

    下面对DoDbInit函数进行分析:
    1)该函数的执行流程是这样的:第一步,申请所有句柄指针保存空间;第二步,创建OCI环境;第三步,申请错误句柄;第四步,申请服务器句柄;第五步,申请服务环境句柄;第六步,连接数据库;第七步,设置服务环境的服务器属性;第八步,申请用户会话句柄,第九步,设置会话所使用的用户帐户和密码;第十步,建立数据库操作会话;第十一步,设置会话服务环境。

    2)实现以上十一步操作的函数均是OCI底层提供的(都以OCI打头)。不管哪一步操作执行失败,都会输出相关的日志,可供排查问题。

    3)所有OCI主要句柄数据结构OCIHDBC的实现如下:

    /* 所有OCI主要句柄数据结构 */
    typedef struct
    {
       OCIEnv        *envhp;              /* 环境句柄 */
       OCIError      *errhp;              /* 错误句柄 */
       OCIServer     *srvhp;              /* 服务器句柄 */
       OCISvcCtx     *svchp;              /* 服务环境句柄 */
       OCISession    *authp;              /* 会话句柄 */
       OCIStmt       *stmthp;             /* 语句句柄 */
    }t_envctx;
    typedef t_envctx  *OCIHDBC;           /* 方便使用定义OCIHDBC数据类型 */

    以上不同的操作是对OCIHDBC结构体中对应的句柄赋值。

    创建结果集操作函数DoRecInit的代码如下:

    static CDbRecordset *DoRecInit()
    {
       CDbRecordset *hRecordset;
    
       hRecordset = (CDbRecordset *)OsGetUB(sizeof(CDbRecordset));
       if (NULL == hRecordset)
       {
           WriteLog("DbInitRecordset: DbInitRecordset[0] fail", NULL,NULL);
           return NULL;
       }
    
       memset((void *)hRecordset, 0, sizeof(CDbRecordset));
       return hRecordset;
    }

    下面对DoRecInit函数进行分析:
    1)该函数的作用是初始化结果集,首先,该函数执行OsGetUB函数申请句柄指针空间,然后执行memset函数初始化结果集。

    2)结果集结构体CDbRecordset的代码如下:

    typedef struct CDbRecordsetTag
    {
       void           *cmd;                             /* 命令缓冲区 */
       int            sqltype;                          /* 1:select 2:other*/
       int            colCount;                         /* 返回列数 */
       char          colfieldname[CDB_MAX_COL_NUM][40];/* 每列列名 */
       int           colfieldlength[CDB_MAX_COL_NUM];  /* 列名宽度 */
       int           pColWidth[CDB_MAX_COL_NUM];      /* 每列宽度 */
       int           pColType[CDB_MAX_COL_NUM];       /* 列类型 */
       char          pRecordBuf[CDB_MAX_COL_NUM][CDB_MAX_COL_WIDTH];/* 列数据 */
       int           pRetColWidth[CDB_MAX_COL_NUM];
       short         pRetIndicator[CDB_MAX_COL_NUM];
    } CDbRecordset;

    如果后续操作要从数据库中获取数据,那么这些数据就用CDbRecordset结构体来存储。

    释放数据库连接操作函数DoDbFree的代码如下:

    static void DoDbFree(OCIHDBC hdbc)
    {
       if (NULL == hdbc)
       {
           return;
       }
       OCISessionEnd(hdbc->svchp, hdbc->errhp, hdbc->authp, (ub4)0);
       OCIServerDetach(hdbc->srvhp, hdbc->errhp, (ub4)OCI_DEFAULT);
    
       OCIHandleFree((dvoid *)hdbc->srvhp, (ub4)OCI_HTYPE_SERVER);
       OCIHandleFree((dvoid *)hdbc->svchp, (ub4)OCI_HTYPE_SVCCTX);
       OCIHandleFree((dvoid *)hdbc->errhp, (ub4)OCI_HTYPE_ERROR);
       OCIHandleFree((dvoid *)hdbc->authp, (ub4)OCI_HTYPE_SESSION);   
       OCIHandleFree((dvoid *)hdbc->envhp, (ub4)OCI_HTYPE_ENV);
    
       OsRetUB((UINT8*)hdbc);
       hdbc = NULL;
    }

    从代码可以看出,该函数的功能是依次释放在DoDbInit函数中所申请的句柄指针。所有的以OCI开头的函数都是OCI底层提供的。

    创建数据库连接函数CDbCreateDb的调用
    在我们编写C代码创建数据库连接的时候,只需要将db_ora_oci_ux.h文件头包括进来,同时直接调用CDbCreateDb函数就可以了。
    示例代码如下:

    INT32 main(void)
    {
       INT8 szDBServerName[50] = {0};
       INT8 szDBName[50]       = {0};
       INT8 szDBUser[50]       = {0};
       INT8 szDBPwd[50]        = {0};
    
       void *pDBHandle         = NULL;
    
       // 获取数据库各参数的值
       memcpy(szDBServerName, "db10_10_10_10",strlen("db10_10_10_10"));
       memcpy(szDBName,       "dbp_166",      strlen("dbp_166"));
       memcpy(szDBUser,        "dbp_166",      strlen("dbp_166"));
       memcpy(szDBPwd,       "dbp_166",     strlen("dbp_166"));
    
       // 连接数据库
       pDBHandle = CDbCreateDb("Oracle", szDBServerName, szDBName,szDBUser, szDBPwd);
    
       if (pDBHandle == NULL)    // 连接失败
       {
           printf("ConnectDB failed! ServiceName:%s, DBName:%s, User:%s,Pwd:%s", szDBServerName, szDBName, szDBUser, szDBPwd);
    
           return -1;
       }
    
       printf("ConnectDB success! ServiceName:%s, DBName:%s, User:%s,Pwd:%s", szDBServerName, szDBName, szDBUser, szDBPwd);
    
       return 0;
    }

    说明:
    1)CDbCreateDb函数的五个输入参数分别是:数据库类型、数据库服务名、数据库名、用户名和密码。除了数据库类型之外,其他几个参数都和具体的数据库有关,需要在安装Oracle数据库的时候进行设置。

    2)只有在数据库句柄分配成功(也就是数据库连接建立成功)的情况下,程序才能执行后续操作;如果数据库句柄分配失败,要及时找到失败原因。



沪ICP备19023445号-2号
友情链接