Nettyの參數優化簡單RPC框架實現

本篇介紹Netty調優,在上篇聊天室的案例中進行改造,手寫一個簡單的RPC實現。

1、超時時間參數

??????? CONNECT_TIMEOUT_MILLIS 是Netty的超時時間參數,屬于客戶端SocketChannel的參數,客戶端連接時如果一定時間沒有連接上,就會拋出 timeout 異常

??????? 如何在代碼中添加參數?在new Bootstrap()時使用

.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 300)

??????? 啟動客戶端,不啟動服務器,發現連接超時

??????? 打上斷點(選擇多線程模式):

??????? 這一行獲取到的值是創建BootStrap時添加CONNECT_TIMEOUT_MILLIS 的值(300)

int connectTimeoutMillis = config().getConnectTimeoutMillis();

???????? 滿足條件,進入If塊:

???????? 這是一個定時任務,延遲CONNECT_TIMEOUT_MILLIS 的值(300)后觸發,執行Runnable中的邏輯,拋出超時異常。

connectTimeoutFuture = eventLoop().schedule(new Runnable() {@Overridepublic void run() {ChannelPromise = AbstractNioChannel.this.connectPromise;ConnectTimeoutException cause = new ConnectTimeoutException("connection timed out: " + remoteAddress);if (connectPromise != null && connectPromise.tryFailure(cause)) {close(voidPromise());}}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);

??????? 主線程和NIO線程也是通過connectPromise進行異步通信的,兩個線程持有的是同一個connectPromise對象。

2、SO_BACKLOG

??????? SO_BACKLOG是一個與服務器套接字相關的參數,主要用于配置服務器套接字的接受隊列大小,屬于ServerSocketChannel的參數

什么是套接字?

套接字是計算機網絡中的一種通信端點,用于在兩個節點之間建立連接并進行數據傳輸,它包含了IP地址端口號,通過這兩個標識符,網絡上的設備可以相互定位和通信。

套接字在客戶端和服務器的工作順序:

服務器端:

  1. 創建套接字。
  2. 綁定到指定的IP地址和端口號。
  3. 監聽連接請求。
  4. 接受連接,創建一個新的套接字用于與客戶端通信。
  5. 讀取或寫入數據。

客戶端

  1. 創建套接字。
  2. 連接到服務器的IP地址和端口號。
  3. 讀取或寫入數據。

???????? 而SO_BACKLOG參數決定了服務器套接字在操作系統內核中維護的一個掛起連接隊列的最大長度。

????????當一個服務器應用程序啟動并監聽某個端口時,它會創建一個服務器套接字,用于等待客戶端的連接請求。

????????當客戶端嘗試連接服務器時,連接請求會首先進入服務器端的一個等待隊列,稱為掛起連接隊列。這個隊列中的連接請求還沒有被服務器應用程序正式接受處理。

????????在大多數操作系統中,套接字操作是由操作系統內核負責管理的。內核會為每個監聽中的服務器套接字維護一個掛起連接隊列。

??????? 如果參數設置的如果隊列已滿,新的連接請求將被拒絕或被操作系統忽略。

????????假設SO_BACKLOG設置為50,這意味著掛起連接隊列的最大長度是50。當第51個連接請求到達時,如果前面的請求還沒有被處理,新的請求將被拒絕。

3、ulimit -n

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);

???????ulimit -n控制了操作系統中一個進程可以打開的最大文件描述符(file descriptor)數量。

什么是文件描述符?

文件描述符是操作系統內核用于管理打開文件的一個抽象概念,包括普通文件、套接字、管道等。每個打開的文件、網絡連接都會占用一個文件描述符。

??????? 為什么要設置最大文件描述符

????????Netty 是一個高性能的網絡框架,設計用于處理大量并發連接。如果文件描述符的限制太低,當連接數超過此限制時,服務器將無法接受新的連接,這將導致連接失敗。?

4、TCP_NODELAY

????????TCP_NODELAY 是 TCP 協議中的一個選項,用于控制 Nagle 算法的啟用或禁用。

