Skip to content

Commit b76b1e9

Browse files
committed
添加QUIC传输配置选项
优化传输协议选择逻辑 局域网分享页面quic标注
1 parent 0b7fbe7 commit b76b1e9

8 files changed

Lines changed: 135 additions & 29 deletions

File tree

Core/Services/Config/KitopiaConfig.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class KitopiaConfig : ConfigBase
3535
[ConfigFieldCategory("设备互传")]
3636
[ConfigField("对外显示名称", "为空时使用当前计算机名称", 0xf45f, ConfigFieldType.字符串)]
3737
public string deviceBroadcastName = string.Empty;
38+
[ConfigField("启用QUIC传输", "关闭后设备互传仅使用TCP", 0xE61C, ConfigFieldType.布尔)]
39+
public bool deviceCommunicationEnableQuic = true;
3840
public string devicePersistentId = string.Empty;
3941
public string devicePrivateKey = string.Empty;
4042

Core/Services/DeviceCommunication/Application/MessageAppService.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,14 @@ private async Task ProcessIncomingMessagesAsync() {
217217
continue;
218218
}
219219

220+
Logger.Information(
221+
"接收信息。 EventType={EventType} Type={MessageType} ConversationId={ConversationId} TransferId={TransferId} Detail={Detail}",
222+
transformedEvent.EventType,
223+
transformedEvent.Message.GetType().Name,
224+
transformedEvent.Message.ConversationId,
225+
transformedEvent.TransferId,
226+
DescribeIncomingEvent(transformedEvent));
227+
220228
await NotifyToastIfNeededAsync(transformedEvent);
221229
await _receiveChannel.Writer.WriteAsync(transformedEvent);
222230
}
@@ -326,12 +334,14 @@ private async ValueTask SendCoreAsync(MessageContext context, AppMessage message
326334
}
327335

328336
Logger.Information(
329-
"发送信息。 Type={MessageType} Protocol={Protocol} Remote={RemoteEndPoint} Route={Route} Command={Command}",
337+
"发送信息。 Type={MessageType} Protocol={Protocol} Remote={RemoteEndPoint} Route={Route} Command={Command} ConversationId={ConversationId} Detail={Detail}",
330338
message.GetType().Name,
331339
context.Protocol,
332340
context.RemoteEndPoint,
333341
envelope.Route,
334-
envelope.Command);
342+
envelope.Command,
343+
message.ConversationId,
344+
DescribeMessage(message));
335345

336346
await _protocolSender.SendEnvelopeAsync(context, envelope, cancellationToken);
337347
}
@@ -457,4 +467,39 @@ private async ValueTask SendDirectImageAsync(
457467

458468
await _protocolSender.SendEnvelopeWithPayloadAsync(context, envelope, payloadStream, cancellationToken: cancellationToken);
459469
}
470+
471+
private static string DescribeIncomingEvent(IncomingMessageEvent messageEvent) {
472+
var payloadBytes = messageEvent.PayloadBytes?.LongLength ?? 0;
473+
var baseDetail = $"{DescribeMessage(messageEvent.Message)}, bytes={messageEvent.BytesTransferred}/{messageEvent.TotalBytes}, payloadBytes={payloadBytes}";
474+
return string.IsNullOrWhiteSpace(messageEvent.Reason)
475+
? baseDetail
476+
: $"{baseDetail}, reason={messageEvent.Reason}";
477+
}
478+
479+
private static string DescribeMessage(AppMessage message) {
480+
return message switch {
481+
TextChatMessage text => $"text={LimitForLog(text.Text)}",
482+
TextClipboardMessage textClipboard => $"clipboardText={LimitForLog(textClipboard.Text)}",
483+
FileOfferChatMessage fileOffer =>
484+
$"transferId={fileOffer.TransferId}, file={fileOffer.FileName}, size={fileOffer.SizeBytes}, contentType={fileOffer.ContentType}",
485+
FileChatMessage file =>
486+
$"channelId={file.ChannelId}, file={file.FileName}, length={file.Length}",
487+
ImageChatMessage image =>
488+
$"transferId={image.TransferId}, size={image.SizeBytes}, contentType={image.ContentType}, isDirect={image.IsDirect}",
489+
FileAcceptChatMessage accept => $"transferId={accept.TransferId}",
490+
FileRejectChatMessage reject => $"transferId={reject.TransferId}, reason={reject.Reason}",
491+
FileCancelChatMessage cancel => $"transferId={cancel.TransferId}, reason={cancel.Reason}",
492+
FileCompleteChatMessage complete => $"transferId={complete.TransferId}",
493+
_ => message.ToString() ?? message.GetType().Name
494+
};
495+
}
496+
497+
private static string LimitForLog(string? text, int maxLength = 120) {
498+
if (string.IsNullOrWhiteSpace(text)) {
499+
return string.Empty;
500+
}
501+
502+
var singleLine = text.ReplaceLineEndings("\\n");
503+
return singleLine.Length <= maxLength ? singleLine : $"{singleLine[..maxLength]}...";
504+
}
460505
}

