C++ mySQL資料庫連接池(windows平臺)

来源:https://www.cnblogs.com/AndreaDO/p/18048838
-Advertisement-
Play Games

C++ MySQL資料庫連接池 新手學了C++多線程,看了些資料練手寫了C++資料庫連接池小項目,自己的源碼地址 關鍵技術點 MySQL資料庫編程、單例模式、queue隊列容器、C++11多線程編程、線程互斥、線程同步通信和 unique_lock、基於CAS的原子整形、智能指針shared_ptr ...


C++ MySQL資料庫連接池

新手學了C++多線程,看了些資料練手寫了C++資料庫連接池小項目,自己的源碼地址

關鍵技術點

MySQL資料庫編程、單例模式、queue隊列容器、C++11多線程編程、線程互斥、線程同步通信和
unique_lock、基於CAS的原子整形、智能指針shared_ptr、lambda表達式、生產者-消費者線程模型

連接池項目

為了提高MySQL資料庫(基於C/S設計)的訪問瓶頸,除了在伺服器端增加緩存伺服器緩存常用的數據之外(例如redis),還可以增加連接池,來提高MySQL Server的訪問效率,在高併發情況下,大量的TCP三次握手、MySQL Server連接認證、MySQL Server關閉連接回收資源和TCP四次揮手所耗費的性能時間也是很明顯的,增加連接池就是為了減少這一部分的性能損耗。
在市場上比較流行的連接池包括阿裡的druid,c3p0以及apache dbcp連接池,它們對於短時間內大量的資料庫增刪改查操作性能的提升是很明顯的,但是它們有一個共同點就是,全部由Java實現的。
那麼本項目就是為了在C/C++項目中,提供MySQL Server的訪問效率,實現基於C++代碼的資料庫連接池模塊。

連接池功能點介紹

連接池一般包含了資料庫連接所用的ip地址、port埠號、用戶名和密碼以及其它的性能參數,例如初始連接量,最大連接量,最大空閑時間、連接超時時間等,該項目是基於C++語言實現的連接池,主要也是實現以上幾個所有連接池都支持的通用基礎功能。

  • 初始連接量(initSize):表示連接池事先會和MySQL Server創建initSize個數的connection連接,當應用發起MySQL訪問時,不用再創建和MySQL Server新的連接,直接從連接池中獲取一個可用的連接就可以,使用完成後,並不去釋放connection,而是把當前connection再歸還到連接池當中。

  • 最大連接量(maxSize):當併發訪問MySQL Server的請求增多時,初始連接量已經不夠使用了,此時會根據新的請求數量去創建更多的連接給應用去使用,但是新創建的連接數量上限是maxSize,不能無限制的創建連接,因為每一個連接都會占用一個socket資源,一般連接池和伺服器程式是部署在一臺主機上的,如果連接池占用過多的socket資源,那麼伺服器就不能接收太多的客戶端請求了。當這些連接使用完成後,再次歸還到連接池當中來維護。

  • 最大空閑時間(maxIdleTime):當訪問MySQL的併發請求多了以後,連接池裡面的連接數量會動態增加,上限是maxSize個,當這些連接用完再次歸還到連接池當中。如果在指定的maxIdleTime裡面,這些新增加的連接都沒有被再次使用過,那麼新增加的這些連接資源就要被回收掉,只需要保持初始連接量initSize個連接就可以了。

  • 連接超時時間(connectionTimeout):當MySQL的併發請求量過大,連接池中的連接數量已經到達maxSize了,而此時沒有空閑的連接可供使用,那麼此時應用從連接池獲取連接無法成功,它通過阻塞的方式獲取連接的時間如果超connectionTimeout時間,那麼獲取連接失敗,無法訪問資料庫。

實現的邏輯圖片

數據表的結構

文章內容不會將MySQL的安裝,基於你已經下載了mysql server 8.0 ,我們建立一個mysql資料庫的數據表來演示後面如何用C++連接數據表,並且寫SQL.

先進入mysql,輸入密碼

mysql -u root -p

創建一個資料庫名叫chat,同時創建數據表

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就代表創建user好了,我們來看看數據表

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    |                |
+-------+-----------------------+------+-----+---------+----------------+

查看一下內容,沒有

mysql> select * from user;
Empty set (0.19 sec)

