C#.网络编程 Socket基础(四) WPF系统Socket TCP协议 服务器与客户端 不同类型文件传输,解决UI线程(异步委托)与工作线程的卡顿问题

一、简介

虽然,本文的前面几篇文章在WinForm中实现了Socket TCP协议 服务器与客户端 不同类型文件传输,详情见

 C#.网络编程 Socket基础(一)Socket TCP协议 实现端到端(服务器与客户端)简单字符串通信

C#.网络编程 Socket基础(二) 基于WinForm系统Socket TCP协议 实现端到端(服务器与客户端)图片传输

C#.网络编程 Socket基础(三) 基于WinForm系统Socket TCP协议 实现端到端(服务器与客户端).txt.word.png等不同类型文件传输

但是,却没有在WPF中实现 Socket TCP协议 服务器与客户端 不同类型文件传输。因此,本文将描述如何在WPF中实现该功能。

同时,解决UI线程与工作线程的卡顿问题,UI卡顿问题是一个重点,可以参考网页:

https://www.jianshu.com/p/1d19514bccea

https://www.cnblogs.com/TianFang/p/3969430.html

https://blog.csdn.net/u010050735/article/details/79491348

 

二、WPF实现

Server端代码

MainWindow.xaml

<Window x:Class="SocketServer.MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SocketServer"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">


    <Grid>
        <Border BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" Height="231" Margin="9,33,0,0" VerticalAlignment="Top" Width="500">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition  Width="1*"/>
                    <ColumnDefinition  Width="3*"/>
                </Grid.ColumnDefinitions>
                <Canvas Grid.Column="0" Background="#FF8383BC">

                    <Label x:Name="Lbl_ReceivefileStatus" Content="接收文件的状态" HorizontalAlignment="Left" Height="31" VerticalAlignment="Top" Width="101" Canvas.Left="14" Canvas.Top="157"/>
                    <Label x:Name="Lbl_ReceivefileName" Content="接收文件的名字" HorizontalAlignment="Left" Height="25" VerticalAlignment="Top" Width="101" Canvas.Left="14" Canvas.Top="28"/>
                    <Label x:Name="Lbl_ReceivefileType" Content="接收文件的类型" HorizontalAlignment="Left" Height="27" VerticalAlignment="Top" Width="101" Canvas.Left="14" Canvas.Top="87"/>

                </Canvas>
                <Canvas Grid.Column="1" Background="Blue">
                    <TextBox Name="TxtBox_ReceivefileStatus"  HorizontalAlignment="Left" Height="22" VerticalAlignment="Top" Width="60" Canvas.Left="300" Canvas.Top="159"/>
                    <TextBox x:Name="TxtBox_ReceivefileName"  HorizontalAlignment="Left" Height="22" VerticalAlignment="Top" Width="352" Canvas.Left="8" Canvas.Top="28"/>
                    <TextBox x:Name="TxtBox_ReceivefileType" HorizontalAlignment="Left" Height="22" VerticalAlignment="Top" Width="352" Canvas.Left="8" Canvas.Top="87"/>
                    <ProgressBar x:Name="progressBar_Rec" Height="22" Width="285" Canvas.Left="9" Canvas.Top="159" RenderTransformOrigin="0.5,0.5" Background="#FF00FFCB"/>
                </Canvas>
            </Grid>
        </Border>
        <Button x:Name="btn_StartServer" Content="aaaa" HorizontalAlignment="Left" Height="22" Margin="193,283,0,0" VerticalAlignment="Top" Width="146" Background="Silver" Click="btn_StartServer_Click"/>
    </Grid>

</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Data;
using System.Drawing;
//using System.Drawing.Imaging;
using System.IO;
using System.Net;
using System.Net.Sockets;
//using System.Windows.Forms;


