歡迎轉載!轉載時請註明出處:http://blog.csdn.net/nfer_zhuang/article/details/46521207
先確認一下我的mysql版本:
$ mysql -V
mysql Ver 14.14 Distrib 5.5.43, for debian-linux-gnu (x86_64) using readline 6.2
前言
在一個 android->jni->網絡通信->C++ Server->mysql 的應用場景下,如何保證:
- 從client發送到server的數據是UTF-8編碼格式
- 存儲在數據庫中的數據是UTF-8編碼格式
- server和數據庫交互使用的是UTF-8格式
注:無需考慮數據在網絡傳輸的過程中編碼格式的問題,可以簡單的理解對於網絡傳輸來講任何的數據格式都可以認爲是純二進制數據。
從Client端到Server端
client端接受用戶輸入是在Android的Java部分,然後通過JNI函數將參數傳遞到C層,然後C層將數據打包封裝後發送到Server端。
那麼問題來了,如何保證Server端收到的數據是UTF-8編碼的呢?
我們上退一步,如何保證Java通過JNI傳遞到C層的參數一定是UTF-8編碼呢?
這裏的關鍵就是:在JNI中要獲取Java層傳遞下來的jstring類型數據,需要使用GetStringUTFChars()函數,具體可以參考下例:
JNIEXPORT void JNICALL Native_testFunc(JNIEnv* env, jobject object, jstring jData)
{
const char* cData = env->GetStringUTFChars(jData, NULL);
testFunc(cData);
env->ReleaseStringUTFChars(jData, cData);
}
在上面的代碼中,我們通過env->GetStringUTFChars(jData, NULL)得到一個const char *類型的數據,然後將這個數據打包封裝後通過網絡傳輸
到Server端。因此,我們要確保env->GetStringUTFChars(jData, NULL)函數返回的一定要是UTF-8編碼格式的數據。
關於GetStringUTFChars()函數,oracle的官方文檔有如下解釋:
const jbyte* GetStringUTFChars(JNIEnv *env, jstring string,jboolean *isCopy);
Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding. This array is valid until it is released by ReleaseStringUTFChars().
從這裏我們可以瞭解到,JNI機制已經幫我們確保了從Java傳遞到C層的參數一定是UTF-8編碼。
數據庫中的數據編碼格式
mysql默認的編碼格式是latin1,可以通過下述命令獲取:
mysql> SHOW VARIABLES LIKE 'character_set_server';
+----------------------+--------+
| Variable_name | Value |
+----------------------+--------+
| character_set_server | latin1 |
+----------------------+--------+
1 row in set (0.00 sec)
如果使用默認的命令創建和操作數據庫,那麼在遇到中文時,會有下述的表現:
mysql> DROP DATABASE IF EXISTS db_test;
Query OK, 1 row affected (0.02 sec)
mysql> CREATE DATABASE db_test;
Query OK, 1 row affected (0.00 sec)
mysql> USE db_test;
Database changed
mysql> CREATE TABLE t_test (
-> f_username VARCHAR(128) NOT NULL,
-> f_nickname VARCHAR(128) NOT NULL,
-> UNIQUE KEY (f_username)
-> )ENGINE=InnoDB;
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO t_test (f_username,f_nickname) VALUES('test','測試');
Query OK, 1 row affected, 1 warning (0.04 sec)
mysql> SELECT * FROM t_test;
+------------+------------+
| f_username | f_nickname |
+------------+------------+
| test | ?? |
+------------+------------+
1 row in set (0.00 sec)
mysql>
注意,上面寫入的是中文"測試",但是讀取後的內容卻是亂碼"??",那麼如何指定編碼格式呢?
在mysql的幫助文檔《10.1.5 Configuring the Character Set and Collation for Applications》中有如下說明:
If applications require data storage using a different character set or collation, you can configure character set information several ways:
- Specify character settings per database.
- Specify character settings at server startup. This causes the server to use the given settings for all applications that do not make other arrangements.
- Specify character settings at configuration time, if you build MySQL from source.
方法一:對每一個數據庫單獨設置編碼格式
方法二:修改mysql的全局配置文件,指定編碼格式
方法三:使用指定的編碼格式重新編譯mysql
重新編譯源碼的方式肯定不合適,修改mysql的全局配置文件也不太好,會影響到所有的數據庫,那麼最優的解決方案就是“在創建數據庫時指定好編碼格式”。
針對上面的測試,我們只修改一行:
CREATE DATABASE db_test DEFAULT CHARACTER SET utf8;
再次使用select查詢後,結果如下:
mysql> SELECT * FROM t_test;
+------------+------------+
| f_username | f_nickname |
+------------+------------+
| test | 測試 |
+------------+------------+
1 row in set (0.00 sec)
關於DEFAULT CHARACTER SET,官方的文檔中是這麼描述的:
Specify character settings per database
To create a database such that its tables will use a given default character set and collation for data storage, use a CREATE DATABASE statement like this:
CREATE DATABASE mydb
DEFAULT CHARACTER SET utf8
DEFAULT COLLATE utf8_general_ci;
Tables created in the database will use utf8 and utf8_general_ci by default for any character columns.
注:上面命令中還提到了DEFAULT COLLATE和utf8_general_ci,其大致意思是:指定數據庫的排序規則,具體的內容請自行搜索。對於UTF-8編碼,默認的排序規則就是utf8_general_ci。
使用api和數據庫交互使用的數據格式
通過控制檯我們得到了正確的輸出結果,那麼在程序中通過api操作數據庫呢。下面就通過一個簡單的測試程序讀來取並打印t_test表中的內容:
#include <stdio.h>
#include <stdlib.h>
#include <mysql/mysql.h>
int main(void)
{
MYSQL conn;
mysql_init(&conn);
if (!mysql_real_connect(&conn, "localhost", "db_test",
"db_test", "db_test", 0, NULL, 0)) {
printf("%s\n", mysql_error(&conn));
return 1;
}
if (mysql_query(&conn, "SELECT * FROM t_test;")) {
printf("%s\n", mysql_error(&conn));
return 1;
}
MYSQL_RES * res = mysql_store_result(&conn);
if (res && mysql_num_rows(res) > 0) {
MYSQL_ROW row;
while ((row = mysql_fetch_row(res)))
printf("%s %s\n", row[0], row[1]);
}
else {
printf("mysql_query failed, error:%s\n", mysql_error(&conn));
}
mysql_free_result(res);
mysql_close(&conn);
return 0;
}
注:上面是通過用戶名db_test@'localhost',密碼db_test,來訪問db_test數據庫,因此在上面創建完成數據庫後,需要給db_test@'localhost'用戶賦權限:
GRANT ALL ON db_test.* TO db_test@'localhost' IDENTIFIED BY 'db_test';
編譯並運行程序,輸出結果如下:
$ g++ mysqlclient.cpp -o mysqlclient -lmysqlclient
$ ./mysqlclient
test ??
咦?怎麼是亂碼???我們用控制檯查詢明明是好的。不過這也說明了數據庫中存儲的是正確的,問題應該只是出在我們的測試代碼上了。
我們在上面的測試程序中添加一行代碼:
mysql_query(&conn, "set names utf8;");
再次編譯後運行:
$ g++ mysqlclient.cpp -o mysqlclient -lmysqlclient
$ ./mysqlclient
test 測試
輸出正常了。。。
總結
在涉及到mysql的操作時,如果要確保寫入/讀出以及保存到數據庫中的數據均按照指定格式進行編碼,那麼需要以下步驟:
- 在創建數據庫時,指定編碼格式,e.g.: CREATE DATABASE db_test DEFAULT CHARACTER SET utf8;
- 在使用api連接數據庫後,通過SET NAMES指定交互的編碼格式,e.g.: mysql_query(&conn, "set names utf8;");
思考問題
- 如果在創建數據庫時,沒有指定編碼格式,而在連接數據庫後,設置了特定的編碼格式,能否正確的讀出之前寫入的數據?
- 在上面的基礎上,先使用特定的編碼格式寫入一條數據,能否使用該編碼格式正確的讀出?
以上兩個問題,請自行思考並驗證。