golang

golang

GOLANG环境设置

技术讨论winlin 发表了文章 • 0 个评论 • 44 次浏览 • 1 天前 • 来自相关话题

原文:http://blog.csdn.net/win_lin/article/details/48265493


GO环境


官网下载GOLANG的安装:golang.org


如果不能翻墙,可以从golangtc或者gocn.io下载。


下载时,如何选择安装包:



  1. MAC,下载包含darwin的pkg或tar(推荐PKG),pkg可以直接安装但是卸载比较麻烦,tar需要自己设置PATH安装麻烦卸载比较方便,例如go1.8.1.darwin-amd64.pkggo1.8.1.darwin-amd64.tar.gz

  2. Windows,一般就是安装包了,设置GOPATH比较麻烦,根据自己的系统是32位还是64位下载,譬如go1.8.1.windows-amd64.msi或者go1.8.1.windows-386.msi

  3. Linux,一般都是64位的,而且需要手动解压和设置GOPATH,当然还有ARM的,可以选择自己需要的下载,譬如go1.8.1.linux-amd64.tar.gz


如果是安装包,双击就可以安装,可以跳过下面这步。如果是linux这种需要自己解压和设置PATH的,步骤如下:


# 先解压和移动目录,以1.8.1为例
tar xf go1.8.1.linux-amd64.tar.gz &&
sudo mkdir -p /usr/local/ &&
sudo mv go /usr/local/go

# 设置PATH,打开文件
sudo vi /etc/profile

# 在文件末尾输入内容,然后保存
export PATH=$PATH:/usr/local/go/bin

# 应用修改的配置
source /etc/profile

UNIX设置GOPATH,存放项目的路径,GOLANG的项目都是绝对路径,从这个目录开始搜索:


# 创建目录,一般都是$HOME下面的go目录,譬如:/home/winlin/go
mkdir -p $HOME/go

# 设置GOPATH,打开文件
sudo vi /etc/profile

# 在文件末尾输入内容,然后保存
export GOPATH=$HOME/go

# 应用修改的配置
source /etc/profile

这样就可以用go build等编译了。譬如执行命令go version


Mac winlin$ go version
go version go1.8.1 darwin/amd64

go get从GITHUB下载一个项目,可以用到自己的项目中,可以看到下载到了GOPATH中:


Mac winlin$ go get github.com/ossrs/go-oryx-lib

Mac winlin$ ls -lh $GOPATH/src/github.com/ossrs
drwxr-xr-x 22 winlin 748B May 25 09:43 go-oryx-lib

GO的环境就配置成功了。

有人在用go-micro吗

技术讨论chrislee 回复了问题 • 5 人关注 • 2 个回复 • 189 次浏览 • 1 天前 • 来自相关话题

GOLANG使用Context管理关联goroutine

技术讨论winlin 发表了文章 • 6 个评论 • 197 次浏览 • 2017-05-19 15:08 • 来自相关话题

一般一个业务很少不用到goroutine的,因为很多方法是需要等待的,例如http.Server.ListenAndServe这个就是等待的,除非关闭了Server或Listener,否则是不会返回的。除非是一个API服务器,否... 查看全部

一般一个业务很少不用到goroutine的,因为很多方法是需要等待的,例如http.Server.ListenAndServe这个就是等待的,除非关闭了Server或Listener,否则是不会返回的。除非是一个API服务器,否则肯定需要另外起goroutine发起其他的服务,而且对于API服务器来说,在http.Handler的处理函数中一般也需要起goroutine,如何管理这些goroutine,在GOLANG1.7提供context.Context


先看一个简单的,如果启动两个goroutine,一个是HTTP,还有个信号处理的收到退出信号做清理:


wg := sync.WaitGroup{}
defer wg.Wait()

wg.Add(1)
go func() {
defer wg.Done()

ss := make(os.Signal, 0)
signal.Notify(ss, syscall.SIGINT, syscall.SIGTERM)
for s := ss {
fmt.Println("Got signal", s)
break
}
}()

wg.Add(1)
go func() {
defer wg.Done()

svr := &http.Server{ Addr:":8080", Handler:nil, }
fmt.Println(svr.ListenAndServe())
}

很清楚,起了两个goroutine,然后用WaitGroup等待它们退出。如果它们之间没有交互,不互相影响,那真的是蛮简单的,可惜这样是不行的,因为信号的goroutine收到退出信号后,应该通知server退出。暴力一点的是直接调用svr.Close(),但是如果有些请求还需要取消怎么办呢?最好用Context了:


wg := sync.WaitGroup{}
defer wg.Wait()

ctx,cancel := context.WithCancel(context.Background())

wg.Add(1)
go func() {
defer wg.Done()

ss := make(chan os.Signal, 0)
signal.Notify(ss, syscall.SIGINT, syscall.SIGTERM)
select {
case <- ctx.Done():
return
case s := <- ss:
fmt.Println("Got signal", s)
cancel() // 取消请求,通知用到ctx的所有goroutine
return
}
}()

wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

svr := &http.Server{ Addr:":8080", Handler:nil, }

go func(){
select {
case <- ctx.Done():
svr.Close()
}
}

fmt.Println(svr.ListenAndServe())
}

这个方式可以在新开goroutine时继续使用,譬如新加一个goroutine,里面读写了UDPConn:


wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

var conn *net.UDPConn
if conn,err = net.Dial("udp", "127.0.0.1:1935"); err != nil {
fmt.Println("Dial UDP server failed, err is", err)
return
}

fmt.Println(UDPRead(ctx, conn))
}()

UDPRead = func(ctx context.Context, conn *net.UDPConn) (err error) {
wg := sync.WaitGroup{}
defer wg.Wait()

ctx, cancel := context.WithCancel(ctx)

wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

for {
b := make([]byte, core.MTUSize)
size, _, err := conn.ReadFromUDP(b)
// 处理UDP包 b[:size]
}
}()

select {
case <-ctx.Done():
conn.Close()
}
return
}

如果只是用到HTTP Server,可以这么写:


func run(ctx contex.Context) {
server := &http.Server{Addr: addr, Handler: nil}
go func() {
select {
case <-ctx.Done():
server.Close()
}
}()

http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
})

fmt.Println(server.ListenAndServe())
}

如果需要提供一个API来让服务器退出,可以这么写:


func run(ctx contex.Context) {
server := &http.Server{Addr: addr, Handler: nil}

ctx, cancel := context.WithCancel(ctx)
http.HandleFunc("/quit", func(w http.ResponseWriter, r *http.Request) {
cancel() // 使用局部的ctx和cancel
})

go func() {
select {
case <-ctx.Done():
server.Close()
}
}()

fmt.Println(server.ListenAndServe())
}

使用局部的ctx和cancel,可以避免cancel传入的ctx,只是影响当前的ctx。

GOLANG使用嵌入结构实现接口

技术讨论winlin 发表了文章 • 0 个评论 • 120 次浏览 • 2017-05-19 13:23 • 来自相关话题

考虑一个Packet接口,一般会返回一个Header,例如:

