【boost网络库从青铜到王者】第七篇:asio网络编程中的异步echo服务器,以应答为主

news2024/12/23 22:49:37

文章目录

  • 1、简介
  • 2、echo模式应答异步服务器
      • 2.1、Session会话类
      • 2.2、Server类为服务器接收连接的管理类
  • 3、客户端
  • 4、隐患
  • 5、总结

1、简介

前文已经介绍了异步操作的api,今天写一个简单的异步echo服务器,以应答为主。

2、echo模式应答异步服务器

2.1、Session会话类

Session类主要是处理客户端消息接收发送的会话类,为了简单起见,我们不考虑粘包问题,也不考虑支持手动调用发送的接口,只以应答的方式发送和接收固定长度 (1024字节长度) 的数据。

“session.h” :

#pragma once
#include<iostream>
#include<string>
#include<boost/asio.hpp>

class Session
{
public:
	Session(boost::asio::io_context& ioc);
public:
	boost::asio::ip::tcp::socket& GetSocket();
	void Start();

protected:
	//接收数据回调 tcp接收缓冲区有数据
	void HandlerRead(const boost::system::error_code& err, size_t bytes_transferred);
	//发送数据回调 tcp发送缓冲区有空闲空间,就会从用户缓冲区拷贝到tcp发送缓冲区,然后发送数据
	void HandleSend(const boost::system::error_code& err);
protected:
	enum {
		max_length = 1024
	};
	//数组接收数据
	char data_[max_length];

private:
	boost::asio::ip::tcp::socket socket_;
};

这段代码是一个使用Boost.Asio库实现的 C++ 类定义,用于网络通信会话。您解释这段代码:

  • 头文件包含:
    • 这些行包括了必要的头文件。#pragma once 是一个预处理指令,确保被包含的头文件只会被包含一次,防止多次包含和潜在的问题。
#pragma once
#include<iostream>
#include<string>
#include<boost/asio.hpp>
  • 类声明:
    • 代码定义了一个名为 SessionC++ 类。它有一个构造函数,接受一个 boost::asio::io_context 对象的引用,并且有两个公共成员函数:GetSocketStart
class Session
{
public:
    Session(boost::asio::io_context& ioc);
public:
    boost::asio::ip::tcp::socket& GetSocket();
    void Start();
protected:
    // ...
private:
    // ...
};

  • 公共成员函数:

    • Session(boost::asio::io_context& ioc):这是 Session 类的构造函数。它接受一个 io_context 的引用作为参数,通常用于管理异步I/O操作。

    • boost::asio::ip::tcp::socket& GetSocket():此函数返回一个TCP套接字对象的引用。它可能允许外部代码访问与此会话相关联的套接字。

    • void Start(): 此函数预计启动会话。代码片段没有提供其实际实现,但它可能启动一些用于网络通信的异步操作。

  • 受保护的成员函数:

    • 这些是受保护的成员函数。它们可能被派生类覆盖或在内部用于处理读取和发送操作。

      • HandlerRead(const boost::system::error_code& err, size_t bytes_transferred): 这个函数似乎是用于处理通过TCP连接接收的数据的回调。它接受错误代码和传输的字节数作为参数。

      • HandleSend(const boost::system::error_code& err): 这个函数似乎是用于处理发送数据完成的回调。它也接受一个错误代码作为参数。

protected:
    void HandlerRead(const boost::system::error_code& err, size_t bytes_transferred);
    void HandleSend(const boost::system::error_code& err);

  • 私有成员变量:
private:
    boost::asio::ip::tcp::socket socket_;

这个私有成员变量 socket_TCP套接字的实例,用于会话内的网络通信。

  • 常量:
enum {
    max_length = 1024
};

这定义了一个名为 max_length 的常量,其值为1024。这很可能是用于接收数据的缓冲区的最大长度。

总的来说,这段代码是一个使用Boost.Asio库的网络会话类的基本轮廓。它提供了管理网络通信所需的结构和组件,但没有在此片段中提供会话行为的实际实现。

“session.cpp” :

#include "Session.h"

Session::Session(boost::asio::io_context& io_context)
	:socket_(io_context)
{
	memset(data_, 0, sizeof(data_));
}

boost::asio::ip::tcp::socket& Session::GetSocket() {
	return socket_;
}

