c++通过unxiodbc,freetds连接sqlserver数据库

在centos环境上使用c++程序访问sql server数据库

说明

在类linux系统上访问远端windows的sqlserver服务器,代码架构如下

graph LR
app(客户端代码)-->unixodbc(unixodbc)
unixodbc-->driver(freetds)
driver-->db[sqlserver数据库]

环境搭建

安装unixodbc

使用yum安装的,默认版本是2.3.1

1
yum install unixodbc

自己下载源码包安装,地址

1
2
# 生成makefile
./configure --prefix=/usr/local/unixODBC-2.3.7 --includedir=/usr/include --libdir=/usr/lib64 --bindir=/usr/bin --sysconfdir=/etc

  • prefix指定安装路径
  • includedir指定头文件地址
  • libdir指定库地址
  • bindir指定工具安装地址
  • sysconfidir指定配置文件,主要有两个配置文件(odbc.ini,odbcinst.ini)

安装freetds

freetds是属于epel,需要先安装epel的软件源

1
yum install epel-release -y

或者手动安装

1
rpm -ivh http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

接着安装freetds

1
yum install -y freetds

环境检查

通过命令odbcinst -j来查看odbc安装情况

1
2
3
4
5
6
7
8
unixODBC 2.3.7
DRIVERS............: /etc/odbcinst.ini
SYSTEM DATA SOURCES: /etc/odbc.ini
FILE DATA SOURCES..: /etc/ODBCDataSources
USER DATA SOURCES..: /root/.odbc.ini
SQLULEN Size.......: 8
SQLLEN Size........: 8
SQLSETPOSIROW Size.: 8

通过命令tsql -C来查看freetds的安装情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Compile-time settings (established with the "configure" script)
Version: freetds v1.1.11
freetds.conf directory: /etc
MS db-lib source compatibility: yes
Sybase binary compatibility: yes
Thread safety: yes
iconv library: yes
TDS version: auto
iODBC: no
unixodbc: yes
SSPI "trusted" logins: no
Kerberos: yes
OpenSSL: no
GnuTLS: yes
MARS: yes

配置

配置freetds

freetds缺省的安装路径是/etc/freetds.conf,增加如下配置,这里增加了我本机的sqlserver地址

1
2
3
[mymssql]
host = 127.0.0.1
port = 1433

这里其他网站说需要带上指定的tds version其实现在版本都是auto,没有必要自己指定,如果需要某些特殊指定可以,具体版本是跟sqlserver的版本有关系的,这里给出对应的映射图

1
2
3
4
5
6
4.2 Sybase SQL Server < 10 and Microsoft SQL Server 6.5
5.0 Sybase SQL Server >= 10
7.0 Microsoft SQL Server 7.0
7.1 Microsoft SQL Server 2000
7.2 Microsoft SQL Server 2005
7.3 Microsoft SQL Server 2008

测试freetds配置

这里就可以通过命令来测试是否ok

1
tsql -S mymssql -U roger -P roger -D roger

正常返回

1
2
3
4
locale is "zh_CN.UTF-8"
locale charset is "UTF-8"
using default charset "utf8"
Setting roger as default database in login packet

进阶freetds配置

官网

配置odbc.ini

修改系统的配置路径/etc/odbc.ini

1
2
3
4
5
6
7
[DB4] #freetds
Driver=/usr/lib64/libtdsodbc.so.0 # 驱动路径
ServerName=mymssql # 这里是刚才freetds.conf配置的别名
UID=roger
PWD=roger
Database=roger
TDS_Version=7.4 # 这里可以省略

测试odbc配置

这里就可以通过命令来测试是否ok

1
2
3
osql -S DB4 -U roger -P roger  
或者
isql DB4 jrzt_hg roger

正常返回如下

1
2
3
4
5
6
7
8
9
+---------------------------------------+
| Connected! |
| |
| sql-statement |
| help [tablename] |
| quit |
| |
+---------------------------------------+
SQL>

进阶odbc配置

官网配置

到这里位置,系统环境搭建完成

代码开发

talk is cheap, show me the code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*
* odbc.cpp
*
* Author: roger
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>

#include "sql.h"
#include "sqlext.h"
#include "sqltypes.h"

SQLHENV m_serverhenv;
SQLHDBC m_serverhdbc;
SQLHSTMT m_serverhstmt;
SQLINTEGER m_num;
SQLINTEGER m_nCode;
int m_nId;

int GetErrInfo(SQLHANDLE stmt)
{
SQLRETURN ret;

int i=1;
SQLCHAR SqlState[6]={0};
SQLINTEGER NativeError=0;
SQLCHAR errorString[256]={0};
SQLSMALLINT MsgLen=0;
char errStr[128]={0};

while(1)
{
if((ret = SQLGetDiagRec(SQL_HANDLE_DBC,m_serverhdbc,i,SqlState,&NativeError,errorString,256,&MsgLen)) != SQL_NO_DATA)
{
sprintf(errStr,"%s, %s\n",SqlState,errorString);
printf(errStr);
}
else if((ret = SQLGetDiagRec(SQL_HANDLE_STMT,stmt,i,SqlState,&NativeError,errorString,256,&MsgLen)) != SQL_NO_DATA)
{
sprintf(errStr,"%s, %s\n",SqlState,errorString);
printf(errStr);
}
else
break;
i++;
}
return 0;
}

