Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
401cecfc14 | ||
|
|
e36917db31
|
||
|
|
47c978544c
|
No files matched your search
@@ -5,6 +5,11 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
description: Existing version tag to publish
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
@@ -21,7 +26,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Fetch exact tagged source
|
- name: Fetch exact tagged source
|
||||||
env:
|
env:
|
||||||
GITEA_REF: ${{ gitea.ref }}
|
GITEA_REF: ${{ inputs.release_tag || gitea.ref }}
|
||||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -63,7 +68,8 @@ jobs:
|
|||||||
const release = metadata.releases.find(item => item.sdk?.version === sdkVersion);
|
const release = metadata.releases.find(item => item.sdk?.version === sdkVersion);
|
||||||
const file = release?.sdk?.files?.find(item => item.rid === 'linux-x64' && item.name.endsWith('.tar.gz'));
|
const file = release?.sdk?.files?.find(item => item.rid === 'linux-x64' && item.name.endsWith('.tar.gz'));
|
||||||
if (!file) throw new Error(`Linux x64 SDK archive not found for ${sdkVersion}`);
|
if (!file) throw new Error(`Linux x64 SDK archive not found for ${sdkVersion}`);
|
||||||
const archiveResponse = await fetch(file.url);
|
const archiveUrl = `https://dotnetcli.blob.core.windows.net/dotnet/Sdk/${sdkVersion}/dotnet-sdk-${sdkVersion}-linux-x64.tar.gz`;
|
||||||
|
const archiveResponse = await fetch(archiveUrl);
|
||||||
if (!archiveResponse.ok) throw new Error(`SDK download failed: ${archiveResponse.status}`);
|
if (!archiveResponse.ok) throw new Error(`SDK download failed: ${archiveResponse.status}`);
|
||||||
await writeFile('/tmp/dotnet-sdk.tar.gz', new Uint8Array(await archiveResponse.arrayBuffer()));
|
await writeFile('/tmp/dotnet-sdk.tar.gz', new Uint8Array(await archiveResponse.arrayBuffer()));
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
本文件记录 `ShrinkNetwork` 在当前工作区中的包内变更。
|
本文件记录 `ShrinkNetwork` 在当前工作区中的包内变更。
|
||||||
|
|
||||||
|
## [0.4.4] - 2026-11-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- TCP 服务端:会话接收循环结束时不再读取已释放 socket 的 `RemoteEndPoint`,改用接入时记录的地址;此前客户端主动断开会抛出 `ObjectDisposedException`,`Disconnected` 事件丢失,上层仍向已断开会话发送并报 `Session N is not connected`。
|
||||||
|
- TCP 客户端:接收循环收尾同样改用连接时记录的地址,本地 `Stop()` 导致的读取中断按正常关闭处理,不再向 Unity 日志抛出 `ObjectDisposedException`。
|
||||||
|
- TCP 服务端:`Stop()` 后挂起的 accept 任务异常被观察,不再产生未观察的 `SocketException`。
|
||||||
|
|
||||||
## [0.2.0] - 2026-05-18
|
## [0.2.0] - 2026-05-18
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<AssemblyName>ShrinkNetwork.Runtime</AssemblyName>
|
<AssemblyName>ShrinkNetwork.Runtime</AssemblyName>
|
||||||
<RootNamespace>ShrinkNetwork</RootNamespace>
|
<RootNamespace>ShrinkNetwork</RootNamespace>
|
||||||
<PackageId>ShrinkSDK.Network</PackageId>
|
<PackageId>ShrinkSDK.Network</PackageId>
|
||||||
<Version>0.4.2</Version>
|
<Version>0.4.4</Version>
|
||||||
<Description>ShrinkSDK messaging, RPC, permission and transport runtime.</Description>
|
<Description>ShrinkSDK messaging, RPC, permission and transport runtime.</Description>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
|
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<PackageReference Include="Kcp-CSharp" Version="1.0.8" />
|
<PackageReference Include="Kcp-CSharp" Version="1.0.8" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="UniTask" Version="2.5.10" />
|
<PackageReference Include="UniTask" Version="2.5.10" />
|
||||||
<PackageReference Include="ShrinkSDK.Runtime.Abstractions" Version="0.1.0" />
|
<PackageReference Include="ShrinkSDK.Runtime.Abstractions" Version="0.2.0" />
|
||||||
<PackageReference Include="ShrinkSDK.CodeGen" Version="0.2.0" PrivateAssets="compile;runtime;contentfiles;native" />
|
<PackageReference Include="ShrinkSDK.CodeGen" Version="0.2.0" PrivateAssets="compile;runtime;contentfiles;native" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -121,9 +121,9 @@ namespace ShrinkNetwork
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var remote = _client.Client.RemoteEndPoint?.ToString() ?? $"{_host}:{_port}";
|
var remote = ShrinkTcpServerTransport.TryGetRemoteAddress(_client) ?? $"{_host}:{_port}";
|
||||||
OnEvent?.Invoke(ShrinkNetworkTransportEvent.Connected(_sessionId, remote));
|
OnEvent?.Invoke(ShrinkNetworkTransportEvent.Connected(_sessionId, remote));
|
||||||
ReceiveLoopAsync(_client, stream, cancellationToken).Forget();
|
ReceiveLoopAsync(stream, remote, cancellationToken).Forget();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -153,7 +153,7 @@ namespace ShrinkNetwork
|
|||||||
return sslStream;
|
return sslStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async UniTaskVoid ReceiveLoopAsync(TcpClient client, Stream stream, CancellationToken cancellationToken)
|
private async UniTaskVoid ReceiveLoopAsync(Stream stream, string remote, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -176,6 +176,14 @@ namespace ShrinkNetwork
|
|||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
catch (Exception) when (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Stop() cancelled the token and disposed the stream; the pending read fails with
|
||||||
|
// ObjectDisposedException or IOException. This is a local, intentional shutdown.
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
catch (IOException ex) when (IsExpectedDisconnect(ex))
|
catch (IOException ex) when (IsExpectedDisconnect(ex))
|
||||||
{
|
{
|
||||||
ShrinkNetworkLogger.Warn($"[ShrinkNetwork] TCP receive loop ended: {ex.Message}");
|
ShrinkNetworkLogger.Warn($"[ShrinkNetwork] TCP receive loop ended: {ex.Message}");
|
||||||
@@ -191,7 +199,8 @@ namespace ShrinkNetwork
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
var remote = client.Client.RemoteEndPoint?.ToString() ?? $"{_host}:{_port}";
|
// Do not read the socket here: Stop() may already have disposed it, and
|
||||||
|
// Socket.RemoteEndPoint would throw ObjectDisposedException out of this async void.
|
||||||
OnEvent?.Invoke(ShrinkNetworkTransportEvent.Disconnected(_sessionId, remote));
|
OnEvent?.Invoke(ShrinkNetworkTransportEvent.Disconnected(_sessionId, remote));
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
@@ -202,7 +211,8 @@ namespace ShrinkNetwork
|
|||||||
if (string.Equals(ex.Message, "Remote closed.", StringComparison.Ordinal))
|
if (string.Equals(ex.Message, "Remote closed.", StringComparison.Ordinal))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return ex.InnerException is SocketException socketException && IsExpectedDisconnect(socketException);
|
return ex.InnerException is ObjectDisposedException ||
|
||||||
|
ex.InnerException is SocketException socketException && IsExpectedDisconnect(socketException);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsExpectedDisconnect(SocketException ex)
|
private static bool IsExpectedDisconnect(SocketException ex)
|
||||||
|
|||||||
@@ -150,7 +150,12 @@ namespace ShrinkNetwork
|
|||||||
var acceptTask = _listener.AcceptTcpClientAsync();
|
var acceptTask = _listener.AcceptTcpClientAsync();
|
||||||
var completedTask = await Task.WhenAny(acceptTask, Task.Delay(Timeout.Infinite, cancellationToken));
|
var completedTask = await Task.WhenAny(acceptTask, Task.Delay(Timeout.Infinite, cancellationToken));
|
||||||
if (completedTask != acceptTask)
|
if (completedTask != acceptTask)
|
||||||
|
{
|
||||||
|
// Listener.Stop() faults the pending accept; observe it so it is not reported as unobserved.
|
||||||
|
_ = acceptTask.ContinueWith(static task => _ = task.Exception,
|
||||||
|
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
client = await acceptTask;
|
client = await acceptTask;
|
||||||
}
|
}
|
||||||
@@ -175,9 +180,9 @@ namespace ShrinkNetwork
|
|||||||
_clients[sessionId] = client;
|
_clients[sessionId] = client;
|
||||||
_streams[sessionId] = stream;
|
_streams[sessionId] = stream;
|
||||||
_sendLocks[sessionId] = new SemaphoreSlim(1, 1);
|
_sendLocks[sessionId] = new SemaphoreSlim(1, 1);
|
||||||
var remoteAddress = client.Client.RemoteEndPoint?.ToString() ?? "unknown";
|
var remoteAddress = TryGetRemoteAddress(client) ?? "unknown";
|
||||||
OnEvent?.Invoke(ShrinkNetworkTransportEvent.Connected(sessionId, remoteAddress));
|
OnEvent?.Invoke(ShrinkNetworkTransportEvent.Connected(sessionId, remoteAddress));
|
||||||
_ = ReceiveLoopAsync(sessionId, client, stream, cancellationToken);
|
_ = ReceiveLoopAsync(sessionId, client, stream, remoteAddress, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +217,8 @@ namespace ShrinkNetwork
|
|||||||
_serverCertificate = new X509Certificate2(tlsOptions.ServerCertificatePath, tlsOptions.ServerCertificatePassword);
|
_serverCertificate = new X509Certificate2(tlsOptions.ServerCertificatePath, tlsOptions.ServerCertificatePassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReceiveLoopAsync(long sessionId, TcpClient client, Stream stream, CancellationToken cancellationToken)
|
private async Task ReceiveLoopAsync(long sessionId, TcpClient client, Stream stream, string remoteAddress,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -241,12 +247,20 @@ namespace ShrinkNetwork
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (_clients.TryRemove(sessionId, out var removed))
|
if (_clients.TryRemove(sessionId, out var removed))
|
||||||
|
{
|
||||||
|
// The stream owns the socket: once it is disposed, Socket.RemoteEndPoint throws
|
||||||
|
// ObjectDisposedException. The address captured at accept time is used instead so the
|
||||||
|
// Disconnected event is always raised.
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (_streams.TryRemove(sessionId, out var ownedStream))
|
if (_streams.TryRemove(sessionId, out var ownedStream))
|
||||||
ownedStream.Dispose();
|
ownedStream.Dispose();
|
||||||
_sendLocks.TryRemove(sessionId, out _);
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
var remoteAddress = removed.Client.RemoteEndPoint?.ToString() ?? "unknown";
|
_sendLocks.TryRemove(sessionId, out _);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
removed.Close();
|
removed.Close();
|
||||||
@@ -260,6 +274,22 @@ namespace ShrinkNetwork
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static string? TryGetRemoteAddress(TcpClient client)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return client.Client?.RemoteEndPoint?.ToString();
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (SocketException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task SendInternalAsync(Stream stream, SemaphoreSlim sendLock, ReadOnlyMemory<byte> packetData,
|
private static async Task SendInternalAsync(Stream stream, SemaphoreSlim sendLock, ReadOnlyMemory<byte> packetData,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "com.cneicy.shrink-network",
|
"name": "com.cneicy.shrink-network",
|
||||||
"version": "0.4.2",
|
"version": "0.4.4",
|
||||||
"displayName": "ShrinkNetwork",
|
"displayName": "ShrinkNetwork",
|
||||||
"description": "面向 Unity 的轻量网络框架,提供会话、消息注册、RPC、权限控制,以及 TCP/KCP 传输抽象。",
|
"description": "面向 Unity 的轻量网络框架,提供会话、消息注册、RPC、权限控制,以及 TCP/KCP 传输抽象。",
|
||||||
"unity": "2022.3",
|
"unity": "2022.3",
|
||||||
|
|||||||
Reference in new issue
Block a user