Frpc 内网穿透客户端配置教程

github: https://github.com/fatedier/frp/releases
1 下载操作系统对应版本的Frpc.exe 客户端程序
2 配置对应的frpc.ini文件
3 切换到Frpc.exe目录,cmd执行:Frpc.exe -c frpc.ini

 例如:frpc.ini 如下

例如  frps服务地址:10.10.10.10  端口10000

本地服务端口5000,  frps 域名219801a6865241m00017testappe.frp.ws.com,端口8000

本地应用唯一实例:[219801a6865241m00017testapp]

 假设本地Api为:http://192.169.137.10/5000/edit;postId=18337057#postBody

内网穿透代理为:http://219801a6865241m00017testappe.frp.ws.com/8000/edit;postId=18337057#postBody

注意:frpc.ini 配置通过请求后台下发

 /// <summary>
 /// 内网穿透代理客户端配置
 /// </summary>
 public class FrpcIni
 {
     public CommonSection Common { get; set; }
     public AppSection App { get; set; }
 }

 /// <summary>
 /// 公共配置节点
 /// </summary>
 public class CommonSection
 {
     public string ServerAddress { get; set; }
     public string ServerPort { get; set; }
     public string AuthenticationMethod { get; set; }
     public string Token { get; set; }
     public string DialServerTimeout { get; set; }
     public string LoginFailExit { get; set; }
 }

 /// <summary>
 /// 应用配置
 /// </summary>
 public class AppSection
 {
     public string Type { get; set; }
     public string RemotePort { get; set; }
     public string LocalIp { get; set; }
     public string LocalPort { get; set; }
     public string UseCompression { get; set; }
     public string CustomDomains { get; set; }
 }

 

    /// <summary>
    /// 代理客户端Ini配置常量
    /// </summary>
    internal class FrpcIniConst
    {
        //与服务端连接段和Key
        public const string CommonSection = "common";
        public const string ServerAddrKey = "server_addr";
        public const string ServerPortKey = "server_port";
        public const string AuthenticationMethodKey = "authentication_method";
        public const string TokenKey = "token";
        public const string DialServerTimeout = "dial_server_timeout";
        public const string LoginFailExit = "login_fail_exit";

        //单应用段和Key
        public const string AppSection = "app_name";
        public const string TypeKey = "type";
        public const string LocalIpKey = "local_ip";
        public const string LocalPortKey = "local_port";
        public const string UseCompressionKey = "use_compression";
        public const string CustomDomainsKey = "custom_domains";
        public const string RemotePortKey = "remote_port";

        public const string FrpcName = "frpc";
    }

  

/// <summary>
/// 代理配置操作
/// </summary>
internal class FrpcConfigurationOperation
{
    /// <summary>
    /// 配置操作
    /// </summary>
    /// <param name="responseRegister">注册响应的包</param>
    /// <param name="localServicePort">本地服务端口号</param>
    /// <param name="appSection">应用节点</param>
    public FrpcConfigurationOperation(ResponseRegister responseRegister, int localServicePort, string appSection)
    {
        _responseRegister = responseRegister;
        _localServicePort = localServicePort;
        _appSection = appSection;
    }

    /// <summary>
    /// 更新配置
    /// </summary>
    /// <param name="frpcIniPath"></param>
    public void UpDateFrpcIni( string frpcIniPath)
    {
        var writeFrpcIni = ResponseToFrpcIni(_responseRegister, _localServicePort, _appSection);
        var readFrpcIni = ReadFrpcIniFromFile(frpcIniPath, _appSection);
        WriteFrpcIniToFile(frpcIniPath, writeFrpcIni, readFrpcIni, _appSection);
    }

    /// <summary>
    /// 响应包转换配置
    /// </summary>
    /// <returns></returns>
    public FrpcIni ResponseToFrpcIni()
    {
        return ResponseToFrpcIni(_responseRegister, _localServicePort, _appSection);
    }

