viewModel:
public partial class TcpServerChannelViewModel : ObservableObject
{
private Socket socketServer = new Socket(
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp
);
private Dictionary<string, Socket> currentClientlist = new Dictionary<string, Socket>();
private CancellationTokenSource cts1 = new();
private Channel<TcpMessage> messageChannel = Channel.CreateUnbounded<TcpMessage>();
private Button myBtn = new();
private Task socketServerTask = Task.CompletedTask;
public Channel<TcpMessage> MessageChannel
{
get => messageChannel;
set => messageChannel = value;
}
public TcpServerChannelViewModel()
{
MyIp = "127.0.0.1";
MyPort = "8889";
}
#region 属性
[ObservableProperty]
private ObservableCollection<string> serverList = new();
[ObservableProperty]
private ObservableCollection<TcpMessage> reciveData = new();
[ObservableProperty]
private string? myIp;
[ObservableProperty]
private string? fanucIp;
[ObservableProperty]
private string? myPort;
[ObservableProperty]
private string sendTxt = string.Empty;
#endregion
#region 命令
[RelayCommand]
public async Task Conn(Button btn)
{
if (string.IsNullOrWhiteSpace(MyIp) || string.IsNullOrWhiteSpace(MyPort))
{
net8Wpf.Core.Diag.PopupBox.Show($"Ip和端口号不能为空", null, 2, "红");
return;
}
IPEndPoint ipe = new IPEndPoint(IPAddress.Parse(MyIp), int.Parse(MyPort));
try
{
socketServer.Bind(ipe);
socketServer.Listen(10);
cts1 = new();
myBtn = btn;
myBtn.IsEnabled = false;
net8Wpf.Core.Diag.PopupBox.Show("服务器开启成功!");
await AddTcpMessage("服务器开启成功!", "Conn");
// 扫描客户端
/*
AcceptClientsAsync 是一个长时间运行的任务,用于不断地接受新的客户端连接。如果在 Conn 方法中使用 await 来等待 AcceptClientsAsync,那么 Conn 方法会被阻塞,直到 AcceptClientsAsync 方法完成。这与服务器的设计初衷相违背,因为 AcceptClientsAsync 是一个应该一直运行的任务,而 Conn 方法需要继续执行其他初始化操作并返回。
通过将 AcceptClientsAsync 任务分配给 socketServerTask 而不使用 await,可以确保服务器能够继续运行其他任务而不被阻塞。
*/
socketServerTask = AcceptClientsAsync(cts1.Token);
// 启动消费者线程
await ProcessMessagesAsync(cts1.Token);
}
catch (Exception ex)
{
net8Wpf.Core.Diag.PopupBox.Show($"ex:Bind:{ex.Message}");
myBtn.IsEnabled = true; // 启用按钮
}
}
[RelayCommand]
public async Task DisConn()
{
cts1.Cancel();
await Task.Delay(200);
socketServer?.Close();
socketServer = new Socket(
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp
);
var tcpMessage = await AddTcpMessage("服务器已关闭", "DisConn");
Application.Current.Dispatcher.Invoke(() =>
{
ReciveData.Add(tcpMessage);
ServerList.Clear();
});
net8Wpf.Core.Diag.PopupBox.Show($"服务器已关闭", null, 2, "红");
myBtn.IsEnabled = true;
}
[RelayCommand]
public async Task SendMsg()
{
if (ServerList.Count > 0)
{
byte[] send = Encoding.ASCII.GetBytes(SendTxt);
List<Task> sendTasks = new();
foreach (var client in ServerList)
{
if (currentClientlist.TryGetValue(client, out var socketClient))
{
sendTasks.Add(socketClient.SendAsync(send, SocketFlags.None));
}
}
try
{
await Task.WhenAll(sendTasks);
net8Wpf.Core.Diag.PopupBox.Show("发送成功!");
}
catch (Exception ex)
{
net8Wpf.Core.Diag.PopupBox.Show($"发送失败: {ex.Message}");
}
}
else
{
net8Wpf.Core.Diag.PopupBox.Show("发送失败!");
}
SendTxt = string.Empty;
}
[RelayCommand]
public void ClearMsg()
{
SendTxt = string.Empty;
}
[RelayCommand]
public void ObXml()
{
string xmlFilePath = ViewNames.XmlFilePath;
XDocument doc = XDocument.Load(xmlFilePath);
SendTxt = doc.ToString();
}
#endregion
#region 私有方法
/// <summary>
/// 扫描客户端
/// </summary>
/// <param name="token">令牌</param>
/// <returns></returns>
private async Task AcceptClientsAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
Socket socketClient = await socketServer.AcceptAsync();
string? client = socketClient?.RemoteEndPoint?.ToString();
if (client != null)
{
if (ServerList.Count >= 9)
{
await RemoveClient(ServerList[0]);
}
currentClientlist[client] = socketClient;
Application.Current.Dispatcher.Invoke(() => ServerList.Add(client));
// 创建接收消息的任务 并发
//_ = Task.Run(() => ReceiveMessagesAsync(socketClient, token));
_ = Task.Run(async () => await ReceiveMessagesAsync(socketClient, token));
//会阻塞 AcceptClientsAsync 方法,直到当前客户端的消息接收任务完成。这会导致服务器无法接受新的客户端连接,违背了并发处理每个客户端连接的初衷
// 直接await ReceiveMessagesAsync(socketClient, token)
}
}
catch (Exception ex)
{
await AddTcpMessage(ex.Message, "catch Exception");
}
}
}
/// <summary>
/// 创建socketClient接收消息的任务
/// </summary>
/// <param name="socketClient">客户端</param>
/// <param name="token">令牌</param>
/// <returns></returns>
private async Task ReceiveMessagesAsync(Socket socketClient, CancellationToken token)
{
byte[] buffer = new byte[1024 * 10 * 10];
string client = socketClient.RemoteEndPoint.ToString();
while (!token.IsCancellationRequested)
{
try
{
int length = await socketClient.ReceiveAsync(buffer, SocketFlags.None, token);
if (length > 0)
{
string data = Encoding.ASCII.GetString(buffer, 0, length);
await AddTcpMessage(data, client);
}
else
{
await AddTcpMessage($"{client}下线了", client);
currentClientlist.Remove(client);
break;
}
}
catch (Exception ex)
{
await AddTcpMessage($"{client}下线了 ex:{ex.Message}", client);
currentClientlist.Remove(client);
break;
}
}
socketClient.Close();
}
/// <summary>
/// 消费者线程
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
private async Task ProcessMessagesAsync(CancellationToken token)
{
await foreach (var message in messageChannel.Reader.ReadAllAsync(token))
{
Application.Current.Dispatcher.Invoke(() => ReciveData.Add(message));
if (message.Client == "catch Exception" || message.Message.Contains("下线了"))
{
Application.Current.Dispatcher.Invoke(() => ServerList.Remove(message.Client));
}
if (message.Message != null && (!message.Message.Contains("下线了")))
{
//默认发送文件 可以删除
string xmlFilePath = ViewNames.XmlFilePath;
// 加载XML文件
XDocument doc = XDocument.Load(xmlFilePath);
// 序列化XML到字符串
string xmlString = doc.ToString(); // 直接调用ToString方法
SendTxt = xmlString;
await SendMsg();
}
}
}
private async Task<TcpMessage> AddTcpMessage(string message, string client)
{
TcpMessage tcpMessage =
new()
{
DateTime = DateTime.Now,
Message = message,
Client = client
};
await messageChannel.Writer.WriteAsync(tcpMessage);
return tcpMessage;
}
private async Task RemoveClient(string client)
{
if (currentClientlist.TryGetValue(client, out var socket))
{
socket?.Close();
currentClientlist.Remove(client);
await AddTcpMessage($"{client}因超过最大连接被移除", client);
Application.Current.Dispatcher.Invoke(() => ServerList.Remove(client));
}
}
#endregion
}
View:
<UserControl
x:Class="net8wpf.Cad.Views.TcpServerChannelView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:net8wpf.Cad.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:prism="http://prismlibrary.com/"
xmlns:vm="clr-namespace:net8wpf.Cad.ViewModels"
d:DataContext="{d:DesignInstance vm:TcpServerChannelViewModel}"
d:DesignHeight="1050"
d:DesignWidth="1680"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="0.3*" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2.5*" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<md:TransitioningContent OpeningEffect="{md:TransitionEffect Kind=ExpandIn}">
<Border
Margin="5,5,5,5"
BorderBrush="Orange"
BorderThickness="3">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
Margin="10,0,0,0"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Width="75"
Margin="50,0,10,0"
FontSize="18"
FontWeight="Bold"
Foreground="White"
Text="服务器Ip" />
<TextBox
Width="130"
Margin="10,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontFamily="Microsoft YaHei"
FontSize="16"
FontWeight="Bold"
Foreground="White"
Text="{Binding MyIp}" />
<TextBlock
Margin="50,0,0,0"
FontSize="18"
FontWeight="Bold"
Foreground="White"
Text="服务器端口号" />
<TextBox
Width="60"
Margin="20,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
FontFamily="Microsoft YaHei"
FontSize="16"
FontWeight="Bold"
Foreground="White"
Text="{Binding MyPort}" />
</StackPanel>
<StackPanel
Grid.Row="1"
Margin="10,0,0,0"
VerticalAlignment="Center"
Orientation="Horizontal">
<Button
Name="BtnConn"
Width="150"
Margin="5,5,5,5"
Background="MediumSeaGreen"
Command="{Binding ConnCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}"
Content="打开Tcp服务器端"
Foreground="White" />
<Button
Width="150"
Margin="10,5,5,5"
Background="OrangeRed"
Command="{Binding DisConnCommand}"
Content="关闭服务器"
Foreground="White" />
<Button
Width="150"
Margin="10,5,5,5"
Background="DarkViolet"
Command="{Binding ObXmlCommand}"
Content="获取Xml内容"
Foreground="White" />
<Button
Width="150"
Margin="10,5,5,5"
Background="DeepSkyBlue"
Command="{Binding ClearMsgCommand}"
Content="清空内容"
Foreground="White" />
<Button
Width="150"
Margin="10,5,5,5"
Command="{Binding SendMsgCommand}"
Content="发送消息"
Foreground="White" />
</StackPanel>
</Grid>
</Border>
</md:TransitioningContent>
<md:TransitioningContent Grid.Column="1" OpeningEffect="{md:TransitionEffect Kind=ExpandIn}">
<Border
Margin="5,5,5,5"
BorderBrush="Orange"
BorderThickness="3">
<Grid>
<ListView ItemsSource="{Binding ServerList}">
<ListView.View>
<GridView>
<GridViewColumn Width="200" Header="客户端名称">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Border>
</md:TransitioningContent>
<md:TransitioningContent Grid.Row="1" OpeningEffect="{md:TransitionEffect Kind=ExpandIn}">
<Border
Margin="5,5,5,5"
BorderBrush="Orange"
BorderThickness="3">
<ListView ItemsSource="{Binding ReciveData}">
<ListView.View>
<GridView>
<GridViewColumn
Width="150"
DisplayMemberBinding="{Binding Client}"
Header="客户端" />
<GridViewColumn
Width="150"
DisplayMemberBinding="{Binding DateTime}"
Header="时间" />
<GridViewColumn
Width="500"
DisplayMemberBinding="{Binding Message}"
Header="消息" />
</GridView>
</ListView.View>
</ListView>
</Border>
</md:TransitioningContent>
<md:TransitioningContent
Grid.Row="1"
Grid.Column="1"
OpeningEffect="{md:TransitionEffect Kind=ExpandIn}">
<Border
Margin="5,5,5,5"
BorderBrush="Orange"
BorderThickness="3">
<StackPanel>
<TextBlock
HorizontalAlignment="Center"
Foreground="White"
Text="发送区域" />
<TextBox
Margin="5,20,5,5"
HorizontalAlignment="Stretch"
AcceptsReturn="True"
Foreground="White"
Text="{Binding SendTxt}" />
</StackPanel>
</Border>
</md:TransitioningContent>
</Grid>
</UserControl>