續上篇,本篇介紹一個完整的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用來編寫服務器應用還是相當方便的,很多細節問題在語言層面或系統庫里已經幫你解決掉了
,可以將主要的 精力放在邏輯的處理上.