Festival & Fuck, Coding, Inner depth, Sister and Others.

某些文章会提到《为什么Go语言这么不受待见》,《真的没必要浪费心思在 Go 语言上》,《我为什么放弃Go语言》,《Why worse is better》等话题。经常重温这些话题,每次都会有新发现。最忌手里有了一个语言,心里便容不下另一个语言。

忽略细节、语法或者设计,Go语言各种好用。考虑到这些因素,Go被喷出翔都不为过。

本文不打算在细节、语法或者设计上扯淡,只举些例子,说一说如何用Go语言写出还凑合的代码。

类、对象、属性,可能还夹杂着一点设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//代码来自 https://github.com/xgdapg/xconn/blob/master/xconn.go,已验证
//Conn 对应一个tcp连接
type Conn struct {
//原生TCP连接
conn net.Conn
//发送数据的channel(类似队列)
send chan *MsgData
//消息处理方法
msgHandler MsgHandler
//tcp缓冲区
recvBuffer []byte
//消息一次封装,后续会进行二次封装
msgPacker MsgPacker
//健康检查周期
pingInterval uint
//健康检查方法
pingHandler PingHandler
//停止健康检查channel
pingStop chan bool
//自定义关闭连接时所调用方法
closeHandler CloseHandler
}
//MsgHandler 为自定义处理消息
type MsgHandler interface {
HandleMsg(*MsgData)
}
//MsgPacker 为自定义拆包解包方式,为了性能可以直接将此段与recvLoop端合并
type MsgPacker interface {
PackMsg(*MsgData) []byte
UnpackMsg([]byte) *MsgData
}
//MsgData 自定义消息类型
type MsgData struct {
Data []byte
Ext interface{}
}
//PingHandler 为自定义心跳命令
type PingHandler interface {
HandlePing()
}
//CloseHandler 为连接断开时的自定义操作
type CloseHandler interface {
HandleClose()
}
//NewConn 为处理新连接方式:启动两个协程,一个只负责读,一个只负责写,
//也可以认为开启了三个协程,第三个协程负责进行定时ping操作
func NewConn(conn net.Conn) *Conn {
c := &Conn{
conn: conn,
send: make(chan *MsgData, 64),
msgHandler: nil,
recvBuffer: []byte{},
msgPacker: nil,
pingInterval: 0,
pingHandler: nil,
pingStop: nil,
closeHandler: nil,
}
go c.recvLoop()
go c.sendLoop()
return c
}
//recoverPanic 程序panic情况下的处理方法(例如向已经关闭的tcp连接写数据会造成panic)
func recoverPanic() {
if err := recover(); err != nil {
//fmt.Println(err)
}
}
//SetMsgHandler 为 Getter Setter 方法
func (this *Conn) SetMsgHandler(hdlr MsgHandler) {
this.msgHandler = hdlr
}
//SetMsgPacker 为 Getter Setter 方法
func (this *Conn) SetMsgPacker(packer MsgPacker) {
this.msgPacker = packer
}
//SetPing 为 Getter Setter 方法
func (this *Conn) SetPing(sec uint, hdlr PingHandler) {
this.pingInterval = sec
this.pingHandler = hdlr
if this.pingStop == nil {
this.pingStop = make(chan bool)
}
if sec > 0 {
go this.pingLoop()
}
}
//SetCloseHandler 为 Getter Setter 方法
func (this *Conn) SetCloseHandler(hdlr CloseHandler) {
this.closeHandler = hdlr
}
//pingLoop 为定时健康检查操作
func (this *Conn) pingLoop() {
defer recoverPanic()
for {
select {
case <-this.pingStop:
return
case <-time.After(time.Duration(this.pingInterval) * time.Second):
this.Ping()
}
}
}
//RawConn 返回原始的tcp连接
func (this *Conn) RawConn() net.Conn {
return this.conn
}
//recvLoop 用于处理接收到的tcp包,并进行拆包等操作,然后调用recvMsg方法进行处理
func (this *Conn) recvLoop() {
defer recoverPanic()
defer this.Close()
buffer := make([]byte, 2048)
//一次封包协议:四个字节(int32)表示包长度,根据包长度截取消息长度作为包。
for {
bytesRead, err := this.conn.Read(buffer)
if err != nil {
return
}
this.recvBuffer = append(this.recvBuffer, buffer[0:bytesRead]...)
for len(this.recvBuffer) > 4 {
length := binary.BigEndian.Uint32(this.recvBuffer[0:4])
readToPtr := length + 4
if uint32(len(this.recvBuffer)) < readToPtr {
break
}
if length == 0 {
if this.pingHandler != nil {
this.pingHandler.HandlePing()
}
} else {
buf := this.recvBuffer[4:readToPtr]
go this.recvMsg(buf)
}
this.recvBuffer = this.recvBuffer[readToPtr:]
}
}
}
//recvMsg 为代理,实际执行的是后台的HandleMsg方法。
func (this *Conn) recvMsg(data []byte) {
defer recoverPanic()
msg := &MsgData{
Data: data,
Ext: nil,
}
//调用UnackMsg对信息进行二次解包
if this.msgPacker != nil {
msg = this.msgPacker.UnpackMsg(data)
}
if this.msgHandler != nil {
this.msgHandler.HandleMsg(msg)
}
}
//sendLoop 用于发送数据包
func (this *Conn) sendLoop() {
defer recoverPanic()
for {
msg, ok := <-this.send
if !ok {
break
}
go this.sendMsg(msg)
}
}
//sendMsg 用于发送数据包,实际先调用PackMsg进行信息持久化,然后二次封包,转换为本框架能接受的形式
func (this *Conn) sendMsg(msg *MsgData) {
defer recoverPanic()
sendBytes := make([]byte, 4)
if msg != nil {
data := msg.Data
if this.msgPacker != nil {
data = this.msgPacker.PackMsg(msg)
}
length := len(data)
binary.BigEndian.PutUint32(sendBytes, uint32(length))
sendBytes = append(sendBytes, data...)
}
this.conn.Write(sendBytes)
}
//Close 关闭连接
func (this *Conn) Close() {
defer recoverPanic()
this.conn.Close()
close(this.send)
if this.pingStop != nil {
close(this.pingStop)
}
if this.closeHandler != nil {
this.closeHandler.HandleClose()
}
}
//SendMsg 用于发送数据
func (this *Conn) SendMsg(msg *MsgData) {
this.send <- msg
}
//SendData 用于发送数据
func (this *Conn) SendData(data []byte) {
this.SendMsg(&MsgData{Data: data, Ext: nil})
}
//Ping 用于健康监测
func (this *Conn) Ping() {
go this.sendMsg(nil)
}

