Redis
场景和需求: 在使用ngrok的时候,我们需要对ngrok的所有连接进行管理,可以理解为对连接上来的用户进行管理。ngrok的原始目的是实现内网穿透,可以从外网访问内网的机器(如树莓派),那么问题来了,如果有100台树莓派,我们如何知道?所以需要在ngrokd服务器上记录下每一台树莓派的ip地址和端口。
目的
在ngrokd服务端记录所有的连接。
方案
记录所有的方式有很多种,可以写入到文件当中、也可以写入到数据库中(如sqlite、mysql,MongoDB,Redis等)。有这么多的方式可以实现我们的目的,当然需要根据你的业务场景选择一种较为合适的。这里我们选用redis,因为redis 是一个NoSql,not only sql,redis不仅仅是一个数据库,redis还是一个发布/订阅的系统。发布/订阅系统可以方便日后与企业级的平台进行交互融合。有了发布/订阅系统,就可以类似微信一样,可以实时的知道有谁连接上了ngrokd服务器。
Redis使用
Redis教程资源在网上一大堆的资料。在本项目中只会用到常用的几个而已。可以通过本项目举一反三了解redis的特性。
修改步骤:
- 增加ivgredis.go
- 修改src/ngrok/msg/conn.go
- 修改src/ngrok/server/tunnel.go
- ivgredis.go文件如下
package ivgredis
//package main
import (
"time"
log "github.com/alecthomas/log4go"
"github.com/gomodule/redigo/redis"
)
var redisClient *redis.Pool
func GetConnFromPool() redis.Conn {
if redisClient != nil {
goto end
}
redisClient = &redis.Pool{
//连接方法
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", "127.0.0.1:6379")
if err != nil {
return nil, err
}
c.Do("SELECT", 0)
return c, nil
},
//DialContext: nil,
//TestOnBorrow: nil,
//最大的空闲连接数,表示即使没有redis连接时依然可以保持N个空闲的连接,而不被清除,随时处于待命状态。
MaxIdle: 1,
//最大的激活连接数,表示同时最多有N个连接
MaxActive: 10,
//最大的空闲连接等待时间,超过此时间后,空闲连接将被关闭
IdleTimeout: 180 * time.Second,
//Wait: false,
//MaxConnLifetime: 0,
}
end:
c := redisClient.Get()
// defer c.Close()
return c
}
func IsSetMemberExist(key string, member string) bool {
c1 := GetConnFromPool()
exists, err := redis.Bool(c1.Do("SISMEMBER", key, member))
defer c1.Close()
if err != nil {
log.Debug("%v", err)
return false
} else {
return exists
}
}
func AddSetMember(key string, member string) (num int64, err error) {
// var str string = {"Type":"NewTunnel","Payload":{"ReqId":"7ead4dedaecaf989","Url":"tcp://ng.ubuntu.local:8022","Protocol":"tcp","Error":""}}
c1 := GetConnFromPool()
num, err = redis.Int64(c1.Do("SADD", key, member))
defer c1.Close()
return num, err
}
func GetSetMembers(key string) (results1 []string, err error) {
c1 := GetConnFromPool()
results1, err = redis.Strings(c1.Do("SMEMBERS", key))
defer c1.Close()
return results1, err
}
// SREM KEY MEMBER1..MEMBERN
func DelSetMember(key string, member string) (results1 int64, err error) {
log.Debug("del %s", member)
c1 := GetConnFromPool()
results1, err = redis.Int64(c1.Do("SREM", key, member))
log.Debug(results1)
defer c1.Close()
return results1, err
}
func PublishConnNgrokClien(channel string, message string) (num int, err error) {
c1 := GetConnFromPool()
defer c1.Close()
return redis.Int(c1.Do("PUBLISH", channel, message))
}
/*
func main() {
var ip1 string
ip1 = "ivg-12-34-56.vkingman.cn"
exists := isSetMemberExist("ngrok_client", ip1)
log.Debug(exists)
val1, err := addSetMember("ngrok_client", ip1)
if err != nil {
log.Debug("%v", err)
} else {
log.Debug("add %d members", val1)
}
results1, err := getSetMembers("ngrok_client")
if err != nil {
log.Debug("set err")
fmt.Println(err)
} else {
log.Debug("SMEMBERS ok")
log.Debug(results1)
}
results2, err := delSetMember("ngrok_client", ip1)
if err != nil {
log.Debug("%v", err)
} else {
log.Debug("del %d member", results2)
}
}
*/
-
修改src/ngrok/msg/conn.go的WriteMsg函数
func WriteMsg(c conn.Conn, msg interface{ }) (err error) { var ( ret int ) buffer, err := Pack(msg) if err != nil { return } c.Debug("Writing message: %s", string(buffer)) err = binary.Write(c, binary.LittleEndian, int64(len(buffer))) if err != nil { return } if _, err = c.Write(buffer); err != nil { return } ret = strings.Index(string(buffer), "NewTunnel") if ret > 0 { log.Error("liubaolong----------------> %s", string(buffer)) bExist := ivgredis.IsSetMemberExist("ngrok_client", string(buffer)) if !bExist { /* add */ t := &IvgNewTunnel{ } err = json.Unmarshal(buffer, &t) log.Debug(" %v", t.Payload.Url) ivgredis.AddSetMember("ngrok_client", t.Payload.Url) _, err = ivgredis.PublishConnNgrokClien("ngrok_client.connect", t.Payload.Url) if err != nil { log.Debug("%v", err) } } } return nil }
-
修改src/ngrok/server/tunnel.go的Shutdown函数
func (t *Tunnel) Shutdown() {
t.Info("liubaolong--190---> Shutting down, %s", t.url)
ret, _ := ivgredis.DelSetMember("ngrok_client", t.url)
if ret > 0 {
log.Debug("del %s", t.url)
_, err := ivgredis.PublishConnNgrokClien("ngrok_client.disconnet", t.url)
if err != nil {
log.Debug("%v", err)
}
}
// mark that we're shutting down
atomic.StoreInt32(&t.closing, 1)
// if we have a public listener (this is a raw TCP tunnel), shut it down
if t.listener != nil {
t.listener.Close()
}
// remove ourselves from the tunnel registry
tunnelRegistry.Del(t.url)
// let the control connection know we're shutting down
// currently, only the control connection shuts down tunnels,
// so it doesn't need to know about it
// t.ctl.stoptunnel <- t
metrics.CloseTunnel(t)
}
go import 需要注意
当首字母小写时,为包内使用,包外无法引用到。