在centos环境上使用c++程序访问sql server数据库
说明
在类linux系统上访问远端windows的sqlserver服务器,代码架构如下
graph LR app(客户端代码)-->unixodbc(unixodbc) unixodbc-->driver(freetds) driver-->db[sqlserver数据库]
环境搭建
安装unixodbc
使用yum安装的,默认版本是2.3.11
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
接着安装freetds1
yum install -y freetds
环境检查
通过命令odbcinst -j来查看odbc安装情况1
2
3
4
5
6
7
8unixODBC 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
15Compile-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
64.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配置
这里就可以通过命令来测试是否ok1
tsql -S mymssql -U roger -P roger -D roger
正常返回1
2
3
4locale 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.ini1
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配置
这里就可以通过命令来测试是否ok1
2
3osql -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 code1
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
*/
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.conf下global下配置client charset = gbk因为sqlserver是windows环境,其对应的编码集是gbk的
定位乱码问题的思路是要静下心来,分析各个环节涉及字符集的情况,不要乱,这个跟mysql定位乱码问题一样,因为写入数据库的过程有多个步骤,客户端通道,目标数据库编码,写入数据编码等环节。