namespace SocketServer
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
         public MainWindow()
        {
            InitializeComponent();
            this.TxtBox_ReceivefileStatus.Text = "0/100";
            this.btn_StartServer.Content = "开启器服务器";
        }

        private void btn_StartServer_Click(object sender, RoutedEventArgs e)
        {
            //System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(()=> {
            //this.Dispatcher.Invoke(() =>
            //{

            //    this.TxtBox_ReceivefileStatus.Text = "AA/100";
            //        //this.btn_StartServer.Content = "监听中...";
            //    });
            //}));
            //thread.Start();
            this.btn_StartServer.Content = "监听中...";
            this.TxtBox_ReceivefileStatus.Text = "AA/100";
            System.Threading.Thread thread1 = new System.Threading.Thread(new System.Threading.ThreadStart(() => {
                Socket receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPEndPoint hostIpEndPoint = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 8888);
                //设置接收数据缓冲区的大小
                byte[] b = new byte[4096];
                receiveSocket.Bind(hostIpEndPoint);
                //监听
                receiveSocket.Listen(2);
                //接受客户端连接
                Socket hostSocket = receiveSocket.Accept();
                //内存流fs的初始容量大小为0,随着数据增长而扩展。
                MemoryStream fs = new MemoryStream();
                //string Path = "C:\\Users\\lanmage2\\Desktop\\AA\\文件1.txt";
                //FileStream fs = new FileStream(Path, FileMode.Open);
                int length = 0;
                //每接受一次,只能读取小于等于缓冲区的大小4096个字节
                while ((length = hostSocket.Receive(b)) > 0)
                {
                    //将接受到的数据b,按长度length放到内存流中。
                    fs.Write(b, 0, length);

                    if (progressBar_Rec.Value < 100)
                    {
                        //进度条的默认值为0
                        progressBar_Rec.Value++;
                        //TxtBox_ReceivefileStatus.Text = "接收:" + progressBar_Rec.Value + "/100";
                    }
                }
                progressBar_Rec.Value = 100;
                // TxtBox_ReceivefileStatus.Text = "接收:" + progressBar_Rec.Value + "/100";
                fs.Flush();

                fs.Seek(0, SeekOrigin.Begin);
                byte[] byteArray = new byte[fs.Length];
                int count = 0;
                while (count < fs.Length)
                {
                    byteArray[count] = Convert.ToByte(fs.ReadByte());
                    count++;
                }
                string Path = "C:\\Users\\lanmage2\\Desktop\\AA";
                //FileStream filestream = new FileStream(Path + "\\文件1.txt", FileMode.OpenOrCreate);
                FileStream filestream = File.Create(Path);
                //filestream.Write(byteArray, 0, byteArray.Length);//不能用
                //System.IO.File.WriteAllBytes(Path, byteArray);//能用

                /*Bitmap类,可以将*/
                //Bitmap Img = new Bitmap(fs);
                //Img.Save(@"reveive.jpg", ImageFormat.Png);
                //关闭写文件流
                fs.Close();
                //关闭接收数据的Socket
                hostSocket.Shutdown(SocketShutdown.Receive);
                hostSocket.Close();
                //关闭发送连接
                receiveSocket.Close();
            }));
            thread1.Start();
            
        }
    }
}

Client 代码:

MainWindow.xaml

