C++ ASIO 实现异步套接字管理

news2024/10/7 8:29:12

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 许可协议。转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/949614.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vulnhub靶机Solstice

下载地址&#xff1a;https://download.vulnhub.com/sunset/solstice.ova 主机发现 arp-scan -l 扫描端口 nmap --min-rate 10000 -p- 192.168.21.147 这里端口有太多于是我就整理了一下 nmap --min-rate 10000 -p- 192.168.21.147 -oA ports 数据整理 cat ports.nmap|grep…

【不良人】官方声明:天罡传电影拍摄三部,第七季仅一句话说明

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析画江湖系列&#xff01; 距离画江湖之不良人第六季完结已经有一段时间了&#xff0c;就在小郑都快忘了这部动漫的时候&#xff0c;突然官方发声了。先是透露了关于画江湖之不良人番外电影天罡传的情报&#xff0c;之后又明…

Ansible自动化运维之playbooks剧本

文章目录 一.playbooks介绍1.playbooks简述2.playbooks剧本格式3.playbooks组成部分4.运行playbooks及检测文件配置 二.模块实战实例1.playbooks模块实战实例2.vars模块实战实例3.指定远程主机sudo切换用户4.when模块实战实例5.with_items迭代模块实战实例6.Templates 模块实战…

windows 搭建 swoole开发环境(官网已支持)

第一步下载&#xff1a;swoole官网下载 swoole-cli-v5.0.3-cygwin-x64.zip 只支持 64 位的系统 第二步解压到指定文件夹&#xff1a;E:\phpstudy_pro\WWW\swoole-cli-v5.0.3-cygwin-x64 第三步设置环境变量&#xff1a;把解压后的文件夹下的 bin 目录路径配置到系统的 Path 环境…

茶凳浅谈-使用QCA7006AQ 让电动汽车成为智慧电网的一环

前言: 智慧电网一词相信大家都已经耳熟能详。智能电网是指采用先进的电力技术和设备、信息与通信技术&#xff0c;系统地实现电网的智能型监测、分析和决策控制&#xff0c;支持新型能源发电和灵活优质用电&#xff0c;具有高自动化水平&#xff0c;并有一定自愈、互动功能的安…

百万级单细胞多组学数据集成

写在前面 这是一篇粉丝来稿&#xff0c;文章题目为“Multi-omics integration in the age of million single-cell data”&#xff0c;于2021年发表于《Nature Reviews Nephrology》上&#xff0c;影响因子为42.439。由于单细胞目前快速的买入了百万级、多组学的时代&#xff…

用WebGPU实现基于物理的渲染

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 最近&#xff0c;我花了相当多的时间在 WebGPU 中使用 IBL&#xff08;基于图像的照明&#xff09;编写 PBR&#xff08;基于物理的渲染&#xff09;渲染器。 PBR 本身并没有什么新奇之处。 这是一项自 2014 年以来就存在的…

Python装饰器(decorators)

本文改编自以下文章&#xff1a;Decorators in Python 装饰器是一个很强大的工具&#xff0c;它允许我们很便捷地修改已有函数或者类的功能&#xff0c;我们可以用装饰器把另一个函数包装起来&#xff0c;扩展一些功能而不需要去修改这个函数代码。 预备知识 在Python中&…

度矩阵、邻接矩阵

度矩阵&#xff08;degree matrix&#xff09; 度矩阵是对角阵&#xff0c;对角上的元素为各个顶点的度&#xff0c;顶点vi的度表示和该顶点相关联的变得数量。 在无向图中&#xff0c;顶点vi的度d(vi)N(i)&#xff08;即与顶点相连的边的数目&#xff09;有向图中&#xff0…

六年北漂:一个普通程序员的成长之路

微信推送规则改了&#xff0c;星标一下公众号&#xff0c;否则可能收不到推送 收拾完东西&#xff0c;终于忙完一天了&#xff0c;坐在桌子前&#xff0c;梳理一下我的北漂经历。 中午刚下飞机&#xff0c;到了住的地方&#xff0c;直接开始下雨&#xff0c;瞬间感受到一股闷热…

