C++ ASIO 實現非同步套接字管理

来源:https://www.cnblogs.com/LyShark/archive/2023/08/29/17665421.html
-Advertisement-
Play Games

Boost ASIO(Asynchronous I/O)是一個用於非同步I/O操作的C++庫,該框架提供了一種方便的方式來處理網路通信、多線程編程和非同步操作。特別適用於網路應用程式的開發,從基本的網路通信到複雜的非同步操作,如遠程式控制製程序、高併發伺服器等都可以使用該框架。該框架的優勢在於其允許處理多個並... ...


Boost ASIO(Asynchronous I/O)是一個用於非同步I/O操作的C++庫,該框架提供了一種方便的方式來處理網路通信、多線程編程和非同步操作。特別適用於網路應用程式的開發,從基本的網路通信到複雜的非同步操作,如遠程式控制製程序、高併發伺服器等都可以使用該框架。該框架的優勢在於其允許處理多個併發連接,而不必創建一個線程來管理每個連接。最重要的是ASIO是一個跨平臺庫,可以運行在任何支持C++的平臺下。

本章筆者將介紹如何通過ASIO框架實現一個簡單的非同步網路套接字應用程式,該程式支持對Socket套接字的存儲,預設將套接字放入到一個Map容器內,當需要使用時只需要將套接字在容器內取出並實現通信,客戶端下線時則自動從Map容器內移除,通過對本章知識的學習讀者可以很容易的構建一個跨平臺的簡單遠控功能。

AsyncTcpClient 非同步客戶端

如下這段代碼實現了一個基本的帶有自動心跳檢測的客戶端,它可以通過非同步連接與伺服器進行通信,並根據不同的命令返回不同的數據。代碼邏輯較為簡單,但為了保證可靠性和穩定性,實際應用中需要進一步優化、處理錯誤和異常情況,以及增加更多的功能和安全性措施。

首先我們封裝實現AsyncConnect類,該類內主要實現兩個功能,其中aysnc_connect()方法用於實現非同步連接到服務端,而port_is_open()方法則用於驗證伺服器特定埠是否開放,如果開放則說明服務端還線上,不開放則說明服務端離線此處嘗試等待一段時間後再次驗證,在調用boost::bind()函數綁定套接字時通過&AsyncConnect::timer_handle()函數來設置一個超時等待時間。

進入到主函數中,首先程式通過while迴圈讓程式保持持續運行,並通過hander.aysnc_connect(ep, 5000) 每隔5秒驗證是否與服務端連接成功,如果連接了則進入內迴圈,在內迴圈中通過hander.port_is_open("127.0.0.1", 10000, 5000)驗證特定埠是否開放,這主要是為了保證服務端斷開後客戶端依然能夠跳轉到外部迴圈繼續等待服務端上線。而當客戶端與服務端建立連接後則會持續在內迴圈中socket.read_some()接收服務端傳來的特定命令,以此來執行不同的操作。

#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <iostream>
#include <string>
#include <boost/asio.hpp> 
#include <boost/bind.hpp>  
#include <boost/array.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>  
#include <boost/noncopyable.hpp>

using namespace std;
using boost::asio::ip::tcp;

// 非同步連接地址與埠
class AsyncConnect
{
public:
	AsyncConnect(boost::asio::io_service& ios, tcp::socket &s)
		:io_service_(ios), timer_(ios), socket_(s) {}

	// 非同步連接
	bool aysnc_connect(const tcp::endpoint &ep, int million_seconds)
	{
		bool connect_success = false;

		// 非同步連接,當連接成功後將觸發 connect_handle 函數
		socket_.async_connect(ep, boost::bind(&AsyncConnect::connect_handle, this, _1, boost::ref(connect_success)));

		// 設置一個定時器  million_seconds 
		timer_.expires_from_now(boost::posix_time::milliseconds(million_seconds));
		bool timeout = false;

		// 非同步等待 如果超時則執行 timer_handle
		timer_.async_wait(boost::bind(&AsyncConnect::timer_handle, this, _1, boost::ref(timeout)));
		do
		{
			// 等待非同步操作完成
			io_service_.run_one();
			// 判斷如果timeout沒超時,或者是連接建立了,則不再等待
		} while (!timeout && !connect_success);
		timer_.cancel();
		return connect_success;
	}