????????Nagle 算法 在前篇中有所提及,簡單的說,當發送方有小數據包要發送時,如果前一個數據包的確認(ACK)尚未收到,Nagle 算法會將這些小數據包暫時存儲起來,直到收到前一個數據包的確認或足夠多的數據可以組成一個較大的數據包。

??????? 可以通過以下的代碼設置是否開啟Nagle 算法 ,同樣地,這個參數屬于ServerSocketChannel

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);

??????? 什么場景下應該禁用Nagle 算法

  • 實時應用:在需要低延遲的實時應用中(例如在線游戲、實時通信應用)。
  • 小數據包頻繁發送:如果應用程序頻繁發送小數據包,并且對每個數據包的發送延遲敏感。

5、SO_SNDBUF & SO_RCVBUF

??????? SO_SNDBUF & SO_RCVBUFSO_BACKLOG類似,也是與網絡套接字相關的兩個重要參數,用于配置發送和接收緩沖區的大小。

????????發送緩沖區用于臨時存儲應用程序要發送到網絡的數據。

????????接收緩沖區用于臨時存儲從網絡接收到的數據,直到應用程序讀取它們。

????????緩沖區過大可能增加延遲,因為數據在緩沖區中停留的時間更長;緩沖區過小可能導致頻繁的緩沖區溢出和數據包丟失。

??????? 可以通過以下的代碼進行設置:

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.childOption(ChannelOption.SO_SNDBUF, 32 * 1024); // 發送緩沖區大小32KB
bootstrap.childOption(ChannelOption.SO_RCVBUF, 32 * 1024); // 接收緩沖區大小32KB

????????如何選擇合適的緩沖區大小

  1. 根據網絡帶寬和延遲:在高帶寬和高延遲的網絡環境中,需要更大的緩沖區來充分利用帶寬。例如,寬帶網絡和跨國連接可能需要更大的緩沖區。
  2. 根據應用需求:不同的應用有不同的需求。實時應用(如視頻流和在線游戲)通常需要較小的緩沖區以減少延遲,而大數據傳輸(如文件下載和大數據處理)可能需要較大的緩沖區以提高吞吐量。
  3. 測試和調優:最佳的緩沖區大小通常需要通過測試和調優來確定。可以通過逐步調整緩沖區大小并監測網絡性能來找到最佳配置。

6、ALLOCATOR

????????ALLOCATOR 參數用于配置 ByteBuf 分配器,ByteBuf的相關概念在前篇中也提到過,大致可以分為池化和非池化:

  • PooledByteBufAllocator:池化分配器,重復使用內存以減少分配和釋放內存的開銷,適用于高并發和性能敏感的應用。
  • UnpooledByteBufAllocator:非池化分配器,每次都進行新的內存分配,適用于內存使用模式不可預測的應用。

??????? 可以通過以下代碼進行設置:

ServerBootstrap bootstrap = new ServerBootstrap();// 使用池化分配器
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);// 使用非池化分配器
// bootstrap.option(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT);

7、RCVBUF_ALLOCATOR

????????RCVBUF_ALLOCATOR 是一個用于管理接收緩沖區大小的機制,用于確定和管理網絡連接上每次讀取操作時分配的字節緩沖區的大小。

??????? 它是一個接口,常用的實現類有:

  • FixedRecvByteBufAllocator:每次讀操作分配固定大小的緩沖區。

???

  • DefaultMaxBytesRecvByteBufAllocator:一個可以限制每次讀取消息數量的實現。

  • ?AdaptiveRecvByteBufAllocator:根據流量動態調整緩沖區大小,這是最常用的實現之一。 ?

8、RPC簡單實現

??????? 接下來會通過一個案例實現簡單的RPC框架。

什么是RPC框架?

RPC(Remote Procedure Call,遠程過程調用)框架是一種使程序能夠通過網絡調用遠程服務器上的函數或方法的技術。

在表面上這種調用方式對用戶是透明的,就像調用本地函數一樣簡單,但實際上底層會通過網絡協議進行通信。

????????8.1、1.0版 ?????

????????首先需要新增RPC的請求和響應消息:

