無人機實戰系列(三)本地攝像頭+遠程GPU轉換深度圖

這篇文章將結合之前寫的兩篇文章 無人機實戰系列(一)在局域網內傳輸數據 和 無人機實戰系列(二)本地攝像頭 + Depth-Anything V2 實現了以下功能:

  • 本地筆記本攝像頭發布圖像 + 遠程GPU實時處理(無回傳);
  • 【異步】本地筆記本攝像頭發布圖像 + 遠程GPU實時處理(回傳至筆記本并展示);
  • 【同步】本地筆記本攝像頭發布圖像 + 遠程GPU實時處理(回傳至筆記本并展示);

建議在運行這個demo之前先查看先前的兩篇文章以熟悉 zmq 庫與 Depth-Anything V2 這個模型;

這里之所以提供兩個在是否回傳上的demo是因為你需要根據自己的實際狀況進行選擇,盡管回傳并顯示深度圖能夠更加直觀查看計算結果但你仍然需要平衡以下兩個方面:

  • 深度本身體積較大,回傳會占用通訊帶寬;
  • 回傳的圖像本地顯示會占用本地算力;

【注意】:這篇文章的代碼需要在 無人機實戰系列(二)本地攝像頭 + Depth-Anything V2 中的文件夾下運行,否則會報錯找不到對應的文件與模型。


本地筆記本攝像頭發布圖像 + 遠程GPU實時處理(無回傳)

這個demo實現了本地筆記本打開攝像頭后將圖像發布出去,遠程GPU服務器接受到圖像后使用 Depth-Anything V2 處理圖像并展示。

本地筆記本發布攝像頭圖像

在下面的代碼中有以下幾點需要注意:

  1. 設置發布頻率 send_fps,較低的發布頻率可以讓減少GPU端的壓力;
  2. 設置發布隊列大小 socket.setsockopt(zmq.SNDHWM, 1),讓發布隊列始終僅有當前幀畫面,降低帶寬壓力;
  3. 設置僅保存最新消息 socket.setsockopt(zmq.CONFLATE, 1),讓發布隊列僅存儲最新的畫面,降低接受端的畫面延遲;
import zmq
import cv2
import timecontext = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5555")  # 本地綁定端口socket.setsockopt(zmq.SNDHWM, 1)        # 發送隊列大小為1
socket.setsockopt(zmq.CONFLATE, 1)      # 僅保存最新消息cap = cv2.VideoCapture(0)  # 讀取攝像頭send_fps = 30       # 限制傳輸的fps,降低接受方的處理壓力while True:start_time = time.time()ret, frame = cap.read()if not ret:continue_, buffer = cv2.imencode('.jpg', frame)  # 編碼成JPEG格式socket.send(buffer.tobytes())  # 發送圖像數據cv2.imshow("Origin image", frame)if cv2.waitKey(1) & 0xFF == ord('q'):breaktime.sleep(max(1/send_fps - (time.time() - start_time), 0))

運行:

$ python camera_pub.py

在這里插入圖片描述

GPU 服務器接受端

在下面的代碼中有以下幾點需要注意:

  1. 綁定發布端地址 socket.connect("tcp://192.168.75.201:5555"),根據你筆記本的地址進行修改;
  2. 僅接受最新消息 socket.setsockopt(zmq.CONFLATE, 1)
  3. 清空舊數據幀 socket.setsockopt(zmq.RCVHWM, 1) && socket.poll(1)