	// 驗證伺服器埠是否開放
	bool port_is_open(std::string address, int port, int timeout)
	{
		try
		{
			boost::asio::io_service io;
			tcp::socket socket(io);
			AsyncConnect hander(io, socket);
			tcp::endpoint ep(boost::asio::ip::address::from_string(address), port);
			if (hander.aysnc_connect(ep, timeout))
			{
				io.run();
				io.reset();
				return true;
			}
			else
			{
				return false;
			}
		}
		catch (...)
		{
			return false;
		}
	}

private:
	// 如果連接成功了,則 connect_success = true
	void connect_handle(boost::system::error_code ec, bool &connect_success)
	{
		if (!ec)
		{
			connect_success = true;
		}
	}

	// 定時器超時timeout = true
	void timer_handle(boost::system::error_code ec, bool &timeout)
	{
		if (!ec)
		{
			socket_.close();
			timeout = true;
		}
	}
	boost::asio::io_service &io_service_;
	boost::asio::deadline_timer timer_;
	tcp::socket &socket_;
};

int main(int argc, char * argv[])
{
	try
	{
		boost::asio::io_service io;
		tcp::socket socket(io);
		AsyncConnect hander(io, socket);
		boost::system::error_code error;
		tcp::endpoint ep(boost::asio::ip::address::from_string("127.0.0.1"), 10000);

		// 迴圈驗證是否線上
	go_:  while (1)
	{
		// 驗證是否連接成功,並定義超時時間為5秒
		if (hander.aysnc_connect(ep, 5000))
		{
			io.run();
			std::cout << "已連接到服務端." << std::endl;

			// 迴圈接收命令
			while (1)
			{
				// 驗證地址埠是否開放,預設等待5秒
				bool is_open = hander.port_is_open("127.0.0.1", 10000, 5000);

				// 客戶端接收數據包
				boost::array<char, 4096> buffer = { 0 };

				// 如果線上則繼續執行
				if (is_open == true)
				{
					socket.read_some(boost::asio::buffer(buffer), error);

					// 判斷收到的命令是否為GetCPU
					if (strncmp(buffer.data(), "GetCPU", strlen("GetCPU")) == 0)
					{
						std::cout << "獲取CPU參數並返回給服務端." << std::endl;
						socket.write_some(boost::asio::buffer("CPU: 15 %"));
					}

					// 判斷收到的命令是否為GetMEM
					if (strncmp(buffer.data(), "GetMEM", strlen("GetMEM")) == 0)
					{
						std::cout << "獲取MEM參數並返回給服務端." << std::endl;
						socket.write_some(boost::asio::buffer("MEM: 78 %"));
					}

					// 判斷收到的命令是否為終止程式
					if (strncmp(buffer.data(), "Exit", strlen("Exit")) == 0)
					{
						std::cout << "終止客戶端." << std::endl;
						return 0;
					}
				}
				else
				{
					// 如果連接失敗,則跳轉到等待環節
					goto go_;
				}
			}
		}
		else
		{
			std::cout << "連接失敗,正在重新連接." << std::endl;
		}
	}
	}
	catch (...)
	{
		return false;
	}

	std::system("pause");
	return 0;
}

AsyncTcpServer 非同步服務端

接著我們來實現非同步TCP伺服器,首先我們需要封裝實現CAsyncTcpServer類,該類使用了多線程來支持非同步通信,每個客戶端連接都會創建一個CTcpConnection類的實例來處理具體的通信操作,該伺服器類在連接建立、數據傳輸和連接斷開時,都會通過事件處理器來通知相關操作,以支持伺服器端的業務邏輯。其頭文件聲明如下所示;

#ifdef _MSC_VER
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#define _WIN32_WINNT 0x0601
#define _CRT_SECURE_NO_WARNINGS
#endif

#pragma once
#include <thread>
#include <array>
#include <boost\bind.hpp>
#include <boost\noncopyable.hpp>
#include <boost\asio.hpp>
#include <boost\asio\placeholders.hpp>

using namespace boost::asio;
using namespace boost::asio::ip;
using namespace boost::placeholders;
using namespace std;

// 每一個套接字連接,都自動對應一個Tcp客戶端連接
class CTcpConnection
{
public:
	CTcpConnection(io_service& ios, int clientId) : m_socket(ios), m_clientId(clientId){}
	~CTcpConnection(){}

