一、简介
虽然,本文的前面几篇文章在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
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
{
///
/// MainWindow.xaml 的交互逻辑
///
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
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
{
///
/// MainWindow.xaml 的交互逻辑
///
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];
///
/// 打开本地文件
///
///
///
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)
{
}
}
}
///
/// 向服务器发送数据
///
///
///
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、本人邮箱[email protected]
3、IP是自己局域网下本机的IP。
4、this.Dispatcher.BeginInvoke()异步执行,不等待委托结束就更新UI,this.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
{
///
/// MainWindow.xaml 的交互逻辑
///
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();
}
}
}