    /// <summary>
    /// 响应包转换配置
    /// </summary>
    /// <param name="responseRegister"></param>
    /// <param name="localServicePort"></param>
    /// <param name="appSection"></param>
    /// <returns></returns>
    private FrpcIni ResponseToFrpcIni(ResponseRegister responseRegister, int localServicePort, string appSection)
    {
        if (responseRegister.Server == null) throw new InvalidOperationException("无法获取服务信息");
        if (!responseRegister.SubDomainList.Any()) throw new InvalidOperationException("无法获取分配的子域");
        var app = responseRegister.SubDomainList.First(s => s.Subdomain.StartsWith(appSection, StringComparison.CurrentCultureIgnoreCase));

        if (app == null) throw new InvalidOperationException("无法获取分配的子域");

        var frpcIni = new FrpcIni
        {
            Common = new CommonSection
            {
                ServerAddress = responseRegister.Server.ServerAddress,
                ServerPort = responseRegister.Server.ServerPort,
                AuthenticationMethod = responseRegister.Server.AuthenticationMethod,
                Token = responseRegister.Server.Token,
                DialServerTimeout = responseRegister.Server.DialServerTimeout,
                LoginFailExit = responseRegister.Server.LoginFailExit,
            },
            App = new AppSection()
        };
        frpcIni.App.Type = app.Type;
        frpcIni.App.RemotePort = app.RemotePort;
        frpcIni.App.UseCompression = app.UseCompression;

        //必须小写,否则路由无法找到:frp已知问题
        frpcIni.App.CustomDomains = app.CustomDomain;
        frpcIni.App.LocalIp = LocalIp;
        frpcIni.App.LocalPort = localServicePort.ToString();
        return frpcIni;
    }

