Festival & Fuck, Coding, Inner depth, Sister and Others.
某些文章会提到《为什么Go语言这么不受待见》,《真的没必要浪费心思在 Go 语言上》,《我为什么放弃Go语言》,《Why worse is better》等话题。经常重温这些话题,每次都会有新发现。最忌手里有了一个语言,心里便容不下另一个语言。
忽略细节、语法或者设计,Go语言各种好用。考虑到这些因素,Go被喷出翔都不为过。
本文不打算在细节、语法或者设计上扯淡,只举些例子,说一说如何用Go语言写出还凑合的代码。
//代码来自 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,用的很克制,却又恰到好处。
Go语言的try catch?
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)
//代码来自《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》有异曲同工之妙。
示范代码:来自《Errors are vales》
//这种写法强烈不推荐!!!!这就是许多人说的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 //重要的事情说两遍:不推荐,但是个人小项目这样写,完全没问题。
推荐如下写法:
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 }
也推荐如下写法:
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?