Core/Services/DeviceCommunication/LocalDataListenerHost.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.IO.Pipelines;
22
using System.Net;
33
using System.Net.Quic;
4+
using Core.Services.Config;
45
using Serilog;
56

67
namespace Core.Services.DeviceCommunication;
@@ -48,6 +49,12 @@ public async Task StartListeningAsync(CancellationToken token = default)
4849

4950
await _tcpListener.StartAsync(token);
5051

52+
if (!ConfigManger.Config.deviceCommunicationEnableQuic)
53+
{
54+
Logger.Information("QUIC local listener disabled by configuration.");
55+
return;
56+
}
57+
5158
if (QuicConnection.IsSupported && QuicListener.IsSupported)
5259
{
5360
await _quicListener.StartAsync(token);
@@ -200,4 +207,5 @@ private static async Task CopyStreamToPipeAsync(Stream source, PipeWriter writer
200207
}
201208
}
202209
}
210+
203211
}

Core/Services/DeviceCommunication/TcpLocalDataListener.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public async Task SendAsync(
115115
remoteIdentityPublicKey.Trim(),
116116
cancellationToken);
117117
await sslStream.WriteAsync(payload, cancellationToken);
118-
await sslStream.FlushAsync(cancellationToken);
118+
await CompleteSendAsync(sslStream, cancellationToken);
119119
}
120120

121121
public async Task SendAsync(
@@ -156,7 +156,7 @@ public async Task SendAsync(
156156
}
157157
}
158158

159-
await sslStream.FlushAsync(cancellationToken);
159+
await CompleteSendAsync(sslStream, cancellationToken);
160160
}
161161

162162
public async Task StopAsync()
@@ -389,6 +389,22 @@ private static async Task CopyStreamToPipeAsync(Stream source, PipeWriter writer
389389
}
390390
}
391391

