65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.44/5 (22投票s)

2008年7月20日

CPOL

2分钟阅读

viewsIcon

48775

不需要任何特殊驱动程序或库的原生 MySql 客户端

Sample - Click to enlarge image

引言

最近我需要创建一个数据库应用程序,我的数据库服务器选择了 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

这个迷你客户端基本上有以下简单的部分

  1. 连接和身份验证
  2. 发送 SQL 命令
  3. 收集并显示结果

现在这是一个非常小的实现,它满足了我的项目第一阶段的所有需求。 它处理所有常见的数据类型,例如 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;
} 

© . All rights reserved.