-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathRpcClient.cs
More file actions
227 lines (194 loc) · 7.67 KB
/
Copy pathRpcClient.cs
File metadata and controls
227 lines (194 loc) · 7.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// Copyright (c) 2017 TPDT
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// SimCivil - SimCivil.Rpc - RpcClient.cs
// Create Date: 2019/05/08
// Update Date: 2019/05/19
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Castle.DynamicProxy;
using DotNetty.Codecs;
using DotNetty.Handlers.Timeout;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using SimCivil.Contract;
using SimCivil.Rpc.Callback;
using SimCivil.Rpc.Timeout;
namespace SimCivil.Rpc
{
public class RpcClient : IDisposable
{
private readonly IChannelHandler _callbackResolver;
private readonly IChannelHandler _decoder = new JsonToMessageDecoder();
private readonly IChannelHandler _encoder = new MessageToJsonEncoder<RpcRequest>();
private readonly ProxyGenerator _generator = new ProxyGenerator();
private readonly IChannelHandler _resolver;
private readonly IConnectionControl _connectionControl;
private int _nextCallbackId;
private long _nextSeq;
public IPEndPoint EndPoint { get; private set; }
public IChannel Channel { get; private set; }
public Dictionary<Type, object> ProxyCache { get; } = new Dictionary<Type, object>();
public Dictionary<long, RpcRequest> ResponseWaitlist { get; } = new Dictionary<long, RpcRequest>();
public int ResponseTimeout { get; set; } = 3000;
/// <summary>
/// Gets or sets the heartbeat delay in miliseconds.
/// </summary>
/// <value>
/// The heartbeat delay.
/// </value>
public int HeartbeatDelay { get; set; } = 2000;
public IInterceptor Interceptor { get; }
public bool Connected { get; private set; }
public Dictionary<int, Delegate> CallBackList { get; } = new Dictionary<int, Delegate>();
public RpcClient() : this(new IPEndPoint(IPAddress.Loopback, 20170)) { }
public RpcClient(IPEndPoint endPoint)
{
EndPoint = endPoint;
Interceptor = new RpcInterceptor(this);
_resolver = new RpcClientResolver(this);
_callbackResolver = new RpcCallbackResolver(this);
_connectionControl = Import<IConnectionControl>();
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (Channel?.Open ?? false)
Channel.CloseAsync().Wait();
}
public event EventHandler<EventArgs<string>> DecodeFail;
public RpcClient Bind(int port)
{
EndPoint = new IPEndPoint(IPAddress.Loopback, port);
return this;
}
public RpcClient Bind(string ip, int port)
{
EndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
return this;
}
public async Task ConnectAsync()
{
if (EndPoint == null)
throw new InvalidOperationException(nameof(EndPoint));
if (Channel?.Open ?? false)
throw new InvalidOperationException(nameof(Channel));
IEventLoopGroup loopGroup = new MultithreadEventLoopGroup();
try
{
var bootstrap = new Bootstrap();
bootstrap.Group(loopGroup)
.Channel<TcpSocketChannel>()
.Option(ChannelOption.SoKeepalive, true)
.Option(ChannelOption.TcpNodelay, true)
.Handler(
new ActionChannelInitializer<ISocketChannel>(
ChannelInit));
Channel = await bootstrap.ConnectAsync(EndPoint);
}
catch
{
Channel?.CloseAsync().Wait();
loopGroup.ShutdownGracefullyAsync().Wait();
throw;
}
Connected = true;
}
protected virtual void ChannelInit(ISocketChannel channel)
{
channel.Pipeline
.AddLast(new IdleStateHandler(0, HeartbeatDelay / 1000, 0))
.AddLast(new ClientIdleHandler(SendHeartbeat))
.AddLast(new LengthFieldPrepender(2))
.AddLast(new LengthFieldBasedFrameDecoder(ushort.MaxValue, 0, 2, 0, 2))
.AddLast(_decoder)
.AddLast(_encoder)
.AddLast(_resolver)
.AddLast(_callbackResolver);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void Disconnect()
{
if (Connected)
{
Channel?.DisconnectAsync();
Connected = false;
Channel = null;
ProxyCache.Clear();
}
}
/// <summary>
/// Imports or gets remote service.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T Import<T>() where T : class
{
if (ProxyCache.TryGetValue(typeof(T), out object service)) return service as T;
var newService = _generator.CreateInterfaceProxyWithoutTarget<T>(Interceptor);
ProxyCache[typeof(T)] = newService;
return newService;
}
[MethodImpl(MethodImplOptions.Synchronized)]
public long GetNextSequence() => _nextSeq++;
protected virtual void OnDecodeFail(EventArgs<string> e)
{
DecodeFail?.Invoke(this, e);
}
/// <summary>Attaches the callback and gets id.</summary>
/// <param name="delegate">The callback to be attached.</param>
/// <returns>Id of attached callback</returns>
public int AttachCallback(Delegate @delegate)
{
int id = Interlocked.Increment(ref _nextCallbackId);
CallBackList[id] = @delegate;
return id;
}
public void SendHeartbeat()
{
Task.Run(
() =>
{
try
{
_connectionControl.Noop();
}
catch
{
// Heartbeat failed
Disconnect();
}
});
}
public void NotifyPacketSent()
{
if (!Connected)
throw new InvalidOperationException("Rpc Client is disconnected");
}
}
}