void InitHandle()
{
SQLRETURN ret;

ret = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&m_serverhenv);

ret = SQLSetEnvAttr(m_serverhenv,SQL_ATTR_ODBC_VERSION,(void*)SQL_OV_ODBC3,0);
if(!SQL_SUCCEEDED(ret))
{
printf("AllocEnvHandle error!\n");
return;
}

ret = SQLAllocHandle(SQL_HANDLE_DBC,m_serverhenv,&m_serverhdbc);
if(!SQL_SUCCEEDED(ret))
{
printf("AllocDbcHandle error!\n");
return;
}

ret = SQLConnect(m_serverhdbc,(SQLCHAR*)"DB4",SQL_NTS,(SQLCHAR*)"roger",SQL_NTS,(SQLCHAR*)"roger",SQL_NTS);

if(!SQL_SUCCEEDED(ret))
{
GetErrInfo(m_serverhstmt);
return;
}

ret = SQLAllocHandle(SQL_HANDLE_STMT,m_serverhdbc,&m_serverhstmt);
}

void ExecSQL_BinPara(char *str)
{
SQLRETURN ret;

SQLCHAR name[32]={0};
SQLCHAR id[16]={0};
SQLLEN pointer_name=sizeof(name);
SQLLEN pointer_id=sizeof(id);

ret=SQLPrepare(m_serverhstmt,(SQLCHAR*)"update ANALYTIC set F4206=?, F4207=?, F4208='测试' where F4200=?",SQL_NTS);

ret=SQLBindParameter(m_serverhstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, pointer_name, 0, (void *)name, pointer_name, &pointer_name);
if( ret != SQL_SUCCESS)
printf("SQLBindParameter name error!\n");

ret=SQLBindParameter(m_serverhstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, pointer_id, 0, (void *)id, pointer_id, &pointer_id);
if( ret != SQL_SUCCESS)
printf("SQLBindParameter id error!\n");

ret=SQLBindParameter(m_serverhstmt, 3, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, (void*)&m_num, 0, 0);
if( ret != SQL_SUCCESS)
printf("SQLBindParameter num error!\n");

memcpy((char*)name,str,20);
strcpy((char*)id,(char *)"测试");
m_num = 181516;

ret = SQLExecute(m_serverhstmt);
//printf("effnum:%d", ret);

if(!SQL_SUCCEEDED(ret))
{
GetErrInfo(m_serverhstmt);
return;
}

ret = SQLFreeStmt(m_serverhstmt, SQL_CLOSE ) ;
if( ret != SQL_SUCCESS)
printf("SQLFreeStmt SQL_CLOSE error!\n");

printf("exec succ\n");
}


void FreeHandle()
{
SQLRETURN ret;

ret=SQLFreeHandle(SQL_HANDLE_STMT,m_serverhstmt);
if(SQL_SUCCESS!=ret && SQL_SUCCESS_WITH_INFO != ret)
printf("free hstmt error!\n");

ret=SQLDisconnect(m_serverhdbc);
if(SQL_SUCCESS!=ret&&SQL_SUCCESS_WITH_INFO!=ret)
printf("disconnected error!\n");

ret=SQLFreeHandle(SQL_HANDLE_DBC,m_serverhdbc);
if(SQL_SUCCESS!=ret&&SQL_SUCCESS_WITH_INFO!=ret)
printf("free hdbc error!\n");

ret=SQLFreeHandle(SQL_HANDLE_ENV,m_serverhenv);
if(SQL_SUCCESS!=ret&&SQL_SUCCESS_WITH_INFO!=ret)
printf("free henv error!\n");
}

int main(void)
{
FILE * f;
char aczTableName[256]={0};
f = fopen("./a.txt", "rb");
if (!f)
{
puts("读取文件出错");
return -1;
}

fseek(f, 0, SEEK_END);
ftell(f);
fseek(f, 0, SEEK_SET);

fread(aczTableName, 1, 10, f);
fclose(f);

InitHandle();
ExecSQL_BinPara(aczTableName);
FreeHandle();

return EXIT_SUCCESS;
}

代码的逻辑是读取配置文件a.txt获取里面的内容,然后更新的对应表中,这里是为了解决对应数据库乱码的问题,具体如何使用odbcapi请参考官方文档,这里提供具体的业务代码,抛砖引玉一下。

这里说下编译的的命令g++ odbc.cpp -o odbc -lodbc如果有问题,检查下odbc安装路径保证头文件跟库是可以找到的。

乱码问题

之前写入数据库存在乱码的情况,可以需要检查下数据库的编码格式,freetds可以配置编码在freetds.confglobal下配置client charset = gbk因为sqlserver是windows环境,其对应的编码集是gbk的

定位乱码问题的思路是要静下心来,分析各个环节涉及字符集的情况,不要乱,这个跟mysql定位乱码问题一样,因为写入数据库的过程有多个步骤,客户端通道,目标数据库编码,写入数据编码等环节。

原创技术分享,您的支持将鼓励我继续创作