当前位置: 首页 > news >正文

C# 使用.NET的SocketAsyncEventArgs实现高效能多并发TCPSocket通信

简介:

 SocketAsyncEventArgs是一个套接字操作得类,主要作用是实现socket消息的异步接收和发送,跟Socket的BeginSend和BeginReceive方法异步处理没有多大区别,它的优势在于完成端口的实现来处理大数据的并发情况。

  • BufferManager类, 管理传输流的大小
  • SocketEventPool类: 管理SocketAsyncEventArgs的一个应用池. 有效地重复使用.
  •  AsyncUserToken类: 这个可以根据自己的实际情况来定义.主要作用就是存储客户端的信息.
  • SocketManager类: 核心,实现Socket监听,收发信息等操作.
  • 额外功能   1.自动检测无效连接并断开    2.自动释放资源

BufferManager类

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Net.Sockets;  
using System.Text;  namespace Plates.Service  
{  class BufferManager  {  int m_numBytes;                 // the total number of bytes controlled by the buffer pool  byte[] m_buffer;                // the underlying byte array maintained by the Buffer Manager  Stack<int> m_freeIndexPool;     //   int m_currentIndex;  int m_bufferSize;  public BufferManager(int totalBytes, int bufferSize)  {  m_numBytes = totalBytes;  m_currentIndex = 0;  m_bufferSize = bufferSize;  m_freeIndexPool = new Stack<int>();  }  // Allocates buffer space used by the buffer pool  public void InitBuffer()  {  // create one big large buffer and divide that   // out to each SocketAsyncEventArg object  m_buffer = new byte[m_numBytes];  }  // Assigns a buffer from the buffer pool to the   // specified SocketAsyncEventArgs object  //  // <returns>true if the buffer was successfully set, else false</returns>  public bool SetBuffer(SocketAsyncEventArgs args)  {  if (m_freeIndexPool.Count > 0)  {  args.SetBuffer(m_buffer, m_freeIndexPool.Pop(), m_bufferSize);  }  else  {  if ((m_numBytes - m_bufferSize) < m_currentIndex)  {  return false;  }  args.SetBuffer(m_buffer, m_currentIndex, m_bufferSize);  m_currentIndex += m_bufferSize;  }  return true;  }  // Removes the buffer from a SocketAsyncEventArg object.    // This frees the buffer back to the buffer pool  public void FreeBuffer(SocketAsyncEventArgs args)  {  m_freeIndexPool.Push(args.Offset);  args.SetBuffer(null, 0, 0);  }  }  
}  

    

SocketEventPool类:

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Net.Sockets;  
using System.Text;  namespace Plates.Service  
{  class SocketEventPool  {  Stack<SocketAsyncEventArgs> m_pool;  public SocketEventPool(int capacity)  {  m_pool = new Stack<SocketAsyncEventArgs>(capacity);  }  public void Push(SocketAsyncEventArgs item)  {  if (item == null) { throw new ArgumentNullException("Items added to a SocketAsyncEventArgsPool cannot be null"); }  lock (m_pool)  {  m_pool.Push(item);  }  }  // Removes a SocketAsyncEventArgs instance from the pool  // and returns the object removed from the pool  public SocketAsyncEventArgs Pop()  {  lock (m_pool)  {  return m_pool.Pop();  }  }  // The number of SocketAsyncEventArgs instances in the pool  public int Count  {  get { return m_pool.Count; }  }  public void Clear()  {  m_pool.Clear();  }  }  
}  

 AsyncUserToken类

using System;  
using System.Collections;  
using System.Collections.Generic;  
using System.Linq;  
using System.Net;  
using System.Net.Sockets;  
using System.Text;  namespace Plates.Service  
{  class AsyncUserToken  {  /// <summary>  /// 客户端IP地址  /// </summary>  public IPAddress IPAddress { get; set; }  /// <summary>  /// 远程地址  /// </summary>  public EndPoint Remote { get; set; }  /// <summary>  /// 通信SOKET  /// </summary>  public Socket Socket { get; set; }  /// <summary>  /// 连接时间  /// </summary>  public DateTime ConnectTime { get; set; }  /// <summary>  /// 所属用户信息  /// </summary>  public UserInfoModel UserInfo { get; set; }  /// <summary>  /// 数据缓存区  /// </summary>  public List<byte> Buffer { get; set; }  public AsyncUserToken()  {  this.Buffer = new List<byte>();  }  }  
}  

  SocketManager类

using Plates.Common;  
using System;  
using System.Collections;  
using System.Collections.Generic;  
using System.Linq;  
using System.Net;  
using System.Net.Sockets;  
using System.Text;  
using System.Threading;  namespace Plates.Service  
{  class SocketManager  {  private int m_maxConnectNum;    //最大连接数  private int m_revBufferSize;    //最大接收字节数  BufferManager m_bufferManager;  const int opsToAlloc = 2;  Socket listenSocket;            //监听Socket  SocketEventPool m_pool;  int m_clientCount;              //连接的客户端数量  Semaphore m_maxNumberAcceptedClients;  List<AsyncUserToken> m_clients; //客户端列表  #region 定义委托  /// <summary>  /// 客户端连接数量变化时触发  /// </summary>  /// <param name="num">当前增加客户的个数(用户退出时为负数,增加时为正数,一般为1)</param>  /// <param name="token">增加用户的信息</param>  public delegate void OnClientNumberChange(int num, AsyncUserToken token);  /// <summary>  /// 接收到客户端的数据  /// </summary>  /// <param name="token">客户端</param>  /// <param name="buff">客户端数据</param>  public delegate void OnReceiveData(AsyncUserToken token, byte[] buff);  #endregion  #region 定义事件  /// <summary>  /// 客户端连接数量变化事件  /// </summary>  public event OnClientNumberChange ClientNumberChange;  /// <summary>  /// 接收到客户端的数据事件  /// </summary>  public event OnReceiveData ReceiveClientData;  #endregion  #region 定义属性  /// <summary>  /// 获取客户端列表  /// </summary>  public List<AsyncUserToken> ClientList { get { return m_clients; } }  #endregion  /// <summary>  /// 构造函数  /// </summary>  /// <param name="numConnections">最大连接数</param>  /// <param name="receiveBufferSize">缓存区大小</param>  public SocketManager(int numConnections, int receiveBufferSize)  {  m_clientCount = 0;  m_maxConnectNum = numConnections;  m_revBufferSize = receiveBufferSize;  // allocate buffers such that the maximum number of sockets can have one outstanding read and   //write posted to the socket simultaneously    m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToAlloc, receiveBufferSize);  m_pool = new SocketEventPool(numConnections);  m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);  }  /// <summary>  /// 初始化  /// </summary>  public void Init()  {  // Allocates one large byte buffer which all I/O operations use a piece of.  This gaurds   // against memory fragmentation  m_bufferManager.InitBuffer();  m_clients = new List<AsyncUserToken>();  // preallocate pool of SocketAsyncEventArgs objects  SocketAsyncEventArgs readWriteEventArg;  for (int i = 0; i < m_maxConnectNum; i++)  {  readWriteEventArg = new SocketAsyncEventArgs();  readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);  readWriteEventArg.UserToken = new AsyncUserToken();  // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object  m_bufferManager.SetBuffer(readWriteEventArg);  // add SocketAsyncEventArg to the pool  m_pool.Push(readWriteEventArg);  }  }  /// <summary>  /// 启动服务  /// </summary>  /// <param name="localEndPoint"></param>  public bool Start(IPEndPoint localEndPoint)  {  try  {  m_clients.Clear();  listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);  listenSocket.Bind(localEndPoint);  // start the server with a listen backlog of 100 connections  listenSocket.Listen(m_maxConnectNum);  // post accepts on the listening socket  StartAccept(null);  return true;  }  catch (Exception)  {  return false;  }  }  /// <summary>  /// 停止服务  /// </summary>  public void Stop()  {  foreach (AsyncUserToken token in m_clients)  {  try  {  token.Socket.Shutdown(SocketShutdown.Both);  }  catch (Exception) { }  }  try  {  listenSocket.Shutdown(SocketShutdown.Both);  }  catch (Exception) { }  listenSocket.Close();  int c_count = m_clients.Count;  lock (m_clients) { m_clients.Clear(); }  if (ClientNumberChange != null)  ClientNumberChange(-c_count, null);  }  public void CloseClient(AsyncUserToken token)  {  try  {  token.Socket.Shutdown(SocketShutdown.Both);  }  catch (Exception) { }  }  // Begins an operation to accept a connection request from the client   //  // <param name="acceptEventArg">The context object to use when issuing   // the accept operation on the server's listening socket</param>  public void StartAccept(SocketAsyncEventArgs acceptEventArg)  {  if (acceptEventArg == null)  {  acceptEventArg = new SocketAsyncEventArgs();  acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);  }  else  {  // socket must be cleared since the context object is being reused  acceptEventArg.AcceptSocket = null;  }  m_maxNumberAcceptedClients.WaitOne();  if (!listenSocket.AcceptAsync(acceptEventArg))  {  ProcessAccept(acceptEventArg);  }  }  // This method is the callback method associated with Socket.AcceptAsync   // operations and is invoked when an accept operation is complete  //  void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)  {  ProcessAccept(e);  }  private void ProcessAccept(SocketAsyncEventArgs e)  {  try  {  Interlocked.Increment(ref m_clientCount);  // Get the socket for the accepted client connection and put it into the   //ReadEventArg object user token  SocketAsyncEventArgs readEventArgs = m_pool.Pop();  AsyncUserToken userToken = (AsyncUserToken)readEventArgs.UserToken;  userToken.Socket = e.AcceptSocket;  userToken.ConnectTime = DateTime.Now;  userToken.Remote = e.AcceptSocket.RemoteEndPoint;  userToken.IPAddress = ((IPEndPoint)(e.AcceptSocket.RemoteEndPoint)).Address;  lock (m_clients) { m_clients.Add(userToken); }  if (ClientNumberChange != null)  ClientNumberChange(1, userToken);  if (!e.AcceptSocket.ReceiveAsync(readEventArgs))  {  ProcessReceive(readEventArgs);  }  }  catch (Exception me)  {  RuncomLib.Log.LogUtils.Info(me.Message + "\r\n" + me.StackTrace);  }  // Accept the next connection request  if (e.SocketError == SocketError.OperationAborted) return;  StartAccept(e);  }  void IO_Completed(object sender, SocketAsyncEventArgs e)  {  // determine which type of operation just completed and call the associated handler  switch (e.LastOperation)  {  case SocketAsyncOperation.Receive:  ProcessReceive(e);  break;  case SocketAsyncOperation.Send:  ProcessSend(e);  break;  default:  throw new ArgumentException("The last operation completed on the socket was not a receive or send");  }  }  // This method is invoked when an asynchronous receive operation completes.   // If the remote host closed the connection, then the socket is closed.    // If data was received then the data is echoed back to the client.  //  private void ProcessReceive(SocketAsyncEventArgs e)  {  try  {  // check if the remote host closed the connection  AsyncUserToken token = (AsyncUserToken)e.UserToken;  if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)  {  //读取数据  byte[] data = new byte[e.BytesTransferred];  Array.Copy(e.Buffer, e.Offset, data, 0, e.BytesTransferred);  lock (token.Buffer)  {  token.Buffer.AddRange(data);  }  //注意:你一定会问,这里为什么要用do-while循环?   //如果当客户发送大数据流的时候,e.BytesTransferred的大小就会比客户端发送过来的要小,  //需要分多次接收.所以收到包的时候,先判断包头的大小.够一个完整的包再处理.  //如果客户短时间内发送多个小数据包时, 服务器可能会一次性把他们全收了.  //这样如果没有一个循环来控制,那么只会处理第一个包,  //剩下的包全部留在token.Buffer中了,只有等下一个数据包过来后,才会放出一个来.  do  {  //判断包的长度  byte[] lenBytes = token.Buffer.GetRange(0, 4).ToArray();  int packageLen = BitConverter.ToInt32(lenBytes, 0);  if (packageLen > token.Buffer.Count - 4)  {   //长度不够时,退出循环,让程序继续接收  break;  }  //包够长时,则提取出来,交给后面的程序去处理  byte[] rev = token.Buffer.GetRange(4, packageLen).ToArray();  //从数据池中移除这组数据  lock (token.Buffer)  {  token.Buffer.RemoveRange(0, packageLen + 4);  }  //将数据包交给后台处理,这里你也可以新开个线程来处理.加快速度.  if(ReceiveClientData != null)  ReceiveClientData(token, rev);  //这里API处理完后,并没有返回结果,当然结果是要返回的,却不是在这里, 这里的代码只管接收.  //若要返回结果,可在API处理中调用此类对象的SendMessage方法,统一打包发送.不要被微软的示例给迷惑了.  } while (token.Buffer.Count > 4);  //继续接收. 为什么要这么写,请看Socket.ReceiveAsync方法的说明  if (!token.Socket.ReceiveAsync(e))  this.ProcessReceive(e);  }  else  {  CloseClientSocket(e);  }  }  catch (Exception xe)  {  RuncomLib.Log.LogUtils.Info(xe.Message + "\r\n" + xe.StackTrace);  }  }  // This method is invoked when an asynchronous send operation completes.    // The method issues another receive on the socket to read any additional   // data sent from the client  //  // <param name="e"></param>  private void ProcessSend(SocketAsyncEventArgs e)  {  if (e.SocketError == SocketError.Success)  {  // done echoing data back to the client  AsyncUserToken token = (AsyncUserToken)e.UserToken;  // read the next block of data send from the client  bool willRaiseEvent = token.Socket.ReceiveAsync(e);  if (!willRaiseEvent)  {  ProcessReceive(e);  }  }  else  {  CloseClientSocket(e);  }  }  //关闭客户端  private void CloseClientSocket(SocketAsyncEventArgs e)  {  AsyncUserToken token = e.UserToken as AsyncUserToken;  lock (m_clients) { m_clients.Remove(token); }  //如果有事件,则调用事件,发送客户端数量变化通知  if (ClientNumberChange != null)  ClientNumberChange(-1, token);  // close the socket associated with the client  try  {  token.Socket.Shutdown(SocketShutdown.Send);  }  catch (Exception) { }  token.Socket.Close();  // decrement the counter keeping track of the total number of clients connected to the server  Interlocked.Decrement(ref m_clientCount);  m_maxNumberAcceptedClients.Release();  // Free the SocketAsyncEventArg so they can be reused by another client  e.UserToken = new AsyncUserToken();  m_pool.Push(e);  }  /// <summary>  /// 对数据进行打包,然后再发送  /// </summary>  /// <param name="token"></param>  /// <param name="message"></param>  /// <returns></returns>  public void SendMessage(AsyncUserToken token, byte[] message)  {  if (token == null || token.Socket == null || !token.Socket.Connected)  return;  try  {  //对要发送的消息,制定简单协议,头4字节指定包的大小,方便客户端接收(协议可以自己定)  byte[] buff = new byte[message.Length + 4];  byte[] len = BitConverter.GetBytes(message.Length);  Array.Copy(len, buff, 4);  Array.Copy(message, 0, buff, 4, message.Length);  //token.Socket.Send(buff);  //这句也可以发送, 可根据自己的需要来选择  //新建异步发送对象, 发送消息  SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs();  sendArg.UserToken = token;  sendArg.SetBuffer(buff, 0, buff.Length);  //将数据放置进去.  token.Socket.SendAsync(sendArg);  }  catch (Exception e){  RuncomLib.Log.LogUtils.Info("SendMessage - Error:" + e.Message);  }  }  }  
}  

使用方法:

SocketManager m_socket = new SocketManager(200, 1024);  

m_socket.Init();  

m_socket.Start(new IPEndPoint(IPAddress.Any, 13909));  

//m_socket.Stop();

相关文章:

C# 使用.NET的SocketAsyncEventArgs实现高效能多并发TCPSocket通信

简介&#xff1a; SocketAsyncEventArgs是一个套接字操作得类&#xff0c;主要作用是实现socket消息的异步接收和发送&#xff0c;跟Socket的BeginSend和BeginReceive方法异步处理没有多大区别&#xff0c;它的优势在于完成端口的实现来处理大数据的并发情况。 BufferManager类…...

设计模式——观察者模式(Observer Pattern)+ Spring相关源码

文章目录 一、观察者模式定义二、例子2.1 菜鸟教程例子2.1.1 定义观察者2.1.2 定义被观察对象2.1.3 使用 2.2 JDK源码 —— Observable2.2.1 观察者接口Observer2.2.1 被观察者对象Observable 2.3 Spring源码 —— AbstractApplicationContext2.3.1 观察者2.3.2 被观察者 2.3 G…...

openpnp - code review - 开机对话框历史记录和贡献者名单

文章目录 openpnp - code review - 开机对话框历史记录和贡献者名单概述笔记D:\my_openpnp\openpnp_dev_2022_0801\src\main\java\org\openpnp\gui\AboutDialog.javaEND openpnp - code review - 开机对话框历史记录和贡献者名单 概述 偶然发现, 自己打包后的openpnp, 开机后…...

JavaSE22——HashMap

集合框架_HashMap 一、概述 HashMap 是用于存储 Key-Value 键值对的集合。 &#xff08;1&#xff09;HashMap 根据键的 hashCode 值存储数据&#xff0c;大多数情况下可以直接定位到它的值&#xff0c;所以具有很快的访问速度&#xff0c;但遍历顺序不确定。 &#xff08;2&…...

「图像 merge」无中生有制造数据

在进行一个新项目的时候&#xff0c;往往缺少一些真实数据&#xff0c;导致没办法进行模型训练&#xff0c;这时候就需要算法工程师自行制作一些数据了&#xff0c;比如这篇文章分享的 bag 目标检测&#xff0c;在检测区域没有真实的 bag数据 此时&#xff0c;就可以采用图像拼…...

RK3588之ArmSoM-W3 + MPP实现多路硬解码拉流

简介 学习完MPP的解码Demo之后&#xff0c;想必大家都想通过一个项目来进行RK3588-MPP的解码实战。本篇文章就基于ArmSoM-W3开发板&#xff0c;开发一个多路硬解码项目&#xff0c;实现四路MPP硬解码拉流显示实现的效果如下&#xff1a; RK3588四路MPP硬解码拉流 环境介绍 硬件…...

【Rust日报】2023-10-29 隆重推出 Rerun 0.10!

Lapce代码编辑器发布v0.3.0 Lapce代码编辑器新发布v0.3.0&#xff01; https://lapce.dev/ 距离我们上次发布已经过去很长一段时间了。我们正忙着在自己的 UI 工具包Floem中重写 Lapce &#xff0c;这将使我们以后对 UI 部分代码的开发变得更容易、更快。 另一件值得注意的事情…...

AI智能识别如何助力PDF,轻松实现文档处理?

AI智能识别如何助力PDF&#xff0c;轻松实现文档处理&#xff1f; 随着科技的不断发展&#xff0c;人工智能&#xff08;AI&#xff09;在各个领域都发挥着重要的作用。其中&#xff0c;文档智能&#xff08; Document AI &#xff09;在金融、医疗、教育、保险、能源、物流等…...

【SA8295P 源码分析】114 - 将Android GVM userdata文件系统从 EXT4 修改为 F2FS

【SA8295P 源码分析】114 - 将Android GVM userdata文件系统从 EXT4 修改为 F2FS 一、代码修改方法1. BoardConfig.mk2. 修改 fstab二、开机进入 adb 验证2.1 验证 userdata 修改 f2fs 文件系统格式成功2.2 测试 f2fs 文件系统性能:androbench.apk系列文章汇总见:《【SA8295P…...

LeetCode 387 字符串中的第一个唯一字符 简单

题目 - 点击直达 1. 387 字符串中的第一个唯一字符1. 题目详情1. 原题链接2. 题目要求3. 基础框架 2. 解题思路1. 思路分析2. 时间复杂度3. 代码实现 1. 387 字符串中的第一个唯一字符 1. 题目详情 给定一个字符串 s &#xff0c;找到 它的第一个不重复的字符&#xff0c;并返…...

线程池--简单版本和复杂版本

目录 一、引言 二、线程池头文件介绍 三、简单版本线程池 1.创建线程池 2.添加任务到线程池 3.子线程执行回调函数 4.摧毁线程池 5.简单版线程池流程分析 四、复杂版本线程池 1.结构体介绍 2.主线程 3.子线程 4.管理线程 一、引言 多线程版服务器一个客户端就需要…...

docker进阶

文章目录 docker 进阶Part1 常用命令总结docker version 查看docker客户端和服务端信息docker info 查看更加详细信息docker images 列出所有镜像基本用法常用选项 docker search 搜索镜像基本用法示例用法 docker pull 拉取镜像基本用法示例用法 docker rmi 删除镜像基本用法示…...

Unity HoloLens 2 应用程序发布

设置3D 启动器画面&#xff0c;glb格式的模型 VS中可以直接生成所有大小的图标...

3D RPG Course | Core 学习日记三:Navigation智能导航地图烘焙

前言 前面我们已经绘制好了一个简单的地图场景&#xff0c;现在我们需要使用Navigation给地图做智能导航&#xff0c;以实现AI自动寻路&#xff0c;以及设置地图的可行走区域以及不可行走区域&#xff0c;Navigation的基础知识、原理、用法在Unity的官方文档&#xff0c;以及网…...

Linux 启用本地ISO作为软件源

环境&#xff1a;sle12sp5 &#xff08;open SUSE&#xff09; 1、禁用现有的源 查看源&#xff1a;sle12sp5 zypper lr -u ➜ sle12sp5 zypper lr -u Repository priorities are without effect. All enabled repositories share the same prior…...

SpringCloud-Alibaba-Nacos2.0.4

SpringCloud-Alibaba-Nacos2.0.4 SpringCloud Alibaba版本选择&#xff08;截止到2023年3月12日&#xff09; Spring Cloud Alibaba VersionSpring Cloud VersionSpring Boot Version2021.0.4.0*Spring Cloud 2021.0.42.6.11 SpringCloud Alibaba-2021.0.4.0组件版本关系 S…...

docker运行镜像相关配置文件

Dockerfile 文件配置 FROM anapsix/alpine-java:8_server-jre_unlimitedMAINTAINER Lion LiRUN mkdir -p /data/sydatasource/logs \/data/sydatasource/temp \/data/skywalking/agentWORKDIR /data/sydatasourceENV SERVER_PORT8220EXPOSE ${SERVER_PORT}ENV TZAsia/Shanghai …...

引擎系统设计思路 - 用户态与系统态隔离

用户态与系统态隔离&#xff1a; a. 外部用户侧的对象或者逻辑&#xff0c;在外部创建使用。内部系统侧的对象或者逻辑&#xff0c;在内部创建使用。 b. 用户状态下对内部系统的操作要立即响应&#xff0c;但是具体如何实际执行系统内部的机制&#xff0c;则是异步并行的。因为…...

致远OA wpsAssistServlet任意文件读取漏洞复现 [附POC]

文章目录 致远OA wpsAssistServlet任意文件读取漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 致远OA wpsAssistServlet任意文件读取漏洞复现 [附POC] 0x01 前言 免责声明&#xff1a;请勿利用…...

​轻量应用服务器有什么优势?如何评价亚马逊云科技轻量应用服务器?

什么是轻量应用服务器&#xff1f; 随着如今各行各业对云计算的需求越来越多&#xff0c;云服务器也被越来越多的企业所广泛采用。其中&#xff0c;轻量应用服务器是一种简单、高效、可靠的云计算服务&#xff0c;能够为开发人员、企业和个人提供轻量级的虚拟专用服务器&#…...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

微服务商城-商品微服务

数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

android13 app的触摸问题定位分析流程

一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...

MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用

文章目录 一、背景知识&#xff1a;什么是 B-Tree 和 BTree&#xff1f; B-Tree&#xff08;平衡多路查找树&#xff09; BTree&#xff08;B-Tree 的变种&#xff09; 二、结构对比&#xff1a;一张图看懂 三、为什么 MySQL InnoDB 选择 BTree&#xff1f; 1. 范围查询更快 2…...

LOOI机器人的技术实现解析:从手势识别到边缘检测

LOOI机器人作为一款创新的AI硬件产品&#xff0c;通过将智能手机转变为具有情感交互能力的桌面机器人&#xff0c;展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家&#xff0c;我将全面解析LOOI的技术实现架构&#xff0c;特别是其手势识别、物体识别和环境…...

WPF八大法则:告别模态窗口卡顿

⚙️ 核心问题&#xff1a;阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程&#xff0c;导致后续逻辑无法执行&#xff1a; var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题&#xff1a…...