到這裡我們MySQL表就創建好了,我們不用管他,我們進行編寫CPP連接資料庫代碼

連接資料庫,並且執行sql語句

打開VS2019,並且創建一個控制台項目,項目結構如圖

main.cpp負責執行主函數代碼,Connect負責編寫封裝資料庫的連接和sql操作,mysqlPool負責編寫資料庫連接池。

但我們還不急著編寫代碼,先導入需要的外部庫,在VS上需要進行相應的頭文件和庫文件的配置,如下:

  • 1.右鍵項目 - C/C++ - 常規 - 附加包含目錄,填寫mysql.h頭文件的路徑
  • 2.右鍵項目 - 鏈接器 - 常規 - 附加庫目錄,填寫libmysql.lib的路徑
  • 3.右鍵項目 - 鏈接器 - 輸入 - 附加依賴項,填寫libmysql.lib庫的名字
  • 4.把libmysql.dll動態鏈接庫(Linux下尾碼名是.so庫)放在工程目錄下

如果你沒有修改過MySQL路徑,一般mysql.h在你的電腦路徑如下

如果你沒有修改過MySQL路徑,一般libmysql.lib在你的電腦路徑如下

libmysql.dll文件存放在你項目文件路徑下麵

1.封裝Mysql.h的介面成connection類

接下來封裝一下mysql的資料庫連接代碼,不懂的看看註釋,也很簡單的調用Mysql.h的介面,我們在connection中額外加入創建時間函數和存活時間函數,不能讓空閑的線程存活時間超過定義的最大空閑時間

connect.h的代碼如下

#pragma once
#include <mysql.h>
#include <string>
#include <ctime>
using namespace std;
/*
封裝MySQL資料庫的介面操作
*/
class Connection
{
public:
	// 初始化資料庫連接
	Connection();
	// 釋放資料庫連接資源
	~Connection();
	// 連接資料庫
	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; // 記錄進入空閑狀態後的起始存活時間
};

connect.cpp的代碼如下

 
#include "public.h"
#include "Connect.h"
#include <iostream>
using namespace std;

Connection::Connection()
{
	// 初始化資料庫連接
	_conn = mysql_init(nullptr);
}

Connection::~Connection()
{
	// 釋放資料庫連接資源
	if (_conn != nullptr)
		mysql_close(_conn);
}

bool Connection::connect(string ip, unsigned short port,
	string username, string password, string dbname)
{
	// 連接資料庫
	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 };
//插入一條數據
	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給你報安全警告,應該是sprintf的問題,你右擊項目,選擇屬性中C++的常規中SDL檢查,設置為否。
編譯運行後,我們返回MySQL的界面,發現數據已經插入成功了

mysql> select * from user;
+----+-----------+-----+------+
| id | name      | age | sex  |
+----+-----------+-----+------+
|  1 | zhang san |  20 | male |
+----+-----------+-----+------+
1 row in set (0.02 sec)

現在我們已經成功能調用外部介面來連接Mysql資料庫了,接下來我們來編寫連接池.

2.編寫連接池

2.1MySQL配置文件和載入配置文件

我們來編寫mySqlPool的代碼,因為資料庫連接池只有一個,所以我們寫成單例模式。同時會有多個服務端進入連接池,所以我們要添加互斥鎖來避免線程之間的衝突。
我們在項目中創建一個名叫mysql.ini配置文件存儲資料庫連接的信息,例如資料庫ip地址,用戶名,密碼等
mysql.ini的內容如下,如果你的用戶名和密碼跟裡面不同,請修改

#資料庫連接池的配置文件
ip=127.0.0.1
port=3306
username=root
password=123456
initSize=10
maxSize=1024
#最大空閑時間預設單位為秒
maxIdleTime=60
#連接超時時間單位是毫米
connectionTimeOut=100

我們把mysqlPool.h文件中需要的函數都聲明好,等會在cpp中實現。

