一、简介
go语言中的网络编程主要通过net包实现,net包提供了网络i/o接口,包括http、tcp/ip、udp、域名解析和unix域socket等。和大多数语言一样go可以使用几行代码便可以启动一个服务器,但是得益于goroutine的配合go实现的服务器拥有强大并发处理能力。
二、socket编程
socket又称套接字,应用程序通常通过套接字向网络发出请求或者应答网络请求。
socket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 我们知道网络 通信 都 是基于 ip+port 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。
go中socket编程实现起来非常方便,下面是处理流程
服务器端:
1、监听端口
2、接受客户端连接
3、创建goroutine处理连接
客户端:
1、建立连接
2、收发数据
3、关闭连接
服务端示例:
package mainimport ( "fmt" "net")func handle(conn net.conn) { //处理连接方法 defer conn.close() //关闭连接 for{ buf := make([]byte,100) n,err := conn.read(buf) //读取客户端数据 if err!=nil { fmt.println(err) return } fmt.printf("read data size %d msg:%s", n, string(buf[0:n])) msg := []byte("hello,world\n") conn.write(msg) //发送数据 }}func main() { fmt.println("start server....") listen,err := net.listen("tcp","0.0.0.0:3000") //创建监听 if err != nil{ fmt.println("listen failed! msg :" ,err) return } for{ conn,errs := listen.accept() //接受客户端连接 if errs != nil{ fmt.println("accept failed") continue } go handle(conn) //处理连接 }}
客户端示例:
package mainimport ( "bufio" "fmt" "net" "os" "strings")func main() { conn, err := net.dial("tcp", "127.0.0.1:3000") if err != nil { fmt.println("err dialing:", err.error()) return } defer conn.close() inputreader := bufio.newreader(os.stdin) for { str, _ := inputreader.readstring('\n') data := strings.trim(str, "\n") if data == "quit" { //输入quit退出 return } _, err := conn.write([]byte(data)) //发送数据 if err != nil { fmt.println("send data error:", err) return } buf := make([]byte,512) n,err := conn.read(buf) //读取服务端端数据 fmt.println("from server:", string(buf[:n])) }}
conn示例还提供其他方法:
type conn interface { // read reads data from the connection. // read can be made to time out and return an error with timeout() == true // after a fixed time limit; see setdeadline and setreaddeadline. read(b []byte) (n int, err error) //读取连接中数据 // write writes data to the connection. // write can be made to time out and return an error with timeout() == true // after a fixed time limit; see setdeadline and setwritedeadline. write(b []byte) (n int, err error) //发送数据 // close closes the connection. // any blocked read or write operations will be unblocked and return errors. close() error //关闭链接 // localaddr returns the local network address. localaddr() addr //返回本地连接地址 // remoteaddr returns the remote network address. remoteaddr() addr //返回远程连接的地址 // setdeadline sets the read and write deadlines associated // with the connection. it is equivalent to calling both // setreaddeadline and setwritedeadline. // // a deadline is an absolute time after which i/o operations // fail with a timeout (see type error) instead of // blocking. the deadline applies to all future and pending // i/o, not just the immediately following call to read or // write. after a deadline has been exceeded, the connection // can be refreshed by setting a deadline in the future. // // an idle timeout can be implemented by repeatedly extending // the deadline after successful read or write calls. // // a zero value for t means i/o operations will not time out. setdeadline(t time.time) error //设置链接读取或者写超时时间 // setreaddeadline sets the deadline for future read calls // and any currently-blocked read call. // a zero value for t means read will not time out. setreaddeadline(t time.time) error //单独设置读取超时时间 // setwritedeadline sets the deadline for future write calls // and any currently-blocked write call. // even if write times out, it may return n > 0, indicating that // some of the data was successfully written. // a zero value for t means write will not time out. setwritedeadline(t time.time) error//单独设置写超时时间}
三、go中http服务处理流程
简介
网络发展,很多网络应用都是构建再 http 服务基础之上。http 协议从诞生到现在,发展从1.0,1.1到2.0也不断再进步。除去细节,理解 http 构建的网络应用只要关注两个端---客户端(clinet)和服务端(server),两个端的交互来自 clinet 的 request,以及server端的response。所谓的http服务器,主要在于如何接受 clinet 的 request,并向client返回response。接收request的过程中,最重要的莫过于路由(router),即实现一个multiplexer器。go中既可以使用内置的mutilplexer --- defautservemux,也可以自定义。multiplexer路由的目的就是为了找到处理器函数(handler),后者将对request进行处理,同时构建response。
最后简化的请求处理流程为:
clinet -> requests -> [multiplexer(router) -> handler -> response -> clinet
因此,理解go中的http服务,最重要就是要理解multiplexer和handler,golang中的multiplexer基于servemux结构,同时也实现了handler接口。
对象说明:
1、hander函数: 具有func(w http.responsewriter, r *http.requests)签名的函数
2、handler函数: 经过handlerfunc结构包装的handler函数,它实现了servehttp接口方法的函数。调用handler处理器的servehttp方法时,即调用handler函数本身。
3、handler对象:实现了handler接口servehttp方法的结构。
handler处理器和handler对象的差别在于,一个是函数,另外一个是结构,它们都有实现了servehttp方法。很多情况下它们的功能类似,下文就使用统称为handler。
handler
golang没有继承,类多态的方式可以通过接口实现。所谓接口则是定义声明了函数签名,任何结构只要实现了与接口函数签名相同的方法,就等同于实现了接口。go的http服务都是基于handler进行处理。
type handler interface { servehttp(responsewriter, *request)}
任何结构体,只要实现了servehttp方法,这个结构就可以称之为handler对象。servemux会使用handler并调用其servehttp方法处理请求并返回响应。
servemux
源码部分:
type servemux struct { mu sync.rwmutex m map[string]muxentry hosts bool }type muxentry struct { explicit bool h handler pattern string}
servemux结构中最重要的字段为m,这是一个map,key是一些url模式,value是一个muxentry结构,后者里定义存储了具体的url模式和handler。
当然,所谓的servemux也实现了servehttp接口,也算是一个handler,不过servemux的servehttp方法不是用来处理request和respone,而是用来找到路由注册的handler,后面再做解释。
server
除了servemux和handler,还有一个结构server需要了解。从http.listenandserve的源码可以看出,它创建了一个server对象,并调用server对象的listenandserve方法:
func listenandserve(addr string, handler handler) error { server := &server{addr: addr, handler: handler} return server.listenandserve()}
查看server的结构如下:
type server struct { addr string handler handler readtimeout time.duration writetimeout time.duration tlsconfig *tls.config maxheaderbytes int tlsnextproto map[string]func(*server, *tls.conn, handler) connstate func(net.conn, connstate) errorlog *log.logger disablekeepalives int32 nextprotoonce sync.once nextprotoerr error }
server结构存储了服务器处理请求常见的字段。其中handler字段也保留handler接口。如果server接口没有提供handler结构对象,那么会使用defautservemux做multiplexer,后面再做分析。
创建http服务
创建一个http服务,大致需要经历两个过程,首先需要注册路由,即提供url模式和handler函数的映射,其次就是实例化一个server对象,并开启对客户端的监听。
http.handlefunc("/", indexhandler)http.listenandserve("127.0.0.1:8000", nil)或server := &server{addr: addr, handler: handler}server.listenandserve()
示例:
package mainimport ("fmt""net/http")func hello(w http.responsewriter, r *http.request) {fmt.println("hello world.")fmt.fprintf(w, "hello world.\n")}func main() {http.handlefunc("/", hello)err := http.listenandserve("0.0.0.0:6000", nil)if err != nil {fmt.println("http listen failed.")}}//curl http://127.0.0.1:6000 // 结果:hello world
路由注册
net/http包暴露的注册路由的api很简单,http.handlefunc选取了defaultservemux作为multiplexer:
func handlefunc(pattern string, handler func(responsewriter, *request)) { defaultservemux.handlefunc(pattern, handler)}
defaultservemux是servemux的一个实例。当然http包也提供了newservemux方法创建一个servemux实例,默认则创建一个defaultservemux:
// newservemux allocates and returns a new servemux.func newservemux() *servemux { return new(servemux) }// defaultservemux is the default servemux used by serve.var defaultservemux = &defaultservemuxvar defaultservemux servemux
defaultservemux的handlefunc(pattern, handler)方法实际是定义在servemux下的:
// handlefunc registers the handler function for the given pattern.func (mux *servemux) handlefunc(pattern string, handler func(responsewriter, *request)) { mux.handle(pattern, handlerfunc(handler))}
handlerfunc是一个函数类型。同时实现了handler接口的servehttp方法。使用handlerfunc类型包装一下路由定义的indexhandler函数,其目的就是为了让这个函数也实现servehttp方法,即转变成一个handler处理器(函数)。
type handlerfunc func(responsewriter, *request)// servehttp calls f(w, r).func (f handlerfunc) servehttp(w responsewriter, r *request) { f(w, r)}
我们最开始写的例子中
http.handlefunc("/",indexhandler)
这样 indexhandler 函数也有了servehttp方法。servemux的handle方法,将会对pattern和handler函数做一个map映射:
func listenandserve(addr string, handler handler) error { server := &server{addr: addr, handler: handler} return server.listenandserve()}// listenandserve listens on the tcp network address srv.addr and then// calls serve to handle requests on incoming connections.// accepted connections are configured to enable tcp keep-alives.// if srv.addr is blank, ":http" is used.// listenandserve always returns a non-nil error.func (srv *server) listenandserve() error { addr := srv.addr if addr == "" { addr = ":http" } ln, err := net.listen("tcp", addr) if err != nil { return err } return srv.serve(tcpkeepalivelistener{ln.(*net.tcplistener)})}
server的listenandserve方法中,会初始化监听地址addr,同时调用listen方法设置监听。最后将监听的tcp对象传入serve方法:
// serve accepts incoming connections on the listener l, creating a// new service goroutine for each. the service goroutines read requests and// then call srv.handler to reply to them.//// for http/2 support, srv.tlsconfig should be initialized to the// provided listener's tls config before calling serve. if// srv.tlsconfig is non-nil and doesn't include the string "h2" in// config.nextprotos, http/2 support is not enabled.//// serve always returns a non-nil error. after shutdown or close, the// returned error is errserverclosed.func (srv *server) serve(l net.listener) error { defer l.close() if fn := testhookserverserve; fn != nil { fn(srv, l) } var tempdelay time.duration // how long to sleep on accept failure if err := srv.setuphttp2_serve(); err != nil { return err } srv.tracklistener(l, true) defer srv.tracklistener(l, false) basectx := context.background() // base is always background, per issue 16220 ctx := context.withvalue(basectx, servercontextkey, srv) for { rw, e := l.accept() if e != nil { select { case <-srv.getdonechan(): return errserverclosed default: } if ne, ok := e.(net.error); ok && ne.temporary() { if tempdelay == 0 { tempdelay = 5 * time.millisecond } else { tempdelay *= 2 } if max := 1 * time.second; tempdelay > max { tempdelay = max } srv.logf("http: accept error: %v; retrying in %v", e, tempdelay) time.sleep(tempdelay) continue } return e } tempdelay = 0 c := srv.newconn(rw) c.setstate(c.rwc, statenew) // before serve can return go c.serve(ctx) }}
监听开启之后,一旦客户端请求到底,go就开启一个协程处理请求,主要逻辑都在serve方法之中。
serve方法比较长,其主要职能就是,创建一个上下文对象,然后调用listener的accept方法用来 获取连接数据并使用newconn方法创建连接对象。最后使用goroutine协程的方式处理连接请求。因为每一个连接都开起了一个协程,请求的上下文都不同,同时又保证了go的高并发。serve也是一个长长的方法:
// serve a new connection.func (c *conn) serve(ctx context.context) { c.remoteaddr = c.rwc.remoteaddr().string() ctx = context.withvalue(ctx, localaddrcontextkey, c.rwc.localaddr()) defer func() { if err := recover(); err != nil && err != erraborthandler { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.stack(buf, false)] c.server.logf("http: panic serving %v: %v\n%s", c.remoteaddr, err, buf) } if !c.hijacked() { c.close() c.setstate(c.rwc, stateclosed) } }() if tlsconn, ok := c.rwc.(*tls.conn); ok { if d := c.server.readtimeout; d != 0 { c.rwc.setreaddeadline(time.now().add(d)) } if d := c.server.writetimeout; d != 0 { c.rwc.setwritedeadline(time.now().add(d)) } if err := tlsconn.handshake(); err != nil { c.server.logf("http: tls handshake error from %s: %v", c.rwc.remoteaddr(), err) return } c.tlsstate = new(tls.connectionstate) *c.tlsstate = tlsconn.connectionstate() if proto := c.tlsstate.negotiatedprotocol; validnpn(proto) { if fn := c.server.tlsnextproto[proto]; fn != nil { h := initnpnrequest{tlsconn, serverhandler{c.server}} fn(c.server, tlsconn, h) } return } } // http/1.x from here on. ctx, cancelctx := context.withcancel(ctx) c.cancelctx = cancelctx defer cancelctx() c.r = &connreader{conn: c} c.bufr = newbufioreader(c.r) c.bufw = newbufiowritersize(checkconnerrorwriter{c}, 4<<10) for { w, err := c.readrequest(ctx) if c.r.remain != c.server.initialreadlimitsize() { // if we read any bytes off the wire, we're active. c.setstate(c.rwc, stateactive) } if err != nil { const errorheaders = "\r\ncontent-type: text/plain; charset=utf-8\r\nconnection: close\r\n\r\n" if err == errtoolarge { // their http client may or may not be // able to read this if we're // responding to them and hanging up // while they're still writing their // request. undefined behavior. const publicerr = "431 request header fields too large" fmt.fprintf(c.rwc, "http/1.1 "+publicerr+errorheaders+publicerr) c.closewriteandwait() return } if iscommonnetreaderror(err) { return // don't reply } publicerr := "400 bad request" if v, ok := err.(badrequesterror); ok { publicerr = publicerr + ": " + string(v) } fmt.fprintf(c.rwc, "http/1.1 "+publicerr+errorheaders+publicerr) return } // expect 100 continue support req := w.req if req.expectscontinue() { if req.protoatleast(1, 1) && req.contentlength != 0 { // wrap the body reader with one that replies on the connection req.body = &expectcontinuereader{readcloser: req.body, resp: w} } } else if req.header.get("expect") != "" { w.sendexpectationfailed() return } c.curreq.store(w) if requestbodyremains(req.body) { registeronhiteof(req.body, w.conn.r.startbackgroundread) } else { if w.conn.bufr.buffered() > 0 { w.conn.r.closenotifyfrompipelinedrequest() } w.conn.r.startbackgroundread() } // http cannot have multiple simultaneous active requests.[*] // until the server replies to this request, it can't read another, // so we might as well run the handler in this goroutine. // [*] not strictly true: http pipelining. we could let them all process // in parallel even if their responses need to be serialized. // but we're not going to implement http pipelining because it // was never deployed in the wild and the answer is http/2. serverhandler{c.server}.servehttp(w, w.req) w.cancelctx() if c.hijacked() { return } w.finishrequest() if !w.shouldreuseconnection() { if w.requestbodylimithit || w.closedrequestbodyearly() { c.closewriteandwait() } return } c.setstate(c.rwc, stateidle) c.curreq.store((*response)(nil)) if !w.conn.server.dokeepalives() { // we're in shutdown mode. we might've replied // to the user without "connection: close" and // they might think they can send another // request, but such is life with http/1.1. return } if d := c.server.idletimeout(); d != 0 { c.rwc.setreaddeadline(time.now().add(d)) if _, err := c.bufr.peek(4); err != nil { return } } c.rwc.setreaddeadline(time.time{}) }}serve方法
使用defer定义了函数退出时,连接关闭相关的处理。然后就是读取连接的网络数据,并处理读取完毕时候的状态。接下来就是调用serverhandler{c.server}.servehttp(w, w.req)方法处理请求了。最后就是请求处理完毕的逻辑。
serverhandler是一个重要的结构,它近有一个字段,即server结构,同时它也实现了handler接口方法servehttp,并在该接口方法中做了一个重要的事情,初始化multiplexer路由多路复用器。
如果server对象没有指定handler,则使用默认的defaultservemux作为路由multiplexer。并调用初始化handler的servehttp方法。
// serverhandler delegates to either the server's handler or// defaultservemux and also handles "options *" requests.type serverhandler struct { srv *server}func (sh serverhandler) servehttp(rw responsewriter, req *request) { handler := sh.srv.handler if handler == nil { handler = defaultservemux } if req.requesturi == "*" && req.method == "options" { handler = globaloptionshandler{} } handler.servehttp(rw, req)}
这里defaultservemux的servehttp方法其实也是定义在servemux结构中的,相关代码如下:
// find a handler on a handler map given a path string.// most-specific (longest) pattern wins.func (mux *servemux) match(path string) (h handler, pattern string) { // check for exact match first. v, ok := mux.m[path] if ok { return v.h, v.pattern } // check for longest valid match. var n = 0 for k, v := range mux.m { if !pathmatch(k, path) { continue } if h == nil || len(k) > n { n = len(k) h = v.h pattern = v.pattern } } return}func (mux *servemux) handler(r *request) (h handler, pattern string) { // connect requests are not canonicalized. if r.method == "connect" { return mux.handler(r.host, r.url.path) } // all other requests have any port stripped and path cleaned // before passing to mux.handler. host := striphostport(r.host) path := cleanpath(r.url.path) if path != r.url.path { _, pattern = mux.handler(host, path) url := *r.url url.path = path return redirecthandler(url.string(), statusmovedpermanently), pattern } return mux.handler(host, r.url.path)}// handler is the main implementation of handler.// the path is known to be in canonical form, except for connect methods.func (mux *servemux) handler(host, path string) (h handler, pattern string) { mux.mu.rlock() defer mux.mu.runlock() // host-specific pattern takes precedence over generic ones if mux.hosts { h, pattern = mux.match(host + path) } if h == nil { h, pattern = mux.match(path) } if h == nil { h, pattern = notfoundhandler(), "" } return}// servehttp dispatches the request to the handler whose// pattern most closely matches the request url.func (mux *servemux) servehttp(w responsewriter, r *request) { if r.requesturi == "*" { if r.protoatleast(1, 1) { w.header().set("connection", "close") } w.writeheader(statusbadrequest) return } h, _ := mux.handler(r) h.servehttp(w, r)}
mux的servehttp方法通过调用其handler方法寻找注册到路由上的handler函数,并调用该函数的servehttp方法,本例则是indexhandler函数。
mux的handler方法对url简单的处理,然后调用handler方法,后者会创建一个锁,同时调用match方法返回一个handler和pattern。 在match方法中,mux的m字段是map[string]muxentry图,后者存储了pattern和handler处理器函数,因此通过迭代m寻找出注册路由的patten模式与实际url匹配的handler函数并返回。
返回的结构一直传递到mux的servehttp方法,接下来调用handler函数的servehttp方法,即indexhandler函数,然后把response写到http.requestwirter对象返回给客户端。
上述函数运行结束即`serverhandler{c.server}.servehttp(w, w.req)`运行结束。接下来就是对请求处理完毕之后上希望和连接断开的相关逻辑。 至此,golang中一个完整的http服务介绍完毕,包括注册路由,开启监听,处理连接,路由处理函数。
总结
多数的web应用基于http协议,客户端和服务器通过request-response的方式交互。一个server并不可少的两部分莫过于路由注册和连接处理。golang通过一个servemux实现了的multiplexer路由多路复用器来管理路由。同时提供一个handler接口提供servehttp用来实现handler处理其函数,后者可以处理实际request并构造response。
servemux和handler处理器函数的连接桥梁就是handler接口。servemux的servehttp方法实现了寻找注册路由的handler的函数,并调用该handler的servehttp方法。
servehttp方法就是真正处理请求和构造响应的地方。 回顾go的http包实现http服务的流程,可见大师们的编码设计之功力。学习有利提高自身的代码逻辑组织能力。更好的学习方式除了阅读,就是实践,接下来,我们将着重讨论来构建http服务。尤其是构建http中间件函数。
四、http客户端工具
net/http不仅提供了服务端处理,还提供了客户端处理功能。
http包中提供了get、post、head、postform方法实现http请求:
//getfunc get(url string) (resp *response, err error) { return defaultclient.get(url)}//postfunc post(url string, contenttype string, body io.reader) (resp *response, err error) { return defaultclient.post(url, contenttype, body)}//headfunc head(url string) (resp *response, err error) { return defaultclient.head(url)}//postformfunc postform(url string, data url.values) (resp *response, err error) { return defaultclient.postform(url, data)}
get请求示例
package mainimport ( "fmt" "net/http" "log" "reflect" "bytes")func main() { resp, err := http.get("http://www.baidu.com") if err != nil { // 错误处理 log.println(err) return } defer resp.body.close() //关闭链接 headers := resp.header for k, v := range headers { fmt.printf("k=%v, v=%v\n", k, v) //所有头信息 } fmt.printf("resp status %s,statuscode %d\n", resp.status, resp.statuscode) fmt.printf("resp proto %s\n", resp.proto) fmt.printf("resp content length %d\n", resp.contentlength) fmt.printf("resp transfer encoding %v\n", resp.transferencoding) fmt.printf("resp uncompressed %t\n", resp.uncompressed) fmt.println(reflect.typeof(resp.body)) buf := bytes.newbuffer(make([]byte, 0, 512)) length, _ := buf.readfrom(resp.body) fmt.println(len(buf.bytes())) fmt.println(length) fmt.println(string(buf.bytes()))}
使用http.do设置请求头、cookie等
package mainimport ( "net/http" "strings" "io/ioutil" "log" "fmt")func main() { client := &http.client{} req, err := http.newrequest("post", "http://www.baidu.com", strings.newreader("name=xxxx&passwd=xxxx")) if err != nil { fmt.println(err) return } req.header.set("content-type", "application/x-www-form-urlencoded; charset=utf-8") //设置请求头信息 resp, err := client.do(req) defer resp.body.close() body, err := ioutil.readall(resp.body) if err != nil { log.println(err) return } var res string res = string(body[:]) fmt.println(res)}
post请求示例
package mainimport ( "net/http" "strings" "fmt" "io/ioutil")func main() { resp, err := http.post("http://www.baidu.com", "application/x-www-form-urlencoded", strings.newreader("username=xxx&password=xxxx")) if err != nil { fmt.println(err) return } defer resp.body.close() body, err := ioutil.readall(resp.body) if err != nil { fmt.println(err) return } fmt.println(string(body))}
postform请求示例
package mainimport ( "net/http" "fmt" "io/ioutil" "net/url")func main() { postparam := url.values{ "name": {"wd"}, "password": {"1234"}, } resp, err := http.postform("https://cn.bing.com/", postparam) if err != nil { fmt.println(err) return } defer resp.body.close() body, err := ioutil.readall(resp.body) if err != nil { fmt.println(err) return } fmt.println(string(body))}
以上就是go语言网络编程、http处理流程详情的详细内容。