void Session::Start() {
	memset(data_, 0, max_length);
	socket_.async_read_some(boost::asio::buffer(data_, max_length),
		std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));
}

void Session::HandlerRead(const boost::system::error_code& err, std::size_t bytes_transferred) {
	if (0 != err.value()) {
		std::cout << "read data failed!err_code is: " << err.value() << " .message: " << err.what() << std::endl;
		delete this;
	}else {
		std::cout << "receive data is: " << data_ << std::endl;

		//大部分服务器这样设计全双工通信
		memset(data_, 0, sizeof(data_));
		//继续让接收/读数据监听,这样就会造成删除bug
		socket_.async_read_some(boost::asio::buffer(data_, sizeof(data_))
			, std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));

		socket_.async_send(boost::asio::buffer(data_, max_length),
			std::bind(&Session::HandleSend, this, std::placeholders::_1));
	}
}

void Session::HandleSend(const boost::system::error_code& err) {
	if (0 != err.value()) {
		std::cout << "send data failed!err code is: " << err.value() << " .message: " << err.what() << std::endl;
	}
	else {
		memset(data_, 0, sizeof(data_));
		//继续让接收/读数据监听
		socket_.async_read_some(boost::asio::buffer(data_, sizeof(data_))
			, std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));
	}

}

这是与之前提供的 Session 类相关的实现文件。解释这段代码:

  • 构造函数:
    • 这是 Session 类的构造函数的实现。它接受一个 boost::asio::io_context 的引用作为参数,并使用该上下文初始化了 socket_。此外,它使用 memset 函数将 data_ 数组的内容初始化为零。
Session::Session(boost::asio::io_context& io_context)
    : socket_(io_context)
{
    memset(data_, 0, sizeof(data_));
}

  • GetSocket 函数:
    • 这个函数返回 socket_ 的引用,允许外部代码访问与会话相关联的TCP套接字。
boost::asio::ip::tcp::socket& Session::GetSocket() {
    return socket_;
}

  • Start 函数,在Start方法中我们调用异步读操作,监听对端发送的消息。当对端发送数据后,触发HandlerRead回调函数:
    • Start 函数似乎是启动会话的函数。它首先使用 memsetdata_ 数组的内容初始化为零,然后调用 socket_async_read_some 函数,启动异步读取操作。当数据到达时,会调用 HandlerRead 回调函数处理读取的数据。
void Session::Start() {
    memset(data_, 0, max_length);
    socket_.async_read_some(boost::asio::buffer(data_, max_length),
        std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));
}

  • HandlerRead 函数,HandlerRead函数内将收到的数据发送给对端,当发送完成后触发HandleSend回调函数:
    • HandlerRead 函数是用于处理异步读取操作完成时的回调。它检查是否有错误发生,如果有错误,它会输出错误消息并删除会话对象。如果没有错误,它输出接收到的数据,并通过再次调用 async_read_some 来继续监听接收/读取数据的操作。同时,它也启动了异步发送操作,将数据发送回客户端。
void Session::HandlerRead(const boost::system::error_code& err, std::size_t bytes_transferred) {
    if (0 != err.value()) {
        std::cout << "read data failed! err_code is: " << err.value() << " . message: " << err.what() << std::endl;
        delete this;
    }
    else {
        std::cout << "receive data is: " << data_ << std::endl;

        // 大部分服务器这样设计全双工通信
        memset(data_, 0, sizeof(data_));
        // 继续让接收/读数据监听, 这样就会造成删除bug
        socket_.async_read_some(boost::asio::buffer(data_, sizeof(data_)),
            std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));

        socket_.async_send(boost::asio::buffer(data_, max_length),
            std::bind(&Session::HandleSend, this, std::placeholders::_1));
    }
}

  • HandleSend 函数:
    • HandleSend 函数用于处理异步发送操作完成时的回调。它检查是否有错误发生,如果有错误,它会输出错误消息并删除会话对象。如果没有错误,它继续通过再次调用 async_read_some 来监听接收/读取数据的操作。HandleSend函数内又一次监听了读事件,如果对端有数据发送过来则触发HandlerRead,我们再将收到的数据发回去。从而达到应答式服务的效果。