type PacketHeader struct {
    ID uint32
    Timesta... 			查看全部
					

考虑一个Packet接口,一般会返回一个Header,例如:


type PacketHeader struct {
ID uint32
Timestamp uint64
}

type Packet interface {
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
Header() *PacketHeader
}

如果是OO的语言,一般会有一个基类,里面包含了Header和实现这个Header:


class BasePacket : public Packet {
protected:
PacketHeader h;
public:
virtual Header() *PacketHeader;
};

class HandshakePacket : public BasePacket {
};

在子类中就都实现了这个Header()方法了,在GOLANG同样可以做到,通过在Header中定义方法,在Packet中包含Header就可以。


func (v *PacketHeader) Header() *PakcetHeader {
return v
}

type HandshakePacket struct {
PacketHeader
}

看起来还差不多的,都可以实现,golang只是代码少一点,清晰一点点而已。考虑要添加一些辅助函数,譬如给Packet添加是否是紧急类型的包,那OO语言得做一次代理:


type Packet interface {
IsErgency() bool
}

class BasePacketHeader {
public:
bool IsErgency() {
return realtime < 3;
}
}

class BasePacket {
public:
bool IsErgency() {
return h.IsErgency();
}
}

而在GOLANG中,只需要在Header实现就好了:


func (v *PacketHeader) IsErgency() bool {
return v.realtime < 3
}

更高级的可以直接嵌入接口。譬如context.Context的实现,cancelCtx直接嵌入了一个接口:


type cancelCtx struct {
Context

通过指定类型,或者初始化的顺序初始化struct


func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{
Context: parent,
done: make(chan struct{}),
}
}

结构嵌套的方式,让组合实现起来非常便捷,避免频繁的代理。

Go能写桌面程序吗?比如winform 这样的

有问必答codinghxl 回复了问题 • 14 人关注 • 10 个回复 • 1269 次浏览 • 2017-05-19 11:39 • 来自相关话题

大家说说看都用啥写Go

技术讨论lmw 回复了问题 • 62 人关注 • 78 个回复 • 3892 次浏览 • 2017-05-19 00:31 • 来自相关话题

使用两个context实现CLOSE包的超时等待

技术讨论winlin 发表了文章 • 0 个评论 • 104 次浏览 • 2017-05-18 20:39 • 来自相关话题

在UDP中,一般发送者发送包后,如果一定的时间对方没有收到,就需要重传。例如UDP实现握手的过程,如果握手的包,比如RTMFP协议的IHELLO,发送给对方后,如果一定1秒没有收到,就应该重发一次,然后等3秒、6秒、9秒,如果最后没有收到就是超时了。查看全部

在UDP中,一般发送者发送包后,如果一定的时间对方没有收到,就需要重传。例如UDP实现握手的过程,如果握手的包,比如RTMFP协议的IHELLO,发送给对方后,如果一定1秒没有收到,就应该重发一次,然后等3秒、6秒、9秒,如果最后没有收到就是超时了。


最后一个Close包,发送者不能等待这么长的时间,所以需要设置一个较短的时间做超时退出。一般收发都是一个context,在最后这个Close包时,收到ctx.Done也不能立刻退出,因为还需要稍微等待,譬如600毫秒如果没有收到响应才能退出。


一个可能的实现是这样:


in := make(chan []byte)

func Close(ctx context.Context) (err error) {
timeous := ... // 1s,3s,6s,9s...
for _, to := range timeouts {
// 发送给对方WriteToUDP("CLOSE", peer)
// 另外一个goroutine读取UDP包到in

select {
case <- time.After(to):
case <- in:
fmt.Println("Close ok")
return
case <- ctx.Done():
fmt.Println("Program quit")
return
}
}
return
}

但是这个问题在于,在程序退出时,一般都会cancel ctx然后调用Close方法,这个地方就不会等待任何的超时,就打印"Program quit"然后返回了。解决方案是用另外一个context。但是如何处理之前的ctx的done呢?可以再起一个goroutine做同步:


in := make(chan []byte)

func Close(ctx context.Context) (err error) {
ctxRead,cancelRead := context.WithCancel(context.Background())
go func(){ // sync ctx with ctxRead
select {
case <-ctxRead.Done():
case <-ctx.Done():
select {
case <-ctxRead.Done():
case <-time.After(600*time.Milliseconds):
cancelRead()
}
}
}()

ctx = ctxRead // 下面直接用ctxRead。
timeous := ... // 1s,3s,6s,9s...
for _, to := range timeouts {
// 发送给对方WriteToUDP("CLOSE", peer)
// 另外一个goroutine读取UDP包到in

select {
case <- time.After(to):
case <- in:
fmt.Println("Close ok")
return
case <- ctx.Done():
fmt.Println("Program quit")
return
}
}
return
}

这样在主要的逻辑中,还是只需要处理ctx,但是这个ctx已经是新的context了。不过在实际的过程中,这个sync的goroutine需要确定起来后,才能继续,否则会造成执行顺序不确定:


sc := make(chan bool, 1)
go func(){ // sync ctx with ctxRead
sc <- true
select {
......
}
<- sc

使用context,来控制多个goroutine的执行和取消,是非常好用的,关键可以完全关注业务的逻辑,而不会引入因为ctx取消或者超时机制而造成的特殊逻辑。

GOLANG实现超时对象检测的最好理解的方式

技术讨论winlin 发表了文章 • 0 个评论 • 151 次浏览 • 2017-05-16 17:39 • 来自相关话题

依赖于心跳的系统,都需要超时检测。比如P2P系统中客户端每隔120秒向数据服务器发送一次数据汇总,服务器就需要维护一个超时时间。比如一个UDP服务器,在和客户端之间创建Session之后,如果没有数据包,一般会有Ping包,说明这个Session是存活的... 查看全部

依赖于心跳的系统,都需要超时检测。比如P2P系统中客户端每隔120秒向数据服务器发送一次数据汇总,服务器就需要维护一个超时时间。比如一个UDP服务器,在和客户端之间创建Session之后,如果没有数据包,一般会有Ping包,说明这个Session是存活的,服务器在发现Session超时后也需要清理。


首先,服务器一般需要维护一个列表,以Peer为例:


type Peer struct {
id uint64
heartbeat time.Time
}

type Server struct {
peers map[uint64]*Peer
lock sync.Mutex
}

创建Peer,同时在收到Ping消息后,更新Peer的心跳时间:


func (v *Server) Create(id uint64) *Peer {
v.lock.Lock()
defer v.lock.UnLock()

p = &Peer { id:id, heartbeat: time.Now(), }
v.peers[id] = p
return p
}

func (v *Server) OnPing(id uint64) {
v.lock.Lock()
defer v.lock.UnLock()

if p,ok := v.peers[id]; ok {
p.heatbeat = time.Now()
}
}

当然,需要起一个goroutine定期扫描这个列表, 假设300秒超时:


go func(v *Server) {
for {
func(){
v.lock.Lock()
defer v.lock.UnLock()

now := time.Now()
for id,p := range v.peers {
if p.heartbeat.Add(300 * time.Second).Before(now) {
delete(v.peers, id)
}
}
}()
time.Sleep(30 * time.Second)
}
}(server)

如果Peers的数目非常多,那么扫描时每次都需要锁定v.peers,会导致其他的业务都无法进行。特别是清理Peer这个过程如果比较复杂,譬如需要发起io请求,是一个费时的操作时,就会造成系统的等待。


一般来说,超时的Peer不会很多,因此可以用chan放一个超时的peer,每个peer专门起一个goroutine来看什么时候超时,这样就可以在检测超时时避免用锁了:


timeout := make(chan *Peer)

func (v *Server) Create(id uint64) *Peer {
v.lock.Lock()
defer v.lock.UnLock()

p = &Peer { id:id, heartbeat: time.Now(), }
v.peers[id] = p
return p

go func(p *Peer) {
for {
tm := p.heartbeat
<- time.After(300 * time.Second)
if tm.Equal(p.heartbeat) {
timeout <- p
break
}
}
}(p)
}

go func(v *Server){
for gw := range timeout {
func(){
lgateways.Lock()
defer lgateways.Unlock()

delete(gateways, gw.port)
}()

// Do something cleanup about the gateway.
}
}(server)

这样就只有在有Peer超时时,才真正锁住Server.peers

GOLANG接口适配,组合方式的灵活接口演化

技术讨论winlin 发表了文章 • 0 个评论 • 187 次浏览 • 2017-05-15 20:49 • 来自相关话题

在OO(Object Oriented)原则中,有一条叫做:优先使用组合,而不是继承。虽然GOLANG并不是OO的语言(没有继承和多态),但是不妨碍GOLANG使用这条原则,而GOLANG的作者就强调过这一点,在GOLANG中是使用组合而非继承来扩展。<... 查看全部

在OO(Object Oriented)原则中,有一条叫做:优先使用组合,而不是继承。虽然GOLANG并不是OO的语言(没有继承和多态),但是不妨碍GOLANG使用这条原则,而GOLANG的作者就强调过这一点,在GOLANG中是使用组合而非继承来扩展。


装逼的说来,继承是一种名词化的语言体系,先进行业务抽象然后设计类体系和继承关系。而组合,强制使用接口,因为组合中使用的总是另外一个对象的接口,通过动词的组合,实现目标,比如不管是什么只要有Write([]byte)(int,error)这个动作,就实现了这个接口,其他对象组合这个接口后,对外也看起来就是个io.Writer的接口。


比如,GOALNG1.8支持了writev,一般在面向对象会这么的搞:


class Socket {
int Write(void*, int);
int Writev(const iovec*, int);
};

对的吧?一个Socket可以写数据,也可以用writev写iovec向量,就是一次性写入多个内存块。



Note: 有时候内存块是不连续的,比如一个Video帧,发送给不同的客户端时,Header是需要修改的,但是Payload都一样,那么可以针对每个客户端只创建一个header,然后公用payload,但是这时候两个内存指针是不连续的,特别是需要同时写入多个视频帧时,writev就很神奇的避免了内存拷贝writev(header+payload),具体参考下writev的资料哈。



这样有个问题,并非所有系统都支持Writev的,并非所有Socket都支持Writev的,如果是自己写个代码,当然是可以随便这么搞的,但是作为标准库,GOLANG当然是不能这么做的。GOLANG就加了一个接口(一个新动作)叫做net.buffersWriter,如果实现了这个接口就用writev。先看用法:


    conn,err := net.Dial("tcp", "127.0.0.1:1935")

buffers := Buffers{
[]byte("once upon a time in "),
[]byte("Gopherland ... "),
}

buffers.WriteTo(conn)

在Buffers的WriteTo方法会判断是否是writev的接口,如果是则用writev写,否则就一个个的写:


func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) {
if wv, ok := w.(buffersWriter); ok {
return wv.writeBuffers(v)
}

实际上conn是net.TcpConn,里面有个fd *net.netFD,它实现了net.buffersWriter接口,所以最后调用的就是(fd *netFD) writeBuffers(v *Buffers)


func (c *conn) writeBuffers(v *Buffers) (int64, error) {
n, err := c.fd.writeBuffers(v)

func (fd *netFD) writeBuffers(v *Buffers) (n int64, err error) {
iovecs = append(iovecs, syscall.Iovec{Base: &chunk[0]})
wrote, _, e0 := syscall.Syscall(syscall.SYS_WRITEV,
uintptr(fd.sysfd),
uintptr(unsafe.Pointer(&iovecs[0])),
uintptr(len(iovecs)))

对于其他没有实现这个接口的对象,就每个向量循环的写。


在看一个例子http.Get(url string),客户端发起一个HTTP请求:


http.Get("http://localhost:1985/api/v1/versions")
// 实际上调用的是:
func (c *Client) Get(url string)
// 然后调用:
(c *Client) Do(req *Request)

在GOLANG1.7中引入了context的概念,用来支持cancel,怎么用的:


ctx,cancel := context.WithCancel(context.Background())

select {
case <- ctx.Done():
// Cancelled.
case <- time.After(...):
// Timeout
case <- other events:
// Other events.
}

如何支持取消的HTTP请求呢?给http.Get加个ctx参数?例如http.Get(ctx, url)这样?那改动得多大啊,而且还不能兼容之前的API,泪奔~看看GOLANG的解决:


ctx,cancel := context.WithCancel(context.Background())
go func(){
req,err := http.NewRequest("http://...")
res,err := http.DefaultClient.Do(req.WithContext(ctx))
defer res.Body.Close()
// 读取res响应结果。
}()

select {
case <- ctx.Done():
case <- time.After(3 * time.Second):
cancel() // Timeout to cancel all requests.
}

使用组合,通过req.WithContext再返回一个*http.Request,实现同样的目的。

GOLANG使用简单类型,在协议解析的妙用

技术讨论winlin 发表了文章 • 0 个评论 • 654 次浏览 • 2017-05-11 15:41 • 来自相关话题

在协议解析中,经常需要用到转换不同的含义,比如声音的采样率,在FLV中定义和AAC中定义是不同的。在FLV中只有4中采样率5512, 11025, 22050, 44100。而在AAC中有16种采样率96000, 8... 查看全部

在协议解析中,经常需要用到转换不同的含义,比如声音的采样率,在FLV中定义和AAC中定义是不同的。在FLV中只有4中采样率5512, 11025, 22050, 44100。而在AAC中有16种采样率96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350(还有4个是保留的)。也就是说,1在FLV中标识11025Hz,而在AAC中表示的是88200Hz。如何实现这个转换呢?


C++当然先得定义枚举:


enum SrsAudioSampleRate
{
SrsAudioSampleRate5512 = 0,
SrsAudioSampleRate11025,
SrsAudioSampleRate22050,
SrsAudioSampleRate44100,
SrsAudioSampleRateForbidden,
};

C++当然是用函数了:


SrsAudioSampleRate aac_to_flv(int v) {
if (v >= 0 && v <=5) {
return SrsAudioSampleRate44100;
} else if (v >=6 && v <= 8) {
return SrsAudioSampleRate22050;
} else if (v >= 9 && v <= 11) {
return SrsAudioSampleRate11025;
} else if (v == 12) {
return SrsAudioSampleRate5512;
} else {
return SrsAudioSampleRateForbidden;
}
}

看起来还是挺简单的。慢着,还有的时候需要打印出采样率来,所以还得搞个函数:


string srs_audio_sample_rate2str(SrsAudioSampleRate v)
{
switch (v) {
case SrsAudioSampleRate5512: return "5512";
case SrsAudioSampleRate11025: return "11025";
case SrsAudioSampleRate22050: return "22050";
case SrsAudioSampleRate44100: return "44100";
default: return "Forbidden";
}
}

拿到一个AAC的采样率,然后转换成FLV的,并打印出来,是这么使用的:


// 从文件或者流中读取出AAC的采样率的值。
int samplingFrequencyIndex = ...;
// 转换成FLV的采样率。
SrsAudioSampleRate sampleRate = aac_to_flv(samplingFrequencyIndex);
// 转换成字符串格式。
string sSampleRate = srs_audio_sample_rate2str(sampleRate);
// 打印采样率。
printf("SampleRate=%d/%sHz\n", sampleRate, sSampleRate);

有什么麻烦的呢?



  1. 函数和类型之间没有关系,每次使用的时候都得去翻手册啊翻手册。

  2. 如果定义成一个struct,那转换的时候又太麻烦了。


还能不能愉快的玩耍呢?用GOLANG吧!先看用法:


var sampleRate AudioSamplingRate
sampleRate.From(samplingFrequencyIndex)
fmt.Printf("SampleRate=%d/%v\n", sampleRate, sampleRate)

就是这么简单(此处应该有掌声)~


其实实现起来也非常自然:


type AudioSamplingRate uint8

const (
AudioSamplingRate5kHz AudioSamplingRate = iota // 0 = 5.5 kHz
AudioSamplingRate11kHz // 1 = 11 kHz
AudioSamplingRate22kHz // 2 = 22 kHz
AudioSamplingRate44kHz // 3 = 44 kHz
AudioSamplingRateForbidden
)

func (v AudioSamplingRate) String() string {
switch v {
case AudioSamplingRate5kHz:
return "5.5kHz"
case AudioSamplingRate11kHz:
return "11kHz"
case AudioSamplingRate22kHz:
return "22kHz"
case AudioSamplingRate44kHz:
return "44kHz"
default:
return "Forbidden"
}
}

func (v *AudioSamplingRate) From(a int) {
switch a {
case 0, 1, 2, 3, 4, 5:
*v = AudioSamplingRate44kHz
case 6, 7, 8:
*v = AudioSamplingRate22kHz
case 9, 10, 11:
*v = AudioSamplingRate11kHz
case 12:
*v = AudioSamplingRate5kHz
default:
*v = AudioSamplingRateForbidden
}
}


Remark: 代码参考go-oryx-lib flv.



有几个地方非常不同:



  1. 虽然GOLANG只是在uint8上面加了函数,但是使用起来方便很多了,以前在C++中用这两个枚举,每次都要跳到枚举的定义来看对应的函数是什么。

  2. GOLANG的switch比较强大,可以case好几个值,和C++的if有点想,但是GOLANG的case更直观,知道这几个值会被转换成另外的值,而if读起来像是将一个范围的值转换,不好懂。

  3. GOLANG的枚举使用const实现,也可以带类型,而且有个iota很强大,特别是在定义那些移位的枚举时就很好用。


好吧,这只是几个小的改进,虽然用起来很方便。来看看在AMF0中基本类型的妙用,AMF0是一种传输格式,和JSON很像,不过JSON是文本的,而AMF0是字节的,都是用来在网络中传输对象的。因此,AMF0定义了几个基本的类型:String, Number, Boolean, Object,其中Object的属性定义为String的属性名和值,值可以是其他的类型。


先看看C++的实现,首先定义一个AMF0Any对象,可以转换成具体的String或者Object等对象:


class SrsAmf0Any {
// 提供转换的函数,获取实际的值。
virtual std::string to_str();
virtual bool to_boolean();
virtual double to_number();
virtual SrsAmf0Object* to_object();
// 当然还得提供判断的函数,得知道是什么类型才能转。
virtual bool is_string();
virtual bool is_boolean();
virtual bool is_number();
virtual bool is_object();
// 提供创建基本类型的函数。
static SrsAmf0Any* str(const char* value = NULL);
static SrsAmf0Any* boolean(bool value = false);
static SrsAmf0Any* number(double value = 0.0);
static SrsAmf0Object* object();
};

在实现时,String和Number等基本类型可以隐藏起来(在cpp中实现):


namespace _srs_internal {
class SrsAmf0String : public SrsAmf0Any {
public:
std::string value;
// 当然它必须实现编码和解码的函数。
virtual int total_size();
virtual int read(SrsBuffer* stream);
virtual int write(SrsBuffer* stream);
};
}

AMF0Object当然得暴露出来的:


class SrsAmf0Object : public SrsAmf0Any {
public:
virtual int total_size();
virtual int read(SrsBuffer* stream);
virtual int write(SrsBuffer* stream);
// 提供设置和读取属性的方法。
virtual void set(std::string key, SrsAmf0Any* value);
virtual SrsAmf0Any* get_property(std::string name);
};

用起来是这样:


// 设置Object的属性,并发送给服务器。
SrsConnectAppPacket* pkt = NULL;
pkt->command_object->set("app", SrsAmf0Any::str(app.c_str()));
pkt->command_object->set("tcUrl", SrsAmf0Any::str(tcUrl.c_str()));

// 读取服务器的响应,取出服务器的IP等信息。
SrsConnectAppResPacket* pkt = NULL;
SrsAmf0Any* data = pkt->info->get_property("data");
if (si && data && data->is_object()) {
SrsAmf0Object* obj = data->to_objet();

SrsAmf0Any* prop = obj->get_property("srs_server_ip");
if (prop && prop->is_string()) {
printf("Server IP: %s\n", prop->to_str().c_str());
}

prop = obj->get_property("srs_pid");
if (prop && prop->is_number()) {
printf("Server PID: %d\n, prop->to_number());
}
}

看起来巨繁琐吧?快用GOLANG,如果换成GOLANG,可以用基本类型定义AMF0的基本类型,这样使用起来是这样:


pkt := or.NewConnectAppPacket()
pkt.CommandObject.Set("tcUrl", amf0.NewString(tcUrl))
pkt.CommandObject.Set("app", amf0.NewString(app))

var res *or.ConnectAppResPacket
if data, ok := res.Args.Get("data").(*amf0.Object); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
fmt.Printf("Server IP: %s\n", string(*data))
}
if data, ok := data.Get("srs_pid").(*amf0.Number); ok {
fmt.Printf("Server PID: %d\n, int(*data))
}
}

区别在于:



  1. C++由于不能在基本类型上定义方法,导致必须创建struct或者class类型,有比较繁琐的类型转换和判断。

  2. GOLANG的类型判断,提供了ok的方式,一句话就能把类型转换弄好,而且接口和实现struct的对象可以重用变量名。

  3. 不必加很多类型判断,没有多余的变量,干净利索,需要维护的信息比较少。


实现起来更舒服,基本类型不用定义struct:


type String string
func (v *String) Size() int {}
func (v *String) UnmarshalBinary(data []byte) (err error) {}
func (v *String) MarshalBinary() (data []byte, err error) {}

type Object struct {}
func (v *Object) Size() int {}
func (v *Object) UnmarshalBinary(data []byte) (err error) {}
func (v *Object) MarshalBinary() (data []byte, err error) {}


Remark:代码参考go-oryx-lib amf0.



更神奇的是,因为Object、EcmaArray和StrictArray都是类似的结构,但是有些细微的差异,因此使用GOLANG的结构体嵌套可以很直接的解决问题:


type Object struct {
objectBase
eof objectEOF
}
type EcmaArray struct {
objectBase
count uint32
eof objectEOF
}
type StrictArray struct {
objectBase
count uint32
}

可以对比下SRS的实现,C++可以采用继承,而GOLANG直接组合那些基本的单元。


爱生活,爱够浪(此处可以响起掌声了)~

protobuf的一个问题

技术讨论lrita 回复了问题 • 1 人关注 • 1 个回复 • 171 次浏览 • 2017-05-11 11:30 • 来自相关话题

关于统一格式 json 返回 map 的 value 定义

技术讨论PureWhite 回复了问题 • 5 人关注 • 4 个回复 • 194 次浏览 • 2017-05-10 23:40 • 来自相关话题

GOLANG将类型作为参数,用反射设置指针的指针,实现类似模板功能

技术讨论winlin 发表了文章 • 0 个评论 • 161 次浏览 • 2017-05-09 20:13 • 来自相关话题

在协议解析中,C++的模板有比较大的作用,有时候我们希望丢弃所有的包,只留下特定类型的包。参考SRS的代码查看全部

在协议解析中,C++的模板有比较大的作用,有时候我们希望丢弃所有的包,只留下特定类型的包。参考SRS的代码SrsRtmpClient::connect_app2


类型系统的设计, SrsConnectAppResPacket继承自SrsPacket


class SrsPacket;
class SrsConnectAppResPacket : public SrsPacket

协议栈提供了expect_message模板函数,接收特定类型的包:


SrsCommonMessage* msg = NULL;
SrsConnectAppResPacket* pkt = NULL;
if ((ret = protocol.expect_message<SrsConnectAppResPacket>(&msg, &pkt)) != ERROR_SUCCESS) {
return ret;
}

SrsAmf0Any* data = pkt->info->get_property("data");
SrsAmf0EcmaArray* arr = data->to_ecma_array();
SrsAmf0Any* prop = arr->ensure_property_string("srs_server_ip");
string srs_server_ip = prop->to_str();

在向服务器发送了ConnectApp后,就等待ConnectAppRes响应包,丢弃所有的其他的。这个时候,类型SrsConnectAppResPacket就作为了一个参数,也就是C++的模板。如果是GOLANG怎么实现呢?没有直接的办法的,因为没有泛型。


在GOLANG中,也需要定义个interface,参考Packet,当然也是有ConnectAppResPacket实现了这个接口(Message是原始消息,它的Payload可以Unmarshal为Packet):


type Message struct { Payload []byte }
type Packet interface {} // Message.Payload = Packet.Marshal()
type ConnectAppResPacket struct { Args amf0.Amf0 }

第一种方法,协议栈只需要收取Message,然后解析Message为Packet,收到packet后使用类型转换,判断不是自己需要的包就丢弃:


func (v *Protocol) ReadMessage() (m *Message, err error)
func (v *Protocol) DecodeMessage(m *Message) (pkt Packet, err error)

不过这两个基础的API,User在使用时,比较麻烦些,每次都得写一个for循环:


var protocol *Protocol

for {
var m *Message
m,_ = protocol.ReadMessage()

var p Packet
p,_ = protocol.DecodeMessage(m)

if res,ok := p.(*ConnectAppResPacket); ok {
if data, ok := res.Args.Get("data").(*amf0.EcmaArray); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
srs_server_ip = string(*data)
}
}
}
}

比较方便的做法,就是用回调函数,协议栈需要提供个ExpectPacket方法:


func (v *Protocol) ExpectPacket(filter func(m *Message, p Packet)(ok bool)) (err error)

这样可以利用回调函数可以访问上面函数的作用域,直接转换类型和设置目标类型的包:


var protocol *Protocol

var res *ConnectAppResPacket
_ = protocol.ExpectPacket(func(m *Message, p Packet) (ok bool){
res,ok = p.(*ConnectAppResPacket)
})

if data, ok := res.Args.Get("data").(*amf0.EcmaArray); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
srs_server_ip = string(*data)
}
}

