C#使用FileSystemWatcher来监控指定文件夹,并使用TCP/IP协议通过Socket发送到另外指定文件夹
项目需求:
局域网内有两台电脑,电脑A(Windows系统)主要是负责接收一些文件(远程桌面粘贴、FTP上传、文件夹共享等方式),希望能在A接收文件后自动传输到电脑B(Windows系统)来做一个备份,同时电脑B上有个目录,如果往这个目录里粘贴文件了,会自动传输给A来保存。
于是通过百度找到了System.IO.FileSystemWatcher这个类,通过它来监听指定的文件夹的一些消息(文件创建、文件修改、文件删除、文件重命名)来做对应的动作,目前只需求监控文件创建,其它事件不作处理。
文件传输方面,可以自己写Socket的Server和Client,但要注意粘包的问题。我这里使用了开源的NewLife.Net(https://github.com/NewLifeX/NewLife.Net),客户端和服务器都是用它的话,内置解决粘包问题的解决方案,而且管理起来很方便,自带日志输出功能强大。这里分享一下实现的代码以及一些问题。
1、创建一个Winform的工程,运行框架为.Netframework4.6
Nuget上引用NewLife.Net
界面结构如下:
本机端口,代表本机作为服务器(server)监听的端口,远程服务器IP及端口就是当本机监控到文件创建时,自动发送给哪台服务器(接收服务器同样需要运行本软件)。
本地监控自动发送文件夹:凡是在指定的这个文件夹中新建(一般是粘贴)的文件都会被自动发送走。
自动保存接收到的文件夹:凡是远程发送过来的文件,都自动保存在此文件夹下面。
2、实现代码
Program.cs中定义两个全局的变量,用来保存文件夹信息
/// <summary>
/// 要监控的接收保存文件夹
/// </summary>
public static string SaveDir = ""; /// <summary>
/// 要监控的发送文件夹
/// </summary>
public static string SendDir = "";
using的一些类
using System;
using System.Data;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using DirectoryWatch.Class;
using NewLife.Data;
using NewLife.Log;
using NewLife.Net;
using NewLife.Net.Handlers;
在窗体加载时,定义一些共用变量以及指定窗体下方TextBox为日志输出载体
private static int remotePort = 0;//远程端口
private static int localPort = 0;//本地端口
private static string remoteIP = "";//远程IP
private FileSystemWatcher watcher;//监控文件夹
private NetServer server;//本地服务
private void MainFrm_Load(object sender, EventArgs e)
{
textBox1.UseWinFormControl();
}
本地监控文件夹选择按钮代码
private void Btn_dirbd_Click(object sender, EventArgs e)
{
using (var folderBrowser = new FolderBrowserDialog())
{
if (folderBrowser.ShowDialog() != DialogResult.OK) return;
Program.SendDir = folderBrowser.SelectedPath;
if (!Directory.Exists(Program.SendDir))
{
MessageBox.Show(@"所选路径不存在或无权访问", @"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (string.Equals(Program.SaveDir.ToLower(), Program.SendDir.ToLower()))
{
MessageBox.Show(@"自动接收文件夹和自动发送文件夹不能是同一个文件夹", @"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
txt_localPath.Text = folderBrowser.SelectedPath;
Program.SendDir = folderBrowser.SelectedPath;
}
}
本地自动保存文件夹选择按钮代码
private void Btn_saveDic_Click(object sender, EventArgs e)
{
using (var folderBrowser = new FolderBrowserDialog())
{
if (folderBrowser.ShowDialog() != DialogResult.OK) return;
Program.SaveDir = folderBrowser.SelectedPath;
if (!Directory.Exists(Program.SendDir))
{
MessageBox.Show(@"所选路径不存在或无权访问", @"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (string.Equals(Program.SaveDir.ToLower(), Program.SendDir.ToLower()))
{
MessageBox.Show(@"自动接收文件夹和自动发送文件夹不能是同一个文件夹", @"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
txt_remoteDir.Text = folderBrowser.SelectedPath;
Program.SaveDir = folderBrowser.SelectedPath;
}
}
启动代码(启动本地监控,启用本地SocketServer服务)
private void Btn_Start_Click(object sender, EventArgs e)
{
int.TryParse(txt_remotePort.Text, out remotePort);
int.TryParse(txt_localPort.Text, out localPort);
if (string.IsNullOrEmpty(txt_remoteIP.Text.Trim()))
{
MessageBox.Show(@"请填写远程服务器IP", @"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
remoteIP = txt_remoteIP.Text.Trim();
if (remotePort == 0)
{
MessageBox.Show(@"请填写远程服务器的端口", @"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (localPort == 0)
{
MessageBox.Show(@"请填写本地服务器要打开的端口", @"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
} if (string.IsNullOrEmpty(Program.SendDir))
{
MessageBox.Show(@"请选择本地自动发送文件夹路径", @"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (string.IsNullOrEmpty(Program.SaveDir))
{
MessageBox.Show(@"请选择本地自动接收发送过来的文件夹路径", @"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (Btn_Start.Text.Equals("停止"))
{
watcher.EnableRaisingEvents = false;
server.Stop("手动停止");
Btn_Start.Text = @"启动";
foreach (Control control in Controls)
{
if (!(control is Button) && !(control is TextBox)) continue;
if (control.Name != "Btn_Start")
{
control.Enabled = true;
}
}
return;
}
watcher = new FileSystemWatcher
{
Path = Program.SendDir,
Filter = "*.*"//监控所有文件
};
watcher.Created += OnProcess;//只监控新增文件
//watcher.Changed += OnProcess;
//watcher.Deleted += new FileSystemEventHandler(OnProcess);
//watcher.Renamed += new RenamedEventHandler(OnRenamed);
watcher.EnableRaisingEvents = true;//是否让监控事件生效
//watcher.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastAccess| NotifyFilters.LastWrite | NotifyFilters.Security | NotifyFilters.Size;
watcher.NotifyFilter = NotifyFilters.FileName;//这是一些通知属性,目前不用
watcher.IncludeSubdirectories = true;//包含子文件夹 server = new NetServer
{
Log = XTrace.Log,
SessionLog = XTrace.Log,
SocketLog = XTrace.Log,
Port = localPort,
ProtocolType = NetType.Tcp
};//使用NewLife.Net创建一个Server服务,只使用TCP协议 server.Received += async (x, y) =>
{
//接收文件
var session = x as NetSession;
if (!(y.Message is Packet pk)) return; int.TryParse(Encoding.UTF8.GetString(pk.ReadBytes(0, 1)), out var fileState);//文件状态1字节
int.TryParse(Encoding.UTF8.GetString(pk.ReadBytes(1, 10)), out var headinfo);//文件总长度10字节
int.TryParse(Encoding.UTF8.GetString(pk.ReadBytes(11, 8)), out var fileNameLength);//文件名长度8字节
var fileName = Encoding.UTF8.GetString(pk.ReadBytes(19, fileNameLength));//文件名
int.TryParse(Encoding.UTF8.GetString(pk.ReadBytes(19 + fileNameLength, 10)), out var offset);//位置偏移量10字节
var data = pk.ReadBytes(29 + fileNameLength, pk.Count - (29 + fileNameLength));//数据内容
if (data.Length == 0) return; await Task.Run(async () =>
{
var writeData = data;
using (var filestream = new FileStream($"{Program.SaveDir}\\{fileName}", FileMode.OpenOrCreate,
FileAccess.Write, FileShare.ReadWrite))
{
filestream.Seek(offset, SeekOrigin.Begin);
await filestream.WriteAsync(writeData, 0, writeData.Length);
await filestream.FlushAsync();
}//数据写入文件
}); XTrace.WriteLine($@"状态:{fileState},编号:{session.ID},文件总长度:{headinfo},文件名长度:{fileNameLength},文件名:{fileName},偏移量:{offset},内容长度:{data.Length}");
//XTrace.Log.Debug(Encoding.UTF8.GetString(pk.Data));//输出日志
};
server.Add<StandardCodec>();//解决粘包,引入StandardCodec
server.Start();
Btn_Start.Text = string.Equals("启动", Btn_Start.Text) ? "停止" : "启动";
foreach (Control control in Controls)
{
if (!(control is Button) && !(control is TextBox)) continue;
if (control.Name != "Btn_Start")
{
control.Enabled = false;
}
}
}
监控事件触发时执行的代码
private static void OnProcess(object source, FileSystemEventArgs e)
{
if (e.ChangeType == WatcherChangeTypes.Created)
{
OnCreated(source, e);
}
//else if (e.ChangeType == WatcherChangeTypes.Changed)
//{
// OnChanged(source, e);
//}
//else if (e.ChangeType == WatcherChangeTypes.Deleted)
//{
// OnDeleted(source, e);
//} }
监控到创建文件时执行代码
/// <summary>
/// 监测文件创建事件,延时10秒后进行写入文件发送队列,防止文件尚未创建完成就执行发送(10秒内复制不完的 同样有问题)
/// 第1位 0代表新文件 1代表续传 2代表最后一次
/// 2--11位 代表文件总长度
/// 12--18 位代表文件名长度
/// 19--N 位 代表文件名信息
/// 19--(N+1)--offset位,代表此次发送文件的偏移量位置
/// 29+(N+1)--结束 代表此次发送的文件内容
/// </summary>
/// <param name="source"></param>
/// <param name="e"></param>
private static void OnCreated(object source, FileSystemEventArgs e)
{
Task.Run(async () =>
{
await Task.Delay(10000);
var TcpClient = new NetUri($"tcp://{remoteIP}:{remotePort}");//需要发送给的远程服务器
var Netclient = TcpClient.CreateRemote();
Netclient.Log = XTrace.Log;
Netclient.LogSend = true;
Netclient.LogReceive = true;
Netclient.Add<StandardCodec>();
Netclient.Received += (s, ee) =>
{
if (!(ee.Message is Packet pk1)) return;
XTrace.WriteLine("收到服务器:{0}", pk1.ToStr());
};
if (!File.Exists(e.FullPath)) return; byte[] data;
using (var streamReader = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
if (streamReader.CanRead)
{
data = new byte[streamReader.Length];
await streamReader.ReadAsync(data, 0, (int)streamReader.Length);
}
else
{
XTrace.Log.Error($"{e.FullPath}不可访问");
return;
}
} var fileState = Encoding.UTF8.GetBytes("0");//新文件发送 var headinfo = new byte[10];//总长度
headinfo = Encoding.UTF8.GetBytes(data.Length.ToString()); var fileNameLength = new byte[8];//文件名长度
fileNameLength = Encoding.UTF8.GetBytes(Encoding.UTF8.GetBytes(e.Name).Length.ToString()); var fileNameByte = new byte[e.Name.Length];//文件名
fileNameByte = Encoding.UTF8.GetBytes(e.Name); var offset = 0;//偏移量
var sendLength = 409600;//单次发送大小
Netclient.Open();
while (data.Length > offset)
{
if (offset > 0)
{
fileState = Encoding.UTF8.GetBytes("1");//追加文件
}
if (sendLength > data.Length - offset)
{
sendLength = data.Length - offset;
fileState = Encoding.UTF8.GetBytes("2");//最后一次发送
}
var offsetByte = new byte[10];//偏移位置byte
offsetByte = Encoding.UTF8.GetBytes(offset.ToString());
//一次发送总byte
var sendData = new byte[1 + 10 + 8 + fileNameByte.Length + 10 + sendLength];
//文件状态0第一次 1追加文件 2最后一次发送
Array.Copy(fileState, 0, sendData, 0, fileState.Length);
//文件总长度
Array.Copy(headinfo, 0, sendData, 1, headinfo.Length);
//文件名长度
Array.Copy(fileNameLength, 0, sendData, 11, fileNameLength.Length);
//文件名信息
Array.Copy(fileNameByte, 0, sendData, 19, fileNameByte.Length);
//此次内容偏移量
Array.Copy(offsetByte, 0, sendData, 19 + fileNameByte.Length, offsetByte.Length);
//一次发送的内容 offsetByte为10byte
Array.Copy(data, offset, sendData, 29 + fileNameByte.Length, sendLength);
offset += sendLength;
var pk = new Packet(sendData);
Netclient.SendMessage(pk);
}
//Netclient.Close("发送完成,关闭连接。");
});
}
效果图:
未实现的功能:
断点续传
原因:
1、在使用过程中,发现NewLife.Net不支持普通Socket编程那样的可以一直Receive来接收后续流的操作(TCP协议),每次流到达都会触发一次事件,从而不得不每次发送的时候都带上一些头部信息(文件名、偏移量、大小等),无形中增大了流量。
2、目前看的效果是NewLife.Net在服务器端接收的时候,包的顺序并不是和客户端Send的时候保持一致(如客户端发送 1 2 3 4 5),服务端可能接收的是2 1 3 5 4这样的顺序,这个问题,可能是跟我用异步有关系,按说TCP是可以保证包的顺序的,我已经在GitHub上提问了,目前等待作者(大石头:https://www.cnblogs.com/nnhy/)解答。
C#使用FileSystemWatcher来监控指定文件夹,并使用TCP/IP协议通过Socket发送到另外指定文件夹的更多相关文章
- TCP/IP协议数据包文件PCAP分析器
一.设计原理 1.PCAP文件构成 参考http://blog.csdn.net/gulu_gulu_jp/article/details/50494909 PCAP文件由一个PCAP文件头和多个PC ...
- 标准C实现基于TCP/IP协议的文件传输
上学期集成程序设计的课堂作业,对于理解TCP/IP实现还是挺有帮助的. TCP/IP编程实现远程文件传输在LUNIX中一般都采用套接字(socket)系统调用. 采用客户/服务器模式,其程序编写步骤如 ...
- 标准C语言实现基于TCP/IP协议的文件传输
TCP/IP编程实现远程文件传输在LUNIX中一般都采用套接字(socket)系统调用. 采用客户/服务器模式,其程序编写步骤如下: 1.Socket系统调用 为了进行网络I/O,服务器和客户机两 ...
- TFTP服务 简单文件传输协议)是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,默认端口号为69
(1)yum安装:tftp.tftp-server (2)启动tftp CentOS 6 service xinetd restart chkconfig tftp on CentOS 7 sys ...
- 文件传输——TCP/IP协议介绍总结
一.链路层 数据链路层的工作特性: 1.为IP模块发送和接收IP数据报2.为ARP模块发送ARP请求和接收ARP应答(ARP:地址解析协议,将IP地址转换成MAC地址)3.为RARP发送RARP请求和 ...
- 图解TCP/IP第五版 -- 文件夹
非常多年前买过<TCP/IP具体解释>3卷,当时可能根本没看,也可能是看了又忘了,没有留下什么印象,当时的书也当做废品卖了. 卖书时的感觉貌似是.买了太多的书,基本都没看,搬家搬来搬去的麻 ...
- QT从入门到入土(九)——TCP/IP网络通信(以及文件传输)
引言 TCP/IP通信(即SOCKET通信)是通过网线将服务器Server端和客户机Client端进行连接,在遵循ISO/OSI模型的四层层级构架的基础上通过TCP/IP协议建立的通讯.控制器可以设置 ...
- C# FileSystemWatcher 在监控文件夹和文件时的用法
概述 最近学习FileSystemWatcher的用法,它主要是监控一个文件夹,当文件夹内的文件要是有更改就要记录下来,我就整理下我对FileSystemWatcher 的理解和用法. FileSys ...
- 定时删除文件夹"$1"下最后修改时间大于当前时间"$2"天的文件
shell 脚本: #!/bin/bash now=`date "+%Y-%m-%d_%H:%M:%S"` #获取当前时间 echo "当前时间: " ...
随机推荐
- Python中最常用的字符串方法!
字符串是字符序列.Python中内置的string类代表基于Unicode国际字符集的字符串.除了Python中常见的操作外,字符串还有一些专属于它们的附加方法.下图显示了所有这些可用的方法: Pyt ...
- docker下安装Redis
Docker介绍 1.节约时间.快速部署和启动 2.节约成本 3.标准化应用发布 4.方便做持续继承 5作为集群中的轻量主机或节点 6.方便构建基于SOA或者微服务架构的系统 Docker中文文档 h ...
- 记一次接口调试错误: {"timestamp":"2019-09-11T03:04:30.036+0000","status":500,"error":"Internal Server Error","message":"Could not write JSON: Object is null; nested exception is com.fasterxml.jackson
接口测试中用postman测试返回是正常的,但是使用其他人去调用就出错了,找了半天,才想起来使用了nginx,用于端口的代理转发.然后根据错误信息发现json格式的某个字段为null,结合日志中的报文 ...
- BayaiM__ORACLE之ASM概念 --V 1.0.0
BayaiM__ORACLE之ASM概念 --V 1.0.0 -------------------------------------- ...
- Windows 10 路由表管理
基本管理命令: route print route命令基本格式: ROUTE [-f] [-p] [-|-] command [destination] [MASK netmask] [gateway ...
- c# WF 第4节 窗体的事件
本节内容: 1:事件是什么? 2:窗体事件在哪可以找到 3:事件有哪些 1:事件是什么? 2:窗体事件在哪里 第一种: 第二种: 3:事件有哪些
- HTML与CSS学习笔记(7)
1.响应式布局 利用媒体查询,即media queries,可以针对不同的媒体类型定义不同的样式,从而实现响应式布局. 常见选项: 媒体类型 and.not min-width.max-width: ...
- fiddler面试题
1.什么叫断点? Break Point:进行接口测试时,为了测试后端功能而设置的. 2.断点有哪些方式? Before Requests:在请求时,没有达到服务器之前设置断点. -- 全局断 ...
- C++编程思想 - 对象的创建和使用
前言 用户定义的数据类型(data type)或类(class),是C++区别于传统过程型语言的地方. 通常将创建好的类库存放在库(library)中. 本篇会使用几个C++类库(class libr ...
- ICCV2019《KPConv: Flexible and Deformable Convolution for Point Clouds》
针对semantic3D数据集: 1.数据集准备: Semantic3D dataset can be found <a href="http://www.semantic3d.net ...