    /// <summary>
    /// 从文件读取代理客户端配置
    /// </summary>
    /// <param name="filePath"></param>
    /// <param name="appSection"></param>
    /// <returns></returns>
    private FrpcIni ReadFrpcIniFromFile(string filePath, string appSection)
    {
        if (!File.Exists(filePath)) return null;
        DeleteErrorSection(filePath, appSection);

        return new FrpcIni
        {
            Common = new CommonSection
            {
                ServerAddress = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.ServerAddrKey),
                ServerPort = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.ServerPortKey),
                AuthenticationMethod = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.AuthenticationMethodKey),
                Token = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.TokenKey),
                DialServerTimeout = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.DialServerTimeout),
                LoginFailExit = IniHelper.Read(filePath, FrpcIniConst.CommonSection, FrpcIniConst.LoginFailExit)
            },
            App = new AppSection
            {
                Type = IniHelper.Read(filePath, appSection, FrpcIniConst.TypeKey),
                LocalIp = IniHelper.Read(filePath, appSection, FrpcIniConst.LocalIpKey),
                LocalPort = IniHelper.Read(filePath, appSection, FrpcIniConst.LocalPortKey),
                UseCompression = IniHelper.Read(filePath, appSection, FrpcIniConst.UseCompressionKey),
                CustomDomains = IniHelper.Read(filePath, appSection, FrpcIniConst.CustomDomainsKey),
                RemotePort = IniHelper.Read(filePath, appSection, FrpcIniConst.RemotePortKey)
            }
        };
    }

    /// <summary>
    /// 向文件写入代理客户端配置
    /// </summary>
    /// <param name="filePath"></param>
    /// <param name="writeFrpcIni"></param>
    /// <param name="readFrpcIni"></param>
    /// <param name="appSection"></param>
    private void WriteFrpcIniToFile(string filePath, FrpcIni writeFrpcIni, FrpcIni readFrpcIni, string appSection)
    {
        if (IsNeedWrite(writeFrpcIni.Common.ServerAddress, readFrpcIni.Common.ServerAddress))
            IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.ServerAddrKey, writeFrpcIni.Common.ServerAddress);

        if (IsNeedWrite(writeFrpcIni.Common.ServerPort, readFrpcIni.Common.ServerPort))
            IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.ServerPortKey, writeFrpcIni.Common.ServerPort);

        if (IsNeedWrite(writeFrpcIni.Common.AuthenticationMethod, readFrpcIni.Common.AuthenticationMethod))
            IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.AuthenticationMethodKey, writeFrpcIni.Common.AuthenticationMethod);

        if (IsNeedWrite(writeFrpcIni.Common.Token, readFrpcIni.Common.Token))
            IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.TokenKey, writeFrpcIni.Common.Token);

        if (IsNeedWrite(writeFrpcIni.Common.DialServerTimeout, readFrpcIni.Common.DialServerTimeout))
            IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.DialServerTimeout, writeFrpcIni.Common.DialServerTimeout);

        if (IsNeedWrite(writeFrpcIni.Common.LoginFailExit, readFrpcIni.Common.LoginFailExit))
            IniHelper.Write(filePath, FrpcIniConst.CommonSection, FrpcIniConst.LoginFailExit, writeFrpcIni.Common.LoginFailExit);

        if (IsNeedWrite(writeFrpcIni.App.Type, readFrpcIni.App.Type))
            IniHelper.Write(filePath, appSection, FrpcIniConst.TypeKey, writeFrpcIni.App.Type);

        if (IsNeedWrite(writeFrpcIni.App.LocalIp, readFrpcIni.App.LocalIp))
            IniHelper.Write(filePath, appSection, FrpcIniConst.LocalIpKey, writeFrpcIni.App.LocalIp);

        if (IsNeedWrite(writeFrpcIni.App.LocalPort, readFrpcIni.App.LocalPort))
            IniHelper.Write(filePath, appSection, FrpcIniConst.LocalPortKey, writeFrpcIni.App.LocalPort);

        if (IsNeedWrite(writeFrpcIni.App.UseCompression, readFrpcIni.App.UseCompression))
            IniHelper.Write(filePath, appSection, FrpcIniConst.UseCompressionKey, writeFrpcIni.App.UseCompression);

        if (IsNeedWrite(writeFrpcIni.App.CustomDomains, readFrpcIni.App.CustomDomains, false))
            IniHelper.Write(filePath, appSection, FrpcIniConst.CustomDomainsKey, writeFrpcIni.App.CustomDomains);

        if (IsNeedWrite(writeFrpcIni.App.RemotePort, readFrpcIni.App.RemotePort, false))
            IniHelper.Write(filePath, appSection, FrpcIniConst.RemotePortKey, writeFrpcIni.App.RemotePort);
    }

    /// <summary>
    /// 需要写入
    /// </summary>
    /// <param name="writeValue"></param>
    /// <param name="readValue"></param>
    /// <param name="ignoreCase"></param>
    /// <returns></returns>
    private bool IsNeedWrite(string writeValue, string readValue, bool ignoreCase = true)
    {
        if (string.IsNullOrWhiteSpace(writeValue)) return true;

        if (ignoreCase)
            return !writeValue.Equals(readValue, StringComparison.CurrentCultureIgnoreCase);

        return !writeValue.Equals(readValue);
    }

    /// <summary>
    /// 删除错误节点
    /// </summary>
    /// <param name="filePath"></param>
    /// <param name="appSection"></param>
    private void DeleteErrorSection(string filePath, string appSection)
    {
        var sections = IniHelper.ReadSections(filePath);
        if (sections == null || sections.Count <= 0) return;
        var ignoreSection = FrpcIniConst.CommonSection;
        foreach (var section in sections)
        {
            if (section.Equals(ignoreSection) || section.Equals(appSection))
                continue;
            IniHelper.DeleteSection(filePath, section);
        }
    }

    private const string LocalIp = "localhost";
    private readonly ResponseRegister _responseRegister;
    private readonly int _localServicePort;
    private readonly string _appSection;
}

  

  /// <summary>
  /// 代理状态
  /// </summary>
  public enum FrpcAgentStatus
  {
      [Description("未知状态")]
      UnKnow = -1,

      [Description("代理开启成功")]
      StartUpSuccess =0,

      [Description("代理开启失败")]
      StartUpFailed = 1,

      [Description("代理关闭")]
      Closed = 2,

      [Description("代理路由冲突")]
      Conflict = 3,

      [Description("代理被拒绝连接")]
      Refused =4,
  }

  