这样已经比较方便了,不过还是需要每次都给个回调函数。要是能直接这样用就好了:


var protocol *Protocol

var res *ConnectAppResPacket
_ = protocol.ExpectPacket(&res)

if data, ok := res.Args.Get("data").(*amf0.EcmaArray); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
srs_server_ip = string(*data)
}
}

这样也是可以做到的,不过协议栈函数要定义为:


func (v *Protocol) ExpectPacket(ppkt interface{}) (err error)

在函数内部,使用reflect判断类型是否符合要求,设置返回值。代码参考ExpectPacket,下面是一个简要说明:


func (v *Protocol) ExpectPacket(ppkt interface{}) (m *Message, err error) {
// 由于ppkt是**ptr, 所以取类型后取Elem(),就是*ptr,用来判断是否实现了Packet接口。
ppktt := reflect.TypeOf(ppkt).Elem()
// ppktv是发现匹配的包后,设置值的。
ppktv := reflect.ValueOf(ppkt)

// 要求参数必须是实现了Packet,避免传递错误的值进来。
if required := reflect.TypeOf((*Packet)(nil)).Elem(); !ppktt.Implements(required) {
return nil,fmt.Errorf("Type mismatch")
}

for {
m, err = v.ReadMessage()
pkt, err = v.DecodeMessage(m)

// 判断包是否是匹配的那个类型,如果不是就丢弃这个包。
if pktt = reflect.TypeOf(pkt); !pktt.AssignableTo(ppktt) {
continue
}

// 相当于 *ppkt = pkt,类似C++中对指针的指针赋值。
ppktv.Elem().Set(reflect.ValueOf(pkt))
break
}
return
}

遗憾的就是这个参数ppkt类型不能是Packet,因为会有类型不匹配;也不能是*Packet,因为在GOLANG中传递接口的指针也是不可以的,会导致类型错误(**ConnectAppResPacket并不能匹配*Packet);这个参数只能是interface{}。不过用法也很简单,只是需要注意参数的传递。


var res *ConnectAppResPacket
// 这是正确的做法,传递res指针的地址,相当于指针的指针。
_ = protocol.ExpectPacket(&res)
// 这是错误的做法,会在ExpectPacket检查返回错误,没有实现Packet接口
_ = protocol.ExpectPacket(res)

用起来还不错。

大家都用什么web框架

技术讨论liucc 回复了问题 • 16 人关注 • 13 个回复 • 1113 次浏览 • 2017-05-09 17:58 • 来自相关话题

如何拆分这类命令行参数?

技术讨论dcb9 回复了问题 • 2 人关注 • 2 个回复 • 191 次浏览 • 2017-05-09 08:20 • 来自相关话题

条新动态, 点击查看
bigwhite

bigwhite 回答了问题 • 2016-10-11 13:03 • 8 个回复 不感兴趣

怎么学习golang?

赞同来自:

记得早期接触go时,看的是Golang之父之一的Rob Pike的Go course 3部曲(3个ppt),如果你没有,可以到[这里](https://pan.baidu.com/s/1miFEDJy)下载(注意:由于rob pike的这个ppt在go 1之前... 显示全部 »
记得早期接触go时,看的是Golang之父之一的Rob Pike的Go course 3部曲(3个ppt),如果你没有,可以到[这里](https://pan.baidu.com/s/1miFEDJy)下载(注意:由于rob pike的这个ppt在go 1之前发布的,ppt上的有些语法与go 1有少许差别,注意识别)。

要想深入了解Go,写出idiomatic的golang代码,官方doc:effective go, go faq,以及go language specification也是不可或缺的。

之后再系统的学习后的资料,建议看k&a写的go圣经-the go programming language、
《Go in action》 以及国内雨痕大师的《go语言学习笔记》(尤其是代码分析的部分),学习笔记一书适合稍微有些go经验,且抱着极大热情挖掘go runtime背后的原理的人去学习。

剩下的就是不断的去用go coding, coding, coding了,无捷径。
astaxie

astaxie 回答了问题 • 2016-10-11 21:41 • 9 个回复 不感兴趣

golang 的channel是否适合做消息队列?

赞同来自:

我猜你看到的文章的担心是万一程序挂了怎么办,在缓冲channel里面的数据就可能丢失了,如果这个是可以忍受的话其实是非常适合做消息队列的
我猜你看到的文章的担心是万一程序挂了怎么办,在缓冲channel里面的数据就可能丢失了,如果这个是可以忍受的话其实是非常适合做消息队列的
name5566

name5566 回答了问题 • 2016-10-12 11:36 • 11 个回复 不感兴趣

golang有没有好的开源游戏框架

赞同来自:

> 使用 Leaf 已知的上线项目:
> * 2014 年,某手游(棋牌)项目上线
> * 2016 年,某 H5 手游项目上线
> * 2016 年,某卡牌手游项目上线
> 正在研发项目 N 个,已知情况 N >= 4

... 显示全部 »
> 使用 Leaf 已知的上线项目:
> * 2014 年,某手游(棋牌)项目上线
> * 2016 年,某 H5 手游项目上线
> * 2016 年,某卡牌手游项目上线
> 正在研发项目 N 个,已知情况 N >= 4

来自:https://github.com/name5566/leaf/wiki
leoliu

leoliu 回答了问题 • 2016-10-12 13:44 • 22 个回复 不感兴趣

求一些golang的教程,书籍也可以

赞同来自:

《The Golang Programming Language》
《Golang 学习笔记》
《The Golang Programming Language》
《Golang 学习笔记》
yougg

yougg 回答了问题 • 2016-10-14 10:01 • 78 个回复 不感兴趣

大家说说看都用啥写Go

赞同来自:

# IDEA大法好
# 天灭vscode 退软保平安
# 人在做,天在看 中文乱码留祸患
# 界面卡顿天地灭 赶紧卸载保平安
# 诚心诚念IDEA好 JetBrains大法平安保
# 众生皆为IDEA来 现世险恶忘前缘
# 开源为你说真相 教你脱险莫拒绝
# ... 显示全部 »
# IDEA大法好
# 天灭vscode 退软保平安
# 人在做,天在看 中文乱码留祸患
# 界面卡顿天地灭 赶紧卸载保平安
# 诚心诚念IDEA好 JetBrains大法平安保
# 众生皆为IDEA来 现世险恶忘前缘
# 开源为你说真相 教你脱险莫拒绝
# 早日不做软粉,早日获得新生
# 上网搜索“九评纳德拉”
# 有 真 相
sryan

sryan 回答了问题 • 2016-10-13 11:44 • 7 个回复 不感兴趣

golang 如何动态创建struct

赞同来自:

静态语言貌似不能直接实现
可以自己实现个map[string]func(string)interface{}
将要动态生成的结构体的函数注册上去
通过string来调用相应的函数来获取对应的结构体
静态语言貌似不能直接实现
可以自己实现个map[string]func(string)interface{}
将要动态生成的结构体的函数注册上去
通过string来调用相应的函数来获取对应的结构体
astaxie

astaxie 回答了问题 • 2016-10-13 22:04 • 12 个回复 不感兴趣

想用golang写个分布式的监控,大神给点建议

赞同来自:

这个问题很有意思,很多场景设计都会来考虑拉和推两种方案,我分别对拉和推两种的优缺点对比以下,你自己权衡一下,欢迎大家继续补充

## 拉的方案(不写agent)
优点:
- 不需要agent,不需要再部署新的程序

缺点:
- 网络中断的情况下,就无法监控机器... 显示全部 »
这个问题很有意思,很多场景设计都会来考虑拉和推两种方案,我分别对拉和推两种的优缺点对比以下,你自己权衡一下,欢迎大家继续补充

## 拉的方案(不写agent)
优点:
- 不需要agent,不需要再部署新的程序

缺点:
- 网络中断的情况下,就无法监控机器的信息

## 推的方案(agent)
优点:
- 本地运行,在和中控机失去网络连接的时候还是可以继续保存监控数据

缺点:
- 需要部署agent,如果机器多得话将来升级也是比较麻烦

拉取和推送其实大家可以考虑,微博的follow逻辑,直播流里面也有同样的问题,很多场景都会遇到

至于说第二种方案走什么协议,这种程序我建议走tcp协议,HTTP的话相对重了一点。
sheepbao

sheepbao 回答了问题 • 2016-10-30 20:16 • 15 个回复 不感兴趣

字符串连接哪一种方式最高效

赞同来自:

```go
package main

import (
"bytes"
"fmt"
"strings"
"time"
)

var way map[int]string
... 显示全部 »
```go
package main

import (
"bytes"
"fmt"
"strings"
"time"
)

var way map[int]string

func benchmarkStringFunction(n int, index int) (d time.Duration) {
v := "ni shuo wo shi bu shi tai wu liao le a?"
var s string
var buf bytes.Buffer

t0 := time.Now()
for i := 0; i < n; i++ {
switch index {
case 0: // fmt.Sprintf
s = fmt.Sprintf("%s[%s]", s, v)
case 1: // string +
s = s + "[" + v + "]"
case 2: // strings.Join
s = strings.Join([]string{s, "[", v, "]"}, "")
case 3: // stable bytes.Buffer
buf.WriteString("[")
buf.WriteString(v)
buf.WriteString("]")
}

}
d = time.Since(t0)
if index == 3 {
s = buf.String()
}
fmt.Printf("string len: %d\t", len(s))
fmt.Printf("time of [%s]=\t %v\n", way[index], d)
return d
}

func main() {
way = make(map[int]string, 5)
way[0] = "fmt.Sprintf"
way[1] = "+"
way[2] = "strings.Join"
way[3] = "bytes.Buffer"

k := 4
d := [5]time.Duration{}
for i := 0; i < k; i++ {
d[i] = benchmarkStringFunction(10000, i)
}
}

```
结果:
```
string len: 410000 time of [fmt.Sprintf]= 426.001476ms
string len: 410000 time of [+]= 307.044147ms
string len: 410000 time of [strings.Join]= 738.44362ms
string len: 410000 time of [bytes.Buffer]= 742.248µs
```
* strings.Join 最慢
* fmt.Sprintf 和 string + 差不多
* bytes.Buffer又比上者快约500倍
CodyGuo

CodyGuo 回答了问题 • 2016-10-30 12:57 • 4 个回复 不感兴趣

Go里面如何写多行的字符串吗?

赞同来自:

```go
`line 1
line 2
line 3`
```
```go
`line 1
line 2
line 3`
```
philosophia14

philosophia14 回答了问题 • 2016-11-05 00:30 • 3 个回复 不感兴趣

新版本Go将会对database/sql进行大量改进

赞同来自:

但愿不要多改..
但愿不要多改..
zdreamx

zdreamx 回答了问题 • 2016-11-08 11:27 • 26 个回复 不感兴趣

beego1.8版本功能征集

赞同来自:

orm有没有考虑分表分区分库的优化或支持
orm有没有考虑分表分区分库的优化或支持
println 是把结果输出到 standard error

fmt.Println 是把结果输出到 standard output
println 是把结果输出到 standard error

fmt.Println 是把结果输出到 standard output

大家是如何处理 golang web 应用静态资源的?

技术讨论astaxie 回复了问题 • 4 人关注 • 1 个回复 • 756 次浏览 • 2016-10-14 13:08 • 来自相关话题

Python 程序员的 Golang 学习指南(II): 开发环境搭建

文章分享Cloudinsight 发表了文章 • 0 个评论 • 1055 次浏览 • 2016-10-12 15:44 • 来自相关话题

Authors: startover

Authors: startover





上一篇文章我们已经对 Golang 有了初步的了解,这篇主要介绍如何在 Ubuntu 14.04 上搭建 Golang 开发环境。


安装 Golang


这里就按照官方文档进行安装即可,如下:



  • 下载并解压安装包到指定目录


$ wget https://storage.googleapis.com ... ar.gz
$ tar -C /usr/local -xzf go1.6.3.linux-amd64.tar.gz


  • 设置 PATH


$ echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc
$ source ~/.bashrc


  • 验证安装


$ go version
go version go1.6.3 linux/amd64

环境变量设置


$ echo "export GOROOT=/usr/local/go" >> ~/.bashrc
$ echo "export GOPATH=$HOME/go" >> ~/.bashrc
$ source ~/.bashrc

其中,GOROOT 为 Golang 的安装目录,只有当 Golang 安装到除 /usr/local 之外的路径时需要设置,反之则不用设置,GOPATH 是 Golang 的开发目录,详细可参考官方文档


开发工具


工欲善其事,必先利其器,作为一名伪 VIMer,这里主要介绍下如何在 Vim 下配置 Golang 开发环境。


由于之前一直使用 k-vim 作为 Python 开发环境,而 k-vim 已经集成了当前使用最为广泛的用于搭建 Golang 开发环境的 vim 插件 vim-go,只是默认没有开启,需要我们手动进行相关设置。


k-vim 中开启 Golang 语言的支持,非常简单,如下:



  • 修改 ~/.vimrc.bundles(开启 golang 支持,并修改 vim-go 的默认配置,增加快捷键配置等)。


let g:bundle_groups=['python', 'javascript', 'markdown', 'html', 'css', 'tmux', 'beta', 'json', 'golang']

" vimgo {{{
let g:go_highlight_functions = 1
let g:go_highlight_methods = 1
let g:go_highlight_structs = 1
let g:go_highlight_operators = 1
let g:go_highlight_build_constraints = 1

let g:go_fmt_fail_silently = 1
let g:go_fmt_command = "goimports"
let g:syntastic_go_checkers = ['golint', 'govet', 'errcheck']

" vim-go custom mappings
au FileType go nmap <Leader>s <Plug>(go-implements)
au FileType go nmap <Leader>i <Plug>(go-info)
au FileType go nmap <Leader>gd <Plug>(go-doc)
au FileType go nmap <Leader>gv <Plug>(go-doc-vertical)
au FileType go nmap <leader>r <Plug>(go-run)
au FileType go nmap <leader>b <Plug>(go-build)
au FileType go nmap <leader>t <Plug>(go-test)
au FileType go nmap <leader>c <Plug>(go-coverage)
au FileType go nmap <Leader>ds <Plug>(go-def-split)
au FileType go nmap <Leader>dv <Plug>(go-def-vertical)
au FileType go nmap <Leader>dt <Plug>(go-def-tab)
au FileType go nmap <Leader>e <Plug>(go-rename)
au FileType go nnoremap <leader>gr :GoRun %<CR>
" }}}



  • 在 Vim 内执行 :PlugInstall,安装 vim-go




  • 在 Vim 内执行 :GoInstallBinaries,下载并安装 vim-go 依赖的二进制工具,goimportsgolint 等。



  • 安装 gotags,使 tagbar 配置生效。


$ go get -u github.com/jstemmer/gotags

我们来看一下最终效果:


Image of Golang Environment in Vim


编写第一个程序


进入工作目录,新建文件 hello.go,如下:


$ cd $GOPATH
$ vim hello.go
package main

import "fmt"

func main() {
fmt.Println("Hello, World!")
}

运行程序:


$ go run hello.go
Hello, World!



本文章为 Cloudinsight 技术团队工程师原创,更多技术文章可访问 Cloudinsight 技术博客Cloudinsight 为可视化系统监控工具,涵盖 Windows、Linux 操作系统,用 Golang 开发的 Cloudinsight Agent 正式开源了,欢迎 fork,Github:https://github.com/cloudinsight/cloudinsight-agent


golang-for-pythonistas 系列持续更新中,欢迎关注~

Python 程序员的 Golang 学习指南(I): Go 之初体验

文章分享Cloudinsight 发表了文章 • 3 个评论 • 985 次浏览 • 2016-10-12 15:27 • 来自相关话题

Authors: startover

Authors: startover





Go 语言简介


Go,又称 golang,是 Google 开发的一种静态强类型,编译型,并发型,并具有垃圾回收功能的编程语言。


Go 语言于2009年11月正式宣布推出,自2012年发布1.0,最新稳定版1.7。目前,Go的相关工具和生态已逐渐趋于完善,也不乏重量级项目,如 Docker, Kubernetes, Etcd, InfluxDB 等。


Go 语言能解决什么样的问题


同绝大多数通用型编程语言相比,Go 语言更多的是为了解决我们在构建大型服务器软件过程中所遇到的软件工程方面的问题而设计的。乍看上去,这么讲可能会让人感觉 Go 非常无趣且工业化,但实际上,在设计过程中就着重于清晰和简洁,以及较高的可组合性,最后得到的反而会是一门使用起来效率高而且很有趣的编程语言,很多程序员都会发现,它有极强的表达力而且功能非常强大。


总结为以下几点:



  • 清晰的依赖关系

  • 清晰的语法

  • 清晰的语义

  • 偏向组合而不是继承

  • 提供简单的编程模型(垃圾回收、并发)

  • 强大的内置工具(gofmt、godoc、gofix等)


建议有兴趣的同学看看 Go在谷歌:以软件工程为目的的语言设计


Go 语言相对 Python 有哪些优势


这里引用一段知乎上某大牛的回答,如下:




  • 部署简单。Go 编译生成的是一个静态可执行文件,除了 glibc 外没有其他外部依赖。这让部署变得异常方便:目标机器上只需要一个基础的系统和必要的管理、监控工具,完全不需要操心应用所需的各种包、库的依赖关系,大大减轻了维护的负担。这和 Python 有着巨大的区别。由于历史的原因,Python 的部署工具生态相当混乱【比如 setuptools, distutils, pip, buildout 的不同适用场合以及兼容性问题】。官方 PyPI 源又经常出问题,需要搭建私有镜像,而维护这个镜像又要花费不少时间和精力。




  • 并发性好。Goroutine 和 channel 使得编写高并发的服务端软件变得相当容易,很多情况下完全不需要考虑锁机制以及由此带来的各种问题。单个 Go 应用也能有效的利用多个 CPU 核,并行执行的性能好。这和 Python 也是天壤之比。多线程和多进程的服务端程序编写起来并不简单,而且由于全局锁 GIL 的原因,多线程的 Python 程序并不能有效利用多核,只能用多进程的方式部署;如果用标准库里的 multiprocessing 包又会对监控和管理造成不少的挑战【我们用的 supervisor 管理进程,对 fork 支持不好】。部署 Python 应用的时候通常是每个 CPU 核部署一个应用,这会造成不少资源的浪费,比如假设某个 Python 应用启动后需要占用 100MB 内存,而服务器有 32 个 CPU 核,那么留一个核给系统、运行 31 个应用副本就要浪费 3GB 的内存资源。




  • 良好的语言设计。从学术的角度讲 Go 语言其实非常平庸,不支持许多高级的语言特性;但从工程的角度讲,Go 的设计是非常优秀的:规范足够简单灵活,有其他语言基础的程序员都能迅速上手。更重要的是 Go 自带完善的工具链,大大提高了团队协作的一致性。比如 gofmt 自动排版 Go 代码,很大程度上杜绝了不同人写的代码排版风格不一致的问题。把编辑器配置成在编辑存档的时候自动运行 gofmt,这样在编写代码的时候可以随意摆放位置,存档的时候自动变成正确排版的代码。此外还有 gofix, govet 等非常有用的工具。



  • 执行性能好。虽然不如 C 和 Java,但通常比原生 Python 应用还是高一个数量级的,适合编写一些瓶颈业务。内存占用也非常省。


从个人对 Golang 的初步使用来说,体验还是相当不错的,但是也有下面几点需要注意:




  • 驼峰式命名风格(依据首字母大小写来决定其是否能被其他包引用),但我更喜欢 Python 的小写字母加下划线命名风格。




  • 没有好用的包管理器,Golang 官方也没有推荐最佳的包管理方案,目前公认的比较好用的有 Godeps, Govendor 及 Glide,而 Python 的包管理器 pip 已形成自己的一套标准。




  • 多行字符串的变量声明需要用反引号(`),Python 里是三个双引号("""),参考http://stackoverflow.com/questions/7933460/how-do-you-write-multiline-strings-in-go




  • Golang 中的类型匹配是很严格的,不同的类型之间通常需要手动转换,所以在字符串拼接时往往需要对整型进行显式转换,如 fmt.Println("num: " + strconv.Itoa(1))



  • Golang 语言语法里的语法糖并不多,如在 Python 中很流行的 map, reduce, range 等,在 Golang 里都没有得到支持。


另外,推荐阅读 Golang 新手开发者要注意的陷阱和常见错误


学习资料推荐


建议先把 Go 的官方文档过一遍,主要有以下几项:



官方文档看完后,基本也算入门了,这时候可以看看 Go 的示例代码,或者去 Project Euler 刷刷题。


当然也可以去知乎看看大牛们都是如何学习的,链接 https://www.zhihu.com/question/23486344


总结


虽然 Go 有很多被诟病的地方,比如 GC 和对错误的处理方式,但没有任何语言是完美的,从实用角度来讲,Go 有着不输于 Python 的开发效率,完善的第三方工具,以及强大的社区支持,这些就足够了。


相关链接:

https://golang.org/doc/

https://talks.golang.org/2012/splash.article

https://www.zhihu.com/question/21409296

https://www.zhihu.com/question/23486344

http://stackoverflow.com/questions/7933460/how-do-you-write-multiline-strings-in-go

http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/

http://www.oschina.net/translate/go-at-google-language-design-in-the-service-of-software-engineering




本文章为 Cloudinsight 技术团队工程师原创,更多技术文章可访问 Cloudinsight 技术博客Cloudinsight 为可视化系统监控工具,涵盖 Windows、Linux 操作系统,用 Golang 开发的 Cloudinsight Agent 正式开源了,欢迎 fork,Github:https://github.com/cloudinsight/cloudinsight-agent


golang-for-pythonistas 系列持续更新中,欢迎关注~

求一些golang的教程,书籍也可以

有问必答titian 回复了问题 • 34 人关注 • 22 个回复 • 2418 次浏览 • 2016-11-06 14:35 • 来自相关话题

golang有没有好的开源游戏框架

技术讨论chrislee 回复了问题 • 19 人关注 • 11 个回复 • 3146 次浏览 • 2017-03-25 09:59 • 来自相关话题

为什么Go里面大多数的接口返回的是int类型

有问必答negronihe 回复了问题 • 4 人关注 • 3 个回复 • 1333 次浏览 • 2016-10-16 13:06 • 来自相关话题

大家推荐哪种golang包管理方式?

有问必答luw2007 回复了问题 • 23 人关注 • 19 个回复 • 2819 次浏览 • 2017-04-19 11:41 • 来自相关话题

有人在用go-micro吗

回复

技术讨论chrislee 回复了问题 • 5 人关注 • 2 个回复 • 189 次浏览 • 1 天前 • 来自相关话题

Go能写桌面程序吗?比如winform 这样的

回复

有问必答codinghxl 回复了问题 • 14 人关注 • 10 个回复 • 1269 次浏览 • 2017-05-19 11:39 • 来自相关话题

大家说说看都用啥写Go

回复

技术讨论lmw 回复了问题 • 62 人关注 • 78 个回复 • 3892 次浏览 • 2017-05-19 00:31 • 来自相关话题

protobuf的一个问题

回复

技术讨论lrita 回复了问题 • 1 人关注 • 1 个回复 • 171 次浏览 • 2017-05-11 11:30 • 来自相关话题

关于统一格式 json 返回 map 的 value 定义

回复

技术讨论PureWhite 回复了问题 • 5 人关注 • 4 个回复 • 194 次浏览 • 2017-05-10 23:40 • 来自相关话题

大家都用什么web框架

回复

技术讨论liucc 回复了问题 • 16 人关注 • 13 个回复 • 1113 次浏览 • 2017-05-09 17:58 • 来自相关话题

如何拆分这类命令行参数?

回复

技术讨论dcb9 回复了问题 • 2 人关注 • 2 个回复 • 191 次浏览 • 2017-05-09 08:20 • 来自相关话题

如果给 web 应用 .exe 添加图标

回复

技术讨论lifei6671 回复了问题 • 3 人关注 • 1 个回复 • 197 次浏览 • 2017-05-08 20:22 • 来自相关话题

请问大家用什么来做配置管理

回复

有问必答xkey 回复了问题 • 5 人关注 • 2 个回复 • 283 次浏览 • 2017-05-08 10:19 • 来自相关话题

GoCN 每日新闻(2017-04-29)

回复

文章分享kennethgao 回复了问题 • 4 人关注 • 1 个回复 • 589 次浏览 • 2017-04-29 11:12 • 来自相关话题

GoCN 每日新闻(2017-04-28)

回复

文章分享astaxie 发起了问题 • 2 人关注 • 0 个回复 • 413 次浏览 • 2017-04-28 15:40 • 来自相关话题

GoCN 每日新闻(2017-04-27)

回复

文章分享aaronwu 回复了问题 • 7 人关注 • 1 个回复 • 740 次浏览 • 2017-04-27 16:03 • 来自相关话题

go 怎么处理一个未知异常??

回复

有问必答lifei6671 回复了问题 • 3 人关注 • 1 个回复 • 289 次浏览 • 2017-04-27 08:46 • 来自相关话题

寻 Go 开源运维监控系统

回复

有问必答t0n9 回复了问题 • 9 人关注 • 4 个回复 • 906 次浏览 • 2017-04-25 23:00 • 来自相关话题

GoCN 每日新闻(2017-04-25)

回复

文章分享astaxie 发起了问题 • 3 人关注 • 0 个回复 • 376 次浏览 • 2017-04-25 21:27 • 来自相关话题

GOLANG环境设置

技术讨论winlin 发表了文章 • 0 个评论 • 44 次浏览 • 1 天前 • 来自相关话题

原文:http://blog.csdn.net/win_lin/article/details/48265493


GO环境


官网下载GOLANG的安装:golang.org


如果不能翻墙,可以从golangtc或者gocn.io下载。


下载时,如何选择安装包:



  1. MAC,下载包含darwin的pkg或tar(推荐PKG),pkg可以直接安装但是卸载比较麻烦,tar需要自己设置PATH安装麻烦卸载比较方便,例如go1.8.1.darwin-amd64.pkggo1.8.1.darwin-amd64.tar.gz

  2. Windows,一般就是安装包了,设置GOPATH比较麻烦,根据自己的系统是32位还是64位下载,譬如go1.8.1.windows-amd64.msi或者go1.8.1.windows-386.msi

  3. Linux,一般都是64位的,而且需要手动解压和设置GOPATH,当然还有ARM的,可以选择自己需要的下载,譬如go1.8.1.linux-amd64.tar.gz


如果是安装包,双击就可以安装,可以跳过下面这步。如果是linux这种需要自己解压和设置PATH的,步骤如下:


# 先解压和移动目录,以1.8.1为例
tar xf go1.8.1.linux-amd64.tar.gz &&
sudo mkdir -p /usr/local/ &&
sudo mv go /usr/local/go

# 设置PATH,打开文件
sudo vi /etc/profile

# 在文件末尾输入内容,然后保存
export PATH=$PATH:/usr/local/go/bin

# 应用修改的配置
source /etc/profile

UNIX设置GOPATH,存放项目的路径,GOLANG的项目都是绝对路径,从这个目录开始搜索:


# 创建目录,一般都是$HOME下面的go目录,譬如:/home/winlin/go
mkdir -p $HOME/go

# 设置GOPATH,打开文件
sudo vi /etc/profile

# 在文件末尾输入内容,然后保存
export GOPATH=$HOME/go

# 应用修改的配置
source /etc/profile

这样就可以用go build等编译了。譬如执行命令go version


Mac winlin$ go version
go version go1.8.1 darwin/amd64

go get从GITHUB下载一个项目,可以用到自己的项目中,可以看到下载到了GOPATH中:


Mac winlin$ go get github.com/ossrs/go-oryx-lib

Mac winlin$ ls -lh $GOPATH/src/github.com/ossrs
drwxr-xr-x 22 winlin 748B May 25 09:43 go-oryx-lib

GO的环境就配置成功了。

GOLANG使用Context管理关联goroutine

技术讨论winlin 发表了文章 • 6 个评论 • 197 次浏览 • 2017-05-19 15:08 • 来自相关话题

一般一个业务很少不用到goroutine的,因为很多方法是需要等待的,例如http.Server.ListenAndServe这个就是等待的,除非关闭了Server或Listener,否则是不会返回的。除非是一个API服务器,否... 查看全部

一般一个业务很少不用到goroutine的,因为很多方法是需要等待的,例如http.Server.ListenAndServe这个就是等待的,除非关闭了Server或Listener,否则是不会返回的。除非是一个API服务器,否则肯定需要另外起goroutine发起其他的服务,而且对于API服务器来说,在http.Handler的处理函数中一般也需要起goroutine,如何管理这些goroutine,在GOLANG1.7提供context.Context


先看一个简单的,如果启动两个goroutine,一个是HTTP,还有个信号处理的收到退出信号做清理:


wg := sync.WaitGroup{}
defer wg.Wait()

wg.Add(1)
go func() {
defer wg.Done()

ss := make(os.Signal, 0)
signal.Notify(ss, syscall.SIGINT, syscall.SIGTERM)
for s := ss {
fmt.Println("Got signal", s)
break
}
}()

wg.Add(1)
go func() {
defer wg.Done()

svr := &http.Server{ Addr:":8080", Handler:nil, }
fmt.Println(svr.ListenAndServe())
}

很清楚,起了两个goroutine,然后用WaitGroup等待它们退出。如果它们之间没有交互,不互相影响,那真的是蛮简单的,可惜这样是不行的,因为信号的goroutine收到退出信号后,应该通知server退出。暴力一点的是直接调用svr.Close(),但是如果有些请求还需要取消怎么办呢?最好用Context了:


wg := sync.WaitGroup{}
defer wg.Wait()

ctx,cancel := context.WithCancel(context.Background())

wg.Add(1)
go func() {
defer wg.Done()

ss := make(chan os.Signal, 0)
signal.Notify(ss, syscall.SIGINT, syscall.SIGTERM)
select {
case <- ctx.Done():
return
case s := <- ss:
fmt.Println("Got signal", s)
cancel() // 取消请求,通知用到ctx的所有goroutine
return
}
}()

wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

svr := &http.Server{ Addr:":8080", Handler:nil, }

go func(){
select {
case <- ctx.Done():
svr.Close()
}
}

fmt.Println(svr.ListenAndServe())
}

这个方式可以在新开goroutine时继续使用,譬如新加一个goroutine,里面读写了UDPConn:


wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

var conn *net.UDPConn
if conn,err = net.Dial("udp", "127.0.0.1:1935"); err != nil {
fmt.Println("Dial UDP server failed, err is", err)
return
}

fmt.Println(UDPRead(ctx, conn))
}()

UDPRead = func(ctx context.Context, conn *net.UDPConn) (err error) {
wg := sync.WaitGroup{}
defer wg.Wait()

ctx, cancel := context.WithCancel(ctx)

wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

for {
b := make([]byte, core.MTUSize)
size, _, err := conn.ReadFromUDP(b)
// 处理UDP包 b[:size]
}
}()

select {
case <-ctx.Done():
conn.Close()
}
return
}

如果只是用到HTTP Server,可以这么写:


func run(ctx contex.Context) {
server := &http.Server{Addr: addr, Handler: nil}
go func() {
select {
case <-ctx.Done():
server.Close()
}
}()

http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
})

fmt.Println(server.ListenAndServe())
}

如果需要提供一个API来让服务器退出,可以这么写:


func run(ctx contex.Context) {
server := &http.Server{Addr: addr, Handler: nil}

ctx, cancel := context.WithCancel(ctx)
http.HandleFunc("/quit", func(w http.ResponseWriter, r *http.Request) {
cancel() // 使用局部的ctx和cancel
})

go func() {
select {
case <-ctx.Done():
server.Close()
}
}()

fmt.Println(server.ListenAndServe())
}

使用局部的ctx和cancel,可以避免cancel传入的ctx,只是影响当前的ctx。

GOLANG使用嵌入结构实现接口

技术讨论winlin 发表了文章 • 0 个评论 • 120 次浏览 • 2017-05-19 13:23 • 来自相关话题

考虑一个Packet接口,一般会返回一个Header,例如:

type PacketHeader struct {
    ID uint32
    Timesta... 			查看全部
					

考虑一个Packet接口,一般会返回一个Header,例如:


type PacketHeader struct {
ID uint32
Timestamp uint64
}

type Packet interface {
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
Header() *PacketHeader
}

如果是OO的语言,一般会有一个基类,里面包含了Header和实现这个Header:


class BasePacket : public Packet {
protected:
PacketHeader h;
public:
virtual Header() *PacketHeader;
};

class HandshakePacket : public BasePacket {
};

在子类中就都实现了这个Header()方法了,在GOLANG同样可以做到,通过在Header中定义方法,在Packet中包含Header就可以。


func (v *PacketHeader) Header() *PakcetHeader {
return v
}

type HandshakePacket struct {
PacketHeader
}

看起来还差不多的,都可以实现,golang只是代码少一点,清晰一点点而已。考虑要添加一些辅助函数,譬如给Packet添加是否是紧急类型的包,那OO语言得做一次代理:


type Packet interface {
IsErgency() bool
}

class BasePacketHeader {
public:
bool IsErgency() {
return realtime < 3;
}
}

class BasePacket {
public:
bool IsErgency() {
return h.IsErgency();
}
}

而在GOLANG中,只需要在Header实现就好了:


func (v *PacketHeader) IsErgency() bool {
return v.realtime < 3
}

更高级的可以直接嵌入接口。譬如context.Context的实现,cancelCtx直接嵌入了一个接口:


type cancelCtx struct {
Context

通过指定类型,或者初始化的顺序初始化struct


func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{
Context: parent,
done: make(chan struct{}),
}
}

结构嵌套的方式,让组合实现起来非常便捷,避免频繁的代理。

使用两个context实现CLOSE包的超时等待

技术讨论winlin 发表了文章 • 0 个评论 • 104 次浏览 • 2017-05-18 20:39 • 来自相关话题

在UDP中,一般发送者发送包后,如果一定的时间对方没有收到,就需要重传。例如UDP实现握手的过程,如果握手的包,比如RTMFP协议的IHELLO,发送给对方后,如果一定1秒没有收到,就应该重发一次,然后等3秒、6秒、9秒,如果最后没有收到就是超时了。查看全部

在UDP中,一般发送者发送包后,如果一定的时间对方没有收到,就需要重传。例如UDP实现握手的过程,如果握手的包,比如RTMFP协议的IHELLO,发送给对方后,如果一定1秒没有收到,就应该重发一次,然后等3秒、6秒、9秒,如果最后没有收到就是超时了。


最后一个Close包,发送者不能等待这么长的时间,所以需要设置一个较短的时间做超时退出。一般收发都是一个context,在最后这个Close包时,收到ctx.Done也不能立刻退出,因为还需要稍微等待,譬如600毫秒如果没有收到响应才能退出。


一个可能的实现是这样:


in := make(chan []byte)

func Close(ctx context.Context) (err error) {
timeous := ... // 1s,3s,6s,9s...
for _, to := range timeouts {
// 发送给对方WriteToUDP("CLOSE", peer)
// 另外一个goroutine读取UDP包到in

select {
case <- time.After(to):
case <- in:
fmt.Println("Close ok")
return
case <- ctx.Done():
fmt.Println("Program quit")
return
}
}
return
}

但是这个问题在于,在程序退出时,一般都会cancel ctx然后调用Close方法,这个地方就不会等待任何的超时,就打印"Program quit"然后返回了。解决方案是用另外一个context。但是如何处理之前的ctx的done呢?可以再起一个goroutine做同步:


in := make(chan []byte)

func Close(ctx context.Context) (err error) {
ctxRead,cancelRead := context.WithCancel(context.Background())
go func(){ // sync ctx with ctxRead
select {
case <-ctxRead.Done():
case <-ctx.Done():
select {
case <-ctxRead.Done():
case <-time.After(600*time.Milliseconds):
cancelRead()
}
}
}()

ctx = ctxRead // 下面直接用ctxRead。
timeous := ... // 1s,3s,6s,9s...
for _, to := range timeouts {
// 发送给对方WriteToUDP("CLOSE", peer)
// 另外一个goroutine读取UDP包到in

select {
case <- time.After(to):
case <- in:
fmt.Println("Close ok")
return
case <- ctx.Done():
fmt.Println("Program quit")
return
}
}
return
}

这样在主要的逻辑中,还是只需要处理ctx,但是这个ctx已经是新的context了。不过在实际的过程中,这个sync的goroutine需要确定起来后,才能继续,否则会造成执行顺序不确定:


sc := make(chan bool, 1)
go func(){ // sync ctx with ctxRead
sc <- true
select {
......
}
<- sc

使用context,来控制多个goroutine的执行和取消,是非常好用的,关键可以完全关注业务的逻辑,而不会引入因为ctx取消或者超时机制而造成的特殊逻辑。

GOLANG实现超时对象检测的最好理解的方式

技术讨论winlin 发表了文章 • 0 个评论 • 151 次浏览 • 2017-05-16 17:39 • 来自相关话题

依赖于心跳的系统,都需要超时检测。比如P2P系统中客户端每隔120秒向数据服务器发送一次数据汇总,服务器就需要维护一个超时时间。比如一个UDP服务器,在和客户端之间创建Session之后,如果没有数据包,一般会有Ping包,说明这个Session是存活的... 查看全部

依赖于心跳的系统,都需要超时检测。比如P2P系统中客户端每隔120秒向数据服务器发送一次数据汇总,服务器就需要维护一个超时时间。比如一个UDP服务器,在和客户端之间创建Session之后,如果没有数据包,一般会有Ping包,说明这个Session是存活的,服务器在发现Session超时后也需要清理。


首先,服务器一般需要维护一个列表,以Peer为例:


type Peer struct {
id uint64
heartbeat time.Time
}

type Server struct {
peers map[uint64]*Peer
lock sync.Mutex
}

创建Peer,同时在收到Ping消息后,更新Peer的心跳时间:


func (v *Server) Create(id uint64) *Peer {
v.lock.Lock()
defer v.lock.UnLock()

p = &Peer { id:id, heartbeat: time.Now(), }
v.peers[id] = p
return p
}

func (v *Server) OnPing(id uint64) {
v.lock.Lock()
defer v.lock.UnLock()

if p,ok := v.peers[id]; ok {
p.heatbeat = time.Now()
}
}

当然,需要起一个goroutine定期扫描这个列表, 假设300秒超时:


go func(v *Server) {
for {
func(){
v.lock.Lock()
defer v.lock.UnLock()

now := time.Now()
for id,p := range v.peers {
if p.heartbeat.Add(300 * time.Second).Before(now) {
delete(v.peers, id)
}
}
}()
time.Sleep(30 * time.Second)
}
}(server)

如果Peers的数目非常多,那么扫描时每次都需要锁定v.peers,会导致其他的业务都无法进行。特别是清理Peer这个过程如果比较复杂,譬如需要发起io请求,是一个费时的操作时,就会造成系统的等待。


一般来说,超时的Peer不会很多,因此可以用chan放一个超时的peer,每个peer专门起一个goroutine来看什么时候超时,这样就可以在检测超时时避免用锁了:


timeout := make(chan *Peer)

func (v *Server) Create(id uint64) *Peer {
v.lock.Lock()
defer v.lock.UnLock()

p = &Peer { id:id, heartbeat: time.Now(), }
v.peers[id] = p
return p

go func(p *Peer) {
for {
tm := p.heartbeat
<- time.After(300 * time.Second)
if tm.Equal(p.heartbeat) {
timeout <- p
break
}
}
}(p)
}

go func(v *Server){
for gw := range timeout {
func(){
lgateways.Lock()
defer lgateways.Unlock()

delete(gateways, gw.port)
}()

// Do something cleanup about the gateway.
}
}(server)

这样就只有在有Peer超时时,才真正锁住Server.peers

GOLANG接口适配,组合方式的灵活接口演化

技术讨论winlin 发表了文章 • 0 个评论 • 187 次浏览 • 2017-05-15 20:49 • 来自相关话题

在OO(Object Oriented)原则中,有一条叫做:优先使用组合,而不是继承。虽然GOLANG并不是OO的语言(没有继承和多态),但是不妨碍GOLANG使用这条原则,而GOLANG的作者就强调过这一点,在GOLANG中是使用组合而非继承来扩展。<... 查看全部

在OO(Object Oriented)原则中,有一条叫做:优先使用组合,而不是继承。虽然GOLANG并不是OO的语言(没有继承和多态),但是不妨碍GOLANG使用这条原则,而GOLANG的作者就强调过这一点,在GOLANG中是使用组合而非继承来扩展。


装逼的说来,继承是一种名词化的语言体系,先进行业务抽象然后设计类体系和继承关系。而组合,强制使用接口,因为组合中使用的总是另外一个对象的接口,通过动词的组合,实现目标,比如不管是什么只要有Write([]byte)(int,error)这个动作,就实现了这个接口,其他对象组合这个接口后,对外也看起来就是个io.Writer的接口。


比如,GOALNG1.8支持了writev,一般在面向对象会这么的搞:


class Socket {
int Write(void*, int);
int Writev(const iovec*, int);
};

对的吧?一个Socket可以写数据,也可以用writev写iovec向量,就是一次性写入多个内存块。



Note: 有时候内存块是不连续的,比如一个Video帧,发送给不同的客户端时,Header是需要修改的,但是Payload都一样,那么可以针对每个客户端只创建一个header,然后公用payload,但是这时候两个内存指针是不连续的,特别是需要同时写入多个视频帧时,writev就很神奇的避免了内存拷贝writev(header+payload),具体参考下writev的资料哈。



这样有个问题,并非所有系统都支持Writev的,并非所有Socket都支持Writev的,如果是自己写个代码,当然是可以随便这么搞的,但是作为标准库,GOLANG当然是不能这么做的。GOLANG就加了一个接口(一个新动作)叫做net.buffersWriter,如果实现了这个接口就用writev。先看用法:


    conn,err := net.Dial("tcp", "127.0.0.1:1935")

buffers := Buffers{
[]byte("once upon a time in "),
[]byte("Gopherland ... "),
}

buffers.WriteTo(conn)

在Buffers的WriteTo方法会判断是否是writev的接口,如果是则用writev写,否则就一个个的写:


func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) {
if wv, ok := w.(buffersWriter); ok {
return wv.writeBuffers(v)
}

实际上conn是net.TcpConn,里面有个fd *net.netFD,它实现了net.buffersWriter接口,所以最后调用的就是(fd *netFD) writeBuffers(v *Buffers)


func (c *conn) writeBuffers(v *Buffers) (int64, error) {
n, err := c.fd.writeBuffers(v)

func (fd *netFD) writeBuffers(v *Buffers) (n int64, err error) {
iovecs = append(iovecs, syscall.Iovec{Base: &chunk[0]})
wrote, _, e0 := syscall.Syscall(syscall.SYS_WRITEV,
uintptr(fd.sysfd),
uintptr(unsafe.Pointer(&iovecs[0])),
uintptr(len(iovecs)))

对于其他没有实现这个接口的对象,就每个向量循环的写。


在看一个例子http.Get(url string),客户端发起一个HTTP请求:


http.Get("http://localhost:1985/api/v1/versions")
// 实际上调用的是:
func (c *Client) Get(url string)
// 然后调用:
(c *Client) Do(req *Request)

在GOLANG1.7中引入了context的概念,用来支持cancel,怎么用的:


ctx,cancel := context.WithCancel(context.Background())

select {
case <- ctx.Done():
// Cancelled.
case <- time.After(...):
// Timeout
case <- other events:
// Other events.
}

如何支持取消的HTTP请求呢?给http.Get加个ctx参数?例如http.Get(ctx, url)这样?那改动得多大啊,而且还不能兼容之前的API,泪奔~看看GOLANG的解决:


ctx,cancel := context.WithCancel(context.Background())
go func(){
req,err := http.NewRequest("http://...")
res,err := http.DefaultClient.Do(req.WithContext(ctx))
defer res.Body.Close()
// 读取res响应结果。
}()

select {
case <- ctx.Done():
case <- time.After(3 * time.Second):
cancel() // Timeout to cancel all requests.
}

使用组合,通过req.WithContext再返回一个*http.Request,实现同样的目的。

GOLANG使用简单类型,在协议解析的妙用

技术讨论winlin 发表了文章 • 0 个评论 • 654 次浏览 • 2017-05-11 15:41 • 来自相关话题

在协议解析中,经常需要用到转换不同的含义,比如声音的采样率,在FLV中定义和AAC中定义是不同的。在FLV中只有4中采样率5512, 11025, 22050, 44100。而在AAC中有16种采样率96000, 8... 查看全部

在协议解析中,经常需要用到转换不同的含义,比如声音的采样率,在FLV中定义和AAC中定义是不同的。在FLV中只有4中采样率5512, 11025, 22050, 44100。而在AAC中有16种采样率96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350(还有4个是保留的)。也就是说,1在FLV中标识11025Hz,而在AAC中表示的是88200Hz。如何实现这个转换呢?


C++当然先得定义枚举:


enum SrsAudioSampleRate
{
SrsAudioSampleRate5512 = 0,
SrsAudioSampleRate11025,
SrsAudioSampleRate22050,
SrsAudioSampleRate44100,
SrsAudioSampleRateForbidden,
};

C++当然是用函数了:


SrsAudioSampleRate aac_to_flv(int v) {
if (v >= 0 && v <=5) {
return SrsAudioSampleRate44100;
} else if (v >=6 && v <= 8) {
return SrsAudioSampleRate22050;
} else if (v >= 9 && v <= 11) {
return SrsAudioSampleRate11025;
} else if (v == 12) {
return SrsAudioSampleRate5512;
} else {
return SrsAudioSampleRateForbidden;
}
}

看起来还是挺简单的。慢着,还有的时候需要打印出采样率来,所以还得搞个函数:


string srs_audio_sample_rate2str(SrsAudioSampleRate v)
{
switch (v) {
case SrsAudioSampleRate5512: return "5512";
case SrsAudioSampleRate11025: return "11025";
case SrsAudioSampleRate22050: return "22050";
case SrsAudioSampleRate44100: return "44100";
default: return "Forbidden";
}
}

拿到一个AAC的采样率,然后转换成FLV的,并打印出来,是这么使用的:


// 从文件或者流中读取出AAC的采样率的值。
int samplingFrequencyIndex = ...;
// 转换成FLV的采样率。
SrsAudioSampleRate sampleRate = aac_to_flv(samplingFrequencyIndex);
// 转换成字符串格式。
string sSampleRate = srs_audio_sample_rate2str(sampleRate);
// 打印采样率。
printf("SampleRate=%d/%sHz\n", sampleRate, sSampleRate);

有什么麻烦的呢?



  1. 函数和类型之间没有关系,每次使用的时候都得去翻手册啊翻手册。

  2. 如果定义成一个struct,那转换的时候又太麻烦了。


还能不能愉快的玩耍呢?用GOLANG吧!先看用法:


var sampleRate AudioSamplingRate
sampleRate.From(samplingFrequencyIndex)
fmt.Printf("SampleRate=%d/%v\n", sampleRate, sampleRate)

就是这么简单(此处应该有掌声)~


其实实现起来也非常自然:


type AudioSamplingRate uint8

const (
AudioSamplingRate5kHz AudioSamplingRate = iota // 0 = 5.5 kHz
AudioSamplingRate11kHz // 1 = 11 kHz
AudioSamplingRate22kHz // 2 = 22 kHz
AudioSamplingRate44kHz // 3 = 44 kHz
AudioSamplingRateForbidden
)

func (v AudioSamplingRate) String() string {
switch v {
case AudioSamplingRate5kHz:
return "5.5kHz"
case AudioSamplingRate11kHz:
return "11kHz"
case AudioSamplingRate22kHz:
return "22kHz"
case AudioSamplingRate44kHz:
return "44kHz"
default:
return "Forbidden"
}
}

func (v *AudioSamplingRate) From(a int) {
switch a {
case 0, 1, 2, 3, 4, 5:
*v = AudioSamplingRate44kHz
case 6, 7, 8:
*v = AudioSamplingRate22kHz
case 9, 10, 11:
*v = AudioSamplingRate11kHz
case 12:
*v = AudioSamplingRate5kHz
default:
*v = AudioSamplingRateForbidden
}
}


Remark: 代码参考go-oryx-lib flv.



有几个地方非常不同:



  1. 虽然GOLANG只是在uint8上面加了函数,但是使用起来方便很多了,以前在C++中用这两个枚举,每次都要跳到枚举的定义来看对应的函数是什么。

  2. GOLANG的switch比较强大,可以case好几个值,和C++的if有点想,但是GOLANG的case更直观,知道这几个值会被转换成另外的值,而if读起来像是将一个范围的值转换,不好懂。

  3. GOLANG的枚举使用const实现,也可以带类型,而且有个iota很强大,特别是在定义那些移位的枚举时就很好用。


好吧,这只是几个小的改进,虽然用起来很方便。来看看在AMF0中基本类型的妙用,AMF0是一种传输格式,和JSON很像,不过JSON是文本的,而AMF0是字节的,都是用来在网络中传输对象的。因此,AMF0定义了几个基本的类型:String, Number, Boolean, Object,其中Object的属性定义为String的属性名和值,值可以是其他的类型。


先看看C++的实现,首先定义一个AMF0Any对象,可以转换成具体的String或者Object等对象:


class SrsAmf0Any {
// 提供转换的函数,获取实际的值。
virtual std::string to_str();
virtual bool to_boolean();
virtual double to_number();
virtual SrsAmf0Object* to_object();
// 当然还得提供判断的函数,得知道是什么类型才能转。
virtual bool is_string();
virtual bool is_boolean();
virtual bool is_number();
virtual bool is_object();
// 提供创建基本类型的函数。
static SrsAmf0Any* str(const char* value = NULL);
static SrsAmf0Any* boolean(bool value = false);
static SrsAmf0Any* number(double value = 0.0);
static SrsAmf0Object* object();
};

在实现时,String和Number等基本类型可以隐藏起来(在cpp中实现):


namespace _srs_internal {
class SrsAmf0String : public SrsAmf0Any {
public:
std::string value;
// 当然它必须实现编码和解码的函数。
virtual int total_size();
virtual int read(SrsBuffer* stream);
virtual int write(SrsBuffer* stream);
};
}

AMF0Object当然得暴露出来的:


class SrsAmf0Object : public SrsAmf0Any {
public:
virtual int total_size();
virtual int read(SrsBuffer* stream);
virtual int write(SrsBuffer* stream);
// 提供设置和读取属性的方法。
virtual void set(std::string key, SrsAmf0Any* value);
virtual SrsAmf0Any* get_property(std::string name);
};

用起来是这样:


// 设置Object的属性,并发送给服务器。
SrsConnectAppPacket* pkt = NULL;
pkt->command_object->set("app", SrsAmf0Any::str(app.c_str()));
pkt->command_object->set("tcUrl", SrsAmf0Any::str(tcUrl.c_str()));

// 读取服务器的响应,取出服务器的IP等信息。
SrsConnectAppResPacket* pkt = NULL;
SrsAmf0Any* data = pkt->info->get_property("data");
if (si && data && data->is_object()) {
SrsAmf0Object* obj = data->to_objet();

SrsAmf0Any* prop = obj->get_property("srs_server_ip");
if (prop && prop->is_string()) {
printf("Server IP: %s\n", prop->to_str().c_str());
}

prop = obj->get_property("srs_pid");
if (prop && prop->is_number()) {
printf("Server PID: %d\n, prop->to_number());
}
}

看起来巨繁琐吧?快用GOLANG,如果换成GOLANG,可以用基本类型定义AMF0的基本类型,这样使用起来是这样:


pkt := or.NewConnectAppPacket()
pkt.CommandObject.Set("tcUrl", amf0.NewString(tcUrl))
pkt.CommandObject.Set("app", amf0.NewString(app))

var res *or.ConnectAppResPacket
if data, ok := res.Args.Get("data").(*amf0.Object); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
fmt.Printf("Server IP: %s\n", string(*data))
}
if data, ok := data.Get("srs_pid").(*amf0.Number); ok {
fmt.Printf("Server PID: %d\n, int(*data))
}
}

区别在于:



  1. C++由于不能在基本类型上定义方法,导致必须创建struct或者class类型,有比较繁琐的类型转换和判断。

  2. GOLANG的类型判断,提供了ok的方式,一句话就能把类型转换弄好,而且接口和实现struct的对象可以重用变量名。

  3. 不必加很多类型判断,没有多余的变量,干净利索,需要维护的信息比较少。


实现起来更舒服,基本类型不用定义struct:


type String string
func (v *String) Size() int {}
func (v *String) UnmarshalBinary(data []byte) (err error) {}
func (v *String) MarshalBinary() (data []byte, err error) {}

type Object struct {}
func (v *Object) Size() int {}
func (v *Object) UnmarshalBinary(data []byte) (err error) {}
func (v *Object) MarshalBinary() (data []byte, err error) {}


Remark:代码参考go-oryx-lib amf0.



更神奇的是,因为Object、EcmaArray和StrictArray都是类似的结构,但是有些细微的差异,因此使用GOLANG的结构体嵌套可以很直接的解决问题:


type Object struct {
objectBase
eof objectEOF
}
type EcmaArray struct {
objectBase
count uint32
eof objectEOF
}
type StrictArray struct {
objectBase
count uint32
}

可以对比下SRS的实现,C++可以采用继承,而GOLANG直接组合那些基本的单元。


爱生活,爱够浪(此处可以响起掌声了)~

GOLANG将类型作为参数,用反射设置指针的指针,实现类似模板功能

技术讨论winlin 发表了文章 • 0 个评论 • 161 次浏览 • 2017-05-09 20:13 • 来自相关话题

在协议解析中,C++的模板有比较大的作用,有时候我们希望丢弃所有的包,只留下特定类型的包。参考SRS的代码查看全部

在协议解析中,C++的模板有比较大的作用,有时候我们希望丢弃所有的包,只留下特定类型的包。参考SRS的代码SrsRtmpClient::connect_app2


类型系统的设计, SrsConnectAppResPacket继承自SrsPacket


class SrsPacket;
class SrsConnectAppResPacket : public SrsPacket

协议栈提供了expect_message模板函数,接收特定类型的包:


SrsCommonMessage* msg = NULL;
SrsConnectAppResPacket* pkt = NULL;
if ((ret = protocol.expect_message<SrsConnectAppResPacket>(&msg, &pkt)) != ERROR_SUCCESS) {
return ret;
}

SrsAmf0Any* data = pkt->info->get_property("data");
SrsAmf0EcmaArray* arr = data->to_ecma_array();
SrsAmf0Any* prop = arr->ensure_property_string("srs_server_ip");
string srs_server_ip = prop->to_str();

在向服务器发送了ConnectApp后,就等待ConnectAppRes响应包,丢弃所有的其他的。这个时候,类型SrsConnectAppResPacket就作为了一个参数,也就是C++的模板。如果是GOLANG怎么实现呢?没有直接的办法的,因为没有泛型。


在GOLANG中,也需要定义个interface,参考Packet,当然也是有ConnectAppResPacket实现了这个接口(Message是原始消息,它的Payload可以Unmarshal为Packet):


type Message struct { Payload []byte }
type Packet interface {} // Message.Payload = Packet.Marshal()
type ConnectAppResPacket struct { Args amf0.Amf0 }

第一种方法,协议栈只需要收取Message,然后解析Message为Packet,收到packet后使用类型转换,判断不是自己需要的包就丢弃:


func (v *Protocol) ReadMessage() (m *Message, err error)
func (v *Protocol) DecodeMessage(m *Message) (pkt Packet, err error)

不过这两个基础的API,User在使用时,比较麻烦些,每次都得写一个for循环:


var protocol *Protocol

for {
var m *Message
m,_ = protocol.ReadMessage()

var p Packet
p,_ = protocol.DecodeMessage(m)

if res,ok := p.(*ConnectAppResPacket); ok {
if data, ok := res.Args.Get("data").(*amf0.EcmaArray); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
srs_server_ip = string(*data)
}
}
}
}

比较方便的做法,就是用回调函数,协议栈需要提供个ExpectPacket方法:


func (v *Protocol) ExpectPacket(filter func(m *Message, p Packet)(ok bool)) (err error)

这样可以利用回调函数可以访问上面函数的作用域,直接转换类型和设置目标类型的包:


var protocol *Protocol

var res *ConnectAppResPacket
_ = protocol.ExpectPacket(func(m *Message, p Packet) (ok bool){
res,ok = p.(*ConnectAppResPacket)
})

if data, ok := res.Args.Get("data").(*amf0.EcmaArray); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
srs_server_ip = string(*data)
}
}

这样已经比较方便了,不过还是需要每次都给个回调函数。要是能直接这样用就好了:


var protocol *Protocol

var res *ConnectAppResPacket
_ = protocol.ExpectPacket(&res)

if data, ok := res.Args.Get("data").(*amf0.EcmaArray); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
srs_server_ip = string(*data)
}
}

这样也是可以做到的,不过协议栈函数要定义为:


func (v *Protocol) ExpectPacket(ppkt interface{}) (err error)

在函数内部,使用reflect判断类型是否符合要求,设置返回值。代码参考ExpectPacket,下面是一个简要说明:


func (v *Protocol) ExpectPacket(ppkt interface{}) (m *Message, err error) {
// 由于ppkt是**ptr, 所以取类型后取Elem(),就是*ptr,用来判断是否实现了Packet接口。
ppktt := reflect.TypeOf(ppkt).Elem()
// ppktv是发现匹配的包后,设置值的。
ppktv := reflect.ValueOf(ppkt)

// 要求参数必须是实现了Packet,避免传递错误的值进来。
if required := reflect.TypeOf((*Packet)(nil)).Elem(); !ppktt.Implements(required) {
return nil,fmt.Errorf("Type mismatch")
}

for {
m, err = v.ReadMessage()
pkt, err = v.DecodeMessage(m)

// 判断包是否是匹配的那个类型,如果不是就丢弃这个包。
if pktt = reflect.TypeOf(pkt); !pktt.AssignableTo(ppktt) {
continue
}

// 相当于 *ppkt = pkt,类似C++中对指针的指针赋值。
ppktv.Elem().Set(reflect.ValueOf(pkt))
break
}
return
}

遗憾的就是这个参数ppkt类型不能是Packet,因为会有类型不匹配;也不能是*Packet,因为在GOLANG中传递接口的指针也是不可以的,会导致类型错误(**ConnectAppResPacket并不能匹配*Packet);这个参数只能是interface{}。不过用法也很简单,只是需要注意参数的传递。


var res *ConnectAppResPacket
// 这是正确的做法,传递res指针的地址,相当于指针的指针。
_ = protocol.ExpectPacket(&res)
// 这是错误的做法,会在ExpectPacket检查返回错误,没有实现Packet接口
_ = protocol.ExpectPacket(res)

用起来还不错。

GoCN 每日新闻(2017-04-30)

文章分享xieyanke 发表了文章 • 0 个评论 • 310 次浏览 • 2017-04-30 14:57 • 来自相关话题

每日新闻(2017-04-30):

  1. Go Interface 详解
  2. 查看全部

GoCN 每日新闻(2017-04-26)

文章分享xieyanke 发表了文章 • 1 个评论 • 400 次浏览 • 2017-04-26 16:00 • 来自相关话题

每日新闻(2017-04-26):

  1. 一个 Go 微信机器人包
  2. 查看全部

Go之旅-for循环

文章分享frankphper 发表了文章 • 0 个评论 • 277 次浏览 • 2017-04-09 22:51 • 来自相关话题

Go语言仅有for一种循环语句,但常用方式都能支持。其中初始化表达式支持函数调用或定义局部变量,需要注意的是初始化语句中的函数仅执行一次,条件表达式中的函数重复执行,规避方式就是在初始化表达式中定义局部变量保存函数返回结果。Go语言中也有goto语句,使... 查看全部

Go语言仅有for一种循环语句,但常用方式都能支持。其中初始化表达式支持函数调用或定义局部变量,需要注意的是初始化语句中的函数仅执行一次,条件表达式中的函数重复执行,规避方式就是在初始化表达式中定义局部变量保存函数返回结果。Go语言中也有goto语句,使用goto语句前,必须先定义标签,标签区分大小写,并且未使用的标签会引发编译错误。和goto定点跳转不同,break、continue用于中断代码块执行。break用于switch、for、select语句,终止整个语句块执行,continue仅用于for循环,终止后续逻辑,立即进入下一轮循环。break和continue配合标签使用,可以在多层嵌套中指定目标层级。



package main

import (
"fmt"
)

// count函数
func count() int {
fmt.Println("count.") // 打印字符串用来查看count函数执行次数
return 3
}

// main函数
func main() {
// for循环
// 初始化表达式,支持函数调用或定义局部变量
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// 类似while循环
i := 0
for i < 10 {
fmt.Println(i)
i++
}
// 类似无限循环
for {
break
}
// 初始化语句中的count函数仅执行一次
for i, c := 0, count(); i < c; i++ {
fmt.Println("a", i)
}

c := 0
// 条件表达式中的count函数重复执行
for c < count() {
fmt.Println("b", c)
c++
}
// 规避条件表达式中的count函数重复执行,在初始化表达式中定义局部变量保存count函数返回结果
count := count()
d := 0
for d < count {
fmt.Println("c", d)
d++
}
// goto定点跳转
// 须先定义标签,并且未用到的标签会引发编译错误
// 不能跳转到其它函数,或内层代码块内
for i := 0; i < 10; i++ {
fmt.Println(i)
if i > 5 {
goto exit
}
}
exit:
fmt.Println("exit.")

// break 用户switch、for、select语句,终止整个语句块执行。continue 仅用于for循环,终止后续逻辑,立即进入下一轮循环
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue // 立即进入下一轮循环
}
if i > 5 {
break // 立即终止整个for循环
}
fmt.Println(i)
}
// 配合标签,break和continue可在多层嵌套中指定目标层级
outer:
for i := 0; i < 5; i++ {
for j := 0; j < 10; j++ {
if j > 2 {
fmt.Println()
continue outer
}

if i > 2 {
break outer
}
fmt.Print(i, ":", j, " ")
}
}

}

实时现在毫秒时钟 RTClock

开源程序Akagi201 发表了文章 • 7 个评论 • 404 次浏览 • 2017-03-22 12:46 • 来自相关话题

找了很久, 没有找到一个在线可用的毫秒时钟. 自己用 websocket 写了一个.

https://github.com/Akagi201/rtc... 查看全部

找了很久, 没有找到一个在线可用的毫秒时钟. 自己用 websocket 写了一个.


https://github.com/Akagi201/rtclock


rtclock



An HTML5 Real Time Clock based on WebSocket



rtclock


Features



  • [x] Use WebSocket to send backend real time clock to frontend.

  • [x] Support go-bindata.


Install



  • go get github.com/Akagi201/rtclock


Build



  • ./gobin.sh (when you modified the html template)

  • go build


Run



  • ./rtclock -h

  • Simple usage: ./rtclock

Go之旅-Switch

文章分享frankphper 发表了文章 • 0 个评论 • 246 次浏览 • 2017-03-15 22:51 • 来自相关话题

Go之旅-Switch

switch支持初始化语句,注意要用分号结束。后跟条件表达式,如果省略条件表达式,默认为true。不需要显示执行break语句,case执行完毕后自动终端。多个匹配条件,其中一个条件符合即可。case执行中断后,... 查看全部

Go之旅-Switch


switch支持初始化语句,注意要用分号结束。后跟条件表达式,如果省略条件表达式,默认为true。不需要显示执行break语句,case执行完毕后自动终端。多个匹配条件,其中一个条件符合即可。case执行中断后,如果需要继续执行下一个case块的内容,在下一个case块结尾执行fallthrough并且可以在fallthrough前使用break语句阻止。但不继续继续后续case块。


package main

import (
"fmt"
)

func main() {
// 简单声明几个变量
a, b, c, d := 1, 2, 3, 4
switch x := 2; x { // switch支持初始化语句,注意要用分号结束。后跟条件表达式,如果省略条件表达式,默认为true。
case a:
fmt.Println("a")
// break // 不需要显示执行break语句,case执行完毕后自动终端。
case a, b: // 多个匹配条件,其中一个条件符合即可。
fmt.Println("b")
fallthrough // case执行中断后,如果需要继续执行下一个case块的内容,在下一个case块结尾执行fallthrough并且可以在fallthrough前使用break语句阻止。但不继续继续后续case块。
case c:
fmt.Println("c")
case d:
fmt.Println("d")
case 5:
fmt.Println("e")
//case 5, 6: // 支持常量,但不能出现重复常量
// fmt.Println("f")
default:
fmt.Println("x") // 只有全部匹配失败后,才会执行default块。
}
}

Go之旅-常量

文章分享frankphper 发表了文章 • 2 个评论 • 233 次浏览 • 2017-03-12 16:42 • 来自相关话题

Go之旅-常量

常量是指程序运行时不可改变的值,常量必须初始化值,定义常量可以指定类型,编译器也可以通过常量初始化值做类型推断。在函数代码块中定义常量,不被使用也不会出现编译错误。在常量组中如果不指定常量类型和初始化值,那么常量会和上一... 查看全部

Go之旅-常量


常量是指程序运行时不可改变的值,常量必须初始化值,定义常量可以指定类型,编译器也可以通过常量初始化值做类型推断。在函数代码块中定义常量,不被使用也不会出现编译错误。在常量组中如果不指定常量类型和初始化值,那么常量会和上一行的非空常量值相同。


// 声明包main
package main

// 导入包
import (
"fmt"
)

// 定义常量
const a = 10 // 必须赋值,可指定类型,也可以编译器通过初始化值类型推断
const b = "Hello World"
const c = false
const d, e = 1, 10

// 常量组
const (
f = true
g = 100
)

// 定义函数main
func main() {
// 函数块中定义的常量,不适用也不会出现编译错误
const (
h = 1
i // 在常量组中不指定常量类型和初始化值,会和上一行非空的常量值相同。
j
k
)
const g = "Hello World"
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d, e)
fmt.Println(f, g)
fmt.Println(i)
fmt.Println(j)
fmt.Println(k)
}

Go之旅-变量

Golangfrankphper 发表了文章 • 3 个评论 • 247 次浏览 • 2017-03-10 00:03 • 来自相关话题

Go之旅-变量

Go语言变量有固定的数据类型,决定了变量内存的长度和存储格式。Go变量只能修改变量值,不能改变变量的数据类型。Go编译器会把未使用的局部变量当做错误,未使用的全局变量不受影响。

查看全部
					

Go之旅-变量


Go语言变量有固定的数据类型,决定了变量内存的长度和存储格式。Go变量只能修改变量值,不能改变变量的数据类型。Go编译器会把未使用的局部变量当做错误,未使用的全局变量不受影响。


// 声明包main
package main
// 导入包
import(
"fmt"
)
// 定义变量
var a int // 初始化为二进制零值
var b = false // 显示初始化变量的值,可以省略变量类型,由编译器自动类型推断
var c, d = 100, "hello" // 一次定义多个变量,数据类型可以不同
// 定义多个变量,建议使用组的形式定义多个变量
var (
e, f int
g, h = 100, "hello"
)

// 定义函数main
func main() {
// 简短模式声明变量
i := 100
/**
*简短模式声明变量,有以下3个条件
*显示初始化
*不能显示指定数据类型
*只能在函数内部使用
*/
// 多变量赋值
x, y := 1, 10
x, y = x+10, y+1 // 先计算右值,然后再对左边变量赋值,必须保证左右值得数据类型相同
// 打印变量的值
fmt.Println(a);
fmt.Println(b);
fmt.Println(c, d)
fmt.Println(e, f)
fmt.Println(g, h)
fmt.Println(i)
fmt.Println(x, y)
}

今天就先到这,欢迎各位Go语言大神一起交流分享。