392+
private static async Task CompleteSendAsync(SslStream sslStream, CancellationToken cancellationToken)
393+
{
394+
await sslStream.FlushAsync(cancellationToken);
395+
await sslStream.ShutdownAsync();
396+
397+
var buffer = new byte[1];
398+
while (true)
399+
{
400+
var read = await sslStream.ReadAsync(buffer, cancellationToken);
401+
if (read == 0)
402+
{
403+
break;
404+
}
405+
}
406+
}
407+
392408
private static X509Certificate2 CreateIdentityCertificate()
393409
{
394410
var privateKey = ConfigManger.Config.devicePrivateKey?.Trim();

Core/ViewModel/Pages/device/DeviceCommunicationPageViewModel.cs

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Specialized;
33
using System.ComponentModel;
44
using System.Net;
5+
using System.Net.Sockets;
56
using Avalonia;
67
using Avalonia.Controls.ApplicationLifetimes;
78
using Avalonia.Controls.Notifications;
@@ -26,6 +27,7 @@ namespace Core.ViewModel.Pages.device;
2627
public partial class DeviceCommunicationPageViewModel : ObservableObject, IDisposable {
2728
private static readonly ILogger Logger = LogManager.Logger.ForContext<DeviceCommunicationPageViewModel>();
2829
private readonly IDeviceDiscoveryService _deviceDiscoveryService;
30+
private readonly ILocalDataListener _localDataListener;
2931
private readonly IMessageAppService _messageAppService;
3032
private readonly IClipboardService _clipboardService;
3133
private readonly IToastService _toastService;
@@ -62,10 +64,12 @@ public int MessageListVersion {
6264

6365
public DeviceCommunicationPageViewModel(
6466
IDeviceDiscoveryService deviceDiscoveryService,
67+
ILocalDataListener localDataListener,
6568
IMessageAppService messageAppService,
6669
IClipboardService clipboardService,
6770
IToastService toastService) {
6871
_deviceDiscoveryService = deviceDiscoveryService;
72+
_localDataListener = localDataListener;
6973
_messageAppService = messageAppService;
7074
_clipboardService = clipboardService;
7175
_toastService = toastService;
@@ -145,11 +149,9 @@ private async Task SendToConversationAsync(DeviceConversationItem conversation,
145149
throw new InvalidOperationException("Invalid target device identity.");
146150
}
147151

148-
var protocol = conversation.SupportQuic && conversation.QuicPort > 0
149-
? LocalDataTransportProtocol.Quic
150-
: LocalDataTransportProtocol.Tcp;
152+
var protocol = SelectTransportProtocol(conversation.SupportQuic, conversation.QuicPort);
151153
var port = protocol == LocalDataTransportProtocol.Quic ? conversation.QuicPort : conversation.TcpPort;
152-
var transportAddress = conversation.PreferredTransportAddress;
154+
var transportAddress = SelectTransportAddress(conversation.Ipv4Address, conversation.Ipv6Address);
153155

154156
if (port <= 0 || transportAddress == IPAddress.None) {
155157
throw new InvalidOperationException("Invalid target address or port.");
@@ -168,11 +170,9 @@ await SendMessageCoreAsync(conversation, text, LocalDataTransportProtocol.Tcp, c
168170

169171
private async Task SendImageToConversationAsync(DeviceConversationItem conversation, ImageChatMessage message,
170172
Stream stream) {
171-
var protocol = conversation.SupportQuic && conversation.QuicPort > 0
172-
? LocalDataTransportProtocol.Quic
173-
: LocalDataTransportProtocol.Tcp;
173+
var protocol = SelectTransportProtocol(conversation.SupportQuic, conversation.QuicPort);
174174
var port = protocol == LocalDataTransportProtocol.Quic ? conversation.QuicPort : conversation.TcpPort;
175-
var transportAddress = conversation.PreferredTransportAddress;
175+
var transportAddress = SelectTransportAddress(conversation.Ipv4Address, conversation.Ipv6Address);
176176

177177
try {
178178
await SendImageCoreAsync(conversation, message, stream, protocol, port, transportAddress);
@@ -187,11 +187,9 @@ await SendImageCoreAsync(conversation, message, stream, LocalDataTransportProtoc
187187

188188
private async Task SendFileToConversationAsync(DeviceConversationItem conversation, FileChatMessage message,
189189
Stream stream) {
190-
var protocol = conversation.SupportQuic && conversation.QuicPort > 0
191-
? LocalDataTransportProtocol.Quic
192-
: LocalDataTransportProtocol.Tcp;
190+
var protocol = SelectTransportProtocol(conversation.SupportQuic, conversation.QuicPort);
193191
var port = protocol == LocalDataTransportProtocol.Quic ? conversation.QuicPort : conversation.TcpPort;
194-
var transportAddress = conversation.PreferredTransportAddress;
192+
var transportAddress = SelectTransportAddress(conversation.Ipv4Address, conversation.Ipv6Address);
195193

196194
try {
197195
await SendFileCoreAsync(conversation, message, stream, protocol, port, transportAddress);
@@ -606,9 +604,7 @@ private void OnFileOfferReceived(FileOfferChatMessage message) {
606604
return;
607605
}
608606

609-
var protocol = conversation.SupportQuic && conversation.QuicPort > 0
610-
? LocalDataTransportProtocol.Quic
611-
: LocalDataTransportProtocol.Tcp;
607+
var protocol = SelectTransportProtocol(conversation.SupportQuic, conversation.QuicPort);
612608
var port = protocol == LocalDataTransportProtocol.Quic ? conversation.QuicPort : conversation.TcpPort;
613609

614610
var timestamp = DateTimeOffset.Now;
@@ -908,24 +904,40 @@ private async Task SendOfferDecisionWithFallbackAsync(
908904
DeviceConversationItem conversation,
909905
IncomingFileOfferChatMessageItem offer,
910906
Func<MessageContext, Task> action) {
911-
var primaryProtocol = offer.Protocol;
907+
var primaryProtocol = offer.Protocol == LocalDataTransportProtocol.Quic
908+
? SelectTransportProtocol(conversation.SupportQuic, conversation.QuicPort)
909+
: LocalDataTransportProtocol.Tcp;
912910
var primaryPort = ResolvePort(conversation, primaryProtocol, offer.Port);
913911
if (primaryPort <= 0) {
914912
throw new InvalidOperationException("Invalid remote port for transfer decision.");
915913
}
916914

917915
try {
918916
await action(BuildContext(conversation, primaryProtocol, primaryPort,
919-
conversation.PreferredTransportAddress));
917+
SelectTransportAddress(conversation.Ipv4Address, conversation.Ipv6Address)));
920918
}
921919
catch (Exception ex) when (primaryProtocol == LocalDataTransportProtocol.Quic && conversation.TcpPort > 0) {
922920
Logger.Warning(ex, "Send transfer decision over QUIC failed, fallback to TCP. DeviceId={DeviceId}",
923921
conversation.DeviceId);
924922
await action(BuildContext(conversation, LocalDataTransportProtocol.Tcp, conversation.TcpPort,
925-
conversation.PreferredTransportAddress));
923+
SelectTransportAddress(conversation.Ipv4Address, conversation.Ipv6Address)));
926924
}
927925
}
928926

927+
private LocalDataTransportProtocol SelectTransportProtocol(bool remoteSupportsQuic, int remoteQuicPort) {
928+
return _localDataListener.SupportsQuic && remoteSupportsQuic && remoteQuicPort > 0
929+
? LocalDataTransportProtocol.Quic
930+
: LocalDataTransportProtocol.Tcp;
931+
}
932+
933+
private static IPAddress SelectTransportAddress(IPAddress ipv4Address, IPAddress ipv6Address) {
934+
if (Socket.OSSupportsIPv6 && ipv6Address != IPAddress.None) {
935+
return ipv6Address;
936+
}
937+
938+
return ipv4Address;
939+
}
940+
929941
private DeviceConversationItem? FindConversationByAddress(IPAddress remoteAddress) {
930942
var normalized = NormalizeAddress(remoteAddress);
931943
return Conversations.FirstOrDefault(conversation =>

Core/ViewModel/Windows/LanFileShareWindowViewModel.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.ObjectModel;
22
using System.Collections.Specialized;
33
using System.Net;
4+
using System.Net.Sockets;
45
using Avalonia.Controls.Notifications;
56
using CommunityToolkit.Mvvm.ComponentModel;
67
using CommunityToolkit.Mvvm.Input;
@@ -18,6 +19,7 @@ namespace Core.ViewModel.Windows;
1819
public partial class LanFileShareWindowViewModel : ObservableObject, IDisposable
1920
{
2021
private readonly IDeviceDiscoveryService _deviceDiscoveryService;
22+
private readonly ILocalDataListener _localDataListener;
2123
private readonly IMessageAppService _messageAppService;
2224
private readonly IToastService _toastService;
2325

@@ -44,10 +46,12 @@ private static Dictionary<string, string> CustomNameMap
4446

4547
public LanFileShareWindowViewModel(
4648
IDeviceDiscoveryService deviceDiscoveryService,
49+
ILocalDataListener localDataListener,
4750
IMessageAppService messageAppService,
4851
IToastService toastService)
4952
{
5053
_deviceDiscoveryService = deviceDiscoveryService;
54+
_localDataListener = localDataListener;
5155
_messageAppService = messageAppService;
5256
_toastService = toastService;
5357

@@ -159,11 +163,9 @@ private async Task SendFileToDeviceAsync(DeviceModel device, FileChatMessage mes
159163
throw new InvalidOperationException("Invalid target device identity.");
160164
}
161165

162-
var protocol = device.SupportQuic && device.QuicPort > 0
163-
? LocalDataTransportProtocol.Quic
164-
: LocalDataTransportProtocol.Tcp;
166+
var protocol = SelectTransportProtocol(device.SupportQuic, device.QuicPort);
165167
var port = protocol == LocalDataTransportProtocol.Quic ? device.QuicPort : device.TcpPort;
166-
var transportAddress = device.PreferredTransportAddress;
168+
var transportAddress = SelectTransportAddress(device.Ipv4Address, device.Ipv6Address);
167169

168170
if (port <= 0 || transportAddress == IPAddress.None)
169171
{
@@ -190,10 +192,27 @@ private async Task SendFileCoreAsync(
190192
LocalDataTransportProtocol protocol,
191193
int port)
192194
{
193-
var sendContext = BuildContext(device, protocol, port, device.PreferredTransportAddress);
195+
var sendContext = BuildContext(device, protocol, port, SelectTransportAddress(device.Ipv4Address, device.Ipv6Address));
194196
await _messageAppService.SendFileChatAsync(sendContext, message, stream);
195197
}
196198

199+
private LocalDataTransportProtocol SelectTransportProtocol(bool remoteSupportsQuic, int remoteQuicPort)
200+
{
201+
return _localDataListener.SupportsQuic && remoteSupportsQuic && remoteQuicPort > 0
202+
? LocalDataTransportProtocol.Quic
203+
: LocalDataTransportProtocol.Tcp;
204+
}
205+
206+
private static IPAddress SelectTransportAddress(IPAddress ipv4Address, IPAddress ipv6Address)
207+
{
208+
if (Socket.OSSupportsIPv6 && ipv6Address != IPAddress.None)
209+
{
210+
return ipv6Address;
211+
}
212+
213+
return ipv4Address;
214+
}
215+
197216
private static MessageContext BuildContext(DeviceModel device, LocalDataTransportProtocol protocol, int port,
198217
IPAddress remoteAddress)
199218
{

KitopiaAvalonia/Windows/LanFileShareWindow.axaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@
7474
<StackPanel Spacing="2">
7575
<StackPanel Orientation="Horizontal" Spacing="4">
7676
<TextBlock Text="{Binding DisplayName}" FontWeight="SemiBold" />
77+
<Label Theme="{DynamicResource TagLabel}" Classes="Blue"
78+
IsVisible="{Binding SupportQuic}">Quic</Label>
7779
<Label Theme="{DynamicResource TagLabel}" Classes="Green"
7880
IsVisible="{Binding HasIpv6}">IPv6</Label>
7981
</StackPanel>

KitopiaAvalonia/Windows/LanFileShareWindow.axaml.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using Avalonia.Controls;
44
using Avalonia.Markup.Xaml;
5+
using Core.Services.DeviceCommunication;
56
using Core.Services.DeviceCommunication.Application;
67
using Core.Services.DeviceCommunication.Discovery;
78
using Core.Services.Interfaces;
@@ -30,12 +31,13 @@ public void Show(IReadOnlyCollection<string> filePaths)
3031
var deviceDiscoveryService = ServiceManager.Services.GetService<IDeviceDiscoveryService>();
3132
var messageAppService = ServiceManager.Services.GetService<IMessageAppService>();
3233
var toastService = ServiceManager.Services.GetService<IToastService>();
33-
if (deviceDiscoveryService is null || messageAppService is null || toastService is null)
34+
var localDataListener = ServiceManager.Services.GetService<ILocalDataListener>();
35+
if (deviceDiscoveryService is null || messageAppService is null || toastService is null|| localDataListener is null)
3436
{
3537
return;
3638
}
3739

38-
vm = new LanFileShareWindowViewModel(deviceDiscoveryService, messageAppService, toastService);
40+
vm = new LanFileShareWindowViewModel(deviceDiscoveryService,localDataListener, messageAppService, toastService);
3941
DataContext = vm;
4042
}
4143

0 commit comments

Comments
 (0)