/// <summary>
/// 代理内网穿透客户端
/// </summary>
public class FrpcAgent
{
    /// <summary>
    /// 连接改变
    /// </summary>
    public event EventHandler<bool> ConnectedChanged;
    /// <summary>
    /// 代理接收信息
    /// </summary>
    public event EventHandler<string> OutputReceived;
    /// <summary>
    /// 代理收错误信息
    /// </summary>
    public event EventHandler<string> ErrorReceived;
    /// <summary>
    /// 代理状态
    /// </summary>
    public event EventHandler<FrpcAgentStatus> AgentStatus;

    public FrpcAgent()
    {
        Application.Current.Dispatcher?.Invoke(() =>
        {
            Application.Current.Exit += Current_Exit;
        });
    }

    /// <summary>
    /// 退出代理
    /// </summary>
    public void Exit()
    {
        Application.Current.Dispatcher?.Invoke(() =>
        {
            Application.Current.Exit -= Current_Exit;
        });
        Current_Exit(null, null);
    }

    /// <summary>
    /// 开启代理
    /// </summary>
    /// <returns></returns>
    public async Task Start(string intranetPenetrationPath)
    {
        await Task.Run(() =>
        {
            _frpcAgentFullName = Path.Combine(intranetPenetrationPath, $"{FrpcIniConst.FrpcName}.exe");
            KillLocalMainModule(_frpcAgentFullName);
            StartProcess(_frpcAgentFullName, AgentIniParameter(), intranetPenetrationPath);
        });
    }

    #region 私有方法
    private void StartProcess(string projectUrl, string arguments, string intranetPenetrationPath)
    {
        ExitedHandle();
        
        _frpcProcess = new Process
        {
            StartInfo =
            {
                UseShellExecute = false,
                WorkingDirectory=intranetPenetrationPath,
                FileName = projectUrl,
                Arguments = arguments,
                CreateNoWindow = true,
                WindowStyle = ProcessWindowStyle.Hidden
            },
            EnableRaisingEvents = true
        };

        //守护重启
        _frpcProcess.Exited += FrpcProcess_Exited;
        _frpcProcess.StartInfo.RedirectStandardInput = true;
        _frpcProcess.StartInfo.RedirectStandardOutput = true;
        _frpcProcess.StartInfo.RedirectStandardError = true;
        _frpcProcess.StartInfo.StandardErrorEncoding = Encoding.UTF8;
        _frpcProcess.StartInfo.StandardOutputEncoding = Encoding.UTF8;

        _frpcProcess.OutputDataReceived += FrpcProcess_OutputDataReceived;
        _frpcProcess.ErrorDataReceived += FrpcProcess_ErrorDataReceived;
        _frpcProcess.Start();
        _frpcProcess.BeginOutputReadLine();
        _frpcProcess.BeginErrorReadLine();
    }

    private void FrpcProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
    {
        if (!string.IsNullOrWhiteSpace(e.Data))
            ErrorReceived?.Invoke(sender, $"{FrpcIniConst.FrpcName} -ErrorDataReceived: {e.Data}");
    }

    private void FrpcProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        var strOutput = e.Data;
        if (string.IsNullOrWhiteSpace(strOutput)) return;
        var agentStatus = GetAgentStatus(strOutput);
        switch (agentStatus)
        {
            case FrpcAgentStatus.StartUpSuccess:
                ConnectedChanged?.Invoke(this, true);
                break;
            case FrpcAgentStatus.StartUpFailed:
                ConnectedChanged?.Invoke(this, false);
                break;
            case FrpcAgentStatus.Closed:
                ConnectedChanged?.Invoke(this, false);
                break;
            case FrpcAgentStatus.Conflict:
                ConnectedChanged?.Invoke(this, false);
                KillLocalMainModule(_frpcAgentFullName);
                AgentStatus?.Invoke(sender, FrpcAgentStatus.Conflict);
                break;
            case FrpcAgentStatus.Refused:
                ConnectedChanged?.Invoke(this, false);
                Exit();
                AgentStatus?.Invoke(sender, FrpcAgentStatus.Refused);
                break;
            case FrpcAgentStatus.UnKnow:
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
        OutputReceived?.Invoke(sender, $"{FrpcIniConst.FrpcName} -OutputDataReceived: {strOutput}");
    }