void Session::HandleSend(const boost::system::error_code& err) {
    if (0 != err.value()) {
        std::cout << "send data failed! err code is: " << err.value() << " . message: " << err.what() << std::endl;
        delete this;
    }
    else {
        memset(data_, 0, sizeof(data_));
        // 继续让接收/读数据监听
        socket_.async_read_some(boost::asio::buffer(data_, sizeof(data_)),
            std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));
    }
}

总的来说,这段代码是 Session 类的实现,它处理了异步的数据读取和发送操作,并提供了一些错误处理和数据清理的逻辑。这是一个基本的网络会话类的实现,用于处理网络通信。

2.2、Server类为服务器接收连接的管理类

Server类为服务器接收连接的管理类。

“server.h":

#pragma once
#include<iostream>
#include<boost/asio.hpp>
#include"Session.h"

class Server
{
public:
	Server(boost::asio::io_context& io_context, int16_t port);
private:
	void StartAccept();
	void HandleAccept(Session* session, const boost::system::error_code& err);
private:
	boost::asio::io_context& io_context_;
	boost::asio::ip::tcp::acceptor acceptor_;
};

这段代码定义了一个名为 ServerC++类,该类似乎用于创建和管理一个基于Boost.Asio库的TCP服务器。以下是对这段代码的解释:

  • 头文件包含:
    • 这些行包括了必要的头文件。#pragma once 用于确保头文件只被包含一次,避免多次包含的问题。
#pragma once
#include <iostream>
#include <boost/asio.hpp>
#include "Session.h"
  • 类声明:
    • 这段代码定义了一个名为 Server 的**C++**类,该类有一个公共构造函数和两个私有成员函数,以及两个私有成员变量。
class Server
{
public:
    Server(boost::asio::io_context& io_context, int16_t port);
private:
    void StartAccept();
    void HandleAccept(Session* session, const boost::system::error_code& err);
private:
    boost::asio::io_context& io_context_;
    boost::asio::ip::tcp::acceptor acceptor_;
};

  • 构造函数:
    • 这是 Server 类的构造函数的实现。构造函数接受一个 boost::asio::io_context 对象的引用和一个端口号作为参数。在构造函数内部,它初始化了 io_context_ 和 acceptor_ 成员变量。io_context_ 用于管理异步操作,而 acceptor_ 用于监听传入的连接。构造函数还调用了 StartAccept 函数来开始接受连接。
Server::Server(boost::asio::io_context& io_context, int16_t port)
    : io_context_(io_context), acceptor_(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
    StartAccept();
}

  • 私有成员函数: StartAccept将要接收连接的acceptor绑定到服务上,其内部就是将accpeptor对应的socket描述符绑定到epolliocp模型上,实现事件驱动。HandleAccept为新连接到来后触发的回调函数。
    • void StartAccept():StartAccept 函数创建一个新的 Session 对象,然后使用 acceptor_async_accept 函数来异步等待并接受传入的连接。当连接被接受时,会调用 HandleAccept 函数来处理连接。
void Server::StartAccept() {
    Session* new_session = new Session(io_context_);
    acceptor_.async_accept(new_session->GetSocket(),
        std::bind(&Server::HandleAccept, this, new_session, std::placeholders::_1));
}

  • void HandleAccept(Session session, const boost::system::error_code& err):*
    • HandleAccept 函数用于处理接受连接操作的回调。如果没有错误发生,它会调用 Session 对象的 Start 方法,启动会话。如果发生错误,它会删除 Session 对象。然后,无论如何,它都会继续调用 StartAccept 函数,以便等待下一个连接。
void Server::HandleAccept(Session* session, const boost::system::error_code& err) {
    if (!err) {
        session->Start();
    } else {
        delete session;
    }
    StartAccept(); // 继续等待下一个连接
}

总的来说,这段代码定义了一个基于Boost.Asio的TCP服务器类 Server,该类在构造函数中初始化了必要的成员变量并开始接受传入的连接。它使用了异步操作来处理连接请求,并在每次连接接受后启动一个新的会话(Session)。

“server.cpp":

#include"server.h"

Server::Server(boost::asio::io_context& io_context,int16_t port)
	:io_context_(io_context)
	,acceptor_(io_context,boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(),port))
{
	StartAccept();
}

void Server::StartAccept() {
	Session* new_session = new Session(io_context_);
	acceptor_.async_accept(new_session->GetSocket(),
		std::bind(&Server::HandleAccept, this, new_session, std::placeholders::_1));
}