import argparse
import cv2
import numpy as np
import torch
import time
import zmqfrom depth_anything_v2.dpt import DepthAnythingV2context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://192.168.75.201:5555")		# 遠程發布端地址
socket.setsockopt(zmq.SUBSCRIBE, b"")
socket.setsockopt(zmq.CONFLATE, 1)      # 僅接受最新消息
socket.setsockopt(zmq.RCVHWM, 1)        # 清空舊數據幀if __name__ == '__main__':parser = argparse.ArgumentParser(description='Depth Anything V2')parser.add_argument('--input-size', type=int, default=518)parser.add_argument('--encoder', type=str, default='vits', choices=['vits', 'vitb', 'vitl', 'vitg'])parser.add_argument('--pred-only', action='store_true', help='only display the prediction')parser.add_argument('--grayscale', action='store_true', help='do not apply colorful palette')args = parser.parse_args()DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'model_configs = {'vits': {'encoder': 'vits', 'features': 64, 'out_channels': [48, 96, 192, 384]},'vitb': {'encoder': 'vitb', 'features': 128, 'out_channels': [96, 192, 384, 768]},'vitl': {'encoder': 'vitl', 'features': 256, 'out_channels': [256, 512, 1024, 1024]},'vitg': {'encoder': 'vitg', 'features': 384, 'out_channels': [1536, 1536, 1536, 1536]}}depth_anything = DepthAnythingV2(**model_configs[args.encoder])depth_anything.load_state_dict(torch.load(f'./models/depth_anything_v2_{args.encoder}.pth', map_location='cpu'))depth_anything = depth_anything.to(DEVICE).eval()margin_width = 50while True:start_time = time.time()# **優化 1: ZMQ 數據接收**try:while socket.poll(1):  # 嘗試不斷讀取新數據,丟棄舊數據msg = socket.recv(zmq.NOBLOCK)zmq_time = time.time()# **優化 2: OpenCV 解碼**raw_frame = cv2.imdecode(np.frombuffer(msg, dtype=np.uint8), 1)decode_time = time.time()# **優化 3: 模型推理**with torch.no_grad():depth = depth_anything.infer_image(raw_frame, args.input_size)infer_time = time.time()# **優化 4: 歸一化 + OpenCV 偽彩色映射**depth = ((depth - depth.min()) / (depth.max() - depth.min()) * 255).astype(np.uint8)if args.grayscale:depth = np.repeat(depth[..., np.newaxis], 3, axis=-1)else:depth = cv2.applyColorMap(depth, cv2.COLORMAP_JET)process_time = time.time()# **優化 5: 合并圖像**split_region = np.ones((raw_frame.shape[0], margin_width, 3), dtype=np.uint8) * 255combined_frame = cv2.hconcat([raw_frame, split_region, depth])cv2.imshow('Raw Frame and Depth Prediction', combined_frame)if cv2.waitKey(1) & 0xFF == ord('q'):breakprint(f"[{args.encoder}] Frame cost time: {time.time() - start_time:.4f} s")print(f"  ZMQ receive:  {zmq_time - start_time:.4f} s")print(f"  Decode:       {decode_time - zmq_time:.4f} s")print(f"  Inference:    {infer_time - decode_time:.4f} s")print(f"  Processing:   {process_time - infer_time:.4f} s")except zmq.Again:print("No msg received, skip...")continue  # 沒有消息就跳過cv2.destroyAllWindows()

運行:

$ python camera_recv.py

在這里插入圖片描述


【異步】本地筆記本攝像頭發布圖像 + 遠程GPU實時處理(回傳至筆記本并展示)

和上面的代碼基本一致,只不過在發送與接收端都增加了一個收發對象,通常情況下使用異步方式處理收發因為可以避免一端服務來不及處理而導致另一端持續等待。

本地筆記本發布攝像頭圖像

import zmq
import cv2
import numpy as np
import timecontext = zmq.Context()# 發布原始數據
pub_socket = context.socket(zmq.PUB)
pub_socket.bind("tcp://*:5555")  # 發布數據# 接收處理結果
pull_socket = context.socket(zmq.PULL)
pull_socket.bind("tcp://*:5556")  # 監聽處理方返回數據send_fps = 30cap = cv2.VideoCapture(0)while True:start_time = time.time()ret, frame = cap.read()if not ret:continue# [可選] 圖像降采樣frame = cv2.pyrDown(frame)frame = cv2.pyrDown(frame)_, buffer = cv2.imencode('.jpg', frame)  # 壓縮圖像pub_socket.send(buffer.tobytes())  # 發布數據# 非阻塞接收處理結果try:processed_data = pull_socket.recv(zmq.NOBLOCK)processed_frame = cv2.imdecode(np.frombuffer(processed_data, dtype=np.uint8), 1)except zmq.Again:print("No image received, continue...")continuecv2.imshow("Processed Frame", processed_frame)if cv2.waitKey(1) & 0xFF == ord('q'):breaktime.sleep(max(1/send_fps - (time.time() - start_time), 0))cv2.destroyAllWindows()

運行:

$ python camera_pub_async.py

在這里插入圖片描述

GPU 服務器接受端(異步)

