闽公网安备 35020302035485号





服务端设置(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实现原理):