@Data
public abstract class Message implements Serializable {// 省略舊的代碼public static final int RPC_MESSAGE_TYPE_REQUEST = 101;public static final int  RPC_MESSAGE_TYPE_RESPONSE = 102;static {// ...messageClasses.put(RPC_MESSAGE_TYPE_REQUEST, RpcRequestMessage.class);messageClasses.put(RPC_MESSAGE_TYPE_RESPONSE, RpcResponseMessage.class);}}

???????? 然后定義一個RPC請求消息類,在請求消息類中,包括了調用接口及接口中方法的信息:

@Getter
@ToString(callSuper = true)
public class RpcRequestMessage extends Message {/*** 調用的接口全限定名,服務端根據它找到實現*/private String interfaceName;/*** 調用接口中的方法名*/private String methodName;/*** 方法返回類型*/private Class<?> returnType;/*** 方法參數類型數組*/private Class[] parameterTypes;/*** 方法參數值數組*/private Object[] parameterValue;public RpcRequestMessage(int sequenceId, String interfaceName, String methodName, Class<?> returnType, Class[] parameterTypes, Object[] parameterValue) {super.setSequenceId(sequenceId);this.interfaceName = interfaceName;this.methodName = methodName;this.returnType = returnType;this.parameterTypes = parameterTypes;this.parameterValue = parameterValue;}@Overridepublic int getMessageType() {return RPC_MESSAGE_TYPE_REQUEST;}
}

??????? 再定義一個響應消息類,包括正常返回的值以及發生異常時的返回值。

@Data
@ToString(callSuper = true)
public class RpcResponseMessage extends Message {/*** 返回值*/private Object returnValue;/*** 異常值*/private Exception exceptionValue;@Overridepublic int getMessageType() {return RPC_MESSAGE_TYPE_RESPONSE;}
}

??????? 定義一個獲取配置文件中接口實現類的工廠類:

public class ServicesFactory {static Properties properties;static Map<Class<?>, Object> map = new ConcurrentHashMap<>();static {try (InputStream in = SerializedConfig.class.getResourceAsStream("/application.properties")) {properties = new Properties();properties.load(in);Set<String> names = properties.stringPropertyNames();for (String name : names) {if (name.endsWith("Service")) {Class<?> interfaceClass = Class.forName(name);Class<?> instanceClass = Class.forName(properties.getProperty(name));map.put(interfaceClass, instanceClass.newInstance());}}} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {throw new ExceptionInInitializerError(e);}}public static <T> T getService(Class<T> interfaceClass) {return (T) map.get(interfaceClass);}
}

????????application.properties?

cn.itcast.server.service.HelloService=cn.itcast.server.service.HelloServiceImpl

?????? HelloService接口及實現類:

public interface HelloService {String sayHello(String name);
}
public class HelloServiceImpl implements HelloService {@Overridepublic String sayHello(String msg) {
//        int i = 1 / 0;return "你好, " + msg;}
}

??????? 準備RPC服務器端和客戶端的代碼,和聊天室案例類似,但是加上了對應的RPC請求消息和響應消息的處理器:

??????? 服務器端:

/**
*
* RPC服務器端
**/
@Slf4j
public class RpcServer {public static void main(String[] args) {NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();RpcRequestMessageHandler RPC_HANDLER = new RpcRequestMessageHandler();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.group(boss, worker);serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProcotolFrameDecoder());ch.pipeline().addLast(LOGGING_HANDLER);ch.pipeline().addLast(MESSAGE_CODEC);ch.pipeline().addLast(RPC_HANDLER);}});Channel channel = serverBootstrap.bind(8080).sync().channel();channel.closeFuture().sync();} catch (InterruptedException e) {log.error("server error", e);} finally {boss.shutdownGracefully();worker.shutdownGracefully();}}
}

??????? 客戶端:

/**
*
*RPC 客戶端
**/
public class RpcClient {public static void main(String[] args) {NioEventLoopGroup group = new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();// rpc 響應消息處理器,待實現RpcResponseMessageHandler RPC_HANDLER = new RpcResponseMessageHandler();try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(group);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProcotolFrameDecoder());ch.pipeline().addLast(LOGGING_HANDLER);ch.pipeline().addLast(MESSAGE_CODEC);ch.pipeline().addLast(RPC_HANDLER);}});Channel channel = bootstrap.connect("localhost", 8080).sync().channel();channel.closeFuture().sync();} catch (Exception e) {log.error("client error", e);} finally {group.shutdownGracefully();}}
}

??????? 先編寫服務器端的自定義RPC消息處理器RpcRequestMessageHandler

@Slf4j
@ChannelHandler.Sharable
public class RpcRequestMessageHandler extends SimpleChannelInboundHandler<RpcRequestMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, RpcRequestMessage message) {RpcResponseMessage responseMessage = new RpcResponseMessage();int sequenceId = message.getSequenceId();responseMessage.setSequenceId(sequenceId);try {//獲取RPC消息對象中將要調用的接口的實現類 寫在配置文件中HelloService service = (HelloService) ServicesFactory.getService(Class.forName(message.getInterfaceName()));//獲取實現類中的方法Method method = service.getClass().getMethod(message.getMethodName(), message.getParameterTypes());//通過反射調用方法Object result = method.invoke(service, message.getParameterValue());responseMessage.setReturnValue(result);} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {e.printStackTrace();responseMessage.setExceptionValue(e);}//觸發出站事件ctx.writeAndFlush(responseMessage);}
}

??????? 通過main方法測試一下:

/*** 測試代碼* @param args* @throws ClassNotFoundException* @throws NoSuchMethodException* @throws InvocationTargetException* @throws IllegalAccessException*/public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {//封裝RPC消息對象RpcRequestMessage message = new RpcRequestMessage(1,"cn.itcast.server.service.HelloService","sayHello",String.class,new Class[]{String.class},new Object[]{"張三"});//獲取RPC消息對象中將要調用的接口的實現類HelloService service = (HelloService) ServicesFactory.getService(Class.forName(message.getInterfaceName()));//獲取實現類中的方法Method method = service.getClass().getMethod(message.getMethodName(), message.getParameterTypes());//調用方法Object result = method.invoke(service, message.getParameterValue());System.out.println(result);}

??????? 編寫客戶端的代碼以及自定義RPC消息返回處理器RpcResponseMessageHandler 暫時只將接收到的消息返回出去:

@Slf4j
@ChannelHandler.Sharable
public class RpcResponseMessageHandler extends SimpleChannelInboundHandler<RpcResponseMessage> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws Exception {log.debug("{}", msg);}
}

??????? 改造客戶端的代碼,發送調用方法請求:

   ChannelFuture future = channel.writeAndFlush(new RpcRequestMessage(1,"cn.itcast.server.service.HelloService","sayHello",String.class,new Class[]{String.class},new Object[]{"張三"})).addListener(promise -> {if (!promise.isSuccess()) {Throwable cause = promise.cause();log.error("error", cause);}});

??????? 它的執行順序是:

??????? 客戶端發送消息,觸發所有出站處理器:

??????? 然后到服務器:

??????? 在RpcRequestMessageHandler 中無論消息處理是否報錯,都會觸發出站處理器將返回值傳遞給客戶端:

??????? 最后再回到客戶端:

??????? 注意:LOGGING_HANDLER和MESSAGE_CODEC是雙向處理,既可以是入站,也可以是出站!


??????? 這樣一個簡單的RPC通信案例就已經實現了。

??????? 8.2、2.0版

??????? 但是在第一版中,用戶在客戶端發送調用請求時,需要自己封裝RpcRequestMessage 請求對象,參數復雜,換做是我是絕對不愿意這樣做的。那么我們對其進行優化。

??????? 改造客戶端,首先定義一個成員變量channel:

private static volatile Channel channel = null;

??????? 然后將原有客戶端的代碼抽取成一個初始化channel的方法:

 private static void initChannel() {NioEventLoopGroup group = new NioEventLoopGroup();LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();RpcResponseMessageHandler RPC_HANDLER = new RpcResponseMessageHandler();Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(group);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ProcotolFrameDecoder());//雙向事件ch.pipeline().addLast(LOGGING_HANDLER);ch.pipeline().addLast(MESSAGE_CODEC);//入站事件ch.pipeline().addLast(RPC_HANDLER);}});try {channel = bootstrap.connect("localhost", 8080).sync().channel();channel.closeFuture().addListener(future -> {group.shutdownGracefully();});} catch (Exception e) {log.error("client error", e);}}

??????? 這個channel只應該存在一個實例,采用雙檢鎖單例的方式獲取:

  /*** 初始化單例channel* @return*/private static Channel getChannel(){if (channel != null){return channel;}synchronized (LOCK){if (channel!=null){return channel;}initChannel();return channel;}}

??????? 復習一下,為什么要使用雙檢鎖模式?

??????? (成員位置的channel可以不用volatile關鍵字?此時的channel對象不是走構造方法new出來的)

???????? 然后創建一個代理對象代理對象負責將請求參數打包并發送給遠程服務器:

 public static <T> T getProxyService(Class<T> serviceClass){ClassLoader classLoader = serviceClass.getClassLoader();Class<?>[] interfaces = new Class[]{serviceClass};Object o = Proxy.newProxyInstance(classLoader, interfaces, (proxy, method, args) -> {//將方法調用轉換成消息對象int sequenceId = SequenceIdGenerator.nextId();RpcRequestMessage message = new RpcRequestMessage(sequenceId,serviceClass.getName(),method.getName(),method.getReturnType(),method.getParameterTypes(),args);//發送消息Channel channel = getChannel();channel.writeAndFlush(message);//異步通信獲取結果DefaultPromise<Object> objectDefaultPromise = new DefaultPromise<>(channel.eventLoop());//向PROMISE中注冊ID和DefaultPromiseRpcResponseMessageHandler.PROMISES.put(sequenceId,objectDefaultPromise);//等待結果objectDefaultPromise.await();if (objectDefaultPromise.isSuccess()){return objectDefaultPromise.getNow();}else {throw new RuntimeException(objectDefaultPromise.cause());}});return (T) o;}

??????? 重點在于向客戶端接收服務器響應的RpcResponseMessageHandler? 中注冊自己的消息ID和promise對象。

//向PROMISE中注冊ID和DefaultPromise
RpcResponseMessageHandler.PROMISES.put(sequenceId,objectDefaultPromise);

??????? 這樣用戶只需要調用代理對象的方法就可以了:

public static void main(String[] args) {HelloService helloService = getProxyService(HelloService.class);System.out.println(helloService.sayHello("張三"));
}

??????? 同時需要修改客戶端接受服務器響應的RpcResponseMessageHandler ,去找到對應消息ID的promise對象,并且移除,然后根據服務器返回的結果寫入成功或異常情況,這時客戶端的

//等待結果
objectDefaultPromise.await();

??????? 獲取到了結果,進行最后的處理。

/*** 接受服務器的響應*/
@Slf4j
@ChannelHandler.Sharable
public class RpcResponseMessageHandler extends SimpleChannelInboundHandler<RpcResponseMessage> {/*** k:消息id* v:消息ID對應的promise對象*/public static final ConcurrentHashMap<Integer, Promise<Object>> PROMISES = new ConcurrentHashMap<>();@Overrideprotected void channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws Exception {int sequenceId = msg.getSequenceId();Promise<Object> promise = PROMISES.remove(sequenceId);Exception exceptionValue = msg.getExceptionValue();Object returnValue = msg.getReturnValue();if (exceptionValue == null) {promise.setSuccess(returnValue);} else {promise.setFailure(exceptionValue);}}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/41024.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/41024.shtml
英文地址,請注明出處:http://en.pswp.cn/web/41024.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring Cloud 是什么?(Spring Cloud 組件介紹)

什么是 Spring Cloud&#xff1f; Spring Cloud 是微服務系統架構的一站式解決方案&#xff0c;是各個微服務架構落地技術的集合體&#xff0c;讓架構師、 開發者在使用微服務理念構建應用系統的時候&#xff0c; 面對各個環節的問題都可以找到相應的組件來處理&#xff0c;比…

二叉樹的遍歷算法:前序、中序與后序遍歷

在數據結構與算法中&#xff0c;二叉樹的遍歷是基礎且重要的操作之一&#xff0c;它允許我們按照某種順序訪問樹中的每個節點。常見的二叉樹遍歷方式有前序遍歷&#xff08;Preorder Traversal&#xff09;、中序遍歷&#xff08;Inorder Traversal&#xff09;和后序遍歷&…

React 19 競態問題解決

競態問題/競態條件 指的是&#xff0c;當我們在交互過程中&#xff0c;由于各種原因導致同一個接口短時間之內連續發送請求&#xff0c;后發送的請求有可能先得到請求結果&#xff0c;從而導致數據渲染出現預期之外的錯誤。 因為防止重復執行可以有效的解決競態問題&#xff0…

聊天廣場(Vue+WebSocket+SpringBoot)

由于心血來潮想要做個聊天室項目 &#xff0c;但是仔細找了一下相關教程&#xff0c;卻發現這么多的WebSocket教程里面&#xff0c;很多都沒有介紹詳細&#xff0c;代碼都有所殘缺&#xff0c;所以這次帶來一個比較完整得使用WebSocket的項目。 目錄 一、效果展示 二、準備工…

html+css+js圖片手動輪播

源代碼在界面圖片后面 輪播演示用的幾張圖片是Bing上的&#xff0c;直接用的幾張圖片的URL&#xff0c;誰加載可能需要等一下&#xff0c;現實中替換成自己的圖片即可 關注一下點個贊吧&#x1f604; 謝謝大佬 界面圖片 源代碼 <!DOCTYPE html> <html lang&quo…

內存對齊宏ALIGN的理解

內存對齊宏ALIGN的理解 在Android Camera HAL代碼中經常看到ALIGN這個宏&#xff0c;主要用來進行內存對齊&#xff0c;下面是v4l2_wrapper.cpp中ALIGN的一些定義 //v4l2_wrapper.cpp中內存分配進行對其的操作和定義#define ALIGN( num, to ) (((num) (to-1)) & (~(to-1)…

【Android】自定義換膚框架03之自定義LayoutInflaterFactory

AppCompatActivity是如何創建View的 Activity通過LayoutInflater解析出XmlLayout相關信息LayoutInflater內部維護了一個InflaterFactory對象InflaterFactory接口包含了一個onCreateView方法&#xff0c;用于創建View將解析出的Xml信息轉為AttributeSet&#xff0c;交給Inflate…

安全測試之使用Docker搭建SQL注入安全測試平臺sqli-labs

1 搜索鏡像 docker search sqli-labs 2 拉取鏡像 docker pull acgpiano/sqli-labs 3 創建docker容器 docker run -d --name sqli-labs -p 10012:80 acgpiano/sqli-labs 4 訪問測試平臺網站 若直接使用虛擬機&#xff0c;則直接通過ip端口號訪問若通過配置域名&#xff0…

PyQt多線程詳解

PyQt多線程是在PyQt框架中利用多線程技術來提高應用程序的響應性和性能的一種方法。下面將詳細說明PyQt多線程的基本概念、應用場景以及實現方式。 一、PyQt多線程的基本概念 在PyQt中&#xff0c;多線程指的是在單個程序實例內同時運行多個線程。每個線程都可以執行不同的任…

第十五章 Nest Pipe(內置及自定義)

NestJS的Pipe是一個用于數據轉換和驗證的特殊裝飾器。Pipe可以應用于控制器&#xff08;Controller&#xff09;的處理方法&#xff08;Handler&#xff09;和中間件&#xff08;Middleware&#xff09;&#xff0c;用于處理傳入的數據。它可以用來轉換和驗證數據&#xff0c;確…

【Linux進階】文件系統5——ext2文件系統(inode)

1.再談inode (1) 理解inode&#xff0c;要從文件儲存說起。 文件儲存在硬盤上&#xff0c;硬盤的最小存儲單位叫做"扇區"&#xff08;Sector&#xff09;。每個扇區儲存512字節&#xff08;相當于0.5KB&#xff09;。操作系統讀取硬盤的時候&#xff0c;不會一個個…

記錄excel表生成一列按七天一個周期的方法

使用excel生成每七天一個周期的列。如下圖所示&#xff1a; 針對第一列的生成辦法&#xff0c;使用如下函數&#xff1a; TEXT(DATE(2024,1,1)(ROW()-2)*7,"yyyy/m/d")&" - "&TEXT(DATE(2024,1,1)(ROW()-1)*7-1,"yyyy/m/d") 特此記錄。…

charles使用教程

安裝與配置 下載鏈接&#xff1a;https://www.charlesproxy.com/download/ 進行移動端抓包&#xff1a; 電腦端配置&#xff1a; 關閉防火墻 Proxy–>勾選 macOS Proxy Proxy–>Proxy Setting–>填入代理端口8888–>勾選Enable transparent http proxying 安裝c…

俄羅斯方塊的python實現

俄羅斯方塊游戲是一種經典的拼圖游戲&#xff0c;玩家需要將不同形狀的方塊拼接在一起&#xff0c;使得每一行都被完全填滿&#xff0c;從而清除這一行并獲得積分。以下是該游戲的算法描述&#xff1a; 1. 初始化 初始化游戲界面&#xff0c;設置屏幕大小、方塊大小、網格大小…

昇思25天學習打卡營第1天|初識MindSpore

# 打卡 day1 目錄 # 打卡 day1 初識MindSpore 昇思 MindSpore 是什么&#xff1f; 昇思 MindSpore 優勢|特點 昇思 MindSpore 不足 官方生態學習地址 初識MindSpore 昇思 MindSpore 是什么&#xff1f; 昇思MindSpore 是全場景深度學習架構&#xff0c;為開發者提供了全…

女生學計算機好不好?感覺計算機分有點高……?

眾所周知&#xff0c;在國內的高校里&#xff0c;計算機專業的女生是非常少的&#xff0c;很多小班30人左右&#xff0c;但是每個班女生人數只有個位數。這就給很多人一個感覺&#xff0c;是不是女生天生就不適合學這個東西呢&#xff1f;女生是不是也應該放棄呢&#xff1f;當…

ubuntu 進入命令行

在Ubuntu中&#xff0c;有幾種方法可以進入命令行界面&#xff1a; 啟動時選擇命令行模式&#xff1a; 在計算機啟動時&#xff0c;如果安裝了GRUB引導加載器&#xff0c;可以通過GRUB菜單選擇進入命令行模式。這通常涉及到在啟動時按下Shift鍵或其他指定鍵來顯示GRUB菜單&…

常見算法和Lambda

常見算法和Lambda 文章目錄 常見算法和Lambda常見算法查找算法基本查找&#xff08;順序查找&#xff09;二分查找/折半查找插值查找斐波那契查找分塊查找擴展的分塊查找&#xff08;無規律的數據&#xff09; 常見排序算法冒泡排序選擇排序插入排序快速排序遞歸快速排序 Array…

SpringBoot新手快速入門系列教程二:MySql5.7.44的免安裝版本下載和配置,以及簡單的Mysql生存指令指南。

我們要如何選擇MySql 目前主流的Mysql有5.0、8.0、9.0 主要區別 MySQL 5.0 發布年份&#xff1a;2005年特性&#xff1a; 基礎事務支持存儲過程、觸發器、視圖基礎存儲引擎&#xff08;如MyISAM、InnoDB&#xff09;外鍵支持基本的全文搜索性能和擴展性&#xff1a; 相對較…

2024年江蘇省研究生數學建模競賽B題火箭煙幕彈運用策略優化論文和代碼分析

經過不懈的努力&#xff0c; 2024年江蘇省研究生數學建模競賽B題火箭煙幕彈運用策略優化論文和代碼已完成&#xff0c;代碼為B題全部問題的代碼&#xff0c;論文包括摘要、問題重述、問題分析、模型假設、符號說明、模型的建立和求解&#xff08;問題1模型的建立和求解、問題2模…