void Server::HandleAccept(Session* new_session, const boost::system::error_code& err)
{
	if (err.value() != 0)
	{
		std::cout << "acceptor session failed.error_code is: " << err.value() << " .message: " << err.what() << std::endl;
		delete new_session;
	}
	else {
		std::cout << "accept new session success!" << std::endl;
		std::cout << "client connect,the ip:" << new_session->GetSocket().remote_endpoint().address() << std::endl;

		new_session->Start();
	}
	//继续监听新的客户端连接
	StartAccept();
}

这段代码是C++server.cpp 文件,它实现了一个基于Boost.Asio库的TCP服务器类 Server 的成员函数。下面是对代码的详细解释:

  • 构造函数:
    • 这是 Server 类的构造函数的实现。构造函数接受一个 boost::asio::io_context 对象的引用和一个端口号作为参数。在构造函数内部,它初始化了 io_context_acceptor_ 成员变量。io_context_ 用于管理异步操作,而 acceptor_ 用于监听传入的连接。构造函数还调用了 StartAccept 函数来开始接受连接。
Server::Server(boost::asio::io_context& io_context, int16_t port)
    : io_context_(io_context)
    , acceptor_(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
    StartAccept();
}

  • StartAccept 函数:
    • StartAccept 函数创建一个新的 Session 对象,然后使用 acceptor_async_accept 函数来异步等待并接受传入的连接。当连接被接受时,会调用 HandleAccept 函数来处理连接。
void Server::StartAccept() {
    Session* new_session = new Session(io_context_);
    acceptor_.async_accept(new_session->GetSocket(),
        std::bind(&Server::HandleAccept, this, new_session, std::placeholders::_1));
}

  • HandleAccept 函数:
    • HandleAccept 函数是用于处理接受连接操作的回调。如果没有错误发生(err.value() == 0),它会输出连接成功的消息,并显示客户端的IP地址。然后,它调用了 Session 对象的 Start 方法来启动会话。如果有错误发生,它会输出错误消息并删除 Session 对象。无论如何,最后它会继续调用 StartAccept 函数,以便等待下一个连接。
void Server::HandleAccept(Session* new_session, const boost::system::error_code& err)
{
    if (err.value() != 0)
    {
        std::cout << "acceptor session failed. error_code is: " << err.value() << " . message: " << err.what() << std::endl;
        delete new_session;
    }
    else {
        std::cout << "accept new session success!" << std::endl;
        std::cout << "client connect, the IP: " << new_session->GetSocket().remote_endpoint().address() << std::endl;

        new_session->Start();
    }
    // 继续监听新的客户端连接
    StartAccept();
}

总的来说,这段代码是 Server 类的成员函数的实现,它用于接受客户端的连接请求,并在接受连接后启动会话。这是一个基本的TCP服务器的一部分,用于处理传入的连接请求。

“main.cpp”:

#include"server.h"

int main() {
	try {
		boost::asio::io_context io_context;
		Server server(io_context, 9273);
		io_context.run();
	}
	catch (std::exception& e) {
		std::cout << "exception: " << e.what() << std::endl;
	}
	return 0;
}

这段代码是一个C++程序的 main 函数,它创建了一个基于Boost.Asio库的TCP服务器并运行它。以下是对代码的详细解释:

  • 头文件包含:
    • 这行代码包含了名为 “server.h” 的头文件,该头文件应该包含了 Server 类的声明以及其他必要的头文件。
#include "server.h"

  • main 函数:
    • boost::asio::io_context io_context;:创建了一个 io_context 对象,它用于管理异步操作。Boost.Asio库通常需要一个 io_context 对象来协调和管理异步操作。

    • Server server(io_context, 9273); 创建了一个 Server 类的对象 server,并传递了 io_context 对象和一个端口号(在此示例中是9273)作为参数。这样做将启动服务器并开始监听指定的端口。

    • io_context.run(); 调用 io_context 对象的 run 方法,开始运行事件循环,该事件循环会一直运行,直到没有待处理的异步操作。在这里,它将一直运行以监听和处理客户端连接请求。

    • catch (std::exception& e) { … }: 这是一个异常处理块,用于捕获任何可能抛出的异常。如果发生异常,它将打印异常的描述信息。

