最简化的无驱动程序原生 MySql 客户端






4.44/5 (22投票s)
不需要任何特殊驱动程序或库的原生 MySql 客户端

引言
最近我需要创建一个数据库应用程序,我的数据库服务器选择了 MySql。 它是免费的,并且可以在 Linux 上以极佳的正常运行时间运行。 问题是,我需要 Windows Mobile 版本,在 Windows 客户端上,你需要安装一个特殊的 ODBC 驱动程序,称为“MySql Connector”,才能通过任何 Microsoft DB API(如 ADO)使用它。 并且每次 MySql 发生重大更改时,比如最近的 4.1 版本,你都需要重新安装它。 现在我不喜欢安装程序和客户端机器上任何不必要的维护。 我希望我的可执行文件能够轻松部署,即点即运行。 我还将其链接到 _msvcrt.dll_,它是所有 Windows 版本的一部分。 这样,就绝对没有必要仅仅为了像 strlen()
这样的函数而安装/分发新的庞大的 Visual C++ 7/8/9 运行时。 我只希望 Microsoft 有一天能够通过服务包部署这些运行时,以便它们更加常见。
背景
好吧,MySql 是一个美丽的开源项目。 他们网站上的源代码包含“libmysql
”。 但是,由于缺少项目文件以及一些奇怪的构建系统,我无法在 Windows 上编译它。 在尝试编译 20 次之后,我决定我不需要该库 99% 的
功能,因此我自己实现了极简的原生 MySql 客户端。 另外一个优点是,现在我可以做一些以前无法想象的事情,比如重用 SQL 连接,而无需在每个命令上都进行缓慢的重新身份验证,或者将非查询命令镜像到更多的 SQL 服务器,并免费获得即时且事务性的备份。
同样有趣的是在更多服务器上进行负载平衡,在那里可以询问 SQL 服务器
它们的 CPU 和内存使用率,并简单地将 SQL 查询发送到负载最低的服务器。
由于非常小巧且独立于外部运行时,因此可以轻松移植到移动设备。
Using the Code
这个迷你客户端基本上有以下简单的部分
- 连接和身份验证
- 发送 SQL 命令
- 收集并显示结果
现在这是一个非常小的实现,它满足了我的项目第一阶段的所有需求。 它处理所有常见的数据类型,例如 string
,整数,日期。 指向文档的链接是源代码的一部分。
所以按照你喜欢的方式重新格式化它,并享受它。 ;)
// Author : Ladislav Nevery 2008
#include <windows.h>
#include <wincrypt.h>
#include <commctrl.h>
#pragma comment(lib,"wsock32.lib")
#pragma comment(lib,"crypt32.lib")
#pragma comment(lib,"comctl32.lib")
char* user = "root";
BYTE* pasw = (BYTE*)"your_pasword";
HWND list; BYTE temp[20], resp[20], *chal;
DWORD WINAPI Sql( void* command , void* onvalue=0, void* onfield=0 ) {
HCRYPTPROV prov; HCRYPTHASH hash; DWORD ret=0,no=20; int i,psw=strlen((char*)pasw); static SOCKET s=0;
SetWindowText(list,(char*)command);
static char* b = (char*)calloc(1<<24,1), *d; // recv buffer is max row size 16 mb
if(!s) {
sockaddr_in addr = {AF_INET,htons(3306), 127,0,0,1 }; // Put your server Port and IP here
s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(connect(s,(sockaddr*)&addr,sizeof(addr)) < 0 ) return MessageBox(0, 0,"Connect Failed ",0);
i=recv(s,b,1<<24,0); if (b[4] < 10 ) return MessageBox(0,b+5,"Need MySql > 4.1",0);
// Read server auth challenge and calc response by making SHA1 hashes from it and password
// Details at: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Handshake_Initialization_Packet
chal=(BYTE*)b+strlen(b+5)+10; memcpy(chal+8,chal+27,13);
if(! CryptAcquireContext(&prov,0,0,PROV_RSA_FULL,0 ) )
CryptAcquireContext(&prov,0,0,PROV_RSA_FULL,CRYPT_NEWKEYSET);
// The following source format is quite wide but only one I found that
// keeps order in which hashes are applied visible and operations categorized
CryptCreateHash(prov,CALG_SHA1,0,0,&hash); CryptHashData(hash,pasw,psw,0); CryptGetHashParam(hash,HP_HASHVAL,temp,&no,0); CryptDestroyHash(hash);
CryptCreateHash(prov,CALG_SHA1,0,0,&hash); CryptHashData(hash,temp, 20,0); CryptGetHashParam(hash,HP_HASHVAL,resp,&no,0); CryptDestroyHash(hash);
CryptCreateHash(prov,CALG_SHA1,0,0,&hash); CryptHashData(hash,chal, 20,0);
CryptHashData(hash,resp, 20,0); CryptGetHashParam(hash,HP_HASHVAL,resp,&no,0); CryptDestroyHash(hash);
CryptReleaseContext( prov,0);
// Construct client auth response
// Details at: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Client_Authentication_Packet
d = b+4;
*(int*)d = 1<<2|1<<9|1<<15|1; d+=4; // features: CLIENT_FOUND_ROWS|CLIENT_PROTOCOL_41|CLIENT_SECURE_CONNECTION|CLIENT_LONG_PASSWORD;
*(int*)d = 1<<24; d+=4; // max packet size = 16Mb
* d = 8; d+=24; // utf8 charset
strcpy(d,user); d+=1 + strlen(user);
* d = 20; d+=1; for(i=0;i<20;i++)
d[i] = resp[i]^temp[i]; d+=22; // XOR encrypt response
*(int*)b = d-b-4 | 1<<24; // calc final packet size and id
send(s,b, d-b,0);
recv(s,(char*)&no,4,0); no&=(1<<24)-1; // in case of login failure server sends us an error text
i=recv(s,b,no,0); if(i==-1||*b) return MessageBox(0,i==-1?"Timeout":b+3,"Login Failed",0);
}
// Send sql command
// Details at: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Command_Packet
d[4]=0x3; strcpy(d+5,(char*)command); *(int*)d=strlen(d+5)+1; i=send(s,d,4+*(int*)d,0);
// Parse and display record set
// Details at: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Result_Set_Header_Packet
char *p=b, txt[1000]={0}; BYTE typ[1000]={0}; int fields=0, field=0, value=0, row=0, exit=0, rc=0;
while (1) {
rc = 0; i=recv(s,(char*)&no,4,0); no&=0xffffff; // This is a bug. server sometimes don't send those 4 bytes together. will fix it
while( rc < no && (i=recv(s,b+rc, no-rc ,0)) > 0 ) rc+=i;
if(i<1) { closesocket(s); s=0; break; } // Connection lost
// 0. For non query sql commands we get just single success or failure response
if(* b==0x00&&!exit) break; // success
if(*(BYTE*)b==0xff&&!exit) { b[*(short*)(b+1)+3]=0; MessageBox(0,b+3,0,0); break; } // failure: show server error text
// 1. first think we receive is number of fields
if(!fields ) { memcpy(&fields,b,no); field=fields; continue; }
// 3. 5. after receiving last field info or row we get this EOF marker
if (*(BYTE*)b==0xfe && no < 9) if(exit++) break; else continue; // EOF
// 4. after receiving all field infos we receive row field values. One row per Receive/Packet
while( value ) {
*txt=0; i=fields-value; __int64 len=1; BYTE g=*(BYTE*)p;
g=(g==0||g==251)?0:(g==252)?2:(g==253)?3:(g==254)?8:1; if(g>1)p++; // Specialy packed length 1-8 bytes
memcpy(&len,p,g); p+=g;
// Here you can Add support for displaying more DB types like blobs etc.
if((typ[i]==0xfe||typ[i]==0xfd||typ[i]==0x03||typ[i]==0x08||typ[i]==0xC)) { // FIELD_TYPE_STRING || FIELD_TYPE_VAR_STRING || FIELD_TYPE_LONG
if(g) memcpy(txt,p,len); txt[len]=0;
typedef long (*TOnValue)(char*,int,int,int); if(onvalue) ret=((TOnValue)onvalue)(txt,row,i,typ[i]);
}
p+=len;
if(!--value) { row++; value=fields; p=b; break; }
}
// 2. Second info we get are field infos like name type etc. One field per Receive/Packet
if( field ) {
i = fields - field;
char* cat = p; p+=1+*p; * cat ++=0;
char* db = p; p+=1+*p; * db ++=0;
char* table = p; p+=1+*p; * table ++=0;
char* table_ = p; p+=1+*p; * table_++=0;
char* name = p; p+=1+*p; * name ++=0;
char* name_ = p; p+=1+*p; * name_++=0; *p++=0;
short charset = *(short*)p; p+=2;
long length = * (long*)p; p+=4;
typ[i] = * (BYTE*)p; p+=1;
short flags = *(short*)p; p+=2;
BYTE digits = * (BYTE*)p; p+=3;
char* Default = p;
if(!--field) value = fields; p=b; length=max(length*3,60); length=min(length,200);
typedef long (*TOnField)(char*,int,int,int); if(onfield) ((TOnField)onfield)(name,row,i,length);
}
}
return ret;
}
void OnValue( char* txt, int row, int col, int typ ) {
LVITEM v={LVIF_TEXT,row,0,0,0,txt};
if(!col) ListView_InsertItem (list,&v);
else ListView_SetItemText(list,row,col,txt);
}
void OnField( char* txt, int row, int col ,int len ) {
LVCOLUMN c={LVCF_WIDTH|LVCF_TEXT,0,len,txt,col};
ListView_InsertColumn(list,col,&c);
}
long GetLong( char* txt ) {
return atol( txt );
}
int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmnd, int show) {
MSG m; WSADATA wsa; WSAStartup(MAKEWORD(1,1),&wsa); InitCommonControls();
list=CreateWindow(WC_LISTVIEW,0,WS_VISIBLE|WS_SIZEBOX|WS_MAXIMIZEBOX|WS_SYSMENU|LVS_REPORT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0,0,0);
ListView_SetExtendedListViewStyle(list ,LVS_EX_FLATSB|LVS_EX_FULLROWSELECT|LVS_EX_HEADERDRAGDROP|LVS_EX_INFOTIP|LVS_EX_ONECLICKACTIVATE|0x10000);
SetWindowLong( ListView_GetHeader(list),GWL_STYLE,GetWindowLong(ListView_GetHeader(list), GWL_STYLE)^HDS_BUTTONS);
long rows = Sql("select count(*) from mysql.user",GetLong); // example to get no of rows
Sql("select * from mysql.user",OnValue,OnField); // example to process data
while(GetMessage(&m,0,0,0)) DispatchMessage( &m);
return 0;
}