#pragma once
#include "public.h"
#include "Connect.h"
#include <queue>
#include <mutex>
#include <string>
#include <atomic>
#include <memory>
#include <functional>
#include <condition_variable>
//因為資料庫連接池子只有一個,所以我們採用單例模式
class mySqlPool {
public:
	//獲取連接池對象實例
	static mySqlPool* getMySqlPool();
	std::shared_ptr<Connection> getConnection();//從連接池獲取一個可用的空閑連接
private:
	mySqlPool();//構造函數私有化
	bool loadConfigFile();//從配置文件中載入配置項
	void produceConnectionTask(); //運行在獨立的線程中,專門負責生產新連接
	//掃描超過maxIdleTime時間的空閑連接,進行隊列的連接回收
	void scannerConnectionTask();

	std::string _ip;//mysql的ip地址
	std::string _dbname;//資料庫的名稱
	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; //記錄連接所創建的connect的數量
	std::condition_variable cv;//設置條件變數,用於生產者線程和消費者線程的通信
};

編寫mySqlPool.cpp 中載入我們上面.ini配置文件的函數

//在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")
		{
			//字元串轉換成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;
}

這樣我們載入配置文件就完成了

2.2編寫連接池單例模式

單例模式確保資料庫連接池在整個應用程式中只有一個實例。這樣,所有需要資料庫連接的線程或操作都可以從這個池中獲取連接,而不是每次都創建新的連接。這大大減少了資源消耗和性能損耗。(如果不懂數據模式單例模式可以百度一下)

我們在.h文件中,我們先將構造函數private化,這樣外部就只能通過介面來獲取,我們在cpp中來編寫具體的實現代碼
構造方法

//mySqlPool.h
//構造方法
mySqlPool::mySqlPool()
{
	if (!loadConfigFile())
	{
		LOG("load Config File is error!");
		return;
	}
	//創建初始數量的連接
	for (int i = 0; i < _initSize; ++i)
	{
		Connection* p = new Connection();
		p->connect(_ip, _port, _username, _password, _dbname);
	}
	//啟動一個新線程,作為連接的生產者
	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;
}

現在我們已經成功的編寫了單例模式,接下來我們開始獲取資料庫的連接。

資料庫連接的線程通信

我們創建一個connect*線程隊列queue來存放MySQL資料庫的連接connect,同時我們還會額外創建兩個線程。

一個線程是生產者,開始從Connect類中獲取initSize個連接加入連接隊列中準備著,當判斷連接隊列empty,又開始獲取連接加入連接隊列中 ,如果不為empty就進入阻塞狀態。

生產者線程代碼

//運行在獨立的線程中,專門負責生產新連接
void mySqlPool::produceConnectionTask()
{
	while (true)
	{
		std::unique_lock<std::mutex> lock(_queueMutex);
		while (!_connectionQue.empty())
			cv.wait(lock);     //隊列不為空不生產線程

		//沒有到上線就可以生產線程
		if (_connectionCnt < _maxSize)
		{
			auto p = new Connection();
			p->connect(_ip, _port, _username, _password, _dbname);
			p->refreshAliveTime();//創建的時候刷新存活時間
			_connectionQue.push(p);
			++_connectionCnt;
		}
		cv.notify_all();
	}
}

另外一個線程是消費者,如果服務端想要獲取隊列中的連接,消費者線程將會從隊列中拿出connection來,如果隊列為empty,線程會處於阻塞狀態。

消費者線程代碼

/從連接池獲取一個可用的空閑連接
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();//創建的時候刷新存活時間
		_connectionQue.push(pcon);
		});
	_connectionQue.pop();
	cv.notify_all();
	return sp;
}

如果隊列裡面大於初始個數的新connection空閑時間大於最大空閑時間,我們將會回收該連接(但是不會完全釋放,我們將其歸還在連接池中)。

上面getConnection代碼的這段就是實現了回收功能

std::shared_ptr<Connection> sp(_connectionQue.front(), [&](Connection* pcon) {
		//保證只能同一時刻只能有一個線程歸還連接給隊列
		std::unique_lock<std::mutex> lock(_queueMutex);
		pcon->refreshAliveTime();//創建的時候刷新存活時間
		_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;//這裡會調用智能指針,回收到隊列中
			}
		}
	}
}

到這裡,我們連接池的代碼已經完成了,接下來是測試一下代碼

連接池的壓力測試

我們分別測試連接個數為10,100,1000時候的性能差異,創建一個test.h文件,編寫測試代碼
註意下麵的測試可能根據不同的電腦性能,可能速度會有所差異。

普通連接

//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 << "普通連接數量為:" << n << "的sql執行時間:" << (end - begin) << "ms" << std::endl;
}

