使用C++實現(xiàn)MySQL數(shù)據(jù)庫連接池
關(guān)鍵技術(shù)點
MySQL數(shù)據(jù)庫編程、單例模式、queue隊列容器、C++11多線程編程、線程互斥、線程同步通信和unique_lock、基于CAS的原子整形、智能指針shared_ptr、lambda表達式、生產(chǎn)者-消費者線程模型
連接池項目
為了提高MySQL數(shù)據(jù)庫(基于C/S設(shè)計)的訪問瓶頸,除了在服務(wù)器端增加緩存服務(wù)器緩存常用的數(shù)據(jù)之外(例如redis),還可以增加連接池,來提高MySQL Server的訪問效率,在高并發(fā)情況下,大量的TCP三次握手、MySQL Server連接認證、MySQL Server關(guān)閉連接回收資源和TCP四次揮手所耗費的性能時間也是很明顯的,增加連接池就是為了減少這一部分的性能損耗。
在市場上比較流行的連接池包括阿里的druid,c3p0以及apache dbcp連接池,它們對于短時間內(nèi)大量的數(shù)據(jù)庫增刪改查操作性能的提升是很明顯的,但是它們有一個共同點就是,全部由Java實現(xiàn)的。
那么本項目就是為了在C/C++項目中,提供MySQL Server的訪問效率,實現(xiàn)基于C++代碼的數(shù)據(jù)庫連接池模塊。
連接池功能點介紹
連接池一般包含了數(shù)據(jù)庫連接所用的ip地址、port端口號、用戶名和密碼以及其它的性能參數(shù),例如初始連接量,最大連接量,最大空閑時間、連接超時時間等,該項目是基于C++語言實現(xiàn)的連接池,主要也是實現(xiàn)以上幾個所有連接池都支持的通用基礎(chǔ)功能。
- 初始連接量(initSize):表示連接池事先會和MySQL Server創(chuàng)建initSize個數(shù)的connection連接,當應(yīng)用發(fā)起MySQL訪問時,不用再創(chuàng)建和MySQL Server新的連接,直接從連接池中獲取一個可用的連接就可以,使用完成后,并不去釋放connection,而是把當前connection再歸還到連接池當中。
- 最大連接量(maxSize):當并發(fā)訪問MySQL Server的請求增多時,初始連接量已經(jīng)不夠使用了,此時會根據(jù)新的請求數(shù)量去創(chuàng)建更多的連接給應(yīng)用去使用,但是新創(chuàng)建的連接數(shù)量上限是maxSize,不能無限制的創(chuàng)建連接,因為每一個連接都會占用一個socket資源,一般連接池和服務(wù)器程序是部署在一臺主機上的,如果連接池占用過多的socket資源,那么服務(wù)器就不能接收太多的客戶端請求了。當這些連接使用完成后,再次歸還到連接池當中來維護。
- 最大空閑時間(maxIdleTime):當訪問MySQL的并發(fā)請求多了以后,連接池里面的連接數(shù)量會動態(tài)增加,上限是maxSize個,當這些連接用完再次歸還到連接池當中。如果在指定的maxIdleTime里面,這些新增加的連接都沒有被再次使用過,那么新增加的這些連接資源就要被回收掉,只需要保持初始連接量initSize個連接就可以了。
- 連接超時時間(connectionTimeout):當MySQL的并發(fā)請求量過大,連接池中的連接數(shù)量已經(jīng)到達maxSize了,而此時沒有空閑的連接可供使用,那么此時應(yīng)用從連接池獲取連接無法成功,它通過阻塞的方式獲取連接的時間如果超connectionTimeout時間,那么獲取連接失敗,無法訪問數(shù)據(jù)庫。
實現(xiàn)的邏輯圖片
數(shù)據(jù)表的結(jié)構(gòu)
文章內(nèi)容不會將MySQL的安裝,基于你已經(jīng)下載了mysql server 8.0 ,我們建立一個mysql數(shù)據(jù)庫的數(shù)據(jù)表來演示后面如何用C++連接數(shù)據(jù)表,并且寫SQL.
先進入mysql,輸入密碼
mysql -u root -p
創(chuàng)建一個數(shù)據(jù)庫名叫chat,同時創(chuàng)建數(shù)據(jù)表
CREATE DATABASE chat; use chat; CREATE TABLE user ( id INT(11) NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL, age INT(11) NOT NULL, sex ENUM('male', 'female') NOT NULL, PRIMARY KEY (id) );
如果輸出OK就代表創(chuàng)建user好了,我們來看看數(shù)據(jù)表
desc user; +-------+-----------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-----------------------+------+-----+---------+----------------+ | id | int | NO | PRI | NULL | auto_increment | | name | varchar(50) | NO | | NULL | | | age | int | NO | | NULL | | | sex | enum('male','female') | NO | | NULL | | +-------+-----------------------+------+-----+---------+----------------+
查看一下內(nèi)容,沒有
mysql> select * from user; Empty set (0.19 sec)
到這里我們MySQL表就創(chuàng)建好了,我們不用管他,我們進行編寫CPP連接數(shù)據(jù)庫代碼
連接數(shù)據(jù)庫,并且執(zhí)行sql語句
打開VS2019,并且創(chuàng)建一個控制臺項目,項目結(jié)構(gòu)如圖
main.cpp負責(zé)執(zhí)行主函數(shù)代碼,Connect負責(zé)編寫封裝數(shù)據(jù)庫的連接和sql操作,mysqlPool負責(zé)編寫數(shù)據(jù)庫連接池。
但我們還不急著編寫代碼,先導(dǎo)入需要的外部庫,在VS上需要進行相應(yīng)的頭文件和庫文件的配置,如下:
- 1.右鍵項目 - C/C++ - 常規(guī) - 附加包含目錄,填寫mysql.h頭文件的路徑
- 2.右鍵項目 - 鏈接器 - 常規(guī) - 附加庫目錄,填寫libmysql.lib的路徑
- 3.右鍵項目 - 鏈接器 - 輸入 - 附加依賴項,填寫libmysql.lib庫的名字
- 4.把libmysql.dll動態(tài)鏈接庫(Linux下后綴名是.so庫)放在工程目錄下
如果你沒有修改過MySQL路徑,一般mysql.h在你的電腦路徑如下
如果你沒有修改過MySQL路徑,一般libmysql.lib在你的電腦路徑如下
libmysql.dll文件存放在你項目文件路徑下面
封裝Mysql.h的接口成connection類
接下來封裝一下mysql的數(shù)據(jù)庫連接代碼,不懂的看看注釋,也很簡單的調(diào)用Mysql.h的接口,我們在connection中額外加入創(chuàng)建時間函數(shù)和存活時間函數(shù),不能讓空閑的線程存活時間超過定義的最大空閑時間
connect.h的代碼如下
#pragma once #include <mysql.h> #include <string> #include <ctime> using namespace std; /* 封裝MySQL數(shù)據(jù)庫的接口操作 */ class Connection { public: // 初始化數(shù)據(jù)庫連接 Connection(); // 釋放數(shù)據(jù)庫連接資源 ~Connection(); // 連接數(shù)據(jù)庫 bool connect(string ip, unsigned short port, string user, string password, string dbname); // 更新操作 insert、delete、update bool update(string sql); // 查詢操作 select MYSQL_RES* query(string sql); // 刷新一下連接的起始的空閑時間點 void refreshAliveTime() { _alivetime = clock(); } // 返回存活的時間 clock_t getAliveeTime()const { return clock() - _alivetime; } private: MYSQL* _conn; // 表示和MySQL Server的一條連接 clock_t _alivetime; // 記錄進入空閑狀態(tài)后的起始存活時間 };
connect.cpp的代碼如下
include "public.h" #include "Connect.h" #include <iostream> using namespace std; Connection::Connection() { // 初始化數(shù)據(jù)庫連接 _conn = mysql_init(nullptr); } Connection::~Connection() { // 釋放數(shù)據(jù)庫連接資源 if (_conn != nullptr) mysql_close(_conn); } bool Connection::connect(string ip, unsigned short port, string username, string password, string dbname) { // 連接數(shù)據(jù)庫 MYSQL* p = mysql_real_connect(_conn, ip.c_str(), username.c_str(), password.c_str(), dbname.c_str(), port, nullptr, 0); return p != nullptr; } bool Connection::update(string sql) { // 更新操作 insert、delete、update if (mysql_query(_conn, sql.c_str())) { LOG(+ "更新失敗:" + sql); return false; } return true; } MYSQL_RES* Connection::query(string sql) { // 查詢操作 select if (mysql_query(_conn, sql.c_str())) { LOG("查詢失敗:" + sql); return nullptr; } return mysql_use_result(_conn); }
在public.h中編寫的代碼,幫助我們輸出日志和警告
#pragma once #include <iostream> #define LOG(str) \ std::cout << __FILE__ << ":"<<__LINE__<<" " \ __TIMESTAMP__ << ":"<<str <<std::endl;
使用這個宏,你可以在代碼中的任何地方輕松輸出日志信息。
我們暫時用這個main先測試下connection類
main.cpp代碼
#include <iostream> #include "Connect.h" int main() { Connection conn; char sql[1024] = { 0 }; //插入一條數(shù)據(jù) sprintf(sql, "insert into user(name,age,sex) values('%s','%d','%s');", "zhang san", 20, "male"); conn.connect("127.0.0.1", 3306, "root", "123456", "chat"); conn.update(sql); //更新sql語句 return 0; }
如果你的vs2019給你報安全警告,應(yīng)該是sprintf的問題,你右擊項目,選擇屬性中C++的常規(guī)中SDL檢查,設(shè)置為否。編譯運行后,我們返回MySQL的界面,發(fā)現(xiàn)數(shù)據(jù)已經(jīng)插入成功了
mysql> select * from user; +----+-----------+-----+------+ | id | name | age | sex | +----+-----------+-----+------+ | 1 | zhang san | 20 | male | +----+-----------+-----+------+ 1 row in set (0.02 sec)
現(xiàn)在我們已經(jīng)成功能調(diào)用外部接口來連接Mysql數(shù)據(jù)庫了,接下來我們來編寫連接池
.
編寫連接池
MySQL配置文件和加載配置文件
我們來編寫mySqlPool的代碼,因為數(shù)據(jù)庫連接池只有一個,所以我們寫成單例模式。同時會有多個服務(wù)端進入連接池,所以我們要添加互斥鎖來避免線程之間的沖突。
我們在項目中創(chuàng)建一個名叫mysql.ini
配置文件存儲數(shù)據(jù)庫連接的信息,例如數(shù)據(jù)庫ip地址,用戶名,密碼等
mysql.ini
的內(nèi)容如下,如果你的用戶名和密碼跟里面不同,請修改
#數(shù)據(jù)庫連接池的配置文件 ip=127.0.0.1 port=3306 username=root password=123456 initSize=10 maxSize=1024 #最大空閑時間默認單位為秒 maxIdleTime=60 #連接超時時間單位是毫米 connectionTimeOut=100
我們把mysqlPool.h文件中需要的函數(shù)都聲明好,等會在cpp中實現(xiàn)。
#pragma once #include "public.h" #include "Connect.h" #include <queue> #include <mutex> #include <string> #include <atomic> #include <memory> #include <functional> #include <condition_variable> //因為數(shù)據(jù)庫連接池子只有一個,所以我們采用單例模式 class mySqlPool { public: //獲取連接池對象實例 static mySqlPool* getMySqlPool(); std::shared_ptr<Connection> getConnection();//從連接池獲取一個可用的空閑連接 private: mySqlPool();//構(gòu)造函數(shù)私有化 bool loadConfigFile();//從配置文件中加載配置項 void produceConnectionTask(); //運行在獨立的線程中,專門負責(zé)生產(chǎn)新連接 //掃描超過maxIdleTime時間的空閑連接,進行隊列的連接回收 void scannerConnectionTask(); std::string _ip;//mysql的ip地址 std::string _dbname;//數(shù)據(jù)庫的名稱 unsigned short _port; //mysql端口號3306 std::string _username;//mysql用戶名 std::string _password;//mysql登陸密碼 int _initSize;//連接池的初始連接量 int _maxSize;//連接池的最大連接量 int _maxIdleTime;//連接池最大空閑時間 int _connectionTimeOut;//連接池獲取連接的超時時間 std::queue<Connection*> _connectionQue;//存儲mysql連接隊列 std::mutex _queueMutex; //維護連接隊列的線程安全互斥鎖 std::atomic_int _connectionCnt; //記錄連接所創(chuàng)建的connect的數(shù)量 std::condition_variable cv;//設(shè)置條件變量,用于生產(chǎn)者線程和消費者線程的通信 };
編寫mySqlPool.cpp 中加載我們上面.ini配置文件
的函數(shù)
//在mySqlPool.cpp中 //加載配置文件 bool mySqlPool::loadConfigFile() { FILE* pf = fopen("mysql.ini", "r"); if (pf == nullptr) { LOG("mysql.ini file is not exits!"); return false; } while (!feof(pf)) //遍歷配置文件 { char line[1024] = { 0 }; fgets(line, 1024, pf); std::string str = line; int idx = str.find('=', 0); //從0開始找'='符號的位置 if (idx == -1)continue; int endidx = str.find('\n', idx);//從idx尋找'\n'的位置,也就是末尾 std::string key = str.substr(0, idx); //獲取配置文件中=號左邊的key //從等號后到末尾,剛好是value的string形式 std::string value = str.substr(idx + 1, endidx - idx - 1); if (key == "ip") { _ip = value; } else if (key == "port") { //字符串轉(zhuǎn)換成unsigned short _port = static_cast<unsigned short>(std::stoul(value)); } else if (key == "username") { _username = value; } else if (key == "password") { _password = value; } else if (key == "dbname") { _dbname = value; } else if (key == "initSize") { _initSize = std::stoi(value); } else if (key == "maxSize") { _maxSize = std::stoi(value); } else if (key == "maxIdleTime") { _maxIdleTime = std::stoi(value); } else if (key == "connectionTimeOut") { _connectionTimeOut = std::stoi(value); } } return true; }
這樣我們加載配置文件就完成了
編寫連接池單例模式
單例模式確保數(shù)據(jù)庫連接池在整個應(yīng)用程序中只有一個實例。這樣,所有需要數(shù)據(jù)庫連接的線程或操作都可以從這個池中獲取連接,而不是每次都創(chuàng)建新的連接。這大大減少了資源消耗和性能損耗。(如果不懂數(shù)據(jù)模式單例模式可以百度一下)
我們在.h文件中,我們先將構(gòu)造函數(shù)private化,這樣外部就只能通過接口來獲取,我們在cpp中來編寫具體的實現(xiàn)代碼
構(gòu)造方法
//mySqlPool.h //構(gòu)造方法 mySqlPool::mySqlPool() { if (!loadConfigFile()) { LOG("load Config File is error!"); return; } //創(chuàng)建初始數(shù)量的連接 for (int i = 0; i < _initSize; ++i) { Connection* p = new Connection(); p->connect(_ip, _port, _username, _password, _dbname); } //啟動一個新線程,作為連接的生產(chǎn)者 std::thread produce(std::bind(&mySqlPool::produceConnectionTask, this)); produce.detach(); //啟動一個新線程,作為空閑連接超時的回收者 std::thread scanner(std::bind(&mySqlPool::scannerConnectionTask, this)); scanner.detach(); }
單例模式
//mySqlPool.h //單例模式 mySqlPool* mySqlPool::getMySqlPool() { static mySqlPool pool; return &pool; }
現(xiàn)在我們已經(jīng)成功的編寫了單例模式,接下來我們開始獲取數(shù)據(jù)庫的連接。
數(shù)據(jù)庫連接的線程通信
我們創(chuàng)建一個connect*線程隊列queue
來存放MySQL數(shù)據(jù)庫的連接connect
,同時我們還會額外創(chuàng)建兩個線程。
一個線程是生產(chǎn)者
,開始從Connect類中獲取initSize
個連接加入連接隊列中準備著,當判斷連接隊列empty,又開始獲取連接加入連接隊列中 ,如果不為empty就進入阻塞狀態(tài)。
生產(chǎn)者線程代碼
//運行在獨立的線程中,專門負責(zé)生產(chǎn)新連接 void mySqlPool::produceConnectionTask() { while (true) { std::unique_lock<std::mutex> lock(_queueMutex); while (!_connectionQue.empty()) cv.wait(lock); //隊列不為空不生產(chǎn)線程 //沒有到上線就可以生產(chǎn)線程 if (_connectionCnt < _maxSize) { auto p = new Connection(); p->connect(_ip, _port, _username, _password, _dbname); p->refreshAliveTime();//創(chuàng)建的時候刷新存活時間 _connectionQue.push(p); ++_connectionCnt; } cv.notify_all(); } }
另外一個線程是消費者,如果服務(wù)端想要獲取隊列中的連接,消費者線程將會從隊列中拿出connection來,如果隊列為empty,線程會處于阻塞狀態(tài)。
消費者線程代碼
/從連接池獲取一個可用的空閑連接 std::shared_ptr<Connection> mySqlPool::getConnection() { std::unique_lock<std::mutex> lock(_queueMutex); while (_connectionQue.empty()) { //如果超時沒有獲取可用的空閑連接返回空 if (std::cv_status::timeout == cv.wait_for(lock, std::chrono::milliseconds(100))) if (_connectionQue.empty()) { LOG("get Connection error"); return nullptr; } } std::shared_ptr<Connection> sp(_connectionQue.front(), [&](Connection* pcon) { //保證只能同一時刻只能有一個線程歸還連接給隊列 std::unique_lock<std::mutex> lock(_queueMutex); pcon->refreshAliveTime();//創(chuàng)建的時候刷新存活時間 _connectionQue.push(pcon); }); _connectionQue.pop(); cv.notify_all(); return sp; }
如果隊列里面大于初始個數(shù)的新connection空閑時間大于最大空閑時間,我們將會回收該連接(但是不會完全釋放,我們將其歸還在連接池中)。
上面getConnection代碼的這段就是實現(xiàn)了回收功能
std::shared_ptr<Connection> sp(_connectionQue.front(), [&](Connection* pcon) { //保證只能同一時刻只能有一個線程歸還連接給隊列 std::unique_lock<std::mutex> lock(_queueMutex); pcon->refreshAliveTime();//創(chuàng)建的時候刷新存活時間 _connectionQue.push(pcon); });
掃描超過maxIdleTime時間的空閑連接,進行隊列的連接回收
//連接線程回收 void mySqlPool::scannerConnectionTask() { while (true) { //通過sleep模擬定時效果,每_maxIdleTime檢查一次 std::this_thread::sleep_for(std::chrono::seconds(_maxIdleTime)); //掃描整個隊列釋放多余的超時連接 std::unique_lock<std::mutex> lock(_queueMutex); while (_connectionCnt > _initSize) { auto p = _connectionQue.front(); if (p->getAliveeTime() >= (_maxIdleTime * 1000)) { _connectionQue.pop(); delete p;//這里會調(diào)用智能指針,回收到隊列中 } } } }
到這里,我們連接池的代碼已經(jīng)完成了,接下來是測試一下代碼
連接池的壓力測試
我們分別測試連接個數(shù)為10,100,1000時候的性能差異,創(chuàng)建一個test.h文件,編寫測試代碼
注意下面的測試可能根據(jù)不同的電腦性能,可能速度會有所差異。
普通連接
//test.h //非線程池的連接 void testSql( int n) { clock_t begin = clock(); std::thread t([&n]() { for (int i = 1; i < n; ++i) { Connection cnn; char sql[1024] = { 0 }; sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male"); cnn.connect("127.0.0.1", 3306, "root", "123456", "chat"); cnn.update(sql); }}); t.join(); clock_t end = clock(); std::cout << "普通連接數(shù)量為:" << n << "的sql執(zhí)行時間:" << (end - begin) << "ms" << std::endl; }
main.cpp中調(diào)用
#include <iostream> #include "Connect.h" #include "mySqlPool.h" #include "test.h" int main() { testSql(10);//普通連接數(shù)量為 : 10的sql執(zhí)行時間 : 2838ms testSql(100);//普通連接數(shù)量為 : 100的sql執(zhí)行時間: 12299 testSql(1000);//普通連接數(shù)量為 : 1000的sql執(zhí)行時間 : 104528ms return 0; }
單線程的線程池
//test.h void f(int n) { mySqlPool* cp = mySqlPool::getMySqlPool(); for (int i = 1; i <= n; ++i) { std::shared_ptr<Connection> sp = cp->getConnection(); char sql[1024] = { 0 }; sprintf(sql, "insert into user(name,age,sex) values('%s',%d,'%s')", "zhang san", 20, "male"); sp->update(sql); } } //測試連接池連接 void testSqlPool(int n) { clock_t begin = clock(); std::thread t1(f, n); t1.join(); clock_t end = clock(); std::cout << "單線程采用數(shù)據(jù)庫連接池,連接數(shù)量為:" << n << "的sql執(zhí)行時間:" << (end - begin) << "ms" << std::endl; }
main.cpp中調(diào)用
#include <iostream> #include "Connect.h" #include "mySqlPool.h" #include "test.h" int main() { testSqlPool(10);//單線程 采用數(shù)據(jù)庫連接池,連接數(shù)量為:10的sql執(zhí)行時間:1745ms testSqlPool(100);//單線程 采用數(shù)據(jù)庫連接池,連接數(shù)量為:100的sql執(zhí)行時間:9779ms testSqlPool(1000);//單線程 采用數(shù)據(jù)庫連接池,連接數(shù)量為:1000的sql執(zhí)行時間 : 86016ms return 0; }
多線程的線程池
//test.h //測試連接池連接 4線程 void testSqlPool4(int n) { int n2 = n / 4; clock_t begin = clock(); std::thread t1(f, n2); std::thread t2(f, n2); std::thread t3(f, n2); std::thread t4(f, n2); t1.join(); t2.join(); t3.join(); t4.join(); clock_t end = clock(); std::cout << "四線程采用數(shù)據(jù)庫連接池,連接數(shù)量為:" << n << "的sql執(zhí)行時間:" << (end - begin) << "ms" << std::endl; }
main.cpp中調(diào)用
#include <iostream> #include "Connect.h" #include "mySqlPool.h" #include "test.h" int main() { testSqlPool4(100);//4條線程 采用數(shù)據(jù)庫連接池,連接數(shù)量為:100的sql執(zhí)行時間 : 3715ms testSqlPool4(1000);//4條線程 采用數(shù)據(jù)庫連接池,連接數(shù)量為:1000的sql執(zhí)行時間 : 34686ms return 0; }
由上面測試數(shù)據(jù)可以得出,普通連接<單線程連接池<多線程連接池,連接池比普通連接還是優(yōu)化很多的。
如果看完了還是對于代碼很陌生,可以下載來看一看。源碼地址
以上就是使用C++實現(xiàn)MySQL數(shù)據(jù)庫連接池的詳細內(nèi)容,更多關(guān)于C++ MySQL數(shù)據(jù)庫連接池的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語言使用鏈表實現(xiàn)學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細介紹了C語言使用鏈表實現(xiàn)學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-12-12C語言結(jié)構(gòu)體內(nèi)存的對齊知識詳解
這篇文章主要介紹了C語言結(jié)構(gòu)體內(nèi)存的對齊的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03C語言學(xué)習(xí)進階篇之萬字詳解指針與qsort函數(shù)
之前的指針詳解中,提到過qsort函數(shù),這個函數(shù)是用來排序的,下面這篇文章主要給大家介紹了關(guān)于C語言指針與qsort函數(shù)的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-08-08Qt實現(xiàn)導(dǎo)出QTableWidget/QTableView數(shù)據(jù)
這篇文章主要介紹了在Qt中實現(xiàn)將QTableWidget或者QTableView中的數(shù)據(jù)直接導(dǎo)出的示例代碼,文中的示例代碼講解詳細,感興趣的可以了解一下2022-01-01C語言中pthread_exit()函數(shù)實現(xiàn)終止線程
本文主要介紹了C語言中pthread_exit()函數(shù)實現(xiàn)終止線程,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05MFC串口通信發(fā)送16進制數(shù)據(jù)的方法
這篇文章主要為大家詳細介紹了MFC串口通信發(fā)送16進制數(shù)據(jù),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-01-01