270 likes | 512 Views
Go networking. Peter Borovansk ý, KAI, I-18, borovan (a)ii.fmph.uniba.sk. Prejdeme si v Go tri úrovne tzv. TCP Stacku, a naprogramujeme klient/server aplikáciu cez sockety, príklad chat sntp udp klient (time server klient) f tp server & ftp klient w ebcrawler
E N D
Go networking Peter Borovanský, KAI, I-18, borovan(a)ii.fmph.uniba.sk Prejdeme si v Go tri úrovne tzv. TCP Stacku, a naprogramujeme • klient/server aplikáciu cez sockety, príklad chat • sntp udp klient(time server klient) • ftp server & ftp klient • webcrawler • tiny wiki – malá web app Zdroje: • http://golang.org/doc/articles/wiki/ • http://jan.newmarch.name/go
Client socket import("bufio""fmt""io""net")funcmain(){// vytvorenie spojenia socket-socket conn,err:=net.Dial("tcp","google.com:80") iferr!=nil{ fmt.Println("connectionerror:"+err.Error()) }else{ // písanie do conn : net.Conn fmt.Fprintf(conn,"HEAD/HTTP/1.0\r\n\r\n") r:=bufio.NewReader(conn)// bufio wrapper for{ line,_,err:=r.ReadLine()// čítanie z conn iferr==io.EOF{ break } fmt.Printf("%s\n",line) } } } net1/netclient1.go
RFC 4330 http://tools.ietf.org/html/rfc4330 Sntp klient conn,err:=net.Dial("udp","0.sk.pool.ntp.org:123") iferr!=nil{return} r:=bufio.NewReader(conn) w:=bufio.NewWriter(conn) data:=make([]byte,48) data[0]=3<<3|3 conn.SetDeadline(time.Now().Add(5*time.Second)) deferconn.Close() w.Write(data)//sendrequest w.Flush() data,_,err=r.ReadLine()//readresponse varsec,fracuint64 sec=uint64(data[43])|uint64(data[42])<<8|uint64(data[41])<<16|uint64(data[40])<<24 frac=uint64(data[47])|uint64(data[46])<<8|uint64(data[45])<<16|uint64(data[44])<<24 nsec:=sec*1e9 nsec+=(frac*1e9)>>32 t:=time.Date(1900,1,1,0,0,0,0,time.UTC).Add(time.Duration(nsec)).Local() fmt.Printf("Networktime:%v\n",t) sntp/sntpclient.go
Jednovláknový server import("bufio""fmt""io""net") funcmain(){ // porty <1024 majú špeciálne určenie ln,err:=net.Listen("tcp",":8080")// server socket iferr!=nil{ fmt.Println("connectionerror:"+err.Error()) }else{ conn,err:=ln.Accept()// blokuje kým ja niekto iferr!=nil{// nepokúsi o spojenie fmt.Println("error:"+err.Error()) }else{ handleConnection(conn)// vieme vytvoriť len } // jedno spojenie na “server” } } net2/netserver.go
HandleConnection funchandleConnection(connnet.Conn){ fmt.Println("handleConnection") r:=bufio.NewReader(conn)// wrapper na conn read stream for{ line,_,err:=r.ReadLine()// čítaj až do konca iferr==io.EOF{ break }// a píš na konzolu fmt.Printf("%s\n",line) } } net2/netserver.go
Viacvláknový server funcmain(){ ln,err:=net.Listen("tcp",":8080") iferr!=nil{ fmt.Println("connectionerror:"+err.Error()) }else{ for{ conn,err:=ln.Accept() iferr!=nil{// server by mal prežiť !!! fmt.Println("error:"+err.Error()) continue }// obslúženie konekcie spustíme v nezávislej gohandleConnection(conn)// gorutine } } } net3/netserver.go
Keď poznáme základy, skúsime vytvoriť jednoduchý chat-server umožnujúci: • viacero pripojení, • broadcastuje status všetkým ChatClient typeChatClientstruct{ clientIDint // poradové číslo klienta reader*bufio.Reader // reader a writer z/do konekcie writer*bufio.Writer // klienta } funcNewChatClient(clientIDint,connnet.Conn)*ChatClient{ return&ChatClient{// konštruktor vytvorí z ID a konekcie clientID:clientID, // ChatClient reader:bufio.NewReader(conn), writer:bufio.NewWriter(conn), } } net4/chatroom.go
ChatRoom typeChatRoomstruct{// všetci ChatClienti clients[]*ChatClient } funcNewChatRoom()*ChatRoom{// prázdny ChatRoom chatRoom:=&ChatRoom{ clients:make([]*ChatClient,0), } returnchatRoom } func(chr*ChatRoom)NewChatClient(connnet.Conn)*ChatClient{ chatclient:=NewChatClient(len(chr.clients)+1,conn) fmt.Printf("newclient:%d\n",chatclient.clientID) chr.clients=append(chr.clients,chatclient) returnchatclient } net4/chatroom.go
ChatRoom func(chr*ChatRoom)handleConnection(connnet.Conn){ chclient := chr.NewChatClient(conn) for{ line,_,err:=chclient.reader.ReadLine() iferr==io.EOF{ break } msg:=fmt.Sprintf("%d>%s\r\n",chclient.clientID,line) fmt.Print(msg)// výpis na konzolu chatroomu for_,client:=rangechr.clients{ ifclient.clientID!=chclient.clientID{ client.writer.WriteString(msg) client.writer.Flush() } }}} net4/chatroom.go
ChatRoom v akcii net4/chatroom.go
FTP Protokol Naprogramujeme časť FTP servera a klienta - podmnožinu FTP protokolu: • dir – obsah adresára • pwd – aktuálna cesta na serveri • cd newdir – zmeň aktuálnu cestu na serveri na newdir • put file – prenes lokálny súbor file na server • get file – prenes súbor file zo serveru do lokálneho adresára server na každú tcp-konekciu nezávisle zavolá gorutinu handleClient: read line from client for { switch line { ‘DIR’: send list directory to client ‘CD’ : change directory to newdir ‘PUT’: start file transfer from client } read line from client } client číta príkazy užívateľa a volá server read line from user for { switch line { ‘dir ....’: send ‘DIR …’ to server, display results ‘put ….’: send ‘PUT …’ to server and start file transfer file to server } read line from user }
FTP server funchandleClient(connnet.Conn){ deferconn.Close()// ak nastane panika, zavri spojenie r:=bufio.NewReader(conn)// reader ku klientovi w:=bufio.NewWriter(conn)// writer for{ line,_,_:=r.ReadLine()// čítaj povel words:=strings.Split(string(line),"")// rozlož switchstrings.ToUpper(words[0]){// podľa prvého caseCD:// ak ‘CD’ ifos.Chdir(words[1])==nil{ // volaj os... s,_:=os.Getwd()// zisti aktuálny w.WriteString(s+"\r\n")// napíš klientovi }else{ w.WriteString("error\r\n")// ak neexistuje } ftpserver.go
Skúška pomocou putty Problémy: • server si nepamätá lokálne hodnoty klienta, pwd... • autorizácia klienta • prenos súborov • šifrovanie obsahu ftpserver.go
FTP klient funcmain(){// client socket sa pripojí na server conn,err:=net.Dial("tcp",host+":8080") stdior:=bufio.NewReader(os.Stdin)// príkazy užívateľa r:=bufio.NewReader(conn)// reader od ftp server w:=bufio.NewWriter(conn)// writer k ftp serveru for{ line,_,_:=stdior.ReadLine()// príkaz od usera words:=strings.Split(string(line),"") switchstrings.ToUpper(words[0]){ // podľa prvého caseCD: w.WriteString("CD"+words[1]+"\r\n") w.Flush() // povedz CD ftp serveru response,_,_:=r.ReadLine()// čítaj jeho ifstring(response)=="error"{// reakciu fmt.Println("Failedtochange ftpclient.go
HTTP client • GET Request-URI CRLF • [GET | HEAD | POST ] Request-URI HTTP-Version CRLF • GET http://google:80 HTTP/1.0 url:="http://google.com" response,err:=http.Head(url) fmt.Println(response.Status)// 200 OK fork,v:=rangeresponse.Header{ // Content-Type: fmt.Println(k+":",v)}// [text/html; charset=ISO-8859-2] response,err=http.Get(url) fmt.Println("\nbody:") reader:=bufio.NewReader(response.Body) for{ line,_,err:=reader.ReadLine() iferr==io.EOF{ break httpclient.go
Čo s telom ? for{ line,_,err:=reader.ReadLine() iferr==io.EOF{break} strline:=string(line) varhttpRef=regexp.MustCompile( `(?i)href\s*=\s*(\"([^"]*\")|'[^']*'|([^'">\s]+))`) matches:=httpRef.FindAllString(strline,-1) for_,match:=rangematches{ fmt.Println(match) }} body: href="/search?" href="http://www.google.sk/imghp?hl=sk&tab=wi" href="http://maps.google.sk/maps?hl=sk&tab=wl" href="http://www.youtube.com/?gl=SK&tab=w1" href="https://mail.google.com/mail/?tab=wm" href="https://drive.google.com/?tab=wo" href="https://www.google.com/calendar?tab=wc" ... httpclient.go
Crawlusesfetchertorecursivelycrawl pagesstartingwithurl,toamaximumofdepth WebCrawler TODO: FetchURLsinparallel. Don'tfetchthesameURLtwice. funcCrawl71(urlstring,depthint,fetcherFetcher){ ifdepth<=0{ return } body,urls,err:=fetcher.Fetch(url) iferr!=nil{ fmt.Println(err) return }// najivné prehľadávanie do hĺbky, bez kontroly fmt.Printf("found:%s%q\n",url,body) for_,u:=rangeurls{ Crawl71(u,depth-1,fetcher) } return } http://tour.golang.org/#71
WebCrawling golang.org var fetcher = &fakeFetcher{ "http://golang.org/": &fakeResult{ "The Go Programming Language", []string{ "http://golang.org/pkg/", "http://golang.org/cmd/", }, }, "http://golang.org/pkg/": &fakeResult{ "Packages", []string{ "http://golang.org/", "http://golang.org/cmd/", "http://golang.org/pkg/fmt/", "http://golang.org/pkg/os/", }, }, "http://golang.org/pkg/fmt/": &fakeResult{ "Package fmt", []string{ "http://golang.org/", "http://golang.org/pkg/", }, }, "http://golang.org/pkg/os/": &fakeResult{ "Package os", []string{ "http://golang.org/", "http://golang.org/pkg/", }, }, } golang.org/pkg golang.org/os golang.org/cmd golang.org/pkg/fmt webcrawler.go
WebCrawlerR // HashMap navštívených linkov varvisited=make(map[string]bool) funcCrawlR(urlstring,depthint,maxDepthint){ ifdepth<=maxDepth{// ak nie som príliš hlboko visited[url]=true // ok, bol som tu ... suburls:=crawlPageR(url,depth)// získaj urls for_,url:=rangesuburls.suburls{// prejdi ich if_,seen:=visited[url];seen{// ak si tam continue// bol, preskoč } CrawlR(url,depth+1,maxDepth)// inak rekurzia } } } [0:http://golang.org/] "The Go Programming Language" [1:http://golang.org/pkg/] "Packages" not found: http://golang.org/cmd/ [2:http://golang.org/pkg/fmt/] "Package fmt" [2:http://golang.org/pkg/os/] "Package os" webcrawler.go
WebCrawlerR typeUrlsstruct{ depthint// hĺbka podstránky od koreňa suburls[]string// zoznam linkov na nej } funccrawlPageR(urlstring,depthint)*Urls{ body,urls,err:=fetcher.Fetch(url)// toto nemáme iferr!=nil{ fmt.Println(err) }else{ fmt.Printf("found[%d:%s]%q\n",depth,url,body) } return&Urls{depth+1,urls} } webcrawler.go
WebCrawler 2 var( // akonáhle prejdeme stránkus adresou url, všetky jej vnorené Urls zapíšeme ho do kanálu globalQueueOfUrls=make(chanUrls) totalRuns=0 // počet spustení crawlPage // t.j. veľkosť fronty nespracovaných Urls visited=make(map[string]bool)// navštívené urls ) funccrawlPage(urlstring,depthint){ body,urls,err:=fetcher.Fetch(url) if err ... fmt.Printf("[%d:%s]%q\n",depth,url,body) globalQueueOfUrls<-Urls{depth+1,urls} } webcrawler.go
WebCrawler 2 funcCrawl(urlstring,depthint){ totalRuns++ // spracuj hlavnú stránku visited[url]=true // navštívili sme ju gocrawlPage(url,0)// pridaj jej Urls do fronty fortotalRuns>0{ // kým je niečo vo fronte totalRuns--// dekrementuj veľkosť fronty next:=<-globalQueueOfUrls // vyber z fronty ifnext.depth>depth{continue}// prihlboko for_,url:=rangenext.suburls{// do hĺbky if_,seen:=visited[url];seen{continue } visited[url]=true totalRuns++ // nerekurzívne spracuj gocrawlPage(url,next.depth)// podstránky }}} webcrawler.go
Web1 packagemain import("fmt""net/http") type Handler interface { ServeHTTP(w ResponseWriter, r *Request)} typeFooint//FooimplementsHandler func(hFoo)ServeHTTP(whttp.ResponseWriter,r*http.Request){ fmt.Println(r.Header["User-Agent"]) fmt.Println(r.Host)// servlet, cgi-script fmt.Fprintf(w,"Foo!") } funcmain(){ varhFoo http.ListenAndServe("localhost:8080",h) } Zdroj:http://golang.org/doc/articles/wiki/
Web2 http.HandleFunc("/view/",viewHandler) http.HandleFunc("/edit/",editHandler) http.ListenAndServe(":8080",nil) • typePagestruct{ Titlestring Body[]byte } • func(p*Page)savePage()error// uloží stránku na server • funcloadPage(titlestring)(*Page,error)// načíta • funcviewHandler(whttp.ResponseWriter,r*http.Request){ title:=r.URL.Path[len("/view/"):] p,_:=loadPage(title) fmt.Fprintf(w,"<h1>%s</h1><div>%s</div>", p.Title,p.Body) } • funceditHandler(whttp.ResponseWriter,r*http.Request) Zdroj:http://golang.org/doc/articles/wiki/
MiniWiki funcviewHandler(whttp.ResponseWriter, r*http.Request,titlestring){ p,err:=loadPage(title) iferr!=nil{ http.Redirect(w,r,"/edit/"+title,http.StatusFound) return } t, err1 :=template.ParseFiles("view.html") err2 := t.Execute(w,p) } funcsaveHandler(whttp.ResponseWriter, r*http.Request,titlestring){ body:=r.FormValue("body") p:=&Page{Title:title,Body:[]byte(body)} err:=p.save() ... Zdroj:http://golang.org/doc/articles/wiki/