Unity Udexreal开发插件包
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

885 lines
30 KiB

//------------------------------------------------------------------------------
// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有
// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权
// CSDN博客:https://blog.csdn.net/qq_40374647
// 哔哩哔哩视频:https://space.bilibili.com/94253567
// Gitee源代码仓库:https://gitee.com/RRQM_Home
// Github源代码仓库:https://github.com/RRQM
// API首页:https://www.yuque.com/rrqm/touchsocket/index
// 交流QQ群:234762506
// 感谢您的下载和使用
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using TouchSocket.Core;
using TouchSocket.Resources;
namespace TouchSocket.Sockets
{
/// <summary>
/// 简单UDP会话。
/// </summary>
public class UdpSession : UdpSessionBase
{
/// <summary>
/// 当收到数据时
/// </summary>
public UdpReceivedEventHandler Received { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="remoteEndPoint"></param>
/// <param name="byteBlock"></param>
/// <param name="requestInfo"></param>
protected override void HandleReceivedData(EndPoint remoteEndPoint, ByteBlock byteBlock, IRequestInfo requestInfo)
{
Received?.Invoke(remoteEndPoint, byteBlock, requestInfo);
}
}
/// <summary>
/// UDP基类服务器。
/// </summary>
public class UdpSessionBase : BaseSocket, IUdpSession, IPluginObject
{
private readonly ConcurrentList<SocketAsyncEventArgs> m_socketAsyncs;
private TouchSocketConfig m_config;
private UdpDataHandlingAdapter m_adapter;
private NetworkMonitor m_monitor;
private IPHost m_remoteIPHost;
private ServerState m_serverState;
private bool m_usePlugin;
/// <summary>
/// 构造函数
/// </summary>
public UdpSessionBase()
{
m_socketAsyncs = new ConcurrentList<SocketAsyncEventArgs>();
Protocol = Protocol.UDP;
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.ReceiveBufferSize = BufferLength;
socket.SendBufferSize = BufferLength;
m_monitor = new NetworkMonitor(null, socket);
}
/// <summary>
/// 处理未经过适配器的数据。返回值表示是否继续向下传递。
/// </summary>
public Func<ByteBlock, bool> OnHandleRawBuffer { get; set; }
/// <summary>
/// 处理经过适配器后的数据。返回值表示是否继续向下传递。
/// </summary>
public Func<ByteBlock, IRequestInfo, bool> OnHandleReceivedData { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
public bool CanSend => m_serverState == ServerState.Running;
/// <summary>
/// <inheritdoc/>
/// </summary>
public virtual bool CanSetDataHandlingAdapter => true;
/// <summary>
/// 获取配置
/// </summary>
public TouchSocketConfig Config => m_config;
/// <summary>
/// <inheritdoc/>
/// </summary>
public IContainer Container => m_config?.Container;
/// <summary>
/// <inheritdoc/>
/// </summary>
public DateTime LastReceivedTime { get; private set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
public DateTime LastSendTime { get; private set; }
/// <summary>
/// 数据处理适配器
/// </summary>
public UdpDataHandlingAdapter DataHandlingAdapter => m_adapter;
/// <summary>
/// 监听器
/// </summary>
public NetworkMonitor Monitor => m_monitor;
/// <summary>
/// <inheritdoc/>
/// </summary>
public IPluginsManager PluginsManager => m_config?.PluginsManager;
/// <summary>
/// <inheritdoc/>
/// </summary>
public virtual Protocol Protocol { get; set; }
/// <summary>
/// 默认远程节点
/// </summary>
public IPHost RemoteIPHost => m_remoteIPHost;
/// <summary>
/// 服务器名称
/// </summary>
public string ServerName => Config?.GetValue(TouchSocketConfigExtension.ServerNameProperty);
/// <summary>
/// 获取服务器状态
/// </summary>
public ServerState ServerState => m_serverState;
/// <summary>
/// 是否已启用插件
/// </summary>
public bool UsePlugin => m_usePlugin;
/// <summary>
/// 退出组播
/// </summary>
/// <param name="multicastAddr"></param>
public void DropMulticastGroup(IPAddress multicastAddr)
{
if (DisposedValue)
{
throw new ObjectDisposedException(GetType().FullName);
}
if (multicastAddr is null)
{
throw new ArgumentNullException(nameof(multicastAddr));
}
if (m_monitor.Socket.AddressFamily == AddressFamily.InterNetwork)
{
MulticastOption optionValue = new MulticastOption(multicastAddr);
m_monitor.Socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DropMembership, optionValue);
}
else
{
IPv6MulticastOption optionValue2 = new IPv6MulticastOption(multicastAddr);
m_monitor.Socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.DropMembership, optionValue2);
}
}
/// <summary>
/// 加入组播。
/// <para>组播地址为 224.0.0.0 ~ 239.255.255.255,其中 224.0.0.0~224.255.255.255 不建议在用户程序中使用,因为它们一般都有特殊用途。</para>
/// </summary>
/// <param name="multicastAddr"></param>
public void JoinMulticastGroup(IPAddress multicastAddr)
{
if (multicastAddr is null)
{
throw new ArgumentNullException(nameof(multicastAddr));
}
if (DisposedValue)
{
throw new ObjectDisposedException(GetType().FullName);
}
if (m_monitor.Socket.AddressFamily == AddressFamily.InterNetwork)
{
MulticastOption optionValue = new MulticastOption(multicastAddr);
m_monitor.Socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, optionValue);
}
else
{
IPv6MulticastOption optionValue2 = new IPv6MulticastOption(multicastAddr);
m_monitor.Socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.AddMembership, optionValue2);
}
}
/// <summary>
/// 设置数据处理适配器
/// </summary>
/// <param name="adapter"></param>
public virtual void SetDataHandlingAdapter(UdpDataHandlingAdapter adapter)
{
if (!CanSetDataHandlingAdapter)
{
throw new Exception($"不允许自由调用{nameof(SetDataHandlingAdapter)}进行赋值。");
}
SetAdapter(adapter);
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
public IService Setup(TouchSocketConfig config)
{
m_config = config;
if (config.IsUsePlugin)
{
PluginsManager.Raise<IConfigPlugin>(nameof(IConfigPlugin.OnLoadingConfig), this, new ConfigEventArgs(config));
}
LoadConfig(m_config);
if (UsePlugin)
{
PluginsManager.Raise<IConfigPlugin>(nameof(IConfigPlugin.OnLoadedConfig), this, new ConfigEventArgs(config));
}
return this;
}
/// <summary>
/// 通过端口配置
/// </summary>
/// <param name="port"></param>
public IService Setup(int port)
{
TouchSocketConfig serverConfig = new TouchSocketConfig();
serverConfig.SetBindIPHost(new IPHost(port));
return Setup(serverConfig);
}
/// <summary>
/// 启动服务
/// </summary>
public IService Start()
{
try
{
if (m_serverState == ServerState.Disposed)
{
throw new Exception("无法重新利用已释放对象");
}
switch (m_serverState)
{
case ServerState.None:
{
if (m_config.GetValue(TouchSocketConfigExtension.BindIPHostProperty) is IPHost iPHost)
{
BeginReceive(iPHost);
}
break;
}
case ServerState.Running:
return this;
case ServerState.Stopped:
{
if (m_config.GetValue(TouchSocketConfigExtension.BindIPHostProperty) is IPHost iPHost)
{
BeginReceive(iPHost);
}
break;
}
case ServerState.Disposed:
{
throw new Exception("无法再次利用已释放对象");
}
}
m_serverState = ServerState.Running;
if (UsePlugin)
{
PluginsManager.Raise<IServicePlugin>(nameof(IServicePlugin.OnStarted), this, new ServiceStateEventArgs(this.m_serverState, default));
}
return this;
}
catch (Exception ex)
{
m_serverState = ServerState.Exception;
if (UsePlugin)
{
PluginsManager.Raise<IServicePlugin>(nameof(IServicePlugin.OnStarted), this, new ServiceStateEventArgs(this.m_serverState, ex) { Message = ex.Message }) ;
}
throw;
}
}
/// <summary>
/// 停止服务器
/// </summary>
public IService Stop()
{
m_monitor?.Socket.Dispose();
m_monitor = null;
m_serverState = ServerState.Stopped;
foreach (var item in m_socketAsyncs)
{
item.SafeDispose();
}
m_socketAsyncs.Clear();
if (UsePlugin)
{
PluginsManager.Raise<IServicePlugin>(nameof(IServicePlugin.OnStarted), this, new ServiceStateEventArgs(this.m_serverState, default));
}
return this;
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
if (!this.DisposedValue)
{
if (disposing)
{
m_monitor?.Socket.Dispose();
m_monitor = null;
m_serverState = ServerState.Disposed;
foreach (var item in m_socketAsyncs)
{
item.SafeDispose();
}
m_socketAsyncs.Clear();
if (UsePlugin)
{
PluginsManager.Raise<IServicePlugin>(nameof(IServicePlugin.OnStarted), this, new ServiceStateEventArgs(this.m_serverState, default));
}
}
}
base.Dispose(disposing);
}
/// <summary>
/// 处理已接收到的数据。
/// </summary>
/// <param name="remoteEndPoint"></param>
/// <param name="byteBlock">以二进制流形式传递</param>
/// <param name="requestInfo">以解析的数据对象传递</param>
protected virtual void HandleReceivedData(EndPoint remoteEndPoint, ByteBlock byteBlock, IRequestInfo requestInfo)
{
}
/// <summary>
/// 当即将发送时,如果覆盖父类方法,则不会触发插件。
/// </summary>
/// <param name="endPoint"></param>
/// <param name="buffer">数据缓存区</param>
/// <param name="offset">偏移</param>
/// <param name="length">长度</param>
/// <returns>返回值表示是否允许发送</returns>
protected virtual bool HandleSendingData(EndPoint endPoint, byte[] buffer, int offset, int length)
{
return true;
}
/// <summary>
/// 加载配置
/// </summary>
/// <param name="config"></param>
protected virtual void LoadConfig(TouchSocketConfig config)
{
if (config == null)
{
throw new Exception("配置文件为空");
}
Logger = Container.Resolve<ILog>();
m_remoteIPHost = config.GetValue(TouchSocketConfigExtension.RemoteIPHostProperty);
BufferLength = config.GetValue(TouchSocketConfigExtension.BufferLengthProperty);
m_usePlugin = config.IsUsePlugin;
if (CanSetDataHandlingAdapter)
{
SetDataHandlingAdapter(Config.GetValue(TouchSocketConfigExtension.UdpDataHandlingAdapterProperty).Invoke());
}
}
/// <summary>
/// 在Socket初始化对象后,Bind之前调用。
/// 可用于设置Socket参数。
/// 父类方法可覆盖。
/// </summary>
/// <param name="socket"></param>
protected virtual void PreviewBind(Socket socket)
{
}
/// <summary>
/// 设置适配器,该方法不会检验<see cref="CanSetDataHandlingAdapter"/>的值。
/// </summary>
/// <param name="adapter"></param>
protected void SetAdapter(UdpDataHandlingAdapter adapter)
{
if (adapter is null)
{
throw new ArgumentNullException(nameof(adapter));
}
if (adapter.m_owner != null)
{
throw new Exception("此适配器已被其他终端使用,请重新创建对象。");
}
if (Config != null)
{
if (Config.GetValue(TouchSocketConfigExtension.MaxPackageSizeProperty) is int v1)
{
adapter.MaxPackageSize = v1;
}
}
adapter.m_owner = this;
adapter.ReceivedCallBack = PrivateHandleReceivedData;
adapter.SendCallBack = DefaultSend;
m_adapter = adapter;
}
private void BeginReceive(IPHost iPHost)
{
int threadCount = Config.GetValue(TouchSocketConfigExtension.ThreadCountProperty);
threadCount = threadCount < 0 ? 1 : threadCount;
Socket socket = new Socket(iPHost.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
{
ReceiveBufferSize = BufferLength,
SendBufferSize = BufferLength,
EnableBroadcast = m_config.GetValue(TouchSocketConfigExtension.EnableBroadcastProperty)
};
if (m_config.GetValue(TouchSocketConfigExtension.ReuseAddressProperty))
{
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}
PreviewBind(socket);
#region Windows下UDP连接被重置错误10054
#if NET45_OR_GREATER
const int SIP_UDP_CONNRESET = -1744830452;
socket.IOControl(SIP_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null);
#else
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
const int SIP_UDP_CONNRESET = -1744830452;
socket.IOControl(SIP_UDP_CONNRESET, new byte[] { 0, 0, 0, 0 }, null);
}
#endif
#endregion
socket.Bind(iPHost.EndPoint);
m_monitor = new NetworkMonitor(iPHost, socket);
switch (m_config.GetValue(TouchSocketConfigExtension.ReceiveTypeProperty))
{
case ReceiveType.Auto:
{
#if NET45_OR_GREATER||NET5_0_OR_GREATER
for (int i = 0; i < threadCount; i++)
{
SocketAsyncEventArgs eventArg = new SocketAsyncEventArgs();
m_socketAsyncs.Add(eventArg);
eventArg.Completed += IO_Completed;
ByteBlock byteBlock = new ByteBlock(BufferLength);
eventArg.UserToken = byteBlock;
eventArg.SetBuffer(byteBlock.Buffer, 0, byteBlock.Capacity);
eventArg.RemoteEndPoint = iPHost.EndPoint;
if (!socket.ReceiveFromAsync(eventArg))
{
ProcessReceive(socket, eventArg);
}
}
#else
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
for (int i = 0; i < threadCount; i++)
{
SocketAsyncEventArgs eventArg = new SocketAsyncEventArgs();
m_socketAsyncs.Add(eventArg);
eventArg.Completed += IO_Completed;
ByteBlock byteBlock = new ByteBlock(BufferLength);
eventArg.UserToken = byteBlock;
eventArg.SetBuffer(byteBlock.Buffer, 0, byteBlock.Capacity);
eventArg.RemoteEndPoint = iPHost.EndPoint;
if (!socket.ReceiveFromAsync(eventArg))
{
ProcessReceive(socket, eventArg);
}
}
}
else
{
Thread thread = new Thread(Received);
thread.IsBackground = true;
thread.Start();
}
#endif
break;
}
default:
throw new Exception("UDP中只支持Auto模式");
}
}
private void Received()
{
while (true)
{
ByteBlock byteBlock = new ByteBlock();
try
{
EndPoint endPoint = m_monitor.IPHost.EndPoint;
int r = m_monitor.Socket.ReceiveFrom(byteBlock.Buffer, ref endPoint);
byteBlock.SetLength(r);
HandleBuffer(endPoint, byteBlock);
}
catch (Exception ex)
{
byteBlock.Dispose();
Logger.Log(LogType.Error, this, ex.Message, ex);
break;
}
}
}
private void HandleBuffer(EndPoint endPoint, ByteBlock byteBlock)
{
try
{
LastReceivedTime = DateTime.Now;
if (OnHandleRawBuffer?.Invoke(byteBlock) == false)
{
return;
}
if (DisposedValue)
{
return;
}
if (m_adapter == null)
{
Logger.Error(this, TouchSocketStatus.NullDataAdapter.GetDescription());
return;
}
m_adapter.ReceivedInput(endPoint, byteBlock);
}
catch (Exception ex)
{
Logger.Log(LogType.Error, this, "在处理数据时发生错误", ex);
}
finally
{
byteBlock.Dispose();
}
}
private void IO_Completed(object sender, SocketAsyncEventArgs e)
{
ProcessReceive((Socket)sender, e);
}
private void PrivateHandleReceivedData(EndPoint remoteEndPoint, ByteBlock byteBlock, IRequestInfo requestInfo)
{
if (OnHandleReceivedData?.Invoke(byteBlock, requestInfo) == false)
{
return;
}
if (m_usePlugin)
{
UdpReceivedDataEventArgs args = new UdpReceivedDataEventArgs(remoteEndPoint, byteBlock, requestInfo);
PluginsManager.Raise<IUdpSessionPlugin>(nameof(IUdpSessionPlugin.OnReceivedData), this, args);
if (args.Handled)
{
return;
}
}
HandleReceivedData(remoteEndPoint, byteBlock, requestInfo);
}
#region 向默认远程同步发送
/// <summary>
/// 向默认终结点发送
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
public virtual void Send(byte[] buffer, int offset, int length)
{
if (m_remoteIPHost == null)
{
throw new Exception("默认终结点为空");
}
Send(m_remoteIPHost.EndPoint, buffer, offset, length);
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="requestInfo"></param>
/// <exception cref="OverlengthException"></exception>
/// <exception cref="Exception"></exception>
public virtual void Send(IRequestInfo requestInfo)
{
if (DisposedValue)
{
return;
}
if (m_adapter == null)
{
throw new ArgumentNullException(nameof(DataHandlingAdapter), TouchSocketStatus.NullDataAdapter.GetDescription());
}
if (!m_adapter.CanSendRequestInfo)
{
throw new NotSupportedException($"当前适配器不支持对象发送。");
}
m_adapter.SendInput(requestInfo);
}
#endregion 向默认远程同步发送
#region 向默认远程异步发送
/// <summary>
/// IOCP发送
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
/// <exception cref="NotConnectedException"></exception>
/// <exception cref="OverlengthException"></exception>
/// <exception cref="Exception"></exception>
public virtual Task SendAsync(byte[] buffer, int offset, int length)
{
return EasyTask.Run(() =>
{
Send(buffer, offset, length);
});
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="requestInfo"></param>
/// <exception cref="OverlengthException"></exception>
/// <exception cref="Exception"></exception>
public virtual Task SendAsync(IRequestInfo requestInfo)
{
return EasyTask.Run(() =>
{
Send(requestInfo);
});
}
#endregion 向默认远程异步发送
#region 向设置的远程同步发送
/// <summary>
/// 向设置的远程同步发送
/// </summary>
/// <param name="remoteEP"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
/// <exception cref="NotConnectedException"></exception>
/// <exception cref="OverlengthException"></exception>
/// <exception cref="Exception"></exception>
public virtual void Send(EndPoint remoteEP, byte[] buffer, int offset, int length)
{
m_adapter.SendInput(remoteEP, buffer, offset, length);
}
#endregion 向设置的远程同步发送
#region 向设置的远程异步发送
/// <summary>
/// 向设置的远程异步发送
/// </summary>
/// <param name="remoteEP"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
/// <exception cref="NotConnectedException"></exception>
/// <exception cref="OverlengthException"></exception>
/// <exception cref="Exception"></exception>
public virtual Task SendAsync(EndPoint remoteEP, byte[] buffer, int offset, int length)
{
return EasyTask.Run(() =>
{
Send(remoteEP, buffer, offset, length);
});
}
#endregion 向设置的远程异步发送
private void ProcessReceive(Socket socket, SocketAsyncEventArgs e)
{
if (m_serverState == ServerState.Running && e.SocketError == SocketError.Success)
{
ByteBlock byteBlock = (ByteBlock)e.UserToken;
byteBlock.SetLength(e.BytesTransferred);
HandleBuffer(e.RemoteEndPoint, byteBlock);
ByteBlock newByteBlock = new ByteBlock(BufferLength);
e.UserToken = newByteBlock;
e.SetBuffer(newByteBlock.Buffer, 0, newByteBlock.Buffer.Length);
try
{
if (!socket.ReceiveFromAsync(e))
{
ProcessReceive(socket, e);
}
}
catch (System.Exception ex)
{
Logger.Log(LogType.Error, this, ex.Message, ex);
}
}
else
{
if (e.SocketError != SocketError.Success)
{
Logger?.Error(this, $"接收出现错误:{e.SocketError},错误代码:{(int)e.SocketError}");
e.Dispose();
}
}
}
#region DefaultSend
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
public void DefaultSend(byte[] buffer, int offset, int length)
{
DefaultSend(m_remoteIPHost.EndPoint, buffer, offset, length);
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="endPoint"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
public void DefaultSend(EndPoint endPoint, byte[] buffer, int offset, int length)
{
if (HandleSendingData(endPoint, buffer, offset, length))
{
if (CanSend)
{
m_monitor.Socket.SendTo(buffer, offset, length, SocketFlags.None, endPoint);
}
LastSendTime = DateTime.Now;
}
}
#endregion DefaultSend
#region DefaultSendAsync
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
public Task DefaultSendAsync(byte[] buffer, int offset, int length)
{
return EasyTask.Run(() =>
{
DefaultSend(buffer, offset, length);
});
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="endPoint"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
public Task DefaultSendAsync(EndPoint endPoint, byte[] buffer, int offset, int length)
{
return EasyTask.Run(() =>
{
DefaultSend(buffer, offset, length);
});
}
#endregion DefaultSendAsync
#region 组合发送
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="transferBytes"></param>
public void Send(IList<ArraySegment<byte>> transferBytes)
{
Send(m_remoteIPHost.EndPoint, transferBytes);
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="endPoint"></param>
/// <param name="transferBytes"></param>
public void Send(EndPoint endPoint, IList<ArraySegment<byte>> transferBytes)
{
if (m_adapter == null)
{
throw new ArgumentNullException(nameof(DataHandlingAdapter), TouchSocketStatus.NullDataAdapter.GetDescription());
}
if (!m_adapter.CanSplicingSend)
{
throw new NotSupportedException("该适配器不支持拼接发送");
}
m_adapter.SendInput(endPoint, transferBytes);
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="transferBytes"></param>
public Task SendAsync(IList<ArraySegment<byte>> transferBytes)
{
return EasyTask.Run(() =>
{
Send(transferBytes);
});
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="endPoint"></param>
/// <param name="transferBytes"></param>
public Task SendAsync(EndPoint endPoint, IList<ArraySegment<byte>> transferBytes)
{
return EasyTask.Run(() =>
{
Send(endPoint, transferBytes);
});
}
#endregion 组合发送
}
}