first commit
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.Common.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// TCP引擎初始化参数
|
||||
/// </summary>
|
||||
public class TcpEngineInitParam
|
||||
{
|
||||
/// <summary>
|
||||
/// TCP服务IP
|
||||
/// </summary>
|
||||
public string IP { get; set; }
|
||||
/// <summary>
|
||||
/// TCP服务端口
|
||||
/// </summary>
|
||||
public int Port { get; set; }
|
||||
/// <summary>
|
||||
/// 设备类型
|
||||
/// </summary>
|
||||
public DeviceType DeviceType { get; set; }
|
||||
/// <summary>
|
||||
/// 最小条码长度(用于区分条码和其他数据),若数据包解析后小于该长度则视为读码失败
|
||||
/// </summary>
|
||||
public int MinCodeLength { get; set; }
|
||||
/// <summary>
|
||||
/// 编码字符
|
||||
/// </summary>
|
||||
public string EncoderWord { get; set; }
|
||||
/// <summary>
|
||||
/// 解码字符
|
||||
/// </summary>
|
||||
public string DecoderWord { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型
|
||||
/// </summary>
|
||||
public enum DeviceType
|
||||
{
|
||||
/// <summary>
|
||||
/// 基恩士
|
||||
/// </summary>
|
||||
KeyEnce = 0,
|
||||
/// <summary>
|
||||
/// 倍加福
|
||||
/// </summary>
|
||||
PepperlAndFuchs = 1,
|
||||
/// <summary>
|
||||
/// 喷墨打印机
|
||||
/// </summary>
|
||||
PenmoPrint= 2
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using DotNetty.Transport.Channels;
|
||||
using System.Net;
|
||||
|
||||
namespace JSMachine.WMS.Netty.Common.Util
|
||||
{
|
||||
public static class ChannelHandlerContextUtil
|
||||
{
|
||||
public static IPInfo ToIPInfo(this IChannelHandlerContext channel, AddressOnwer addressOnwer)
|
||||
{
|
||||
IPEndPoint iPAddress = addressOnwer == AddressOnwer.Local ? (IPEndPoint)channel.Channel.LocalAddress
|
||||
: (IPEndPoint)channel.Channel.RemoteAddress;
|
||||
string ip = iPAddress?.Address.MapToIPv4().ToString();
|
||||
int port = iPAddress == null ? 0 : iPAddress.Port;
|
||||
|
||||
return new IPInfo { IP = ip, Port = port };
|
||||
}
|
||||
}
|
||||
|
||||
public class IPInfo
|
||||
{
|
||||
public string IP { get; set; }
|
||||
public int Port { get; set; }
|
||||
}
|
||||
|
||||
public enum AddressOnwer
|
||||
{
|
||||
Local = 0,
|
||||
Remote = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<SccProjectName>Svn</SccProjectName>
|
||||
<SccProvider>SubversionScc</SccProvider>
|
||||
<SccAuxPath>Svn</SccAuxPath>
|
||||
<SccLocalPath>Svn</SccLocalPath>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ApplicationIcon />
|
||||
<OutputType>Library</OutputType>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNetty.Buffers" Version="0.7.5" />
|
||||
<PackageReference Include="DotNetty.Codecs" Version="0.7.5" />
|
||||
<PackageReference Include="DotNetty.Common" Version="0.7.5" />
|
||||
<PackageReference Include="DotNetty.Handlers" Version="0.7.5" />
|
||||
<PackageReference Include="DotNetty.Transport" Version="0.7.5" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.ComponentModel.Composition" Version="7.0.0" />
|
||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="NettyUdp\Codec\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\JSMachine.WMS.Common\JSMachine.WMS.Common.csproj" />
|
||||
<ProjectReference Include="..\JSMachine.WMS.Infrastructure\JSMachine.WMS.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,59 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Codecs;
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.Netty.NettyAsClient.Command;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.Codec
|
||||
{
|
||||
public class Decoder : ByteToMessageDecoder
|
||||
{
|
||||
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
|
||||
{
|
||||
if (input != null)
|
||||
{
|
||||
int length = input.ReadableBytes;
|
||||
byte[] bytes = new byte[length];
|
||||
|
||||
string msg = Encoding.UTF8.GetString(bytes);
|
||||
|
||||
LogHelper.Info(msg);
|
||||
|
||||
output.Add(input);
|
||||
|
||||
try
|
||||
{
|
||||
//IMessagePackage message = MessagePackageMange.Instance.BinaryDeserializeToObject(bytes);
|
||||
//IMessagePackage message = JsonConvert.DeserializeObject<IMessagePackage>(Encoding.UTF8.GetString(bytes));
|
||||
|
||||
string json = Encoding.UTF8.GetString(bytes);
|
||||
JObject jobj = JObject.Parse(json);
|
||||
//当找不到这个节点的值的时候则自动赋0,因此,命令类型不可以是0,否则无法解析的命令都自动归为0类型了
|
||||
//Common.MessagePack.CommandType cmdType = (Common.MessagePack.CommandType)jobj.Value<int>("ComType");
|
||||
|
||||
//switch (cmdType)
|
||||
//{
|
||||
// case Common.MessagePack.CommandType.g_eKeepAlive:
|
||||
// baseCmd = JsonConvert.DeserializeObject<HeartBeartCmd>(json);
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
//}
|
||||
|
||||
//output.Add(baseCmd);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"解码远程地址:{context.Channel.RemoteAddress}----发送的数据异常");
|
||||
}
|
||||
|
||||
input.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Codecs;
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.Codec
|
||||
{
|
||||
public class EnCoderPrint : MessageToByteEncoder<byte[]>
|
||||
{
|
||||
private string _encoderWord;
|
||||
|
||||
public EnCoderPrint() : base() { }
|
||||
|
||||
public EnCoderPrint(string encoderWord)
|
||||
{
|
||||
_encoderWord = encoderWord;
|
||||
}
|
||||
protected override void Encode(IChannelHandlerContext context, byte[] message, IByteBuffer output)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] body = message;
|
||||
//byte[] tail = Encoding.UTF8.GetBytes(_encoderWord);
|
||||
|
||||
output.WriteBytes(body);
|
||||
//output.WriteBytes(tail);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"TCP编码异常,异常信息 {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Codecs;
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.Netty.NettyAsClient.Command;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.Codec
|
||||
{
|
||||
public class Encoder : MessageToByteEncoder<string>
|
||||
{
|
||||
private string _encoderWord;
|
||||
|
||||
public Encoder():base() { }
|
||||
|
||||
public Encoder(string encoderWord)
|
||||
{
|
||||
_encoderWord= encoderWord;
|
||||
}
|
||||
|
||||
protected override void Encode(IChannelHandlerContext context, string message, IByteBuffer output)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] body = Encoding.UTF8.GetBytes(message);
|
||||
byte[] tail = Encoding.UTF8.GetBytes(_encoderWord);
|
||||
|
||||
output.WriteBytes(body);
|
||||
output.WriteBytes(tail);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"TCP编码异常,异常信息 {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// 请求客户端读取条码的命令,消息标识为 T。
|
||||
/// </summary>
|
||||
public class BarCodeReadCmd : BaseCmd
|
||||
{
|
||||
public override string Msg { get; set; } = "T";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// 条码读取异常命令
|
||||
/// </summary>
|
||||
public class BarCodeReadExceptionCmd : BaseCmd
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// 条码上传命令
|
||||
/// </summary>
|
||||
public class BarCodeUploadCmd : BaseCmd
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// Netty 客户端命令基类,定义待发送或处理的原始消息内容。
|
||||
/// </summary>
|
||||
public class BaseCmd
|
||||
{
|
||||
/// <summary>
|
||||
/// 命令消息正文。
|
||||
/// </summary>
|
||||
public virtual string Msg { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.Command
|
||||
{
|
||||
public class PenmoReadCmd : BaseCmd
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using JSMachine.WMS.Common.Cache.Model;
|
||||
using JSMachine.WMS.Common.Cache;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using JSMachine.WMS.Netty.NettyAsClient.Command;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.CommandHandler
|
||||
{
|
||||
public class BarCodeCmdHandlerL : BaseCmdhandler
|
||||
{
|
||||
public async override Task<bool> Handle(BaseCmd msg)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
if (msg is BarCodeUploadCmd barCodeUploadCmd)
|
||||
{
|
||||
GlobalMemoryCache.AddCach(
|
||||
RemoteIPInfo.IP,
|
||||
new BarCodeInfo
|
||||
{
|
||||
AddTime = DateTime.Now,
|
||||
BarCode = barCodeUploadCmd.Msg,
|
||||
IP = RemoteIPInfo.IP
|
||||
},
|
||||
new MemoryCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30),
|
||||
});
|
||||
|
||||
LogHelper.Info($"收到条码信息({RemoteIPInfo.IP}): 【{barCodeUploadCmd.Msg}】");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.Netty.NettyAsClient.Command;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using static System.Console;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.CommandHandler
|
||||
{
|
||||
public class BarCodeReadExceptionCmdHandler : BaseCmdhandler
|
||||
{
|
||||
public async override Task<bool> Handle(BaseCmd msg)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
if (msg is BarCodeReadExceptionCmd barCodeReadExceptionCmd)
|
||||
{
|
||||
LogHelper.Warn($"相机【{base.RemoteIPInfo.IP}】条码读取失败,返回信息 {barCodeReadExceptionCmd.Msg}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Netty.Common.Util;
|
||||
using JSMachine.WMS.Netty.NettyAsClient.Command;
|
||||
using Prism.Events;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.CommandHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Netty 客户端命令处理器基类,提供通道上下文及收发端地址信息。
|
||||
/// </summary>
|
||||
[InheritedExport("NettyAsClient")]
|
||||
public abstract class BaseCmdhandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前命令所属的 Netty 通道上下文。
|
||||
/// </summary>
|
||||
public IChannelHandlerContext Channel { get; set; }
|
||||
/// <summary>
|
||||
/// 远端地址信息。
|
||||
/// </summary>
|
||||
public IPInfo RemoteIPInfo => Channel.ToIPInfo(AddressOnwer.Remote);
|
||||
/// <summary>
|
||||
/// 本地地址信息。
|
||||
/// </summary>
|
||||
public IPInfo LocalIPInfo=> Channel.ToIPInfo(AddressOnwer.Local);
|
||||
/// <summary>
|
||||
/// 处理一条客户端命令。
|
||||
/// </summary>
|
||||
/// <param name="msg">待处理的命令。</param>
|
||||
/// <returns>处理成功返回 <see langword="true"/>。</returns>
|
||||
public abstract Task<bool> Handle(BaseCmd msg);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Handlers.Timeout;
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.Netty.Common.Model;
|
||||
using JSMachine.WMS.Netty.NettyAsClient.Transport;
|
||||
using Prism.Events;
|
||||
using Prism.Ioc;
|
||||
using System;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient
|
||||
{
|
||||
public class EchoClientChannelHandler : ChannelHandlerAdapter
|
||||
{
|
||||
private NettyClientTransport _nettyTransport;
|
||||
private TcpEngineInitParam _tcpEngineInitParam1;
|
||||
IEventAggregator _eventAggregator= ContainerLocator.Container.Resolve<IEventAggregator>();
|
||||
|
||||
public EchoClientChannelHandler() : base() { }
|
||||
|
||||
public EchoClientChannelHandler(TcpEngineInitParam tcpEngineInitParam)
|
||||
{
|
||||
_tcpEngineInitParam1= tcpEngineInitParam;
|
||||
}
|
||||
|
||||
public override async void ChannelActive(IChannelHandlerContext context)
|
||||
{
|
||||
_nettyTransport = new NettyClientTransport(context, _tcpEngineInitParam1);
|
||||
TransportManager.DicClientChannels.AddOrUpdate(_tcpEngineInitParam1.IP, p => context, (s, p) => context);
|
||||
|
||||
base.ChannelActive(context);
|
||||
|
||||
//开启喷墨打印机
|
||||
if(_tcpEngineInitParam1.DeviceType== DeviceType.PenmoPrint)
|
||||
LogHelper.Info("已成功连接服务端");
|
||||
}
|
||||
|
||||
public override void ChannelRead(IChannelHandlerContext context, object message)
|
||||
{
|
||||
//LogsTool.Default.Info("收到服务端TCP信息");
|
||||
if (message is IByteBuffer byteBuffer)
|
||||
_nettyTransport.ChannelReadHandle(byteBuffer);
|
||||
}
|
||||
|
||||
public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();
|
||||
|
||||
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
|
||||
{
|
||||
//LogsTool.Default.Error("Exception: " + exception);
|
||||
context.CloseAsync();
|
||||
}
|
||||
|
||||
public override void HandlerAdded(IChannelHandlerContext context)
|
||||
{
|
||||
base.HandlerAdded(context);
|
||||
}
|
||||
|
||||
public override void HandlerRemoved(IChannelHandlerContext context)
|
||||
{
|
||||
LogHelper.Warn($"跟服务端的连接断开");
|
||||
base.HandlerRemoved(context);
|
||||
}
|
||||
|
||||
public override void UserEventTriggered(IChannelHandlerContext context, object evt)
|
||||
{
|
||||
IdleStateEvent eventState = evt as IdleStateEvent;
|
||||
|
||||
//if (eventState.State == IdleState.ReaderIdle)
|
||||
//{
|
||||
// //没有任何读取
|
||||
// Reconnect();
|
||||
//}
|
||||
//else if (eventState.State == IdleState.WriterIdle)
|
||||
//{
|
||||
// //没有任何写入
|
||||
// Reconnect();
|
||||
//}
|
||||
//else
|
||||
if (eventState.State == IdleState.AllIdle)
|
||||
{
|
||||
//没有任何交互
|
||||
Reconnect();
|
||||
}
|
||||
|
||||
base.UserEventTriggered(context, evt);
|
||||
|
||||
void Reconnect()
|
||||
{
|
||||
//context.CloseAsync();
|
||||
//new NettyEngine().InitEngine(NettyTransport.RemoteIpInfo.IP, NettyTransport.RemoteIpInfo.Port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Codecs;
|
||||
using DotNetty.Transport.Bootstrapping;
|
||||
using DotNetty.Transport.Channels;
|
||||
using DotNetty.Transport.Channels.Sockets;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.Netty.Common.Model;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// TCP客户端引擎
|
||||
/// </summary>
|
||||
public class TcpClientEngine
|
||||
{
|
||||
private Bootstrap SocketBootstrap = new();
|
||||
private MultithreadEventLoopGroup WorkGroup = new();
|
||||
private IChannel channel;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 TCP 客户端编解码管道并启动后台连接监视。
|
||||
/// </summary>
|
||||
/// <param name="tcpEngineInitParam">服务端地址、端口及协议编解码配置。</param>
|
||||
public async Task InitEngine(TcpEngineInitParam tcpEngineInitParam)
|
||||
{
|
||||
LogHelper.Info("正在启动TCP客户端引擎-----------");
|
||||
|
||||
SocketBootstrap
|
||||
.Group(WorkGroup)
|
||||
.Channel<TcpSocketChannel>()
|
||||
.Option(ChannelOption.TcpNodelay, true)
|
||||
.Option(ChannelOption.ConnectTimeout, TimeSpan.FromSeconds(2))
|
||||
.Option(ChannelOption.SoKeepalive, true)
|
||||
//以下两种方式都是设置一次性能接收的数据包大小
|
||||
//.Option(ChannelOption.SoRcvbuf, 4 * 1024)
|
||||
.Option(ChannelOption.RcvbufAllocator, new FixedRecvByteBufAllocator(25))
|
||||
.Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
|
||||
{
|
||||
IChannelPipeline pipeline = channel.Pipeline;
|
||||
|
||||
if (!string.IsNullOrEmpty(tcpEngineInitParam.DecoderWord))
|
||||
{
|
||||
IByteBuffer delimiter = Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes("\r"));
|
||||
//简单的协议可以直接使用自带的分隔符解码器
|
||||
pipeline.AddLast("framing-dec", new DelimiterBasedFrameDecoder(32, true, delimiter));
|
||||
}
|
||||
else
|
||||
{
|
||||
//pipeline.AddLast("framing-enc", new LengthFieldPrepender(2));
|
||||
pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ushort.MaxValue, 0, 2, 0, 2));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(tcpEngineInitParam.EncoderWord))
|
||||
{
|
||||
//自定义编码器,编解码器必须写在处理器之前,否则数据包将不会经过编解码器
|
||||
pipeline.AddLast(new JSMachine.WMS.Netty.NettyAsClient.Codec.Encoder(tcpEngineInitParam.EncoderWord));
|
||||
}
|
||||
else
|
||||
{
|
||||
pipeline.AddLast(new JSMachine.WMS.Netty.NettyAsClient.Codec.EnCoderPrint(tcpEngineInitParam.EncoderWord));
|
||||
}
|
||||
|
||||
|
||||
pipeline.AddLast("echo", new EchoClientChannelHandler(tcpEngineInitParam));
|
||||
}));
|
||||
|
||||
await MonitorTcpConnection(tcpEngineInitParam.IP, tcpEngineInitParam.Port);
|
||||
}
|
||||
|
||||
private async Task MonitorTcpConnection(string serverIP, int port)
|
||||
{
|
||||
// 连接断开后持续重试,保证扫码设备临时掉线时能够自动恢复通信。
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (channel == null || !channel.Active)
|
||||
{
|
||||
try
|
||||
{
|
||||
channel = await SocketBootstrap.ConnectAsync(new IPEndPoint(IPAddress.Parse(serverIP), port));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"连接服务端 {serverIP} 失败,错误信息 {ex.Message} 即将重新连接");
|
||||
}
|
||||
}
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.Netty.Common.Model;
|
||||
using JSMachine.WMS.Netty.Common.Util;
|
||||
using JSMachine.WMS.Netty.NettyAsClient.Command;
|
||||
using JSMachine.WMS.Netty.NettyAsClient.CommandHandler;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.ComponentModel.Composition.Hosting;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.Transport
|
||||
{
|
||||
public class NettyClientTransport
|
||||
{
|
||||
private IChannelHandlerContext ChannelHandlerContext;
|
||||
private IPInfo RemoteIpInfo;
|
||||
private TcpEngineInitParam _tcpEngineInitParam;
|
||||
|
||||
[ImportMany("NettyAsClient")]
|
||||
private List<BaseCmdhandler> AllCmds { get; set; }
|
||||
|
||||
private Lazy<CompositionContainer> _Container = new(() =>
|
||||
{
|
||||
AggregateCatalog catalog = new();
|
||||
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
|
||||
|
||||
return new CompositionContainer(catalog);
|
||||
});
|
||||
|
||||
public NettyClientTransport(IChannelHandlerContext channel, TcpEngineInitParam tcpEngineInitParam)
|
||||
{
|
||||
_tcpEngineInitParam= tcpEngineInitParam;
|
||||
|
||||
_Container.Value.ComposeParts(this);
|
||||
|
||||
ChannelHandlerContext = channel;
|
||||
|
||||
foreach (BaseCmdhandler cmd in AllCmds)
|
||||
{
|
||||
cmd.Channel = channel;
|
||||
}
|
||||
|
||||
RemoteIpInfo = channel.ToIPInfo(AddressOnwer.Remote);
|
||||
}
|
||||
|
||||
public Task ChannelReadHandle(IByteBuffer byteBuffer)
|
||||
{
|
||||
string msg = byteBuffer.ToString(Encoding.UTF8);
|
||||
|
||||
//int length = byteBuffer.ReadableBytes;
|
||||
//byte[] array = new byte[length];
|
||||
//byteBuffer.GetBytes(byteBuffer.ReaderIndex, array);
|
||||
|
||||
//string msg = Encoding.UTF8.GetString(array);
|
||||
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
foreach (BaseCmdhandler cmd in AllCmds)
|
||||
{
|
||||
BaseCmd baseCmd;
|
||||
if (msg.Length < _tcpEngineInitParam.MinCodeLength)
|
||||
baseCmd = new BarCodeReadExceptionCmd() { Msg = msg };
|
||||
else
|
||||
{
|
||||
if (_tcpEngineInitParam.DeviceType != DeviceType.PenmoPrint)
|
||||
baseCmd = new BarCodeUploadCmd() { Msg = msg };
|
||||
else
|
||||
baseCmd = new PenmoReadCmd() { Msg = msg };
|
||||
//baseCmd = new BarCodeUploadCmd() { Msg = msg };
|
||||
}
|
||||
bool isMsgHasBeenHandled = await cmd.Handle(baseCmd);
|
||||
if (isMsgHasBeenHandled)
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsClient.Transport
|
||||
{
|
||||
public static class TransportManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 已连接的客户端
|
||||
/// key-ip 一个客户端仅允许一个连接
|
||||
/// </summary>
|
||||
|
||||
public static readonly ConcurrentDictionary<string, IChannelHandlerContext> DicClientChannels = new();
|
||||
|
||||
/// <summary>
|
||||
/// 移除有问题通道
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
public static void RemoveInActiveChannel(IChannelHandlerContext channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
DicClientChannels
|
||||
.Where(p => p.Value == channel)
|
||||
.Select(p => p.Key)
|
||||
.ToList()
|
||||
?.ForEach(p => DicClientChannels.Remove(p, out IChannelHandlerContext channelHandlerContext));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"移除有问题通道出错: {ex.Message} \n {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定客户端发送消息
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
public async static Task<bool> SendMsg(string ip, string msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!DicClientChannels.ContainsKey(ip))
|
||||
{
|
||||
LogHelper.Error($"{ip}连接失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
IChannelHandlerContext channel = DicClientChannels[ip];
|
||||
|
||||
await channel.Channel.WriteAndFlushAsync(msg);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"发送消息出错: {ex.Message} \n {ex.StackTrace}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定客户端发送byte数组
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
public async static Task<bool> SendMsgByte(string ip, byte[] msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!DicClientChannels.ContainsKey(ip))
|
||||
{
|
||||
LogHelper.Error($"{ip}连接失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
IChannelHandlerContext channel = DicClientChannels[ip];
|
||||
|
||||
await channel.Channel.WriteAndFlushAsync(msg);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"发送消息出错: {ex.Message} \n {ex.StackTrace}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送广播消息
|
||||
/// </summary>
|
||||
/// <param name="messagePackage"></param>
|
||||
public static void BroadcastMsg(string brodCastMsg)
|
||||
{
|
||||
try
|
||||
{
|
||||
DicClientChannels.Keys.ToList().ForEach(async key =>
|
||||
{
|
||||
await DicClientChannels[key].Channel.WriteAndFlushAsync(brodCastMsg);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"发送广播消息出错: {ex.Message} \n {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Codecs;
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsServer.Codec
|
||||
{
|
||||
public class Decoder : ByteToMessageDecoder
|
||||
{
|
||||
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
|
||||
{
|
||||
if (input != null)
|
||||
{
|
||||
int length = input.ReadableBytes;
|
||||
byte[] bytes = new byte[length];
|
||||
input.ReadBytes(bytes);
|
||||
try
|
||||
{
|
||||
//IMessagePackage message = MessagePackageMange.Instance.BinaryDeserializeToObject(bytes);
|
||||
output.Add(null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"解码远程地址:{context.Channel.RemoteAddress}----发送的数据异常");
|
||||
}
|
||||
|
||||
input.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Codecs;
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsServer.Codec
|
||||
{
|
||||
public class Encoder : MessageToByteEncoder<string>
|
||||
{
|
||||
protected override void Encode(IChannelHandlerContext context, string message, IByteBuffer output)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] body = Encoding.UTF8.GetBytes(message);
|
||||
byte[] tail = Encoding.UTF8.GetBytes("\r");
|
||||
|
||||
output.WriteBytes(body);
|
||||
output.WriteBytes(tail);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"TCP编码异常,异常信息 {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsServer.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// 扫码命令
|
||||
/// </summary>
|
||||
public class BarCodeReadCmd : BaseCmd
|
||||
{
|
||||
public override string Msg { get; set; } = "LON\r\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace JSMachine.WMS.Netty.NettyAsServer.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// 条码上传命令
|
||||
/// </summary>
|
||||
public class BarCodeUploadCmd : BaseCmd
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsServer.Command
|
||||
{
|
||||
public class BaseCmd
|
||||
{
|
||||
public virtual string Msg { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using JSMachine.WMS.Common;
|
||||
using JSMachine.WMS.Common.Cache.Model;
|
||||
using JSMachine.WMS.Common.Cache;
|
||||
using JSMachine.WMS.Common.Dto.Http.In;
|
||||
using JSMachine.WMS.Netty.NettyAsServer.Command;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsServer.CommandHandler
|
||||
{
|
||||
public class BarCodeCmdHandlerL : BaseCmdhandler
|
||||
{
|
||||
public async override Task<bool> Handle(BaseCmd msg)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
if (msg is BarCodeUploadCmd barCodeUploadCmd)
|
||||
{
|
||||
GlobalMemoryCache.AddCach<string, BarCodeInfo>(
|
||||
RemoteIPInfo.IP,
|
||||
new BarCodeInfo
|
||||
{
|
||||
AddTime = DateTime.Now,
|
||||
BarCode = barCodeUploadCmd.Msg,
|
||||
IP = RemoteIPInfo.IP
|
||||
},
|
||||
new MemoryCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(2),
|
||||
SlidingExpiration = TimeSpan.FromSeconds(1)
|
||||
});
|
||||
|
||||
//bool? exist = Global.AppSettings.PaperFeedConfig.CodeScannerConfig?.Any(p => p.IP == RemoteIPInfo.IP);
|
||||
|
||||
//IEventAggregator.GetEvent<UploadBarCodeEvent>().Publish(new BarCodeUploadParam
|
||||
//{
|
||||
// BarCode = barCodeUploadCmd.Msg,
|
||||
// BusinessType = exist != null && exist.Value ? "0" : "1"
|
||||
//});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Netty.Common.Util;
|
||||
using JSMachine.WMS.Netty.NettyAsServer.Command;
|
||||
using Prism.Events;
|
||||
using Prism.Ioc;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsServer.CommandHandler
|
||||
{
|
||||
[InheritedExport("NettyAsServer")]
|
||||
public abstract class BaseCmdhandler
|
||||
{
|
||||
protected static IEventAggregator IEventAggregator=ContainerLocator.Container.Resolve<IEventAggregator>();
|
||||
|
||||
public IChannelHandlerContext Channel { get; set; }
|
||||
public IPInfo RemoteIPInfo => Channel.ToIPInfo(AddressOnwer.Remote);
|
||||
public abstract Task<bool> Handle(BaseCmd msg);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Handlers.Timeout;
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.Netty.Common.Util;
|
||||
using JSMachine.WMS.Netty.NettyAsServer.Transport;
|
||||
using System;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsServer
|
||||
{
|
||||
public class EchoServerChannelHandler : ChannelHandlerAdapter
|
||||
{
|
||||
private NettyServerTransport NettyTransport;
|
||||
|
||||
public override void ChannelActive(IChannelHandlerContext context)
|
||||
{
|
||||
LogHelper.Info($"客户端 {context.ToIPInfo(AddressOnwer.Remote).IP} 已连接");
|
||||
NettyTransport = new NettyServerTransport(context);
|
||||
|
||||
base.ChannelActive(context);
|
||||
}
|
||||
|
||||
public override void ChannelRead(IChannelHandlerContext context, object message)
|
||||
{
|
||||
//LogsTool.Default.Info("收到服务端TCP信息");
|
||||
if(message is IByteBuffer byteBuffer)
|
||||
NettyTransport.ChannelReadHandle(byteBuffer);
|
||||
}
|
||||
|
||||
public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();
|
||||
|
||||
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
|
||||
{
|
||||
//LogsTool.Default.Error("Exception: " + exception);
|
||||
TransportManager.RemoveInActiveChannel(context);
|
||||
context.CloseAsync();
|
||||
}
|
||||
|
||||
public override void HandlerAdded(IChannelHandlerContext context)
|
||||
{
|
||||
base.HandlerAdded(context);
|
||||
}
|
||||
|
||||
public override void HandlerRemoved(IChannelHandlerContext context)
|
||||
{
|
||||
LogHelper.Warn($"客户端 {context.ToIPInfo(AddressOnwer.Remote).IP} 下线.");
|
||||
TransportManager.RemoveInActiveChannel(context);
|
||||
base.HandlerRemoved(context);
|
||||
}
|
||||
|
||||
public override void UserEventTriggered(IChannelHandlerContext context, object evt)
|
||||
{
|
||||
IdleStateEvent eventState = evt as IdleStateEvent;
|
||||
|
||||
if (eventState.State == IdleState.ReaderIdle)
|
||||
{
|
||||
//没有任何读取
|
||||
RemoveInactiveChannel();
|
||||
}
|
||||
else if (eventState.State == IdleState.WriterIdle)
|
||||
{
|
||||
//没有任何写入
|
||||
RemoveInactiveChannel();
|
||||
}
|
||||
else
|
||||
if (eventState.State == IdleState.AllIdle)
|
||||
{
|
||||
//没有任何交互
|
||||
RemoveInactiveChannel();
|
||||
}
|
||||
|
||||
base.UserEventTriggered(context, evt);
|
||||
|
||||
void RemoveInactiveChannel()
|
||||
{
|
||||
context.CloseAsync();
|
||||
TransportManager.RemoveInActiveChannel(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Codecs;
|
||||
using DotNetty.Handlers.Timeout;
|
||||
using DotNetty.Transport.Bootstrapping;
|
||||
using DotNetty.Transport.Channels;
|
||||
using DotNetty.Transport.Channels.Sockets;
|
||||
using JSMachine.WMS.Common;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.Netty.NettyAsServer.Codec;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Encoder = JSMachine.WMS.Netty.NettyAsServer.Codec.Encoder;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsServer
|
||||
{
|
||||
public static class TcpServerEngine
|
||||
{
|
||||
private static ServerBootstrap SocketBootstrap = new();
|
||||
private static IEventLoopGroup BossGroup => new MultithreadEventLoopGroup(2);
|
||||
private static IEventLoopGroup WorkGroup = new MultithreadEventLoopGroup();
|
||||
|
||||
public static async void InitEngine(int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.Info("正在启动TCP服务端-----------");
|
||||
|
||||
SocketBootstrap
|
||||
.Group(BossGroup, WorkGroup)
|
||||
.Channel<TcpServerSocketChannel>()
|
||||
.Option(ChannelOption.TcpNodelay, true)
|
||||
//.Option(ChannelOption.ConnectTimeout, TimeSpan.FromSeconds(1000))
|
||||
.Option(ChannelOption.SoKeepalive, true)
|
||||
|
||||
//以下两种方式都是设置一次性能接收的数据包大小
|
||||
//.Option(ChannelOption.SoRcvbuf, 4 * 1024)
|
||||
.Option(ChannelOption.RcvbufAllocator, new FixedRecvByteBufAllocator(8 * 1024))
|
||||
.ChildHandler(new ActionChannelInitializer<ISocketChannel>(channel =>
|
||||
{
|
||||
IChannelPipeline pipeline = channel.Pipeline;
|
||||
//心跳超时时间配置
|
||||
//pipeline.AddLast(new IdleStateHandler(100,100,100));
|
||||
|
||||
IByteBuffer delimiter = Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes("[CR]\r\n"));
|
||||
//简单的协议可以直接使用自带的分隔符解码器
|
||||
pipeline.AddLast("framing-dec", new DelimiterBasedFrameDecoder(4000, true, delimiter));
|
||||
|
||||
//自定义编码器
|
||||
pipeline.AddLast(new Encoder());
|
||||
|
||||
pipeline.AddLast("echo", new EchoServerChannelHandler());
|
||||
}));
|
||||
|
||||
await SocketBootstrap.BindAsync(port);
|
||||
LogHelper.Info("TCP服务启动成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"TCP服务启动失败,错误信息 {ex.Message}");
|
||||
await Task.WhenAll(
|
||||
BossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),
|
||||
WorkGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Netty.Common.Util;
|
||||
using JSMachine.WMS.Netty.NettyAsServer.Command;
|
||||
using JSMachine.WMS.Netty.NettyAsServer.CommandHandler;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.ComponentModel.Composition.Hosting;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsServer.Transport
|
||||
{
|
||||
public class NettyServerTransport
|
||||
{
|
||||
public IChannelHandlerContext ChannelHandlerContext;
|
||||
public IPInfo RemoteIpInfo;
|
||||
|
||||
[ImportMany("NettyAsServer")]
|
||||
private List<BaseCmdhandler> AllCmds { get; set; }
|
||||
|
||||
private Lazy<CompositionContainer> _Container = new(() =>
|
||||
{
|
||||
AggregateCatalog catalog = new();
|
||||
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
|
||||
|
||||
return new CompositionContainer(catalog);
|
||||
});
|
||||
|
||||
public NettyServerTransport(IChannelHandlerContext channel)
|
||||
{
|
||||
_Container.Value.ComposeParts(this);
|
||||
|
||||
ChannelHandlerContext = channel;
|
||||
|
||||
foreach (BaseCmdhandler cmd in AllCmds)
|
||||
{
|
||||
cmd.Channel = channel;
|
||||
}
|
||||
|
||||
RemoteIpInfo = channel.ToIPInfo(AddressOnwer.Remote);
|
||||
|
||||
if (TransportManager.DicClientChannels.ContainsKey(RemoteIpInfo.IP))
|
||||
TransportManager.DicClientChannels.TryRemove(RemoteIpInfo.IP,out IChannelHandlerContext channelHandlerContext);
|
||||
TransportManager.DicClientChannels.TryAdd(RemoteIpInfo.IP, channel);
|
||||
}
|
||||
|
||||
public Task ChannelReadHandle(IByteBuffer byteBuffer)
|
||||
{
|
||||
int length = byteBuffer.ReadableBytes;
|
||||
byte[] array = new byte[length];
|
||||
byteBuffer.GetBytes(byteBuffer.ReaderIndex, array);
|
||||
|
||||
string msg = Encoding.UTF8.GetString(array);
|
||||
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
foreach (BaseCmdhandler cmd in AllCmds)
|
||||
{
|
||||
bool isMsgHasBeenHandled = await cmd.Handle(new BarCodeUploadCmd { Msg = msg });
|
||||
if (isMsgHasBeenHandled)
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Common.Cache;
|
||||
using JSMachine.WMS.Common.Cache.Model;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.Netty.NettyAsServer.Command;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyAsServer.Transport
|
||||
{
|
||||
public static class TransportManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 已连接的客户端
|
||||
/// key-ip 一个客户端仅允许一个连接
|
||||
/// </summary>
|
||||
|
||||
public static readonly ConcurrentDictionary<string, IChannelHandlerContext> DicClientChannels = new();
|
||||
|
||||
/// <summary>
|
||||
/// 移除有问题通道
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
public static void RemoveInActiveChannel(IChannelHandlerContext channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
DicClientChannels
|
||||
.Where(p => p.Value == channel)
|
||||
.Select(p => p.Key)
|
||||
.ToList()
|
||||
?.ForEach(p => DicClientChannels.Remove(p, out IChannelHandlerContext channelHandlerContext));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"移除有问题通道出错: {ex.Message} \n {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定客户端发送消息
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="msg"></param>
|
||||
/// <returns></returns>
|
||||
public async static Task<bool> SendMsg(string ip, string msg)
|
||||
{
|
||||
var result = false;
|
||||
try
|
||||
{
|
||||
if (!DicClientChannels.ContainsKey(ip))
|
||||
return result;
|
||||
|
||||
IChannelHandlerContext channel = DicClientChannels[ip];
|
||||
await channel.Channel.WriteAndFlushAsync(msg);
|
||||
result = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"指定客户端发送消息出错: {ex.Message} \n {ex.StackTrace}");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送广播消息
|
||||
/// </summary>
|
||||
/// <param name="messagePackage"></param>
|
||||
public static void BroadcastMsg(IByteBuffer messagePackage)
|
||||
{
|
||||
try
|
||||
{
|
||||
DicClientChannels.Keys.ToList().ForEach(async key =>
|
||||
{
|
||||
await DicClientChannels[key].Channel.WriteAndFlushAsync(messagePackage);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"发送广播消息出错: {ex.Message} \n {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送扫码命令并获取返回结果
|
||||
/// </summary>
|
||||
/// <param name="ip">具体扫码器的IP</param>
|
||||
/// <returns></returns>
|
||||
public async static Task<BarCodeInfo> SendReadBarCodeCmd(string ip)
|
||||
{
|
||||
var barCodeInfo = new BarCodeInfo();
|
||||
try
|
||||
{
|
||||
string msg = new BarCodeReadCmd().Msg;
|
||||
bool bRet = await SendMsg(ip, msg);
|
||||
if (!bRet)
|
||||
{
|
||||
LogHelper.Error($"向 {ip} 发送条码扫描命令失败");
|
||||
return null;
|
||||
}
|
||||
DateTime sendTime = DateTime.Now;
|
||||
int count = 0;
|
||||
while (true)
|
||||
{
|
||||
count++;
|
||||
Thread.Sleep(100);
|
||||
|
||||
barCodeInfo = GlobalMemoryCache.GetCach<string, BarCodeInfo>(ip);
|
||||
if (barCodeInfo == null || barCodeInfo.AddTime < sendTime)
|
||||
{
|
||||
if (count < 20)
|
||||
continue;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"向 {ip} 发送条码扫描命令返回结果失败{ex.Message} \n {ex.StackTrace}");
|
||||
}
|
||||
return barCodeInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyUdp.Command
|
||||
{
|
||||
public class BaseCmd : IBaseCmd
|
||||
{
|
||||
public virtual CommandType ComType { get; set; }
|
||||
}
|
||||
|
||||
public enum CommandType
|
||||
{
|
||||
/// <summary>
|
||||
/// 心跳包
|
||||
/// </summary>
|
||||
g_eKeepAlive = 1,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace JSMachine.WMS.Netty.NettyUdp.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// 心跳
|
||||
/// </summary>
|
||||
public class HeartBeartCmd : BaseCmd
|
||||
{
|
||||
public override CommandType ComType { get; set; } = CommandType.g_eKeepAlive;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyUdp.Command
|
||||
{
|
||||
public interface IBaseCmd
|
||||
{
|
||||
CommandType ComType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Netty.Common.Util;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyUdp.CommandHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理基类
|
||||
/// </summary>
|
||||
[InheritedExport("NettyUdp")]
|
||||
public abstract class BaseCmdhandler
|
||||
{
|
||||
public IChannelHandlerContext Channel { get; set; }
|
||||
public IPInfo RemoteIPInfo => Channel.ToIPInfo(AddressOnwer.Remote);
|
||||
public abstract Task<bool> Handle(string msg);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using JSMachine.WMS.Netty.NettyUdp.Command;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyUdp.CommandHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 命令处理器
|
||||
/// </summary>
|
||||
public class ShipDynamicsMsgHandler : BaseCmdhandler
|
||||
{
|
||||
public async override Task<bool> Handle(string msg)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using DotNetty.Transport.Channels;
|
||||
using DotNetty.Transport.Channels.Sockets;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.Netty.NettyUdp.Transport;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyUdp
|
||||
{
|
||||
public class EchoClientChannelHandler : SimpleChannelInboundHandler<DatagramPacket>
|
||||
{
|
||||
public static NettyUdpTransport NettyTransport;
|
||||
|
||||
public override void ChannelActive(IChannelHandlerContext context)
|
||||
{
|
||||
NettyTransport = new NettyUdpTransport(context);
|
||||
base.ChannelActive(context);
|
||||
}
|
||||
|
||||
public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();
|
||||
|
||||
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
|
||||
{
|
||||
LogHelper.Error($"JSMachine.WMS.Netty.NettyUdp.EchoClientChannelHandler.ChannelReadComplete 中发生异常,异常信息 {exception.Message}\n{exception.StackTrace}");
|
||||
|
||||
context.CloseAsync();
|
||||
}
|
||||
|
||||
public override void HandlerAdded(IChannelHandlerContext context)
|
||||
{
|
||||
base.HandlerAdded(context);
|
||||
}
|
||||
|
||||
public override void HandlerRemoved(IChannelHandlerContext context)
|
||||
{
|
||||
Console.WriteLine($"服务端{context}下线.");
|
||||
base.HandlerRemoved(context);
|
||||
}
|
||||
|
||||
protected override void ChannelRead0(IChannelHandlerContext ctx, DatagramPacket msg)
|
||||
{
|
||||
string msgStr = msg.Content.ToString(Encoding.UTF8);
|
||||
NettyTransport.ChannelReadHandle(msgStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using DotNetty.Transport.Channels;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using JSMachine.WMS.Netty.Common.Util;
|
||||
using JSMachine.WMS.Netty.NettyUdp.Command;
|
||||
using JSMachine.WMS.Netty.NettyUdp.CommandHandler;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.ComponentModel.Composition.Hosting;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyUdp.Transport
|
||||
{
|
||||
public class NettyUdpTransport
|
||||
{
|
||||
public IChannelHandlerContext ChannelHandlerContext;
|
||||
public IPInfo RemoteIpInfo;
|
||||
|
||||
[ImportMany("NettyUdp")]
|
||||
private List<BaseCmdhandler> AllCmds { get; set; }
|
||||
|
||||
private Lazy<CompositionContainer> _Container = new Lazy<CompositionContainer>(() =>
|
||||
{
|
||||
AggregateCatalog catalog = new();
|
||||
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
|
||||
|
||||
return new CompositionContainer(catalog);
|
||||
});
|
||||
|
||||
public NettyUdpTransport(IChannelHandlerContext channel)
|
||||
{
|
||||
_Container.Value.ComposeParts(this);
|
||||
|
||||
ChannelHandlerContext = channel;
|
||||
|
||||
foreach (BaseCmdhandler cmd in AllCmds)
|
||||
{
|
||||
cmd.Channel = channel;
|
||||
}
|
||||
|
||||
RemoteIpInfo = channel.ToIPInfo(AddressOnwer.Remote);
|
||||
}
|
||||
|
||||
public Task ChannelReadHandle(string msg)
|
||||
{
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
foreach (BaseCmdhandler cmd in AllCmds)
|
||||
{
|
||||
bool isMsgHasBeenHandled = await cmd.Handle(msg);
|
||||
if (isMsgHasBeenHandled)
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> WriteMsg(BaseCmd msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ChannelHandlerContext.Channel.Active)
|
||||
{
|
||||
await ChannelHandlerContext.Channel.WriteAndFlushAsync(msg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"向TCP通道写入数据失败,错误信息 {ex.Message}");
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using DotNetty.Transport.Bootstrapping;
|
||||
using DotNetty.Transport.Channels;
|
||||
using DotNetty.Transport.Channels.Sockets;
|
||||
using JSMachine.WMS.Infrastructure.Helper;
|
||||
using System;
|
||||
|
||||
namespace JSMachine.WMS.Netty.NettyUdp
|
||||
{
|
||||
public class UdpEngine
|
||||
{
|
||||
public async void InitEngine()
|
||||
{
|
||||
try
|
||||
{
|
||||
MultithreadEventLoopGroup group = new();
|
||||
Bootstrap bootstrap = new();
|
||||
bootstrap
|
||||
.Group(group)
|
||||
.Channel<SocketDatagramChannel>() //UDP连接方式
|
||||
.Option(ChannelOption.SoBroadcast, true) //广播形式获取数据
|
||||
.Option(ChannelOption.SoReuseaddr, true) //可以复用端口号
|
||||
.Handler(new EchoClientChannelHandler());
|
||||
await bootstrap.BindAsync(503); //绑定本地端口号开始监听
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error($"UDP连接失败,错误信息 {ex.Message}\n{ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user