<Window x:Class="SocketClient.MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SocketClient"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Border BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" Height="231" Margin="9,33,0,0" VerticalAlignment="Top" Width="500">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition  Width="1*"/>
                    <ColumnDefinition  Width="3*"/>
                </Grid.ColumnDefinitions>
                <Canvas Grid.Column="0" Background="#FF00FFCB">

                    <Label x:Name="Lbl_SendFileStatus" Content="发送文件的状态" HorizontalAlignment="Left" Height="31" VerticalAlignment="Top" Width="101" Canvas.Left="14" Canvas.Top="147"/>
                    <Label x:Name="Lbl_SendFileName" Content="发送文件的名字" HorizontalAlignment="Left" Height="25" VerticalAlignment="Top" Width="101" Canvas.Left="14" Canvas.Top="28"/>
                    <Label x:Name="Lbl_SendFileType" Content="发送文件的类型" HorizontalAlignment="Left" Height="27" VerticalAlignment="Top" Width="101" Canvas.Left="14" Canvas.Top="87"/>
                    <Label x:Name="Lbl_SendFilePath" Content="发送文件的路径" HorizontalAlignment="Left" Height="27" VerticalAlignment="Top" Width="101" Canvas.Left="14" Canvas.Top="194"/>

                </Canvas>
                <Canvas Grid.Column="1" Background="#FF00FFF9">
                    <TextBox x:Name="TxtBox_SendFileName"  HorizontalAlignment="Left" Height="22" VerticalAlignment="Top" Width="352" Canvas.Left="8" Canvas.Top="28"/>
                    <TextBox x:Name="TxtBox_SendFileType" HorizontalAlignment="Left" Height="22" VerticalAlignment="Top" Width="352" Canvas.Left="8" Canvas.Top="87"/>
                    <ProgressBar x:Name="progressBar_File" Height="22" Width="285" Canvas.Left="9" Canvas.Top="149" RenderTransformOrigin="0.5,0.5" Background="#FF00FFCB"/>
                    <TextBox Name="TxtBox_SendFileStatus"  HorizontalAlignment="Left" Height="22" VerticalAlignment="Top" Width="60" Canvas.Left="300" Canvas.Top="149"/>
                    <TextBox Name="TxtBox_SendFilePath"  HorizontalAlignment="Left" Height="22" VerticalAlignment="Top" Width="351" Canvas.Left="10" Canvas.Top="197"/>

                </Canvas>
            </Grid>
        </Border>
        <Button x:Name="btn_OpenFile" Content="打开" HorizontalAlignment="Left" Height="22" Margin="119,279,0,0" VerticalAlignment="Top" Width="146" Background="Silver" Click="btn_OpenFile_Click"/>
        <Button x:Name="btn_SendFile" Content="发送" HorizontalAlignment="Left" Height="22" Margin="294,279,0,0" VerticalAlignment="Top" Width="146" Background="Silver" Click="btn_SendFile_Click"/>
        <Button x:Name="Client"  HorizontalAlignment="Left" Height="26" Margin="94,4,0,0" VerticalAlignment="Top" Width="350" Content="Client" Background="#FF00FFEE" BorderThickness="3" BorderBrush="#FF151411"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Net;
using System.Net.Sockets;
using Microsoft.Win32;

namespace SocketClient
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        static Socket sendsocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        //OpenFileDialog表示打开文件对话框。
        OpenFileDialog openFileDialog1 = new OpenFileDialog();
        Byte[] imgByte = new byte[1024];

        /// <summary>
        /// 打开本地文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_OpenFile_Click(object sender, EventArgs e)
        {
            this.btn_OpenFile.Content = "已经打开";
            //可以打开文件的类型
            this.openFileDialog1.Filter = "Image Files(*.BMP;*.JPG;*.GIF;*.PNG)|*.BMP;*.JPG;*.GIF;*.PNG" + "|All Files (*.*)|*.*";
            //如果文件对话框已经打开
            if (this.openFileDialog1.ShowDialog() == true)
            {
                try
                {
                    string path = this.openFileDialog1.FileName;
                    TxtBox_SendFilePath.Text = path;
                    //path表示文件所在的路径。
                    //FileMode.Open表示打开文件。 FileMode.Create表示创建一个文件并覆盖原有的文件。当然还有其他方式。
                    //表示可以读文件(只读)。
                    FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
                    imgByte = new Byte[fs.Length];
                    //将文件读到imgByte中。
                    fs.Read(imgByte, 0, imgByte.Length);
                    fs.Close();
                }
                catch (Exception)
                {
                }
            }
        }
        /// <summary>
        /// 向服务器发送数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_SendFile_Click(object sender, EventArgs e)
        {
            //实例化socket        
            IPEndPoint ipendpiont = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 8888);
            sendsocket.Connect(ipendpiont);
            MessageBox.Show("服务器IP:" + sendsocket.RemoteEndPoint);
            sendsocket.Send(imgByte);
            sendsocket.Shutdown(System.Net.Sockets.SocketShutdown.Send);
            sendsocket.Close();
            sendsocket.Dispose();
        }

    }
}

