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 }