使用Dotnetty 写一个Tcp client,在网络故障或服务器关闭tcp监听再开启后能够自动重连,即保持连接状态。
遇到了发声 I/O 错误(I/O Error Occured)问题,在github的Dotnetty项目也有人遇到,作为一个问题仍然OPEN。
在连接未OPEN、连接关闭、连接异常时,等待5秒后重连
protected override async Task<bool> connectAsync()
{
if (eventLoopGroup == null) eventLoopGroup = new MultithreadEventLoopGroup();
X509Certificate2 tlsCertificate = null;
string targetHost = null;
if (isSsl)
{
tlsCertificate = new X509Certificate2(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, sslKeyFile), sslPassword);
targetHost = tlsCertificate.GetNameInfo(X509NameType.DnsName, false);
}
var bootstrap = new Bootstrap();
bootstrap
.Group(eventLoopGroup)
.Channel<TcpSocketChannel>()
.Option(ChannelOption.TcpNodelay, true)
.Handler(new ActionChannelInitializer<IChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
if (tlsCertificate != null)
{
pipeline.AddLast(TlsHandler.Server(tlsCertificate));
}
if (enableNettyLog) pipeline.AddLast(new LoggingHandler(loggerSource));
pipeline.AddLast(handlerList.ToArray());
}));
try
{
IChannel clientChannel = await bootstrap.ConnectAsync(serverEndPoint);
if (clientChannel.Open)
{
channel = clientChannel;
Logger.Info($"与 {channel.RemoteAddress} 建立连接", loggerSource);
_ = clientChannel.CloseCompletion.ContinueWith((t, s) =>
{
Logger.Info($"与 {channel.RemoteAddress} 断开连接", loggerSource);
scheduleReconnect();//auto reconnect when channel closed.
}, this, TaskContinuationOptions.ExecuteSynchronously);
}
else
{
Logger.Warning($"clientChannel not open, retry after {reconnectDelay.Seconds} s", loggerSource);
scheduleReconnect();//auto reconnect when connect failed.
}
}
catch(Exception ex)
{
Logger.Warning($"can't connect to {serverIp} port:{serverPort}: {ex.Message}, retry after {reconnectDelay.Seconds} s", loggerSource);
scheduleReconnect(); //auto reconnect when connect error.
}
return true;
}
private void scheduleReconnect()
{
if (!disconnected)
{
eventLoopGroup.Schedule(async () => await connectAsync(), reconnectDelay);
}
}
在.net6 或者 .net framework4.6.1/4.7.2 的Console Application 中,都能够自动重连,唯独.net framework Class Library 不行,我用dotnetty的源代码跟踪2个小时,发现不是dotnetty重连问题,是我的Handler在重连成功后ChannelActive里有个发送握手报文helloMsg的逻辑,而该握手报文的IByteBuffer在重连时已经被释放freeed,是不能重复使用的,I/O 错误就是这里抛出的。
只需要在ChannelActive里调用Unpooled.WrappedBuffer(helloMsg)就没问题了。
ctor()
{
helloBuffer = Unpooled.WrappedBuffer(helloMsg);// helloMsg doesn't change
}
public override void ChannelActive(IChannelHandlerContext ctx)
{
this.ctx = ctx;
if (helloBuffer != null)
{
ctx.WriteAndFlushAsync(helloBuffer);
}
}