golang初體驗


學習golang的時間斷斷續續加起來也有將近一個月了,這期間都是偶看翻幾頁書,沒有寫過實際的代碼.最近做一個app項目,是一個展示類 的軟件,當客戶要看某個圖片時首先向服務器發出一個請求,比對圖片的版本,如果版本與本地一致,則直接顯示,如果版本落后了則由服務 器將最新的版本發送給客戶端.

對服務器的需求就是一個簡單的版本比對和文件傳輸,於是打算用go去實現,正好也可以練練手.

在設計上,受到以往框架設計的影響,還是使用了wpacket,rpacket和共享buffer這個方案,不同的地方是go的網絡API不支持 gather io,所以buffer沒有被實現為buffer list,而是一整塊的連續內存,當buffer空間不足時需要手動的去擴展內存.

下面說點與以往用C設計網絡框架不同的地方,對於已經習慣了使用異步回調方式來使用網絡的我,剛開始用go來做一個網絡框架還是稍有不適. go的網絡API都是同步的,也不存在select,epoll,poll這類東西,這也就意味着需要使用goroutine來實現並發.

我的做法是這樣的,首先定義了一個類型Tcpconnection

type Tcpsession struct{
    Conn net.Conn
    Packet_que chan interface{}
    Send_que chan *packet.Wpacket
    raw bool
    send_close bool
}

其中Conn成員是連接對象,Packet_que是一個管道,用於接收收到的網絡包和連接事件.Send_que是另一個管道,用來將需要發送 的數據包傳給sender。

func NewTcpSession(conn net.Conn,raw bool)(*Tcpsession){
    session := &Tcpsession{Conn:conn,Packet_que:make(chan interface{},1024),Send_que:make(chan *packet.Wpacket,1024),raw:raw,send_close:false}
    if raw{
        go dorecv_raw(session)
    }else{
        go dorecv(session)
    }
    go dosend(session)
    return session
}

在建立一個新連接之后,啟動兩個goroutine分別用於執行recv和send.

func dorecv_raw(session *Tcpsession){
    for{
        recvbuf := make([]byte,packet.Max_bufsize)
        _,err := session.Conn.Read(recvbuf)
        if err != nil {
            session.Packet_que <- "rclose"
            return
        }
        rpk := packet.NewRpacket(packet.NewBufferByBytes(recvbuf),true)
        session.Packet_que <- rpk
    }
}

func dosend(session *Tcpsession){
    for{
        wpk,ok :=  <-session.Send_que
        if !ok {
            return
        }
        _,err := session.Conn.Write(wpk.Buffer().Bytes())
        if err != nil {
            session.send_close = true
            return
        }
        if wpk.Fn_sendfinish != nil{
            wpk.Fn_sendfinish(session,wpk)
        }
    }
}

dorecv的工就只是簡單的接收數據,將收到的原始數據打包成rpacket,然后寫到管道Packet_que中。 dosend則是從'Send_que'中提取出要待發送的wpacket,然后send出去.

然后來看下主過程:

func main(){
    service := ":8010"
    tcpAddr,err := net.ResolveTCPAddr("tcp4", service)
    if err != nil{
        fmt.Printf("ResolveTCPAddr")
    }
    listener, err := net.ListenTCP("tcp", tcpAddr)
    if err != nil{
        fmt.Printf("ListenTCP")
    }
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        session := tcpsession.NewTcpSession(conn,true)
        fmt.Printf("a client comming\n")
        go tcpsession.ProcessSession(session,process_client,session_close)
    }
}

主過程等待在listen上,每當接受一個新的連接就用這個連接作為參數創建一個Tcpsession,然后啟動一個 goroutine運行ProcessSession

func ProcessSession(tcpsession *Tcpsession,process_packet func (*Tcpsession,*packet.Rpacket),session_close func (*Tcpsession)){
    for{
        msg,ok := <- tcpsession.Packet_que
        if !ok {
            fmt.Printf("client disconnect\n")
            return
        }
        switch msg.(type){
            case * packet.Rpacket:
                rpk := msg.(*packet.Rpacket)
                process_packet(tcpsession,rpk)
            case string:
                str := msg.(string)
                if str == "rclose"{
                    session_close(tcpsession)
                    close(tcpsession.Packet_que)
                    close(tcpsession.Send_que)
                    tcpsession.Conn.Close()
                    return
                }
        }
    }
}

ProcessSession的工作就是不斷的從Packet_que中取出消息,如果消息是一個rpacket就回調使用者傳進來的process_packet函數。 如果是一個網絡連接斷開的事件則清理這個Tcpsession.

對每個連接,使用3個goroutine,2個channel,一個goroutine用於發送,一個用於接收和拆包,一個用於處理數據包.這個模式基本上就是標准的 go模式了.至於性能,在測試程序中我沒有啟用goroutine在多核心上運行的特性,也就說整個進程就是一個單線程的程序.其效率並不比實現同樣功能 的C程序差.

 

項目地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM