• 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/)解答。

  • 相关阅读:
    9 与python2交互
    8 内置函数(未完成)
    7 事务
    [转]jquery.vTicker(垂直滚动)
    2015年工作总结(2016-02-02)
    jQuery plugin : jqTransform
    JQuery知识总结
    JQuery事件处理的注意事项
    JQuery基本选择器
    [二]JQueryMobile常用的组件介绍
  • 原文地址:https://www.cnblogs.com/wdw984/p/11008385.html
Copyright © 2020-2023  润新知