    private void FrpcProcess_Exited(object sender, EventArgs e)
    {
        ExitedHandle();
    }

    private void ExitedHandle()
    {
        if (_frpcProcess == null) return;
        
        _frpcProcess.Exited -= FrpcProcess_Exited;
        ConnectedChanged?.Invoke(this, false);
        OutputReceived?.Invoke(this, $"{FrpcIniConst.FrpcName} -Exited:");
        _frpcProcess.Dispose();
        _frpcProcess = null;
    }

    /// <summary>
    /// 返回代理状态
    /// </summary>
    /// <param name="strOutput"></param>
    /// <returns></returns>
    private FrpcAgentStatus GetAgentStatus(string strOutput)
    {
        var success = strOutput.ToLower().Contains(Proxy) && strOutput.ToLower().Contains(ProxySuccess);
        if (success) return FrpcAgentStatus.StartUpSuccess;

        var error = strOutput.ToLower().Contains(Proxy) && strOutput.ToLower().Contains(ProxyError);
        if (error) return FrpcAgentStatus.StartUpFailed;

        var close = strOutput.ToLower().Contains(Closing) || strOutput.ToLower().Contains(Closed) || strOutput.ToLower().Contains(Failed);
        if (close) return FrpcAgentStatus.Closed;

        var conflict = strOutput.ToLower().Contains(Conflict);
        if (conflict) return FrpcAgentStatus.Conflict;

        var refused = strOutput.ToLower().Contains(Refused);
        if (refused) return FrpcAgentStatus.Refused;
        return FrpcAgentStatus.UnKnow;
    }

    /// <summary>
    /// 杀掉当前应用域下的代理客户端
    /// </summary>
    private void KillLocalMainModule(string agentFullName)
    {
        Process[] services = null;
        try
        {
            services = Process.GetProcessesByName(FrpcIniConst.FrpcName);
            if (services.Length <= 0) return;
            foreach (var service in services)
            {
                if (service.MainModule != null &&
                    service.MainModule.FileName.StartsWith(agentFullName,
                        StringComparison.CurrentCultureIgnoreCase))
                    service.Kill();
            }
        }
        catch (Exception e)
        {
            ErrorReceived?.Invoke(null, e.Message);
        }
        finally
        {
            if (services != null)
            {
                foreach (var process in services)
                {
                    process.Dispose();
                }
            }
        }
    }

    private void Current_Exit(object sender, ExitEventArgs e)
    {
        try
        {
            if (_frpcProcess != null)
            {
                _frpcProcess.Exited -= FrpcProcess_Exited;
                _frpcProcess.OutputDataReceived -= FrpcProcess_OutputDataReceived;
                _frpcProcess.ErrorDataReceived -= FrpcProcess_ErrorDataReceived;
            }

            _frpcProcess?.Kill();
            _frpcProcess?.Close();
            _frpcProcess?.Dispose();
            _frpcProcess = null;
        }
        catch (Exception ex)
        {
            ErrorReceived?.Invoke(null, ex.Message);
        }
    }

    /// <summary>
    /// 代理配置
    /// </summary> 
    private string AgentIniParameter() => $"-c {FrpcIniConst.FrpcName}.ini";
    #endregion

    #region 私有字段
    //代理开启成功
    private const string Proxy = "proxy";
    private const string ProxySuccess = "success";

    //代理开启失败
    private const string ProxyError = "error";
    private const string Failed = "failed";

    //断开
    private const string Closing = "closing";
    private const string Closed = "closed";

    //冲突
    private const string Conflict = "conflict";
    private const string Refused = "refused";

    //代理客户端完整路径
    private string _frpcAgentFullName;
    private Process _frpcProcess;
    #endregion
}

  

 

 

posted on 2024-08-01 17:25  TanZhiWei  阅读(235)  评论(0编辑  收藏  举报