三、UI卡顿问题

1、简单的卡顿问题        

在前面的Server MainWindow.xaml.cs,我们会注意到第一个地方,我把工程线程(或耗时线程)放到了 thread1 中去。这样,点击btn_StartServer_Click后,才不会发生阻塞,可以动态更新btn_StartServer的Content(但是,程序运行到 if (progressBar_Rec.Value < 100)   这行会出错。下面会提到UI线程与工作者线程的关系)。如图:

 

若是,都放在主线程,就会发生阻塞,btn_StartServer的Content更新不了,如下:

 

发生阻塞问题的关键所在,是在 Socket hostSocket = receiveSocket.Accept();这一行,因为它一直等待客户端连接,耗时太长,所以UI(即btn_StartServer的Content)不能更新。

四、UI线程无法直接调用工作线程

      你可能会发现,当运行我上面的程序的时候,抛出异常信息   “调用线程无法访问此对象,因为另一个线程拥有该对象”。根本原因是UI线程无法直接在工作线程中使用,WPF中只有UI线程才能操作UI元素,非UI线程要访问UI时就会报异常了。

相关的网页解决方法:

1(网址1)、WPF 工作者线程中,无法直接访问UI线程(比如给UI的控件赋值)http://www.cnblogs.com/527289276qq/p/5264887.html

一般采用如下代码来解决:

//方法一
            this.Dispatcher.Invoke((Action)delegate()
            {
               //你的代码
            }); 

            //方法二
            App.Current.Dispatcher.Invoke((Action)delegate()
            {
                //你的代码
            }); 

2(网址2)

https://blog.csdn.net/conganguo/article/details/73692677

https://www.cnblogs.com/DemonJ/p/6265989.html

System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(delegate
{
    try
    {

    }
    catch (Exception ex)
    {

    }
}));

 

 我采取的办法,采用委托,将代码放到this.Dispatcher.Invoke中,可以动态更新UI,如图

五、效果图

未点击:

 

六,总结

1、解决UI卡顿问题,往往通过创建UI线程、工作线程,将其区分开来。

2、本人邮箱479166938@qq.com

3、IP是自己局域网下本机的IP。

4、this.Dispatcher.BeginInvoke()异步执行,不等待委托结束就更新UIthis.Dispatcher.BeginInvoke()同步执行,需等待委托执行完才更新UI。

5、附录,将服务器端的代码规范后如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace SocketServer
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
         public MainWindow()
        {
            InitializeComponent();
            this.TxtBox_ReceivefileStatus.Text = "0/100";
            this.btn_StartServer.Content = "开启器服务器";
        }

        private void btn_StartServer_Click(object sender, RoutedEventArgs e)
        {
            this.btn_StartServer.Content = "监听中...";
            //点击后改变背景色
            this.btn_StartServer.Background = System.Windows.Media.Brushes.Red;
            Thread receiveThread = new Thread(ReceiveMessage);
            receiveThread.Start();
        }

        private void ReceiveMessage()
        {
            Socket receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint hostIpEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888);
            //设置接收数据缓冲区的大小
            byte[] b = new byte[4096];
            receiveSocket.Bind(hostIpEndPoint);
            //监听
            receiveSocket.Listen(2);
            //接受客户端连接
            Socket hostSocket = receiveSocket.Accept();
            //内存流fs的初始容量大小为0,随着数据增长而扩展。
            MemoryStream fs = new MemoryStream();
            int length = 0;
            //每接受一次,只能读取小于等于缓冲区的大小4096个字节
            this.Dispatcher.Invoke((Action)delegate ()
               {
                   while ((length = hostSocket.Receive(b)) > 0)
                    {
                        //将接受到的数据b,按长度length放到内存流中。
                        fs.Write(b, 0, length);

                        if (progressBar_Rec.Value < 100)
                        {
                            //进度条的默认值为0
                            progressBar_Rec.Value++;
                            TxtBox_ReceivefileStatus.Text = "接收:" + progressBar_Rec.Value + "/100";
                        }
                    }
                    progressBar_Rec.Value = 100;
                    TxtBox_ReceivefileStatus.Text = "接收:" + progressBar_Rec.Value + "/100";
                }
            );
        
            fs.Flush();
            byte[] byteArray = new byte[fs.Length];
            int count = 0;
            while (count < fs.Length)
            {
                byteArray[count] = Convert.ToByte(fs.ReadByte());
                count++;
            }
            string Path = "C:\\Users\\lanmage2\\Desktop\\AA\\文件1.txt";
            //FileStream filestream = new FileStream(Path + "\\文件1.txt", FileMode.OpenOrCreate);
            //FileStream filestream = File.Create(Path);
            //filestream.Write(byteArray, 0, byteArray.Length);//不能用
            System.IO.File.WriteAllBytes(Path, byteArray);//能用

            /*Bitmap类,可以将*/
            //Bitmap Img = new Bitmap(fs);
            //Img.Save(@"reveive.jpg", ImageFormat.Png);
            //关闭写文件流
            fs.Close();
            //关闭接收数据的Socket
            hostSocket.Shutdown(SocketShutdown.Receive);
            hostSocket.Close();
            //关闭发送连接
            receiveSocket.Close();
        } 
    }
}

 

 

 

 

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
WPF TCP服务器是基于Windows Presentation Foundation(WPF)框架开发的一个TCP服务器应用程序。它可以在客户端服务器之间建立TCP连接,实现数据的传输和通信。 CSDN是一个知名的文IT技术社区,提供各种IT技术相关的学习资源和社交平台。在CSDN上,你可以找到很多关于WPF TCP服务器的源代码示例和教程。 下面是一个简单的WPF TCP服务器的示例代码: ``` using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Windows; namespace TcpServerWpfApp { public partial class MainWindow : Window { private TcpListener server; private TcpClient client; public MainWindow() { InitializeComponent(); } private void StartServerButton_Click(object sender, RoutedEventArgs e) { try { server = new TcpListener(IPAddress.Parse("127.0.0.1"), 8000); server.Start(); StartServerButton.IsEnabled = false; StopServerButton.IsEnabled = true; ListenForClients(); } catch (Exception ex) { MessageBox.Show(ex.Message); } } private async void ListenForClients() { try { while (true) { client = await server.AcceptTcpClientAsync(); HandleClientCommunication(client); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } private async void HandleClientCommunication(TcpClient tcpClient) { try { NetworkStream stream = tcpClient.GetStream(); byte[] buffer = new byte[1024]; int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); string dataReceived = Encoding.ASCII.GetString(buffer, 0, bytesRead); byte[] responseBuffer = Encoding.ASCII.GetBytes("Server received: " + dataReceived); await stream.WriteAsync(responseBuffer, 0, responseBuffer.Length); stream.Close(); tcpClient.Close(); } catch (Exception ex) { MessageBox.Show(ex.Message); } } private void StopServerButton_Click(object sender, RoutedEventArgs e) { try { server.Stop(); StartServerButton.IsEnabled = true; StopServerButton.IsEnabled = false; } catch (Exception ex) { MessageBox.Show(ex.Message); } } } } ``` 这是一个简单的WPF窗口应用程序,包含了"StartServerButton"和"StopServerButton"两个按钮。当点击"StartServerButton"按钮时,服务器将开始监听特定IP地址和端口(这里是本地回环地址127.0.0.1和端口号8000),并将按钮状态设置为不可用。之后,服务器将等待与客户端的连接,并将接收到的客户端数据发送回客户端。点击"StopServerButton"按钮将停止服务器的监听,并将按钮状态还原为可用状态。 希望这个简单的示例代码能够帮助您理解WPF TCP服务器的基本原理和使用方法。如果你需要更详细的示例代码和教程,建议您在CSDN上搜索相关资源。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

我爱AI

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

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

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

打赏作者

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

抵扣说明:

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

余额充值