golang文件傳輸服務


續上篇,本篇介紹一個完整的golang文件傳輸服務器。

完整的代碼可以看服務器,客戶端

網絡使用的框架如上篇介紹,這里就不再復述.

首先定義3個命令碼:

const (
    request_file = 1
    file_size = 2
    transfering = 3
)

request_file用於請求文件傳輸,附帶的命令參數是文件key.

file_size用於通告客戶端文件的大小.

transfering用於傳輸文件內容,附帶參數是文件內容的二進制數據.

服務器的文件配置示例

../learnyouhaskell.pdf=haskell
../golang.1.1.2.chm=golang
../NodeJS.pdf=NodeJS

上面的文件配置了3個文件可供傳輸=左邊是文件路徑,右邊是請求文件時使用的key.

服務器啟動時首先調用loadfile將文件導入到內存中,然后根據定義的key,將文件內容插入到字典filemap中:

func loadfile(){
    //從配置導入文件
    F,err := os.Open("./config.txt")
    if err != nil {
        fmt.Printf("config.txt open failed\n")
        return
    }
    filemap = make(map[string][]byte)
    bufferReader := bufio.NewReader(F)
    eof := false
    for !eof {
        line,err := bufferReader.ReadString('\n')
        if err == io.EOF{
            err = nil
            eof = true
        }else if err != nil{
            fmt.Printf("parse file error\n")
            return
        }
        if len(line) > 1 {
            line = line[0:len(line)-1]//drop '\n'
            fileconfig := strings.Split(line,"=")
            if len(fileconfig) == 2 {
                buf, err := ioutil.ReadFile(fileconfig[0])
                if err != nil {
                    fmt.Printf("%s load error\n",fileconfig[0])
                }else{  
                    filemap[fileconfig[1]] = buf
                    fmt.Printf("%s load success,key %s\n",fileconfig[0],fileconfig[1])
                }
            }
        }
    }

    if filemap["golang"] == nil {
        fmt.Printf("golang not found\n")
    }

    fmt.Printf("loadfile finish\n") 
}

接着是服務其的packet_handler:

func process_client(session *tcpsession.Tcpsession,rpk *packet.Rpacket){
    cmd,_ := rpk.Uint16()
    if cmd == request_file {
        if session.Ud() != nil {
            fmt.Printf("already in transfer session\n")
        }else
        {
            filename,_ := rpk.String()
            filecontent := filemap[filename]
            if filecontent == nil {
                fmt.Printf("%s not found\n",filename)
                session.Close()
            }else{
                fmt.Printf("request file %s\n",filename)
                tsession := &transfer_session{filecontent:filecontent,ridx:0}
                session.SetUd(tsession)

                wpk := packet.NewWpacket(packet.NewByteBuffer(64),false)
                wpk.PutUint16(file_size)
                wpk.PutUint32(uint32(len(filecontent)))
                session.Send(wpk,nil)
                tsession.send_file(session)
            }   
        }
    }else{
        fmt.Printf("cmd error,%d\n",cmd)
        session.Close()
    }
}

如果收到的消息是requestfile,首先查看請求的文件是否存在,如果存在則創建一個文件傳輸過程transfersession,

並將它與tcpsession綁定,然后發出一個文件大小通告包,緊接着立即調用send_file開始發送文件內容.

func (this *transfer_session)send_file(session *tcpsession.Tcpsession){
    remain := len(this.filecontent) - this.ridx
    sendsize := 0
    if remain >= 16000 {
        sendsize = 16000
    }else{
        sendsize = remain
    }
    wpk := packet.NewWpacket(packet.NewByteBuffer(uint32(sendsize)),false)
    wpk.PutUint16(transfering)
    wpk.PutBinary(this.filecontent[this.ridx:this.ridx+sendsize])
    session.Send(wpk,send_finish)
    this.ridx += sendsize
}

sendfile中根據當前發送位置判斷還有多少內容需要發送,如果剩余內容小於16000字節就將所剩數據一次性

發出,否則 發送16000字節的數據,並調整發送位置。注意到Send函數帶了一個sendfinish函數作為參數,其作用

是當數據包發送 完成后回調send_finish函數.

func send_finish (s interface{},wpk *packet.Wpacket){
    session := s.(*tcpsession.Tcpsession)
    tsession := session.Ud().(*transfer_session)
    if tsession.check_finish(){
        session.Close()
        return
    }
    tsession.send_file(session)
}

send_finish的作用是判斷文件是否已經發送完,如果發完斷開連接,否則接着發送剩余部分.

總結一下,golang用來編寫服務器應用還是相當方便的,很多細節問題在語言層面或系統庫里已經幫你解決掉了

,可以將主要的 精力放在邏輯的處理上.


免責聲明!

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



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