	int                        m_clientId;
	tcp::socket                m_socket;
	array<BYTE, 16 * 1024>     m_buffer;
};

typedef shared_ptr<CTcpConnection> TcpConnectionPtr;

class CAsyncTcpServer
{
public:
	class IEventHandler
	{
	public:
		IEventHandler(){}
		virtual ~IEventHandler(){}
		virtual void ClientConnected(int clientId) = 0;
		virtual void ClientDisconnect(int clientId) = 0;
		virtual void ReceiveData(int clientId, const BYTE* data, size_t length) = 0;
	};
public:
	CAsyncTcpServer(int maxClientNumber, int port);
	~CAsyncTcpServer();
	void AddEventHandler(IEventHandler* pHandler){ m_EventHandlers.push_back(pHandler); }

	void Send(int clientId, const BYTE* data, size_t length);
	string GetRemoteAddress(int clientId);
	string GetRemotePort(int clientId);

private:
	void bind_hand_read(CTcpConnection* client);
	void handle_accept(const boost::system::error_code& error);
	void handle_read(CTcpConnection* client, const boost::system::error_code& error, size_t bytes_transferred);

private:
	thread m_thread;
	io_service m_ioservice;
	io_service::work m_work;
	tcp::acceptor m_acceptor;
	int m_maxClientNumber;
	int m_clientId;
	TcpConnectionPtr m_nextClient;
	map<int, TcpConnectionPtr> m_clients;
	vector<IEventHandler*> m_EventHandlers;
};

接著來實現AsyncTcpServer頭文件中的功能函數,此功能函數的實現如果讀者不明白原理可自行將其提交給ChatGPT解析,這裡就不再解釋功能了。

// By: 朱迎春 (基礎改進版)
#include "AsyncTcpServer.h"

// CAsyncTcpServer的實現
CAsyncTcpServer::CAsyncTcpServer(int maxClientNumber, int port)
	: m_ioservice()
	, m_work(m_ioservice)
	, m_acceptor(m_ioservice)
	, m_maxClientNumber(maxClientNumber)
	, m_clientId(0)
{
	m_thread = thread((size_t(io_service::*)())&io_service::run, &m_ioservice);
	m_nextClient = make_shared<CTcpConnection>(m_ioservice, m_clientId);
	m_clientId++;

	tcp::endpoint endpoint(tcp::v4(), port);
	m_acceptor.open(endpoint.protocol());
	m_acceptor.set_option(tcp::acceptor::reuse_address(true));
	m_acceptor.bind(endpoint);
	m_acceptor.listen();

	// 非同步等待客戶端連接
	m_acceptor.async_accept(m_nextClient->m_socket, boost::bind(&CAsyncTcpServer::handle_accept, this, boost::asio::placeholders::error));
}

CAsyncTcpServer::~CAsyncTcpServer()
{
	for (map<int, TcpConnectionPtr>::iterator it = m_clients.begin(); it != m_clients.end(); ++it)
	{
		it->second->m_socket.close();
	}
	m_ioservice.stop();
	m_thread.join();
}

// 根據ID號同步給特定客戶端發送數據包
void CAsyncTcpServer::Send(int clientId, const BYTE* data, size_t length)
{
	map<int, TcpConnectionPtr>::iterator it = m_clients.find(clientId);
	if (it == m_clients.end())
	{
		return;
	}
	it->second->m_socket.write_some(boost::asio::buffer(data, length));
}

// 根據ID號返回客戶端IP地址
string CAsyncTcpServer::GetRemoteAddress(int clientId)
{
	map<int, TcpConnectionPtr>::iterator it = m_clients.find(clientId);
	if (it == m_clients.end())
	{
		return "0.0.0.0";
	}
	std::string remote_address = it->second->m_socket.remote_endpoint().address().to_string();
	return remote_address;
}

// 根據ID號返回埠號
string CAsyncTcpServer::GetRemotePort(int clientId)
{
	map<int, TcpConnectionPtr>::iterator it = m_clients.find(clientId);
	char ref[32] = { 0 };
	if (it == m_clients.end())
	{
		return "*";
	}
	unsigned short remote_port = it->second->m_socket.remote_endpoint().port();
	std::string str = _itoa(remote_port, ref, 10);
	return str;
}

