SegmentFault go进阶教程最新的文章
2017-08-16T20:02:50+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
go语言实现短链接生成算法
https://segmentfault.com/a/1190000010698202
2017-08-16T20:02:50+08:00
2017-08-16T20:02:50+08:00
Dr_Zhang
https://segmentfault.com/u/zhangboyang
4
<blockquote><p>短链接服务</p></blockquote>
<p>在一些应用的分享文案中,经常需要包含一个打开实际页面的链接,而这个链接可能会非常的长(因为可能会有很多很多参数。。)这样的分享文案不仅不够美观,而且在一些平台会受到限制,比如weibo的140字。这时候我们就需要采用一个短链接服务了。</p>
<p>短链服务实际上是对长链接的一个1对N映射。在访问短链的时候,通过应用或web服务器进行跳转,就能访问到实际的页面。我们只需将长链完成映射,存储这样的对应关系,就实现了短链生成服务。</p>
<blockquote><p>算法</p></blockquote>
<p>将任意一条长链接映射为6位字符长度的字符串,而不会造成短链接的重复。(不是绝对的,在一个很大数量级的数值之内)</p>
<ol>
<li>将原长链接进行md5校验和计算,生成32位字符串。</li>
<li>将32位字符串每8位划分一段,得到4段子串。将每个字串(16进制形式)转化为整型数值,与0x3FFFFFFF(30位1)按位与运算,生成一个30位的数值。</li>
<li>将上述生成的30位数值按5位为单位依次提取,得到的数值与0x0000003D按位与,获取一个0-61的整型数值,作为从字符数组中提取字符的索引。得到6个字符就生成了一个短链。</li>
<li>4段字串共可以生成4个短链。</li>
</ol>
<blockquote><p>实现</p></blockquote>
<p><a href="https://link.segmentfault.com/?enc=k0tdAqhF%2BzkbA401uiC4xA%3D%3D.W8RKEUnXKouRaiflX%2FzADqoXflkmmlW%2FguwXd%2FKhn76W69KZWiBKfkjZHibQwOk2" rel="nofollow">https://github.com/by-zhang/s...</a> (求star。。)</p>
一个go语言实现的简洁TCP通信框架
https://segmentfault.com/a/1190000010535922
2017-08-07T12:25:48+08:00
2017-08-07T12:25:48+08:00
Dr_Zhang
https://segmentfault.com/u/zhangboyang
1
<h2>stpro 一个基于tcp协议实现的简洁通信框架</h2>
<h2>a skeleton for communication based on TCP</h2>
<blockquote><p>github:<a href="https://link.segmentfault.com/?enc=8x6rkZbtjtCD5R5tuzaSAg%3D%3D.QgCXAxtevRrw5r9aZ56dTigLaII5wZP2cTr%2F%2FuY4lxgKEhtSfVp5%2BzAIC6nSmvbD" rel="nofollow">https://github.com/by-zhang/s...</a> 厚脸皮求star</p></blockquote>
<h3>特性</h3>
<ul>
<li>引入go包即可使用</li>
<li>实现了crc校验,保证数据传输的完整性与正确性</li>
<li>调用方式简单明了</li>
</ul>
<h3>快速开始</h3>
<h4>1. 引入</h4>
<pre><code> import "stpro"
</code></pre>
<h4>2. server 端</h4>
<pre><code> /** 三步搭建服务端
1 定义任意名称struct的数据结构,必须包含Pmap、Phost两个
字段,其中Phost为服务端ip+port拼接的字符串,Pmap为自定
义数据包类型与数据包名称的映射。
2 实例化对象为字段赋值,实现对应已定义`包名称`的数据包处
理方法,方法名必为"P[包名称]",如type包的处理方法为Ptype
。方法中请定义数据处理逻辑,输入输入皆为[]byte类型。
3 stpro.New()传入实例化的对象,如无报错则服务端开始监听,
并按照你所定义的逻辑处理数据包,返回响应数据。
**/
package main
import (
"fmt"
"stpro"
)
type Server struct {
Phost string
Pmap map[uint8]string
}
func (m Server) Ptype(in []byte) (out []byte) {
fmt.Printf("客户端发来type包:%s\n", in)
/** process... **/
bytes := []byte("hello1")
return bytes
}
func (m Server) Pname(in []byte) (out []byte) {
fmt.Printf("客户端发来name包:%s\n", in)
/** process... **/
bytes := []byte("hello2")
return bytes
}
func main() {
m := Model{
Phost: ":9091",
Pmap: make(map[uint8]string),
}
m.Pmap[0x01] = "type"
m.Pmap[0x02] = "name"
err := stpro.New(m)
if err != nil {
fmt.Println(err)
}
}
</code></pre>
<h4>3.client端</h4>
<pre><code> /**
三部搭建客户端
1 数据结构同服务端。
2 P[type]方法是发送对应包后接收到响应数据的处理方法。
3 实例化对象,并调用Send(type byte, content []byte)方
法发送数据到客户端,接收到的数据后会自定按照上述定
义方法处理。
**/
package main
import (
"fmt"
"stpro"
)
type Client struct {
Phost string
Pmap map[byte]string
}
func (c Client) Ptype(in []byte) {
fmt.Printf("收到了type包的回复:%s\n", in)
}
func (c Client) Pname(in []byte) {
fmt.Printf("收到了name包的回复:%s\n", in)
}
func main() {
client, err := stpro.NewClient(Client{
Phost: "192.168.1.106:9091",
Pmap: map[byte]string{
0x01: "type",
0x02: "name",
},
})
if err != nil {
fmt.Println(err)
return
}
err = client.Send(0x02, []byte("jintianzhenhao"))
if err != nil {
fmt.Println(err)
return
}
err = client.Send(0x01, []byte("jintianzhenhao3333"))
if err != nil {
fmt.Println(err)
return
}
}</code></pre>
如何在go中使用protobuf
https://segmentfault.com/a/1190000010477733
2017-08-03T11:20:00+08:00
2017-08-03T11:20:00+08:00
Dr_Zhang
https://segmentfault.com/u/zhangboyang
1
<p>protobuf是Google开发出来的一个语言无关、平台无关的数据序列化工具,在rpc或tcp通信等很多场景都可以使用。通俗来讲,如果客户端和服务端使用的是不同的语言,那么在服务端定义一个数据结构,通过protobuf转化为字节流,再传送到客户端解码,就可以得到对应的数据结构。这就是protobuf神奇的地方。并且,它的通信效率极高,“一条消息数据,用protobuf序列化后的大小是json的10分之一,xml格式的20分之一,是二进制序列化的10分之一”。</p>
<blockquote><p>安装</p></blockquote>
<ul><li><p>编译安装protobuf的编译器<code>protoc</code></p></li></ul>
<pre><code> wget https://github.com/google/protobuf/releases/download/v2.6.1/protobuf-2.6.1.tar.gz
tar zxvf protobuf-2.6.1.tar.gz
cd protobuf-2.6.1./configure
make
make install
执行 protoc -h 查看安装是否成功</code></pre>
<ul><li><p>安装插件 <code>protoc-gen-go</code>,它是一个go程序,编译它之后将可执行文件执行路径写入环境变量</p></li></ul>
<pre><code> go get github.com/golang/protobuf/protoc-gen-go</code></pre>
<ul><li><p>获取<code>proto</code>包</p></li></ul>
<pre><code> go get github.com/golang/protobuf/proto</code></pre>
<blockquote><p>在go中使用</p></blockquote>
<p>protobuf的使用方法是将数据结构写入到.proto文件中,使用protoc编译器编译(间接使用了插件)得到一个新的go包,里面包含go中可以使用的数据结构和一些辅助方法。</p>
<blockquote><p>编写test.proto文件</p></blockquote>
<pre><code> package example;
enum FOO { X = 17; };
message Test {
required string label = 1;
optional int32 type = 2 [default=77];
repeated int64 reps = 3;
optional group OptionalGroup = 4 {
required string RequiredField = 5;
}
}
编译:
执行 protoc --go_out=. *.proto 生成 test.pb.go 文件
将test.pb.go文件放入example文件夹(对应上面package)中,作为example包</code></pre>
<blockquote><p>try</p></blockquote>
<pre><code> package main
import (
"log"
"github.com/golang/protobuf/proto"
"example"
)
func main() {
test := &example.Test {
Label: proto.String("hello"),
Type: proto.Int32(17),
Reps: []int64{1, 2, 3},
Optionalgroup: &example.Test_OptionalGroup {
RequiredField: proto.String("good bye"),
},
}
data, err := proto.Marshal(test)
if err != nil {
log.Fatal("marshaling error: ", err)
}
newTest := &example.Test{}
err = proto.Unmarshal(data, newTest)
if err != nil {
log.Fatal("unmarshaling error: ", err)
}
// Now test and newTest contain the same data.
if test.GetLabel() != newTest.GetLabel() {
log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel())
}
//test.GetOptionalgroup().GetRequiredField()
//etc
}</code></pre>
<blockquote><p>一些对应关系</p></blockquote>
<ul>
<li><p>message Test对为 struct 结构,其属性字段有了对应的get方法,在go中可以使用test.GetLabel()、test.GetType()获取test对象的属性</p></li>
<li><p>OptionalGroup对应为 struct中的内嵌struct</p></li>
<li><p>proto文件中repeated属性对于slice结构</p></li>
<li><p>test.Reset()可以使其所有属性置为0值</p></li>
<li><p>使用Marshal和Unmarshal可以轻松的编码和解码</p></li>
</ul>
<p>这些只是一些特性,想要仔细研究可以查看github上的wiki:<a href="https://link.segmentfault.com/?enc=FkqKikmP4IJ1d9j11hIH1g%3D%3D.yNa%2FAuFtqRxE1UXEnf2G3%2FxDFA%2B35Cuko25EvO8UpesDbJlrFKdmc6tWj3BV9kDm" rel="nofollow">https://github.com/golang/pro...</a></p>
区分“并发”与“并行”的概念
https://segmentfault.com/a/1190000010404111
2017-07-29T02:44:35+08:00
2017-07-29T02:44:35+08:00
Dr_Zhang
https://segmentfault.com/u/zhangboyang
2
<p>并发与并行这两个概念是令人困惑的,但在go语言的编程中是必须要理解的。</p>
<blockquote><p>并发(concurrent)</p></blockquote>
<p>并发是指两种或两种以上的行为在系统中<code>同时存在</code>,至于这两个行为是否在某一时刻同时“执行”,在并发的概念中并不考虑。<br>在go语言中,go语句可以开启一个新的goroutine,这就典型的并发。不同的goroutines在程序运行期间可能同时存在着, 至于这些goroutines在某一个时刻是不是同时运行, 我们不去关心。<br>事实上, 在单核CPU系统中, goroutines的运转是依赖cpu的时间片轮转算法的,即交替执行。但这的的确确是并发,原因是系统具备了同时处理多种行为的能力。实际上这是一种人类无法直接感知的“伪并行”,只不过从表面上看来,“像是同时执行的”。</p>
<blockquote><p>并行(parellel)</p></blockquote>
<p>并行意味着多个动作在某一时段是<code>同时执行</code>的。在多核CPU的前提下, go可以为goroutines指定运算需要的处理器数量, 这样的话, goroutines就是真正的并行了,每个goroutine有独立的CPU为自己运算,而不需要公用一个CPU来轮转运算。</p>
<blockquote><p>对比</p></blockquote>
<p>可以说并发是一个逻辑上的概念,并行是一个物理运行状态的概念。并行是并发的一个“子集”,并发包含并行。</p>
interface
https://segmentfault.com/a/1190000010403321
2017-07-28T23:43:16+08:00
2017-07-28T23:43:16+08:00
Dr_Zhang
https://segmentfault.com/u/zhangboyang
1
<blockquote><p>interface是什么</p></blockquote>
<p>interface被称为接口,是一种类型,其本质是一组<code>抽象方法的集合</code>。凡是实现这些抽象方法的对象,都可以被称为“实现了这个接口”。其存在意义是为了规定对象的一组行为。</p>
<blockquote><p>interface举例</p></blockquote>
<pre><code>package main
import (
"fmt"
)
type Singer interface {
sing()
}
type Man struct {
lyric string
}
type Bird struct {
lyric string
}
func (m Man) sing() {
fmt.Println(m.lyric)
}
func (b Bird) sing() {
fmt.Println(b.lyric)
}
func main() {
var in Singer
in = Man{"I'm a brave man"}
in.sing()
in = Bird{"I'm a small bird"}
in.sing()
}
</code></pre>
<p>上述事例中我们定义了一个名为Singer的接口,它包含一个抽象方法sing()(当然也可以包含很多抽象方法)。接着我们又分别为Man对象和Bird对象实现了sing()方法,即这两个对象都实现了Singer接口。于是,这两种对象就都可以使用Singer接口对应的变量来存储了!使用这个接口变量就如同调用其对应的对象一样容易。</p>
<blockquote><p>空interface</p></blockquote>
<p>interface{} 是一个空interface,实现它不需要实现任何抽象函数,也就是说所有的类型都实现了空interface。于是,一个空interface变量可以存入任何值。实际的用处是,当不确定传入函数的参数类型时,可以使用interface{}代替。并且,我们有特定的语法可以判断具体存入interface{}变量的类型。</p>
<pre><code>package main
import (
"fmt"
"reflect"
)
type Ele interface{}
type List []Ele
func main() {
list := make(List, 4)
list[0] = 1
list[1] = 'c'
list[2] = "string"
list[3] = [2]int{5, 6}
for index, val := range list {
switch typeval := val.(type) {
case int:
fmt.Printf("list[%d] is an int(%d)\n", index, typeval)
case string:
fmt.Printf("list[%d] is a string(%s)\n", index, typeval)
case rune:
fmt.Printf("list[%d] is a rune(%c)\n", index, typeval)
default:
fmt.Printf("list[%d] is a different type(%s)\n", index, reflect.TypeOf(typeval))
}
}
}</code></pre>
<p><code>注意</code>:这种switch和val.(type)配合的语法是特有的,在switch以外的任何地方都不能使用类似于val.(type)这种形式。</p>
<blockquote><p>一个特别的interface</p></blockquote>
<p>我们很熟悉的fmt.Println函数中可以传入int、string、rune、array等多种类型作为参数,而控制台实际输出的字符串反映了每种类型的值。这就是因为它们都实现了源码中<br>Stringer接口,如下。</p>
<pre><code>type Stringer interface {
String() string
}</code></pre>
<p>有趣的一点是,当我们定义一个新的数据类型时,如果也实现了Stringer这个接口,那么它同样也可以被fmt包格式化输出,并且是按照你所定义的格式。</p>
<pre><code>package main
import (
"fmt"
)
type Man struct {
name string
age int
}
func (m Man) String() (result string) {
result = fmt.Sprintf("I'm a man. My name is %s and I'm %d years old.\n", m.name, m.age)
return
}
func main() {
man := Man{"Bob", 18}
fmt.Println(man)
}
output:
I'm a man. My name is Bob and I'm 18 years old.</code></pre>
go中的面向对象
https://segmentfault.com/a/1190000010211111
2017-07-16T23:24:51+08:00
2017-07-16T23:24:51+08:00
Dr_Zhang
https://segmentfault.com/u/zhangboyang
4
<p>总体来看,go语言中的面向对象在使用方式上是灵活易用的,可以说设计理念真的很先进,让人有一种如沐春风的感觉。</p>
<p>如果你在学生时代经历了一个从c到c++的学习历程,你是否还记得,老师会说c++是面向对象的,所以我们不必再使用c中的结构体作为数据结构。我们只需定义的是c++中的类,因为类中不只有成员属性,也有成员函数。换句话说, class是可以完美替代struct的,而且更强大。</p>
<p>回到go中,我们的面向对象使用的就是<code>struct</code>,但时代不同了,这次我们的struct也可以有"成员函数"了。</p>
<blockquote><p>定义一个典型的面向对象方式</p></blockquote>
<pre><code>
package main
import "fmt"
type Human struct {
height float32
weight int
}
func (h Human) BMIindex() (index int){
index = h.weight / int(h.height * h.height)
return
}
func main() {
person := Human{1.83, 75}
fmt.Printf("this person's height is %.2f m\n", person.height)
fmt.Printf("this person's weight is %d kg\n", person.weight)
fmt.Printf("this person's BMI index is %d\n", person.BMIindex())
}
</code></pre>
<p>在main函数中我们初始化了一个Human对象,并分别读取了他们的属性height和weight,最后调用了Human对象的成员函数BMIindex(),通过计算获得了这个人的"体质指数"。</p>
<blockquote><p>Receiver</p></blockquote>
<p>上述例子中,一个Human对象的成员函数就是通过Receiver来定义的。我们给一个普通的func添加了Receiver(就是上述示例中的h Human),就构成了一个<code>method</code>BMIindex()。在这种情况下,这个函数只能依赖于一个Human对象来起作用,而不能被独立调用。其正确的调用方式就是上述的person.BMIindex()</p>
<p>下述又一个例子,我们希望通过定义成员函数来改变对象的成员属性。</p>
<pre><code>package main
import "fmt"
type Human struct {
height float32
weight int
}
func (h Human) BMIindex() (index int){
index = h.weight / int(h.height * h.height)
return
}
func (h Human) setHeight(height float32) {
h.height = height
}
func main() {
person := Human{1.83, 75}
fmt.Printf("this person's height is %.2f m\n", person.height)
fmt.Printf("this person's weight is %d kg\n", person.weight)
fmt.Printf("this person's BMI index is %d\n", person.BMIindex())
person.setHeight(1.90)
fmt.Printf("this person's height is %.2f m\n", person.height)
}
输出结果:
this person's height is 1.83 m
this person's weight is 75 kg
this person's BMI index is 25
this person's height is 1.83 m
</code></pre>
<p>可以看出,我们调用person.setHeight(1.90)之后,person的height属性并没有改变为1.90。而为了解决这个问题,我们需要改变receiver。我们将setHeight()函数定义为下述形式即可。</p>
<pre><code>func (h *Human) BMIindex() (index int){
index = h.weight / int(h.height * h.height)
return
}
</code></pre>
<p>原因:我们将对象的<code>指针类型</code>作为receiver,才能改变其属性的值。其本质为,我们可以把receiver看作func的独特的一个参数。如果传递的是一个对象类型,那么函数中改变的实际上是这个对象的副本;而如果传递一个指针类型,改变的才是这个对象本身。</p>
<p>关于receiver的选择,可以这样理解,如果需要获取对象属性(get),则选用对象作为receiver;如果需要改变对象属性(set),则选取指针作为receiver。</p>
<blockquote><p>method的适用范围</p></blockquote>
<p>上述示例中,我们定义的method都是对应于struct的,但实际上method的定义可以依赖于所有的<code>自定义类型</code>。所谓自定义类型,就是通过type语句给一些内置类型起了个"别名"后所定义的新类型。</p>
<pre><code>
package main
import "fmt"
type Sex string
func (s *Sex) change(){
if *s == Sex("女") {
*s = Sex("男")
}
}
func main() {
sex := Sex("女")
fmt.Println("previous sex is ", sex)
sex.change()
fmt.Println("current sex is ", sex)
}
</code></pre>
<p>这里,我们新定义了一个类型Sex,并且为其定义了一个method change()。</p>
<blockquote><p>面向对象中的继承</p></blockquote>
<pre><code>package main
import "fmt"
type Human struct {
height float32
weight int
}
type Woman struct {
Human
sex string
}
func (h Human) BMIindex() (index int){
index = h.weight / int(h.height * h.height)
return
}
func main() {
woman := Woman{Human{1.65, 50}, "女"}
fmt.Printf("this woman's height is %.2f m\n", woman.height)
fmt.Printf("this woman's wight is %d kg\n", woman.weight)
fmt.Printf("this woman's BMIindex is %d\n", woman.BMIindex())
}
</code></pre>
<p>这个例子展现了Woman对Human的继承。在Woman结构体中包含了<code>匿名字段</code>Human,那么Human中包含的属性也是属于一个Woman对象的属性,Human对象的method同样也可以被Woman对象所使用。值得注意的是,这个method可以被<code>重写</code>。只需要再定义一个以Woman对象类型为receiver的BMIindex(),那么再次调用woman.BMIindex时,实际调用的函数是新定义的这个函数。这就是method的重写。</p>
<blockquote><p>访问属性</p></blockquote>
<p>如果你对面向对象中的访问属性很熟悉的话,你一定知道public、private和protected作为访问修饰符的作用。而在go语言中,我们使用<code>大小写</code>来区分。</p>
<p>标识符首字母大写,相当于public属性。这样的成员属性或成员函数可以被在<code>包外部</code>被调用。例如上述Woman、BMIindex。</p>
<p>标识符首字母小写,相当于protected属性。这样的成员属性或成员函数只能在<code>包内部</code>被使用。</p>
go的异常处理机制
https://segmentfault.com/a/1190000010203475
2017-07-16T00:30:11+08:00
2017-07-16T00:30:11+08:00
Dr_Zhang
https://segmentfault.com/u/zhangboyang
3
<p>在java或php等很多面向对象的语言中, 异常处理是依靠throw、catch来进行的。在go语言中,panic和recover函数在作用层面分别对等throw和catch语句,当然也存在不同之处。</p>
<p>从设计层面来看,panic和recover函数适用于那些真正的异常(例如整数除0),而throw catch finally机制常常被用来处理一些业务层面的自定义异常。因此在go语言中,panic和recover要慎用。</p>
<p>上述两种异常机制的使用中,在处理异常时<code>控制流程</code>的走向也是相似的。</p>
<p>下面将分别举例说明:</p>
<blockquote><p>try catch finally机制</p></blockquote>
<pre><code> try{
throw new Exception();
} catch(Exception $e) {
do something ...
} finally {
}
</code></pre>
<p>这种机制中,我们把可能抛出异常的语句或抛出自定义异常的语句放置到try语句块中,而在catch块中,我们将上述语句抛出的异常捕获,针对不同的异常进行报警或log等处理。之后,控制流程进入到finally语句块中。若没有finally语句,控制流程将进入到catch之后的语句中。也就是说,在这种机制中,控制流程是转移到同一层级中异常捕获之后的语句中。</p>
<blockquote><p>panic recover defer机制</p></blockquote>
<p>在go的异常机制中,panic可以将原有的控制流程中断,进入到一个"恐慌"流程。这种恐慌流程可以显式调用panic()函数产生或者由运行时错误产生(例如访问越界的数组下标)。panic会在调用它的函数中向本层和它的所有上层逐级抛出,若一直没有recover将其捕获,程序退出后会产生crash;若在某层defer语句中被recover捕获,控制流程将进入到recover之后的语句中。</p>
<pre><code> /* example 1 */
package main
import (
"fmt"
)
func f() {
defer func() {
fmt.Println("b")
if err := recover();err != nil {
fmt.Println(err)
}
fmt.Println("d")
}()
fmt.Println("a")
panic("a bug occur")
fmt.Println("c")
}
func main() {
f()
fmt.Println("x")
}
</code></pre>
<p>在上述举例中,输出结果为:</p>
<pre><code> a
b
a bug occur
d
x
</code></pre>
<p>这说明,在f函数中抛出的panic被自己defer语句中的recover捕获,然后控制流程进入到recover之后的语句中,即打印d、打印x,之后进程正常退出。</p>
<pre><code> /* example 2 */
package main
import (
"fmt"
)
func g() {
defer func() {
fmt.Println("b")
if err := recover();err != nil {
fmt.Println(err)
}
fmt.Println("d")
}()
f()
fmt.Println("e")
}
func f() {
fmt.Println("a")
panic("a bug occur")
fmt.Println("c")
}
func main() {
g()
fmt.Println("x")
}
</code></pre>
<p>上述案例的输出结果是:</p>
<pre><code> a
b
a bug occur
d
x
</code></pre>
<p>进程经历了这样一个过程:f()中抛出panic,由于自身没有定义defer语句,panic被抛到g()中。g()的defer语句中定义了recover,捕获panic后并执行完defer剩余的语句,之后控制流程被转交到main()函数中,直至进程结束。</p>