import argparse
import cv2
import numpy as np
import torch
import time
import zmqfrom depth_anything_v2.dpt import DepthAnythingV2context = zmq.Context()
sub_socket = context.socket(zmq.SUB)
sub_socket.connect("tcp://192.168.75.201:5555")
sub_socket.setsockopt(zmq.SUBSCRIBE, b"")
sub_socket.setsockopt(zmq.CONFLATE, 1)      # 僅接受最新消息
sub_socket.setsockopt(zmq.RCVHWM, 1)        # 清空舊數據幀# 發送處理結果
push_socket = context.socket(zmq.PUSH)
push_socket.connect("tcp://192.168.75.201:5556")if __name__ == '__main__':parser = argparse.ArgumentParser(description='Depth Anything V2')parser.add_argument('--input-size', type=int, default=518)parser.add_argument('--encoder', type=str, default='vits', choices=['vits', 'vitb', 'vitl', 'vitg'])parser.add_argument('--pred-only', action='store_true', help='only display the prediction')parser.add_argument('--grayscale', action='store_true', help='do not apply colorful palette')args = parser.parse_args()DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'model_configs = {'vits': {'encoder': 'vits', 'features': 64, 'out_channels': [48, 96, 192, 384]},'vitb': {'encoder': 'vitb', 'features': 128, 'out_channels': [96, 192, 384, 768]},'vitl': {'encoder': 'vitl', 'features': 256, 'out_channels': [256, 512, 1024, 1024]},'vitg': {'encoder': 'vitg', 'features': 384, 'out_channels': [1536, 1536, 1536, 1536]}}depth_anything = DepthAnythingV2(**model_configs[args.encoder])depth_anything.load_state_dict(torch.load(f'./models/depth_anything_v2_{args.encoder}.pth', map_location='cpu'))depth_anything = depth_anything.to(DEVICE).eval()margin_width = 50while True:start_time = time.time()# **優化 1: ZMQ 數據接收**try:while sub_socket.poll(1):  # 嘗試不斷讀取新數據,丟棄舊數據msg = sub_socket.recv(zmq.NOBLOCK)msg = sub_socket.recv()zmq_time = time.time()# **優化 2: OpenCV 解碼**raw_frame = cv2.imdecode(np.frombuffer(msg, dtype=np.uint8), 1)decode_time = time.time()# **優化 3: 模型推理**with torch.no_grad():depth = depth_anything.infer_image(raw_frame, args.input_size)infer_time = time.time()# **優化 4: 歸一化 + OpenCV 偽彩色映射**depth = ((depth - depth.min()) / (depth.max() - depth.min()) * 255).astype(np.uint8)if args.grayscale:depth = np.repeat(depth[..., np.newaxis], 3, axis=-1)else:depth = cv2.applyColorMap(depth, cv2.COLORMAP_JET)process_time = time.time()# **優化 5: 合并圖像**split_region = np.ones((raw_frame.shape[0], margin_width, 3), dtype=np.uint8) * 255combined_frame = cv2.hconcat([raw_frame, split_region, depth])cv2.imshow('Raw Frame and Depth Prediction', combined_frame)if cv2.waitKey(1) & 0xFF == ord('q'):breakprint(f"[{args.encoder}] Frame cost time: {time.time() - start_time:.4f} s")print(f"  ZMQ receive:  {zmq_time - start_time:.4f} s")print(f"  Decode:       {decode_time - zmq_time:.4f} s")print(f"  Inference:    {infer_time - decode_time:.4f} s")print(f"  Processing:   {process_time - infer_time:.4f} s")_, buffer = cv2.imencode('.jpg', combined_frame)push_socket.send(buffer.tobytes())  # 發送回處理結果except zmq.Again:print("No msg received, skip...")continue  # 沒有消息就跳過cv2.destroyAllWindows()

運行:

$ python camera_recv_async.py

在這里插入圖片描述


【同步】本地筆記本攝像頭發布圖像 + 遠程GPU實時處理(回傳至筆記本并展示)

通常情況下這種視頻流的傳遞不會考慮同步方式,因為這需要發布方與接收端保持一致,對網絡穩定性有較高的要求。

本地筆記本發布攝像頭圖像

這個demo需要注意以下幾點:

  1. 設置發送端為請求響應模式 context.socket(zmq.REQ)
  2. 阻塞等待服務器回傳數據 pub_socket.recv()
