github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/les/freeclient.go (about)

     1  
     2  //<developer>
     3  //    <name>linapex 曹一峰</name>
     4  //    <email>linapex@163.com</email>
     5  //    <wx>superexc</wx>
     6  //    <qqgroup>128148617</qqgroup>
     7  //    <url>https://jsq.ink</url>
     8  //    <role>pku engineer</role>
     9  //    <date>2019-03-16 19:16:38</date>
    10  //</624450093826707456>
    11  
    12  
    13  //包les实现轻以太坊子协议。
    14  package les
    15  
    16  import (
    17  	"io"
    18  	"math"
    19  	"sync"
    20  	"time"
    21  
    22  	"github.com/ethereum/go-ethereum/common/mclock"
    23  	"github.com/ethereum/go-ethereum/common/prque"
    24  	"github.com/ethereum/go-ethereum/ethdb"
    25  	"github.com/ethereum/go-ethereum/log"
    26  	"github.com/ethereum/go-ethereum/rlp"
    27  )
    28  
    29  //FreeClientPool实现限制连接时间的客户端数据库
    30  //对每个客户机,并管理接受/拒绝传入连接,甚至
    31  //排除一些连接的客户机。池计算最近的使用时间
    32  //对于每个已知客户机(当客户机
    33  //已连接,未连接时按指数递减)。低级别客户
    34  //最新的用法是首选的,未知节点具有最高优先级。已经
    35  //连接的节点会收到一个有利于它们的小偏差,以避免接受
    36  //立刻把客户赶出去。
    37  //
    38  //注意:池可以使用任何字符串来标识客户机。使用签名
    39  //如果知道钥匙有负面影响,那么就没有意义了。
    40  //客户端的值。目前,LES协议管理器使用IP地址
    41  //(没有端口地址)以标识客户机。
    42  type freeClientPool struct {
    43  	db     ethdb.Database
    44  	lock   sync.Mutex
    45  	clock  mclock.Clock
    46  	closed bool
    47  
    48  	connectedLimit, totalLimit int
    49  
    50  	addressMap            map[string]*freeClientPoolEntry
    51  	connPool, disconnPool *prque.Prque
    52  	startupTime           mclock.AbsTime
    53  	logOffsetAtStartup    int64
    54  }
    55  
    56  const (
    57  recentUsageExpTC     = time.Hour   //“最近”服务器使用的指数加权窗口的时间常数
    58  fixedPointMultiplier = 0x1000000   //常量将对数转换为定点格式
    59  connectedBias        = time.Minute //这种偏见适用于已经建立联系的客户,以避免他们很快被淘汰。
    60  )
    61  
    62  //NewFreeClientPool创建新的免费客户端池
    63  func newFreeClientPool(db ethdb.Database, connectedLimit, totalLimit int, clock mclock.Clock) *freeClientPool {
    64  	pool := &freeClientPool{
    65  		db:             db,
    66  		clock:          clock,
    67  		addressMap:     make(map[string]*freeClientPoolEntry),
    68  		connPool:       prque.New(poolSetIndex),
    69  		disconnPool:    prque.New(poolSetIndex),
    70  		connectedLimit: connectedLimit,
    71  		totalLimit:     totalLimit,
    72  	}
    73  	pool.loadFromDb()
    74  	return pool
    75  }
    76  
    77  func (f *freeClientPool) stop() {
    78  	f.lock.Lock()
    79  	f.closed = true
    80  	f.saveToDb()
    81  	f.lock.Unlock()
    82  }
    83  
    84  //成功握手后应调用Connect。如果连接是
    85  //拒绝,不需要呼叫断开。
    86  //
    87  //注意:disconnectfn回调不应阻塞。
    88  func (f *freeClientPool) connect(address string, disconnectFn func()) bool {
    89  	f.lock.Lock()
    90  	defer f.lock.Unlock()
    91  
    92  	if f.closed {
    93  		return false
    94  	}
    95  	e := f.addressMap[address]
    96  	now := f.clock.Now()
    97  	var recentUsage int64
    98  	if e == nil {
    99  		e = &freeClientPoolEntry{address: address, index: -1}
   100  		f.addressMap[address] = e
   101  	} else {
   102  		if e.connected {
   103  			log.Debug("Client already connected", "address", address)
   104  			return false
   105  		}
   106  		recentUsage = int64(math.Exp(float64(e.logUsage-f.logOffset(now)) / fixedPointMultiplier))
   107  	}
   108  	e.linUsage = recentUsage - int64(now)
   109  //检查(Linusage+ConnectedBias)是否小于连接池中的最高条目
   110  	if f.connPool.Size() == f.connectedLimit {
   111  		i := f.connPool.PopItem().(*freeClientPoolEntry)
   112  		if e.linUsage+int64(connectedBias)-i.linUsage < 0 {
   113  //把它踢出去接受新客户
   114  			f.connPool.Remove(i.index)
   115  			f.calcLogUsage(i, now)
   116  			i.connected = false
   117  			f.disconnPool.Push(i, -i.logUsage)
   118  			log.Debug("Client kicked out", "address", i.address)
   119  			i.disconnectFn()
   120  		} else {
   121  //保留旧客户并拒绝新客户
   122  			f.connPool.Push(i, i.linUsage)
   123  			log.Debug("Client rejected", "address", address)
   124  			return false
   125  		}
   126  	}
   127  	f.disconnPool.Remove(e.index)
   128  	e.connected = true
   129  	e.disconnectFn = disconnectFn
   130  	f.connPool.Push(e, e.linUsage)
   131  	if f.connPool.Size()+f.disconnPool.Size() > f.totalLimit {
   132  		f.disconnPool.Pop()
   133  	}
   134  	log.Debug("Client accepted", "address", address)
   135  	return true
   136  }
   137  
   138  //连接终止时应调用Disconnect。如果断开
   139  //由池本身使用disconnectfn启动,然后调用disconnect是
   140  //不必要,但允许。
   141  func (f *freeClientPool) disconnect(address string) {
   142  	f.lock.Lock()
   143  	defer f.lock.Unlock()
   144  
   145  	if f.closed {
   146  		return
   147  	}
   148  	e := f.addressMap[address]
   149  	now := f.clock.Now()
   150  	if !e.connected {
   151  		log.Debug("Client already disconnected", "address", address)
   152  		return
   153  	}
   154  
   155  	f.connPool.Remove(e.index)
   156  	f.calcLogUsage(e, now)
   157  	e.connected = false
   158  	f.disconnPool.Push(e, -e.logUsage)
   159  	log.Debug("Client disconnected", "address", address)
   160  }
   161  
   162  //Logoffset计算对数的时间相关偏移量
   163  //最近使用的表示法
   164  func (f *freeClientPool) logOffset(now mclock.AbsTime) int64 {
   165  //注意:这里fixedpointmultipler用作乘数;除数的原因
   166  //是为了避免Int64溢出。我们假设int64(recentusageexptc)>>固定点乘数。
   167  	logDecay := int64((time.Duration(now - f.startupTime)) / (recentUsageExpTC / fixedPointMultiplier))
   168  	return f.logOffsetAtStartup + logDecay
   169  }
   170  
   171  //calclogusage将最近的用法从线性表示转换为对数表示
   172  //断开对等机连接或关闭客户端池时
   173  func (f *freeClientPool) calcLogUsage(e *freeClientPoolEntry, now mclock.AbsTime) {
   174  	dt := e.linUsage + int64(now)
   175  	if dt < 1 {
   176  		dt = 1
   177  	}
   178  	e.logUsage = int64(math.Log(float64(dt))*fixedPointMultiplier) + f.logOffset(now)
   179  }
   180  
   181  //FreeClientPoolStorage是池数据库存储的RLP表示形式
   182  type freeClientPoolStorage struct {
   183  	LogOffset uint64
   184  	List      []*freeClientPoolEntry
   185  }
   186  
   187  //loadfromdb从数据库存储还原池状态
   188  //(初始化时自动调用)
   189  func (f *freeClientPool) loadFromDb() {
   190  	enc, err := f.db.Get([]byte("freeClientPool"))
   191  	if err != nil {
   192  		return
   193  	}
   194  	var storage freeClientPoolStorage
   195  	err = rlp.DecodeBytes(enc, &storage)
   196  	if err != nil {
   197  		log.Error("Failed to decode client list", "err", err)
   198  		return
   199  	}
   200  	f.logOffsetAtStartup = int64(storage.LogOffset)
   201  	f.startupTime = f.clock.Now()
   202  	for _, e := range storage.List {
   203  		log.Debug("Loaded free client record", "address", e.address, "logUsage", e.logUsage)
   204  		f.addressMap[e.address] = e
   205  		f.disconnPool.Push(e, -e.logUsage)
   206  	}
   207  }
   208  
   209  //savetodb将池状态保存到数据库存储
   210  //(关机时自动调用)
   211  func (f *freeClientPool) saveToDb() {
   212  	now := f.clock.Now()
   213  	storage := freeClientPoolStorage{
   214  		LogOffset: uint64(f.logOffset(now)),
   215  		List:      make([]*freeClientPoolEntry, len(f.addressMap)),
   216  	}
   217  	i := 0
   218  	for _, e := range f.addressMap {
   219  		if e.connected {
   220  			f.calcLogUsage(e, now)
   221  		}
   222  		storage.List[i] = e
   223  		i++
   224  	}
   225  	enc, err := rlp.EncodeToBytes(storage)
   226  	if err != nil {
   227  		log.Error("Failed to encode client list", "err", err)
   228  	} else {
   229  		f.db.Put([]byte("freeClientPool"), enc)
   230  	}
   231  }
   232  
   233  //FreeClientPoolentry表示池已知的客户端地址。
   234  //连接后,最近的使用量计算为linusage+int64(clock.now())
   235  //断开连接时,计算为exp(logusage-logoffset),其中logoffset
   236  //服务器运行时,也会随着时间线性增长。
   237  //线性和对数表示之间的转换发生在连接时
   238  //或者断开节点。
   239  //
   240  //注:linusage和logusage是不断增加偏移量的值,因此
   241  //even though they are close to each other at any time they may wrap around int64
   242  //时间的限制。应进行相应的比较。
   243  type freeClientPoolEntry struct {
   244  	address            string
   245  	connected          bool
   246  	disconnectFn       func()
   247  	linUsage, logUsage int64
   248  	index              int
   249  }
   250  
   251  func (e *freeClientPoolEntry) EncodeRLP(w io.Writer) error {
   252  	return rlp.Encode(w, []interface{}{e.address, uint64(e.logUsage)})
   253  }
   254  
   255  func (e *freeClientPoolEntry) DecodeRLP(s *rlp.Stream) error {
   256  	var entry struct {
   257  		Address  string
   258  		LogUsage uint64
   259  	}
   260  	if err := s.Decode(&entry); err != nil {
   261  		return err
   262  	}
   263  	e.address = entry.Address
   264  	e.logUsage = int64(entry.LogUsage)
   265  	e.connected = false
   266  	e.index = -1
   267  	return nil
   268  }
   269  
   270  //poolSetIndex callback is used by both priority queues to set/update the index of
   271  //队列中的元素。需要索引来删除顶部元素以外的元素。
   272  func poolSetIndex(a interface{}, i int) {
   273  	a.(*freeClientPoolEntry).index = i
   274  }
   275