作为一个专用于处理TCP链接的框架,实际上xconn(上文中的代码)进行了两次封装,连消息发送、信息拆包封包、甚至接收信息都进行了二次封装。

实际代码中,可以进行简化操作,将二次的部分简化为一次。

将代码写得和上面一样工整,便已经超越大部分猿了。

上个例子中作者用到了recover,用的很克制,却又恰到好处。

至于defer和panic

Go语言的try catch?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import (
"fmt"
"github.com/manucorporat/try"
)
func main() {
try.This(func() {
panic("my panic")
}).Finally(func() {
fmt.Println("this must be printed after the catch")
}).Catch(func(e try.E) {
// Print crash
fmt.Println(e)
})
}

以上代码纯属搞笑,个人不建议工程项目中使用如此写法,但是这种做法可以借鉴。

工程代码:(用于Go与数据库Transaction)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//代码来自《Go语言游戏项目应用情况汇报》
func (db *Database) Transaction(work func()) {
db.lock.Lock()
defer db.lock.UnLock()
//事务控制
defer func() {
if err := recover; err == nil {
db.commit(info)
} else {
db.rollback()
//选择性抛出panic
panic(TransError{err})
}
}()
//执行传入的函数
work()
}

《Go语言游戏项目应用情况汇报》是我所能找到的,为数不多的几个敢开放部分工程代码的分享。整体代码比较整洁,适于新手学习。

以上对err的处理方法写法,和《Errors are values》有异曲同工之妙。

err的一种处理方式

示范代码:来自《Errors are vales》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//这种写法强烈不推荐!!!!这就是许多人说的Go程序一大半都在check error
_, err = fd.Write(p0[a:b])
if err != nil {
return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
return err
}
// and so on
//重要的事情说两遍:不推荐,但是个人小项目这样写,完全没问题。

推荐如下写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var err error
write := func(buf []byte) {
if err != nil {
return
}
_, err = w.Write(buf)
}
write(p0[a:b])
write(p1[c:d])
write(p2[e:f])
// and so on
if err != nil {
return err
}

也推荐如下写法:

1
2
3
4
5
6
func (ew *errWriter) write(buf []byte) {
if ew.err != nil {
return
}
_, ew.err = ew.w.Write(buf)
}

其他:

正名:为什么选择Go语言。

答:因为简单,并且也不会别的。

建议:尽量选择Go1.6及以上版本,避免GC造成程序STW。

至于GC性能,可以参考 Go1.6中的gc pause已经完全超越JVM了吗?

原文禁止转载,因此提炼出关键字:从效果看XXX,但XXX;并且,XXX,XXX。

So, Why worse is better?