import zmq
import cv2
import numpy as np
import timecontext = zmq.Context()# 發布原始數據
pub_socket = context.socket(zmq.REQ)        # 使用請求響應模式
pub_socket.bind("tcp://*:5555")  # 發布數據send_fps = 30
cap = cv2.VideoCapture(0)while True:start_time = time.time()ret, frame = cap.read()if not ret:continue# [可選] 圖像降采樣frame = cv2.pyrDown(frame)frame = cv2.pyrDown(frame)try:_, buffer = cv2.imencode('.jpg', frame)  # 壓縮圖像pub_socket.send(buffer.tobytes())  # 發布數據print("Waitting for server processed.")processed_data = pub_socket.recv()processed_frame = cv2.imdecode(np.frombuffer(processed_data, dtype=np.uint8), 1)except zmq.Again:print("No image received, continue...")continuecv2.imshow("Processed Frame", processed_frame)if cv2.waitKey(1) & 0xFF == ord('q'):breaktime.sleep(max(1/send_fps - (time.time() - start_time), 0))cv2.destroyAllWindows()

運行:

$ python camera_pub_sync.py

在這里插入圖片描述

GPU 服務器接受端

這個demo需要注意以下幾點:

  1. 設置接受端為請求響應模式 context.socket(zmq.REP)
  2. 阻塞接受發布端數據 sub_socket.recv()
  3. 將處理好的數據進行同步回傳 sub_socket.send(buffer.tobytes())
import argparse
import cv2
import numpy as np
import torch
import time
import zmqfrom depth_anything_v2.dpt import DepthAnythingV2context = zmq.Context()
sub_socket = context.socket(zmq.REP)
sub_socket.connect("tcp://192.168.75.201:5555")if __name__ == '__main__':parser = argparse.ArgumentParser(description='Depth Anything V2')parser.add_argument('--input-size', type=int, default=518)parser.add_argument('--encoder', type=str, default='vits', choices=['vits', 'vitb', 'vitl', 'vitg'])parser.add_argument('--pred-only', action='store_true', help='only display the prediction')parser.add_argument('--grayscale', action='store_true', help='do not apply colorful palette')args = parser.parse_args()DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'model_configs = {'vits': {'encoder': 'vits', 'features': 64, 'out_channels': [48, 96, 192, 384]},'vitb': {'encoder': 'vitb', 'features': 128, 'out_channels': [96, 192, 384, 768]},'vitl': {'encoder': 'vitl', 'features': 256, 'out_channels': [256, 512, 1024, 1024]},'vitg': {'encoder': 'vitg', 'features': 384, 'out_channels': [1536, 1536, 1536, 1536]}}depth_anything = DepthAnythingV2(**model_configs[args.encoder])depth_anything.load_state_dict(torch.load(f'./models/depth_anything_v2_{args.encoder}.pth', map_location='cpu'))depth_anything = depth_anything.to(DEVICE).eval()margin_width = 50while True:start_time = time.time()# **優化 1: ZMQ 數據接收**try:msg = sub_socket.recv()zmq_time = time.time()# **優化 2: OpenCV 解碼**raw_frame = cv2.imdecode(np.frombuffer(msg, dtype=np.uint8), 1)decode_time = time.time()# **優化 3: 模型推理**with torch.no_grad():depth = depth_anything.infer_image(raw_frame, args.input_size)infer_time = time.time()# **優化 4: 歸一化 + OpenCV 偽彩色映射**depth = ((depth - depth.min()) / (depth.max() - depth.min()) * 255).astype(np.uint8)if args.grayscale:depth = np.repeat(depth[..., np.newaxis], 3, axis=-1)else:depth = cv2.applyColorMap(depth, cv2.COLORMAP_JET)process_time = time.time()# **優化 5: 合并圖像**split_region = np.ones((raw_frame.shape[0], margin_width, 3), dtype=np.uint8) * 255combined_frame = cv2.hconcat([raw_frame, split_region, depth])cv2.imshow('Raw Frame and Depth Prediction', combined_frame)if cv2.waitKey(1) & 0xFF == ord('q'):breakprint(f"[{args.encoder}] Frame cost time: {time.time() - start_time:.4f} s")print(f"  ZMQ receive:  {zmq_time - start_time:.4f} s")print(f"  Decode:       {decode_time - zmq_time:.4f} s")print(f"  Inference:    {infer_time - decode_time:.4f} s")print(f"  Processing:   {process_time - infer_time:.4f} s")_, buffer = cv2.imencode('.jpg', combined_frame)sub_socket.send(buffer.tobytes())  # 發送回處理結果except zmq.Again:print("No msg received, skip...")continue  # 沒有消息就跳過cv2.destroyAllWindows()

