内容简介:网上有两篇文章已对ngrok原理讲得很清晰了:
从去年开始就对 go 语言产生一点兴趣,总感觉 java 有时太过臃肿,是时候尝试一种新语言了。我看了几本关于go的书,不过看完就忘了,最近开发一个微信公众号项目,使用ngrok做内网穿透,顺道研究一下ngrok源码,巩固一下go语言。
网上有两篇文章已对ngrok原理讲得很清晰了:
https://blog.messyidea.com/archives/41/
https://tonybai.com/2015/05/14/ngrok-source-intro/
我试图记录一些细节,然后实现在个简单的ngrok。
- 源码/ngrok/cache/lru.go,这是随手点开的一个文件,从名字可以看出它的用处:实现一个lru(Least Recently Used)缓存,在以往的项目中,我使用的本地缓存是guava提供的工具,很久前我还用过WeakReferenceMap作为一个简单缓存。以下是ngrok中的源码
type LRUCache struct { mu sync.Mutex // list & table of *entry objects list *list.List table map[string]*list.Element // Our current size, in bytes. Obviously a gross simplification and low-grade // approximation. size uint64 // How many bytes we are limiting the cache to. capacity uint64 } type Item struct { Key string Value Value }
虽然算法很简单,但有几个地方还是值得把玩的。list是一个链表,存储的是item,它可以看成是一个具有优先级的队列,每次访问一个元素时,都会对将相应元素移至队首。这样当list长度超过capacity时,就可以移除队尾的元素。而table是用作索引,通过一个键查询缓存时,通过table得到list中一个element的引用。而list保存的item有一个key属性对应用table中的key,这样很方便对table做移除操作。
以前我想在java中用PriorityBolckingQueue,和ConcurrentHashMap实现一个类似的缓存(不仅是LRU缓存,它们组合实现多种缓存算法),但只想到用map保存键值对,而queue用来做优先队列(这种双向索引的方式我没想到)。说不定用JDK中的HashLinkedMap更容易实现LRU,因为他已同时具备链表和索引的功能。
完整代码如下:
// Copyright 2012, Google Inc. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // The implementation borrows heavily from SmallLRUCache (originally by Nathan // Schrenk). The object maintains a doubly-linked list of elements in the // When an element is accessed it is promoted to the head of the list, and when // space is needed the element at the tail of the list (the least recently used // element) is evicted. package cache import ( "container/list" "encoding/gob" "fmt" "io" "os" "sync" "time" ) type LRUCache struct { mu sync.Mutex // list & table of *entry objects list *list.List table map[string]*list.Element // Our current size, in bytes. Obviously a gross simplification and low-grade // approximation. size uint64 // How many bytes we are limiting the cache to. capacity uint64 } // Values that go into LRUCache need to satisfy this interface. type Value interface { Size() int } type Item struct { Key string Value Value } type entry struct { key string value Value size int time_accessed time.Time } func NewLRUCache(capacity uint64) *LRUCache { return &LRUCache{ list: list.New(), table: make(map[string]*list.Element), capacity: capacity, } } func (lru *LRUCache) Get(key string) (v Value, ok bool) { lru.mu.Lock() defer lru.mu.Unlock() element := lru.table[key] if element == nil { return nil, false } lru.moveToFront(element) return element.Value.(*entry).value, true } func (lru *LRUCache) Set(key string, value Value) { lru.mu.Lock() defer lru.mu.Unlock() if element := lru.table[key]; element != nil { lru.updateInplace(element, value) } else { lru.addNew(key, value) } } func (lru *LRUCache) SetIfAbsent(key string, value Value) { lru.mu.Lock() defer lru.mu.Unlock() if element := lru.table[key]; element != nil { lru.moveToFront(element) } else { lru.addNew(key, value) } } func (lru *LRUCache) Delete(key string) bool { lru.mu.Lock() defer lru.mu.Unlock() element := lru.table[key] if element == nil { return false } lru.list.Remove(element) delete(lru.table, key) lru.size -= uint64(element.Value.(*entry).size) return true } func (lru *LRUCache) Clear() { lru.mu.Lock() defer lru.mu.Unlock() lru.list.Init() lru.table = make(map[string]*list.Element) lru.size = 0 } func (lru *LRUCache) SetCapacity(capacity uint64) { lru.mu.Lock() defer lru.mu.Unlock() lru.capacity = capacity lru.checkCapacity() } func (lru *LRUCache) Stats() (length, size, capacity uint64, oldest time.Time) { lru.mu.Lock() defer lru.mu.Unlock() if lastElem := lru.list.Back(); lastElem != nil { oldest = lastElem.Value.(*entry).time_accessed } return uint64(lru.list.Len()), lru.size, lru.capacity, oldest } func (lru *LRUCache) StatsJSON() string { if lru == nil { return "{}" } l, s, c, o := lru.Stats() return fmt.Sprintf("{\"Length\": %v, \"Size\": %v, \"Capacity\": %v, \"OldestAccess\": \"%v\"}", l, s, c, o) } func (lru *LRUCache) Keys() []string { lru.mu.Lock() defer lru.mu.Unlock() keys := make([]string, 0, lru.list.Len()) for e := lru.list.Front(); e != nil; e = e.Next() { keys = append(keys, e.Value.(*entry).key) } return keys } func (lru *LRUCache) Items() []Item { lru.mu.Lock() defer lru.mu.Unlock() items := make([]Item, 0, lru.list.Len()) for e := lru.list.Front(); e != nil; e = e.Next() { v := e.Value.(*entry) items = append(items, Item{Key: v.key, Value: v.value}) } return items } func (lru *LRUCache) SaveItems(w io.Writer) error { items := lru.Items() encoder := gob.NewEncoder(w) return encoder.Encode(items) } func (lru *LRUCache) SaveItemsToFile(path string) error { if wr, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil { return err } else { defer wr.Close() return lru.SaveItems(wr) } } func (lru *LRUCache) LoadItems(r io.Reader) error { items := make([]Item, 0) decoder := gob.NewDecoder(r) if err := decoder.Decode(&items); err != nil { return err } lru.mu.Lock() defer lru.mu.Unlock() for _, item := range items { // XXX: copied from Set() if element := lru.table[item.Key]; element != nil { lru.updateInplace(element, item.Value) } else { lru.addNew(item.Key, item.Value) } } return nil } func (lru *LRUCache) LoadItemsFromFile(path string) error { if rd, err := os.Open(path); err != nil { return err } else { defer rd.Close() return lru.LoadItems(rd) } } func (lru *LRUCache) updateInplace(element *list.Element, value Value) { valueSize := value.Size() sizeDiff := valueSize - element.Value.(*entry).size element.Value.(*entry).value = value element.Value.(*entry).size = valueSize lru.size += uint64(sizeDiff) lru.moveToFront(element) lru.checkCapacity() } func (lru *LRUCache) moveToFront(element *list.Element) { lru.list.MoveToFront(element) element.Value.(*entry).time_accessed = time.Now() } func (lru *LRUCache) addNew(key string, value Value) { newEntry := &entry{key, value, value.Size(), time.Now()} element := lru.list.PushFront(newEntry) lru.table[key] = element lru.size += uint64(newEntry.size) lru.checkCapacity() } func (lru *LRUCache) checkCapacity() { // Partially duplicated from Delete for lru.size > lru.capacity { delElem := lru.list.Back() delValue := delElem.Value.(*entry) lru.list.Remove(delElem) delete(lru.table, delValue.key) lru.size -= uint64(delValue.size) } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入浅出Ext JS
何启伟、徐会生、康爱媛 / 人民邮电出版社 / 2010-5 / 69.00元
以用户为中心的时代,应用的界面外观变得越来越重要。然而,很多程序员都缺乏美术功底,要开发出界面美观的应用实属不易。Ext JS的出现,为广大程序员解决了这一难题。它有丰富多彩的界面和强大的功能,是开发具有炫丽外观的RIA应用的最佳选择。 本书是《深入浅出Ext JS》的升级版,涵盖了最新发布的Ext JS 3.2新特性,并对上一版的内容进行增补,充实了示例代码,同时补充了两个功能强大的实例。......一起来看看 《深入浅出Ext JS》 这本书的介绍吧!