void CAsyncTcpServer::handle_accept(const boost::system::error_code& error)
{
	if (!error)
	{
		// 判斷連接數目是否達到最大限度
		if (m_maxClientNumber > 0 && m_clients.size() >= m_maxClientNumber)
		{
			m_nextClient->m_socket.close();
		}
		else
		{
			// 發送客戶端連接的消息
			for (int i = 0; i < m_EventHandlers.size(); ++i)
			{
				m_EventHandlers[i]->ClientConnected(m_nextClient->m_clientId);
			}

			// 設置非同步接收數據
			bind_hand_read(m_nextClient.get());

			// 將客戶端連接放到客戶表中
			m_clients.insert(make_pair(m_nextClient->m_clientId, m_nextClient));

			// 重置下一個客戶端連接
			m_nextClient = make_shared<CTcpConnection>(m_ioservice, m_clientId);
			m_clientId++;
		}
	}

	// 非同步等待下一個客戶端連接
	m_acceptor.async_accept(m_nextClient->m_socket, boost::bind(&CAsyncTcpServer::handle_accept, this, boost::asio::placeholders::error));
}

void CAsyncTcpServer::bind_hand_read(CTcpConnection* client)
{
	client->m_socket.async_read_some(boost::asio::buffer(client->m_buffer),
		boost::bind(&CAsyncTcpServer::handle_read, this, client, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
	return;

	client->m_socket.async_receive(boost::asio::buffer(client->m_buffer),
		boost::bind(&CAsyncTcpServer::handle_read, this, client, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));

	boost::asio::async_read(client->m_socket, boost::asio::buffer(client->m_buffer),
		boost::bind(&CAsyncTcpServer::handle_read, this, client, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}

void CAsyncTcpServer::handle_read(CTcpConnection* client, const boost::system::error_code& error, size_t bytes_transferred)
{
	if (!error)
	{
		// 發送收到數據的信息
		for (int i = 0; i < m_EventHandlers.size(); ++i)
		{
			m_EventHandlers[i]->ReceiveData(client->m_clientId, client->m_buffer.data(), bytes_transferred);
		}
		bind_hand_read(client);
	}
	else
	{
		// 發送客戶端離線的消息
		for (int i = 0; i < m_EventHandlers.size(); ++i)
		{
			m_EventHandlers[i]->ClientDisconnect(client->m_clientId);
		}
		m_clients.erase(client->m_clientId);
	}
}

AsyncTcpServer 類調用

服務端首先定義CEventHandler類並繼承自CAsyncTcpServer::IEventHandler介面,該類內需要我們實現三個方法,方法ClientConnected用於在客戶端連接時觸發,方法ClientDisconnect則是在登錄客戶端離開時觸發,而當客戶端有數據發送過來時則ReceiveData方法則會被觸發。

方法ClientConnected當被觸發時自動將clientId客戶端Socket套接字放入到tcp_client_id全局容器記憶體儲起來,而當ClientDisconnect客戶端退出時,則直接遍歷這個迭代容器,找到序列號並通過tcp_client_id.erase將其剔除;

// 客戶端連接時觸發
virtual void ClientConnected(int clientId)
{
	// 將登錄客戶端加入到容器中
	tcp_client_id.push_back(clientId);
}
  
// 客戶端退出時觸發
virtual void ClientDisconnect(int clientId)
{
	// 將登出的客戶端從容器中移除
	vector<int>::iterator item = find(tcp_client_id.begin(), tcp_client_id.end(), clientId);
	if (item != tcp_client_id.cend())
		tcp_client_id.erase(item);
}

ReceiveData一旦收到數據,則直接將其列印輸出到屏幕,即可實現客戶端參數接收的目的;

// 客戶端獲取數據
virtual void ReceiveData(int clientId, const BYTE* data, size_t length)
{
	std::cout << std::endl;
	PrintLine(80);
	std::cout << data << std::endl;
	PrintLine(80);
	std::cout << "[Shell] # ";
}

相對於接收數據而言,發送數據則是通過同步的方式進行,當我們需要發送數據時,只需要將數據字元串放入到一個BYTE*位元組數組中,併在調用tcpServer.Send時將所需參數,套接字ID,緩衝區Buf數據,以及長度傳遞即可實現將數據發送給指定的客戶端;

// 同步發送數據到指定的線程中
void send_message(CAsyncTcpServer& tcpServer, int clientId, std::string message, int message_size)
{
	// 獲取長度
	BYTE* buf = new BYTE(message_size + 1);
	memset(buf, 0, message_size + 1);

	for (int i = 0; i < message_size; i++)
	{
		buf[i] = message.at(i);
	}
	tcpServer.Send(clientId, buf, message_size);
}

客戶端完整代碼如下所示,運行客戶端後讀者可自行使用不同的命令來接收參數返回值;

#include "AsyncTcpServer.h"
#include <string>
#include <vector>
#include <iostream>
#include <boost/tokenizer.hpp>

using namespace std;

// 存儲當前客戶端的ID號
std::vector<int> tcp_client_id;

// 輸出特定長度的行
void PrintLine(int line)
{
	for (int x = 0; x < line; x++)
	{
		printf("-");
	}
	printf("\n");
}

class CEventHandler : public CAsyncTcpServer::IEventHandler
{
public:
	// 客戶端連接時觸發
	virtual void ClientConnected(int clientId)
	{
		// 將登錄客戶端加入到容器中
		tcp_client_id.push_back(clientId);
	}

	// 客戶端退出時觸發
	virtual void ClientDisconnect(int clientId)
	{
		// 將登出的客戶端從容器中移除
		vector<int>::iterator item = find(tcp_client_id.begin(), tcp_client_id.end(), clientId);
		if (item != tcp_client_id.cend())
			tcp_client_id.erase(item);
	}

	// 客戶端獲取數據
	virtual void ReceiveData(int clientId, const BYTE* data, size_t length)
	{
		std::cout << std::endl;
		PrintLine(80);
		std::cout << data << std::endl;
		PrintLine(80);
		std::cout << "[Shell] # ";
	}
};

// 同步發送數據到指定的線程中
void send_message(CAsyncTcpServer& tcpServer, int clientId, std::string message, int message_size)
{
	// 獲取長度
	BYTE* buf = new BYTE(message_size + 1);
	memset(buf, 0, message_size + 1);

	for (int i = 0; i < message_size; i++)
	{
		buf[i] = message.at(i);
	}
	tcpServer.Send(clientId, buf, message_size);
}

int main(int argc, char* argv[])
{
	CAsyncTcpServer tcpServer(10, 10000);
	CEventHandler eventHandler;
	tcpServer.AddEventHandler(&eventHandler);
	std::string command;

	while (1)
	{
		std::cout << "[Shell] # ";
		std::getline(std::cin, command);

		if (command.length() == 0)
		{
			continue;
		}
		else if (command == "help")
		{
			printf(" _            ____             _        _   \n");
			printf("| |   _   _  / ___|  ___   ___| | _____| |_  \n");
			printf("| |  | | | | \\___ \\ / _ \\ / __| |/ / _ \\ __| \n");
			printf("| |__| |_| |  ___) | (_) | (__|   <  __/ |_  \n");
			printf("|_____\\__, | |____/ \\___/ \\___|_|\\_\\___|\\__| \n");
			printf("      |___/                                 \n\n");
			printf("Usage: LySocket \t PowerBy: LyShark.com \n");
			printf("Optional: \n\n");
			printf("\t ShowSocket        輸出所有Socket容器 \n");
			printf("\t GetCPU            獲取CPU數據 \n");
			printf("\t GetMemory         獲取記憶體數據 \n");
			printf("\t Exit              退出客戶端 \n\n");
		}
		else
		{
			// 定義分詞器: 定義分割符號為[逗號,空格]
			boost::char_separator<char> sep(", --");
			typedef boost::tokenizer<boost::char_separator<char>> CustonTokenizer;
			CustonTokenizer tok(command, sep);

			// 將分詞結果放入vector鏈表
			std::vector<std::string> vecSegTag;
			for (CustonTokenizer::iterator beg = tok.begin(); beg != tok.end(); ++beg)
			{
				vecSegTag.push_back(*beg);
			}
			// 解析 [shell] # ShowSocket
			if (vecSegTag.size() == 1 && vecSegTag[0] == "ShowSocket")
			{
				PrintLine(80);
				printf("客戶ID \t 客戶IP地址 \t 客戶埠 \n");
				PrintLine(80);
				for (int x = 0; x < tcp_client_id.size(); x++)
				{
					std::cout << tcp_client_id[x] << " \t "
						<< tcpServer.GetRemoteAddress(tcp_client_id[x]) << " \t "
						<< tcpServer.GetRemotePort(tcp_client_id[x]) << std::endl;
				}
				PrintLine(80);
			}

			// 解析 [shell] # GetCPU --id 100
			if (vecSegTag.size() == 3 && vecSegTag[0] == "GetCPU")
			{
				char *id = (char *)vecSegTag[2].c_str();
				send_message(tcpServer, atoi(id), "GetCPU", strlen("GetCPU"));
			}

			// 解析 [shell] # GetMemory --id 100
			if (vecSegTag.size() == 3 && vecSegTag[0] == "GetMemory")
			{
				char* id = (char*)vecSegTag[2].c_str();
				send_message(tcpServer, atoi(id), "GetMEM", strlen("GetMEM"));
			}

			// 解析 [shell] # Exit --id 100
			if (vecSegTag.size() == 3 && vecSegTag[0] == "Exit")
			{
				char* id = (char*)vecSegTag[2].c_str();
				send_message(tcpServer, atoi(id), "Exit", strlen("Exit"));
			}
		}
	}
	return 0;
}

案例演示

首先運行服務端程式,接著運行多個客戶端,即可實現自動上線;

當用戶需要通信時,只需要指定id序號到指定的Socket套接字編號即可;

本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/d0805aed.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!


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

-Advertisement-
Play Games
更多相關文章
  • #### 本文為[李你幹嘛](https://www.cnblogs.com/liniganma)原創,轉載請註明出處:[Pybind11綁定C++抽象類(DLL介面)](https://www.cnblogs.com/liniganma/p/17666063.html) # 摘要 假設我們將DLL ...
  • 數據類型是編程中的重要概念。數據類型指定了變數值的大小和類型。 Go是靜態類型的,這意味著一旦變數類型被定義,它只能存儲該類型的數據。 Go有三種基本數據類型: - bool:表示布爾值,要麼是true,要麼是false。 - 數值型:表示整數類型、浮點數值和複數類型。 - string:表示字元串 ...
  • ### Java 8 的改進 - 速度更快 - 代碼更少(**Lambda表達式**) - 引入強大的**Stream API** - 便於並行 - 最大化減少空指針異常(**Optional**) - **Nashorn**引擎,允許在JVM上運行**js**應用 - **並行流**就是把一個內容 ...
  • 概述 Redis底層有六種數據類型包括:簡單動態字元串、雙向鏈表、壓縮列表、哈希表、跳錶和整數數組。這六種數據結構五大數據類型關係如下: String:簡單動態字元串 List:雙向鏈表、壓縮列表 Hash:壓縮列表、哈希表 Sorted Set:壓縮列表、跳錶 Set:哈希表、整數數組 數據類型和 ...
  • 背景: 根據以下簡單的代碼示例,我們將從源碼的角度分析其中的關鍵載入執行步驟,對pytest整體流程架構有個初步學習。 代碼示例: import pytest def test_add(): assert 1 + 1 == 2 def test_sub(): assert 2 - 1 == 1 通過 ...
  • # SPI是如何規避雙親委派機制的? # 1、何為雙親委派機制? > 雙親委派機制是什麼? 雙親委派機制指的是Java中類載入機制的特性。 > 雙親委派機制是作用於什麼地方? 雙親委派機制主要作用於類載入的時候。 > 類載入器 首先需要清晰的知道,雙親委派機制指的是類載入的特性。在瞭解其特性之前,我 ...
  • # core 工廠模式(Factory Pattern)是一種創建型設計模式,用於創建對象而不暴露對象創建的邏輯。它將對象的實例化過程封裝在一個工廠類中,客戶端通過調用工廠類的方法來創建對象,從而實現瞭解耦和靈活性。 工廠模式的核心思想是將對象的創建與使用分離。客戶端不直接實例化對象,而是通過調用工 ...
  • # Python條件控制和迴圈語句(if while for ) ## 條件控制 ### 概念:Python 條件語句是通過一條或多條語句的執行結果(True 或者 False)來決定執行的代碼塊 ### ![if1.png](https://img1.imgtp.com/2023/08/29/0U ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...