運行:

$ python camera_recv_sync.py

在這里插入圖片描述

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

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

相關文章

讀取羅克韋爾AllenBradley Micro-Logix1400 羅克韋爾 CIP PCCC通信協議

通信協議實例下載 <-----實例下載 MicroLogix 1400的通信能力 MicroLogix 1400支持多種通信協議&#xff0c;包括CIP&#xff08;通過EtherNet/IP實現&#xff09;、Modbus RTU/TCP、DF1等4812。其硬件集成以太網端口&#xff0c;便于通過EtherNet/IP進行CIP通信15。 CIP…

Python游戲編程之賽車游戲6-5

1 碰撞檢測 在顯示了玩家汽車和“敵人”汽車之后&#xff0c;接下來就要實現玩家與“敵人”的碰撞檢測了。 代碼如圖1所示。 圖1 碰撞檢測代碼 第72行代碼通過pygame.sprite.spritecollideany()函數判斷P1和enemies是否發生了碰撞&#xff0c;如果發生碰撞&#xff0c;該函數…

【QT 網絡編程】HTTP協議(二)

文章目錄 &#x1f31f;1.概述&#x1f31f;2.代碼結構概覽&#x1f31f;3.代碼解析&#x1f338;Http_Api_Manager - API管理類&#x1f338;Http_Request_Manager- HTTP請求管理類&#x1f338;ThreadPool - 線程池&#x1f338;TestWindow- 測試類 &#x1f31f;4.運行效果&…

保姆級! 本地部署DeepSeek-R1大模型 安裝Ollama Api 后,Postman本地調用 deepseek

要在Postman中訪問Ollama API并調用DeepSeek模型,你需要遵循以下步驟。首先,確保你有一個有效的Ollama服務器實例運行中,并且DeepSeek模型已經被加載。 可以參考我的這篇博客 保姆級!使用Ollama本地部署DeepSeek-R1大模型 并java通過api 調用 具體的代碼實現參考我這個博…

在PHP Web開發中,實現異步處理有幾種常見方式的優缺點,以及最佳實踐推薦方法

1. 消息隊列 使用消息隊列&#xff08;如RabbitMQ、Beanstalkd、Redis&#xff09;將任務放入隊列&#xff0c;由后臺進程異步處理。 優點&#xff1a; 任務持久化&#xff0c;系統崩潰后任務不丟失。 支持分布式處理&#xff0c;擴展性強。 實現步驟&#xff1a; 安裝消息…

算法15--BFS