main.cpp中調用

#include <iostream>
#include "Connect.h"
#include "mySqlPool.h"
#include "test.h"

int main()
{
	testSql(10);//普通連接數量為 : 10的sql執行時間 : 2838ms
	testSql(100);//普通連接數量為 : 100的sql執行時間: 12299
	testSql(1000);//普通連接數量為 : 1000的sql執行時間 : 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 << "單線程採用資料庫連接池,連接數量為:" << n << "的sql執行時間:" << (end - begin) << "ms" << std::endl;
}

main.cpp中調用

#include <iostream>
#include "Connect.h"
#include "mySqlPool.h"
#include "test.h"

int main()
{
  testSqlPool(10);//單線程 採用資料庫連接池,連接數量為:10的sql執行時間:1745ms
  testSqlPool(100);//單線程 採用資料庫連接池,連接數量為:100的sql執行時間:9779ms 
  testSqlPool(1000);//單線程 採用資料庫連接池,連接數量為:1000的sql執行時間 : 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 << "四線程採用資料庫連接池,連接數量為:" << n << "的sql執行時間:" << (end - begin) << "ms" << std::endl;
}

main.cpp中調用

#include <iostream>
#include "Connect.h"
#include "mySqlPool.h"
#include "test.h"

int main()
{
	testSqlPool4(100);//4條線程 採用資料庫連接池,連接數量為:100的sql執行時間 : 3715ms
    testSqlPool4(1000);//4條線程 採用資料庫連接池,連接數量為:1000的sql執行時間 : 34686ms
	return 0;
}

由上面測試數據可以得出,普通連接<單線程連接池<多線程連接池,連接池比普通連接還是優化很多的。
文章裡面如果有問題的請評論講出,希望可以多多包含一下新人不足,如果看完了還是對於代碼很陌生,可以下載來看一看。源碼地址


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • SSM整合就是將MVC三層架構和框架核心API組件交給SpringIoC容器管理! 一般需要配置兩個IoC容器進行三層架構組件管理。 容器名 盛放組件 web容器 web相關組件(controller,springmvc核心組件) root容器 業務和持久層相關組件(service,aop,tx,d ...
  • 代理模式(Proxy Design Pattern)在不改變原始類(或叫被代理類)代碼的情況下,通過引入代理類來給原始類附加功能。通過GPT來一探原理。 ...
  • 前言: 編程語言本身沒有優劣之分,不同的語言適合不同的場景,文中說的建議,多是站在小白要就業的立場給出的建議。 正文 我們看這張導圖,這張圖右側是前端類的語言,左側是服務端的語言: 我們先說右側。 第一個就是 JavaScript,簡稱 JS。 我強調下他和 Java 是沒有關係的,只是名字類似。 ...
  • 工業網關是一種用於連接工業設備和網路的關鍵設備,它能夠將不同協議、不同傳輸速率的工業設備連接到網路上,實現數據的傳輸和共用。不同類型的工業網關之間存在一些區別,以下是一些常見的工業網關類型及其區別: ...
  • 1. 有人說 Python 性能沒那麼 Low? 這個我用 pypy 2.7 確認了下,確實沒那麼差, 如果用 NumPy 或其他版本 Python 的話,性能更快。但 pypy 還不完善,pypy3 在 beta, 所以一般情況,我是說一般情況下,這點比較讓人不爽。 2. 有人說怎麼沒有 C#、R ...
  • 1.創建 2.配置tomcat 3.創建webapp step01,war包 step02 創建web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xml ...
  • 3. Java程式流程式控制制(重點) 程式的三種控制結構 3.1 分支結構 if, switch 3.1.1 if if 分支 根據條件(真或假)來決定執行某段代碼。 if分支應用場景 if 第一種形式 執行流程: 首先判斷條件表達式的結果,如果為true執行語句體,為false就不執行語句體。 if ...
  • C-18.MySQL8其他新特性 1.MySQL8新特性概述 MySQL從5.7版本直接跳躍發佈了8.0版本,可見是一個令人興奮的里程碑的版本。MySQL 8版本在功能上,做了顯著的改進與增強,開發者對MySQL的源代碼進行了重構,最突出的一點是對MySQL Optimizer優化器進行了改進。不僅 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...