int main() {
    try {
        boost::asio::io_context io_context;
        Server server(io_context, 9273);
        io_context.run();
    }
    catch (std::exception& e) {
        std::cout << "exception: " << e.what() << std::endl;
    }
    return 0;
}

总的来说,这个 main 函数创建了一个TCP服务器,并通过调用 io_context.run() 启动服务器并进入事件循环,等待客户端连接请求。如果发生异常,它会捕获异常并打印错误信息。这是一个简单的服务器入口点,用于启动服务器应用程序。

3、客户端

客户端的设计用之前的同步模式即可,客户端不需要异步的方式,因为客户端并不是以并发为主,当然写成异步收发更好一些。 这里代码就不展示了,有兴趣去前一篇文章查看。

运行服务器之后再运行客户端,输入字符串后,就可以收到服务器应答的字符串了。
在这里插入图片描述
在这里插入图片描述
echo应答模式:
在这里插入图片描述

4、隐患

demo示例为仿照asio官网编写的,其中存在隐患,就是当服务器即将发送数据前(调用async_write前),此刻客户端中断,服务器此时调用async_write会触发发送回调函数,判断ec为非0进而执行delete this逻辑回收session。但要注意的是客户端关闭后,在tcp层面会触发读就绪事件,服务器会触发读事件回调函数。在读事件回调函数中判断错误码ec为非0,进而再次执行delete操作,从而造成二次析构,这是极度危险的。
在这里插入图片描述

5、总结

本文介绍了异步的应答服务器设计,但是这种服务器并不会在实际生产中使用,主要有两个原因:

  • 因为该服务器的发送和接收以应答的方式交互,而并不能做到应用层想随意发送的目的,也就是未做到完全的收发分离(全双工逻辑)。
  • 该服务器未处理粘包,序列化,以及逻辑和收发线程解耦等问题。
  • 该服务器存在二次析构的风险。

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

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

相关文章

R | R包默认安装路径的查看及修改

R | R包默认安装路径的查看及修改 一、R包安装位置查看二、已安装R包查询三、R包安装位置修改四、R包安装位置永久修改 在【R: R package安装的几种方式】【R: R版本更新及R包迁移&#xff08;详细步骤&#xff09;】两篇文章中介绍过R包的常见安装方式&#xff0c;以及在不同R…

一道求导题:1004T3

需要知识: ( x n ) ′ n x n − 1 (x^n)nx^{n-1} (xn)′nxn−1 ( s i n x ) ′ c o s x (sinx)cosx (sinx)′cosx [ f ( g ( x ) ) ] ′ f ′ ( g ( x ) ) g ′ ( x ) [f(g(x))]f(g(x))\times g(x) [f(g(x))]′f′(g(x))g′(x) 推完之后&#xff0c;考虑导函数与x轴的交点…

测试2023

1 企业级全栈测试平台 RunnerGO 1.1 Flow流拖拽自由组合&#xff0c;实时协作和共享 Flow自由拖拽自由组合&#xff0c;可以实现在进行一个接口后并发执行后续的步骤 接口自定义权重&#xff0c;根据Flow流自由组合配合接口自定义权重可以模拟真实业务分流的场景 全链路场景&am…

Tomcat基础与优化

Tomcat介绍 Tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;Tomcat具有处理HTML页面的功能&#xff0c;通常作为一个Servlet和JSP容器&#xff0c;单独运行…

“在 ArchiMate EA 建模中的组合关系:构建块和依赖关系

简介 在企业架构&#xff08;EA&#xff09;建模领域&#xff0c;结构关系在描绘架构内静态一致性方面起着至关重要的作用。其中一个关键的结构关系是组合关系&#xff0c;这是 ArchiMate 语言中深植的概念&#xff0c;提供了一个全面的框架&#xff0c;用于表达元素如何组合形…

C++的继承基础和虚继承原理

1.继承概念 “继承”是面向对象语言的三大特性之一&#xff08;封装、继承、多态&#xff09;。 继承&#xff08;inheritance&#xff09;机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性基础上进行扩展&#xff0c;增加功能&…

COO、CSR、adj_coo、adj_csr详解:稀疏矩阵与稀疏邻接矩阵的存储格式及转换

文章目录 一、COO二、CSR三、adj_coo四、adj_csr五、格式转换代码 稀疏图&#xff1a;数据结构中对于稀疏图的定义为&#xff1a;有很少条边或弧&#xff08;边的条数 ∣ E ∣ |E| ∣E∣ 远小于 ∣ V ∣ 2 |V|^2 ∣V∣2&#xff09;的图称为稀疏图&#xff0c;反之边的条数 …

