在C# 中 使用 System.Threading.Channels 建立一个高性能的TcpServer

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>

  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潘诺西亚的火山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值