揭秘:房产小程序如何助力售楼业务提升

随着移动互联网的发展&#xff0c;小程序已经成为各行各业进行营销推广的利器之一。对于房地产行业而言&#xff0c;小程序同样具有巨大的潜力。下面&#xff0c;我们将介绍如何使用乔拓云平台开发一款吸睛的房地产营销小程序。 第一步&#xff1a;注册登录乔拓云平台&#xff…

Linux基础--文件描述符

目录 一.C文件接口 1.hello.c写文件 2.hello.c读文件 3.输出信息到显示器 二、系统文件I/O 0.系统调用和库函数 1.open 2.write 3.read 4.close 三、文件描述符fd 1. 0 & 1 & 2 2.文件描述符分配规则 3.重定向 4.使用dup2系统调用 四、FILE 一.C文件接…

为了做好农业,拼多多请来顶尖农业专家当独立董事

8月29日&#xff0c;拼多多发布截至6月30日的2023年第二季度业绩报告。财报显示&#xff0c;拼多多集团今年第二季度收入为523亿元&#xff0c;同比增长66%&#xff0c;远超市场预期。 财报发布的同时&#xff0c;拼多多还宣布&#xff0c;其董事会已聘任荷兰瓦赫宁根大学终身…

【办公自动化】使用Python批量处理Excel文件并转为csv文件

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

一文了解聚合支付

第四方支付是相对于第三方支付而提出的概念&#xff0c;又被称为“聚合支付”是指通过聚合第三方支付平台、合作银行、等多种支付工具进行的综合支付服务。 简言而之&#xff0c;把支付接口聚合到一个平台上面&#xff0c;来给商家或者个人来提供支付服务。 第四方支付集中了各…

为什么深度网络(vgg,resnet)最后都不使用softmax(概率归一)函数,而是直接加fc层?

这个问题很简单&#xff0c;并不是没有使用softmax&#xff0c;而是没有显式使用softmax。 随着深度学习框架的发展&#xff0c;为了更好的性能&#xff0c;部分框架选择了在使用交叉熵损失函数时默认加上softmax&#xff0c;这样无论你的输出层是什么&#xff0c;只要用了nn.…

【GO】LGTM_Grafana_Tempo(2) --- 官方用例改后实操

最近在尝试用 LGTM 来实现 Go 微服务的可观测性&#xff0c;就顺便整理一下文档。 Tempo 会分为 4 篇文章&#xff1a; Tempo 的架构官网测试实操跑通gin 框架发送 trace 数据到 tempogo-zero 微服务框架使用发送数据到 tempo 根据官方文档实操跑起来 tempo&#xff0c;中间根…

测试理论与方法----软件测试工作流程第一个环节:提取测试需求

测试理论与方法 一、软件测试流程 1、软件测试定义 软件&#xff1a;程序数据文档&#xff1a;不仅仅包含应用程序&#xff0c;还应该包含和这个程序相关的数据&#xff0c;文档 软件测试&#xff1a;测试的对象&#xff1a;应用程序&#xff0c;数据&#xff0c;文档 软件…

java八股文面试[数据库]——慢查询优化

分析慢查询日志 直接分析慢查询日志&#xff0c; mysql使用explain sql语句进行模拟优化器来执行分析。 oracle使用explain plan for sql语句进行模拟优化器来执行分析。 table | type | possible_keys | key |key_len | ref | rows | Extra EXPLAIN列的解释&#xff1a; ta…

六、事务-3.事务四大特性

1、原子性 事务是一组操作&#xff0c;这组操作是不可分割的最小操作单元&#xff0c;这组操作要么全部执行成功&#xff0c;要么全部执行失败。 如&#xff1a;三步转账操作&#xff0c;当中只要有一步操作失败了&#xff0c;整个就失败了。 2、一致性 事务完成时&#xff…