Leetcode 231.2的幂

给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n 2x &#xff0c;则认为 n 是 2 的幂次方。 示例 1&#xff1a; 输入&#xff1a;n 1 输出&#xff1a;tr…

vs2015 报错“无法找到要定向的合适SDK”

关于Visual Studio&#xff1a;找不到合适的SDK来定位 | 码农家园 报错如下&#xff1a; 解决如下&#xff1a;

在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示例 1&#xff1a…

办公网络构建

办公网络项目背景 XX州市益智软件科技有限公司是XX市第九职业技术学校校办企业&#xff0c;依托学校人力技术、场地资源&#xff0c;面向市场独立经营、服务社会&#xff0c;主要从事网络设备销售、网络综合布线与网络管理。该公司现租用实训基地二层作为公司的办公经营场地…

为何需关注各ZKP方案的benchmarks?

1. 引言 近期&#xff0c;研究人员和工程人员有大量关于谁是最好的证明系统的争论&#xff1a; 2023年8月29日&#xff0c;StarkWare团队对比了FRI和KZG2023年8月30日&#xff0c;JustinThaler和Srinath Setty讨论FRI和KZG谁的性能更佳&#xff1f; 不过&#xff0c;在深入be…

小谈设计模式(17)—状态模式

小谈设计模式&#xff08;17&#xff09;—状态模式 专栏介绍专栏地址专栏介绍 状态模式关键角色上下文(Context)抽象状态(State)具体状态(Concrete State) 核心思想Java程序实现首先&#xff0c;我们定义一个抽象状态类 State&#xff0c;其中包含一个处理请求的方法 handleRe…

IIC控制器(2):PS端

书接上文&#xff1a; I2C控制器练习&#xff08;1&#xff09;_NoNoUnknow的博客-CSDN博客 SPI协议与FPGA的自动升级和多启动-CSDN博客 本文主要做一些基本知识的补充和工程参考。 写IIC需要注意的事情&#xff1a; 1.查询芯片手册获得slave地址&#xff0c;以及寄存器地址…

『力扣每日一题12』:只出现一次的数字

一、题目 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 示例 1 &#xff1a; 输入&…

MySQL备份恢复

MySQL日志管理 在数据库保存数据时&#xff0c;有时候不可避免会出现数据丢失或者被破坏&#xff0c;这样情况下&#xff0c;我们必须保证数据的安全性和完整性&#xff0c;就需要使用日志来查看或者恢复数据了。 MySQL日志类型 日志类型计入文件中的信息类型错误日志记录启…

Android Studio实现简易计算器(带横竖屏,深色浅色模式,更该按钮颜色,selector,style的使用)

目录 前言 运行结果&#xff1a; 运行截屏&#xff08;p50e&#xff09; apk文件 源码文件 项目结构 总览 MainActivity.java drawable 更改图标的方法&#xff1a; blackbutton.xml bluebuttons.xml greybutton.xml orangebuttons.xml whitebutton.xml layout 布…

uniapp项目实践总结(二十七)苹果应用商店上架教程

导语&#xff1a;之前介绍了如何打包一个苹果安装包文件&#xff0c;如果想要上架苹果 ios 应用商店&#xff0c;那么就来这里学习一下方法吧。 目录 准备材料上架步骤审核事项 准备材料 基本信息 构建版本&#xff1a;需要一个 ipa 格式安装包&#xff1b;logo&#xff1a…

YOLOv5、YOLOv8改进:RepVGG结构

1.简介 论文参考&#xff1a;最新RepVGG结构: Paper 我们所说的“VGG式”指的是&#xff1a; 没有任何分支结构。即通常所说的plain或feed-forward架构。 仅使用3x3卷积。 仅使用ReLU作为激活函数。 主要创新点为结构重参数化。在训练时&#xff0c;网络的结构是多分支进…

计数排序详解

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 前言 这种排序在部分情境下出奇地好&#xff0c;也是一种不错的排序 思路 有一个无序数组&#xff0c;我们从中找到最小和最大的数&#xff0c;最大的数减最小的数1的大小就是我们将要新建数组的大小&#xff0c;这个新建数…