博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
节操,代码,修养,妹子和其他(Go语言版)
阅读量:6696 次
发布时间:2019-06-25

本文共 6896 字,大约阅读时间需要 22 分钟。

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,用的很克制,却又恰到好处。

至于defer和panic

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》有异曲同工之妙。

err的一种处理方式

示范代码:来自《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 errorwrite := 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 onif 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性能,可以参考 。

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

So, Why worse is better?

你可能感兴趣的文章
栈(C语言实现)
查看>>
SQL 建立临时表和变量表
查看>>
python-灰色预测平均房价趋势kera深度学习库的介绍
查看>>
linux——查看系统日志错误并解决
查看>>
cuda+ffmpeg+opengl解码rtsp h264码流多路
查看>>
Android权限大全代码
查看>>
svn:previous operation has not finished
查看>>
PHP Socket 编程进阶指南
查看>>
PHP-CPP开发扩展(一)
查看>>
Git常用命令
查看>>
【html】使用img标签和背景图片之间的区别
查看>>
JDK源码阅读(一) ArrayList
查看>>
Quartz1.8.5例子(六)
查看>>
leetcode524
查看>>
leetcode806
查看>>
(29)odoo的可用小图标
查看>>
MVC ViewBag传值
查看>>
通过面试题学习零散知识:Java面试题整理
查看>>
达成目标5步法则——雷达里奥/核聚
查看>>
CentOS虚拟机通过主机网络上网
查看>>