BFS 原理經典例題解決FloodFill 算法[733. 圖像渲染](https://leetcode.cn/problems/flood-fill/description/)[200. 島嶼數量](https://leetcode.cn/problems/number-of-islands/description/)[695. 島嶼的最大面積](https://leetcode.cn/problems/max-area-of-island/descrip…

網絡空間安全(2)應用程序安全

前言 應用程序安全&#xff08;Application Security&#xff0c;簡稱AppSec&#xff09;是一個綜合性的概念&#xff0c;它涵蓋了應用程序從開發到部署&#xff0c;再到后續維護的整個過程中的安全措施。 一、定義與重要性 定義&#xff1a;應用程序安全是指識別和修復應用程序…

Plantsimulation中機器人怎么通過阻塞角度設置旋轉135°

創建一個這樣的簡單模型。 檢查PickAndPlace的角度表。源位于180的角位置&#xff0c;而物料終結位于90的角位置。“返回默認位置”選項未被勾選。源每分鐘生成一個零件。啟動模擬時&#xff0c;Plant Simulation會選擇兩個位置之間的最短路徑。示例中的機器人無法繞135的角位…

Fisher信息矩陣(Fisher Information Matrix, FIM)與自然梯度下降:機器學習中的優化利器

Fisher信息矩陣與自然梯度下降&#xff1a;機器學習中的優化利器 在機器學習尤其是深度學習中&#xff0c;優化模型參數是一個核心任務。我們通常依賴梯度下降&#xff08;Gradient Descent&#xff09;來調整參數&#xff0c;但普通的梯度下降有時會顯得“笨拙”&#xff0c;…

Spring Boot集成Swagger API文檔:傻瓜式零基礎教程

Springfox Swagger 是一個用于構建基于 Spring Boot 的 RESTful API 文檔的開源工具。它通過使用注解來描述 API 端點&#xff0c;自動生成易于閱讀和理解的 API 文檔。Springfox 通過在運行時檢查應用程序&#xff0c;基于 Spring 配置、類結構和各種編譯時 Java 注釋來推斷 A…

接口測試基礎 --- 什么是接口測試及其測試流程?

接口測試是軟件測試中的一個重要部分&#xff0c;它主要用于驗證和評估不同軟件組件之間的通信和交互。接口測試的目標是確保不同的系統、模塊或組件能夠相互連接并正常工作。 接口測試流程可以分為以下幾個步驟&#xff1a; 1.需求分析&#xff1a;首先&#xff0c;需要仔細…

kafka-集群縮容

一. 簡述&#xff1a; 當業務增加時&#xff0c;服務瓶頸&#xff0c;我們需要進行擴容。當業務量下降時&#xff0c;為成本考慮。自然也會涉及到縮容。假設集群有 15 臺機器&#xff0c;預計縮到 10 臺機器&#xff0c;那么需要做 5 次縮容操作&#xff0c;每次將一個節點下線…

Spring Boot 概要(官網文檔解讀)

Spring Boot 概述 Spring Boot 是一個高效構建 Spring 生產級應用的腳手架工具&#xff0c;它簡化了基于 Spring 框架的開發過程。 Spring Boot 也是一個“構件組裝門戶”&#xff0c;何為構件組裝門戶呢&#xff1f;所謂的“構件組裝門戶”指的是一個對外提供的Web平臺&#x…

Linux 命令大全完整版(12)

Linux 命令大全 5. 文件管理命令 ln(link) 功能說明&#xff1a;連接文件或目錄。語  法&#xff1a;ln [-bdfinsv][-S <字尾備份字符串>][-V <備份方式>][--help][--version][源文件或目錄][目標文件或目錄] 或 ln [-bdfinsv][-S <字尾備份字符串>][-V…

遺傳算法初探

組成要素 編碼 分為二進制編碼、實數編碼和順序編碼 初始種群的產生 分為隨機方法、基于反向學習優化的種群產生。 基于反向學習優化的種群其思想是先隨機生成一個種群P(N)&#xff0c;然后按照反向學習方法生成新的種群OP(N),合并兩個種群&#xff0c;得到一個新的種群S(N…

【算法】堆

堆 heap&#xff0c;一棵完全二叉樹&#xff0c;使用數組實現的&#xff0c;但具備完全二叉樹的一些性質。一般總是滿足以下性質&#xff1a; 堆中某個節點的值總是不大于或不小于其父節點的值&#xff1b;堆總是一棵完全二叉樹。&#xff08;即除了最底層&#xff0c;其他層…

C/C++高性能Web開發框架全解析:2025技術選型指南

一、工業級框架深度解析&#xff08;附性能實測&#xff09; 1. Drogon v2.1&#xff1a;異步框架性能王者 核心架構&#xff1a; Reactor 非阻塞I/O線程池&#xff08;參考Nginx模型&#xff09; 協程實現&#xff1a;基于Boost.Coroutine2&#xff08;兼容C11&#xff09;…

使用PHP接入純真IP庫:實現IP地址地理位置查詢

引言 在日常開發中,我們經常需要根據用戶的IP地址獲取其地理位置信息,例如國家、省份、城市等。純真IP庫(QQWry)是一個常用的IP地址數據庫,提供了豐富的IP地址與地理位置的映射關系。本文將介紹如何使用PHP接入純真IP庫,并通過一個完整的案例演示如何實現IP地址的地理位…

Django ORM 的常用字段類型、外鍵關聯的跨表引用技巧,以及 `_` 和 `__` 的使用場景

一、Django ORM 常用字段類型 1. 基礎字段類型 字段類型說明示例CharField字符串字段&#xff0c;必須指定 max_lengthname models.CharField(max_length50)IntegerField整數字段age models.IntegerField()BooleanField布爾值字段is_active models.BooleanField()DateFiel…

java遞歸求自然數列的前n項和

概述 實現 /*** 數列 1 2 3 ... n ...* 遞歸求數列的前n項和* param n* return*/private static long calSum(long n){if (n1) return 1;else {return ncalSum(n-1); // 前n項的和 即第n項的值前n-1項的和}}測試用例 public static void main(String[] args) {long res1 cal…