服务端设置(frps.ini): [common] bind_port = 7000 //此处填写客户端监听的服务端端口号 vhost_http_port = 8080 //此处填写用户访问的端口号 客户端配置(frpc.ini): [common] server_addr = x.x.x.x //此处填写服务端IP地址 server_port = 7000 //此处填写服务端配置的bind_port [web] type = http //此处规定转发请求的协议类型 local_port = 80 //此处规定本地服务启动的地址 custom_domains = www.duidaima.com //此处可以填写自定义域名(需要在IP地址下配置域名解析)当我们配置完上述的文件后,用户的访问请求将会经过如下的步骤:
func runServer(cfg config.ServerCommonConf) (err error) { log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel, cfg.LogMaxDays, cfg.DisableLogColor) if cfgFile != "" { log.Info("frps uses config file: %s", cfgFile) } else { log.Info("frps uses command line arguments for config") } // !important 核心代码1 svr, err := server.NewService(cfg) if err != nil { return err } log.Info("frps started successfully") // !important 核心代码2 svr.Run() return }在frp/cmd/frps/root.go中
for{ // !important 核心代码3 conn, session, err := svr.login() if err != nil { xl.Warn("login to server failed: %v", err) // if login_fail_exit is true, just exit this program // otherwise sleep a while and try again to connect to server if svr.cfg.LoginFailExit { return err } util.RandomSleep(10*time.Second, 0.9, 1.1) } else { // login success // !important 核心代码4 ctl := NewControl(svr.ctx, svr.runID, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort, svr.authSetter) ctl.Run() svr.ctlMu.Lock() svr.ctl = ctl svr.ctlMu.Unlock() break } }在frp/cmd/client/service.go中
func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error) { xl := xlog.FromContextSafe(pxy.ctx) // try all connections from the pool for i := 0; i < pxy.poolCount+1; i++ { // !important 核心代码5 if workConn, err = pxy.getWorkConnFn(); err != nil { xl.Warn("failed to get work connection: %v", err) return } xl.Debug("get a new work connection: [%s]", workConn.RemoteAddr().String()) xl.Spawn().AppendPrefix(pxy.GetName()) workConn = frpNet.NewContextConn(pxy.ctx, workConn) ...... // !important 核心代码6 err := msg.WriteMsg(workConn, &msg.StartWorkConn{ ProxyName: pxy.GetName(), SrcAddr: srcAddr, SrcPort: uint16(srcPort), DstAddr: dstAddr, DstPort: uint16(dstPort), Error: "", }) } }在frp/server/proxy.go中
func (pxy *TCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { // !important 核心代码7 HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, conn, []byte(pxy.clientCfg.Token), m) }在frp/client/proxy/proxy.go中
func (ctl *Control) writer() { xl := ctl.xl defer func() { if err := recover(); err != nil { xl.Error("panic error: %v", err) xl.Error(string(debug.Stack())) } }() defer ctl.allShutdown.Start() defer ctl.writerShutdown.Done() encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token)) if err != nil { xl.Error("crypto new writer error: %v", err) ctl.allShutdown.Start() return } for { m, ok := <-ctl.sendCh if !ok { xl.Info("control writer is closing") return } // !important 核心代码8 if err := msg.WriteMsg(encWriter, m); err != nil { xl.Warn("write message to control connection error: %v", err) return } } }在frp/server/control.go中
// !important 核心代码9 func (ctl *Control) reader() { xl := ctl.xl defer func() { if err := recover(); err != nil { xl.Error("panic error: %v", err) xl.Error(string(debug.Stack())) } }() defer ctl.readerShutdown.Done() defer close(ctl.closedCh) encReader := crypto.NewReader(ctl.conn, []byte(ctl.clientCfg.Token)) for { m, err := msg.ReadMsg(encReader) if err != nil { if err == io.EOF { xl.Debug("read from control connection EOF") return } xl.Warn("read error: %v", err) ctl.conn.Close() return } ctl.readCh <- m } }核心代码9:frpc读取frps转发的信息
本文所介绍的内网穿透技术相关的实现方式其实在我们的日常开发生活中有更多的使用场景,当我们深入了解了当前IP地址以及内外网的实现方式后,我们不难发现,当我们将内网穿透的图片稍加修改后就成为了我们常用的另一种功能的实现方式(VPN实现原理):