对于golang而言,要搭建一个服务器端十分简单,仅仅只需几十行代码就可以实现:
代码引用自:
https://github.com/pmlpml/golang-learning/tree/master/web/basic
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
type MyMux struct {
defaultMux *http.ServeMux
}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Println("do some extension here!")
if p.defaultMux == nil {
p.defaultMux = http.DefaultServeMux
}
p.defaultMux.ServeHTTP(w, r)
return
}
func sayhelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() //解析参数,默认是不会解析的
fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Println(r.Form["url_long"])
for k, v := range r.Form {
fmt.Println("key:", k)
fmt.Println("val:", strings.Join(v, ""))
}
fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
}
func main() {
http.HandleFunc("/", sayhelloName) //设置访问的路由
err := http.ListenAndServe(":9090", &MyMux{}) //设置监听的端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
那么,它是在golang中是如何实现的呢?我们可以通过一些工具跟踪分析源码。代码架构图:
ListenAndServe(addr string, handler Handler)
+ server.ListenAndServe()
| net.Listen("tcp", addr)
+ srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
| srv.setupHTTP2_Serve()
| baseCtx := context.Background()
+ for {}
| l.Accept()
| + select ... //为什么
| c := srv.newConn(rw)
| c.setState(c.rwc, StateNew) // before Serve can return
+ go c.serve(ctx) // 新的链接 goroutine
| ... // 构建 w , r
| serverHandler{c.server}.ServeHTTP(w, w.req)
| ... // after Serve
1. ListenAndServe函数
函数源码解读如下:
func ListenAndServe(addr string, handler Handler) error {
//创建一个Server并初始化
server := &Server{Addr: addr, Handler: handler}
//返回error
return server.ListenAndServe()
}
2. net.ListenAndServe
函数源码解读如下:
func (srv *Server) ListenAndServe() error {
//根据Server结构体定义,addr为空,则赋值为":http"
addr := srv.Addr
if addr == "" {
addr = ":http"
}
//监听地址为addr的tcp网络,返回一个Listener接口及error
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
//建立一个TCP keep-alive监听器,处理请求
//使用了类型断言,ln接口是否实现了net.TCPListener接口
//返回error
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
3. net.Listen
源码解读如下:
func Listen(network, address string) (Listener, error) {
//解析地址,返回地址列表
//假如err = nil, 则addrs至少包含一个地址
addrs, err := DefaultResolver.resolveAddrList(context.Background(), "listen", network, address, nil)
if err != nil {
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
}
var l Listener
//返回addrs中第一个满足IPv4的地址,并做类型断言
//根据不同的地址类型,调用不同的监听方法
//可以了解TCP socket与Unix socket区分
switch la := addrs.first(isIPv4).(type) {
case *TCPAddr:
l, err = ListenTCP(network, la)
case *UnixAddr:
l, err = ListenUnix(network, la)
default:
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
}
if err != nil {
return nil, err // l is non-nil interface containing nil pointer
}
return l, nil
}
4. srv.Serve
源码及分析如下:
func (srv *Server) Serve(l net.Listener) error {
//延迟关闭监听器l,防止内存泄漏
defer l.Close()
//设置srv的校验规则,如果设置,则执行。
//fn为函数
if fn := testHookServerServe; fn != nil {
fn(srv, l)
}
//设置接收到错误后睡眠时间,也即超时
var tempDelay time.Duration
配置HTTP/2
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
//跟踪Listener
srv.trackListener(l, true)
defer srv.trackListener(l, false)
//context包在下文详细介绍
//context用于在不同的go goroutine进行传递数据、取消signal等
//Context对象会形成一个树,Background为树的根;该树的根节点与叶节点为继承关系
baseCtx := context.Background() // base is always background, per Issue 16220
//将请求作用域的数据与 Context 对象建立关系
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
//等待并返回下一个listener(即l得下一个)
rw, e := l.Accept()
if e != nil {
//出现错误,执行select
//从srv.getDoneChan中读到数据,则返回ErrServerClosed
//否则,继续执行
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
//类型断言。如果e为net.Error类型变量,且为临时错误
if ne, ok := e.(net.Error); ok && ne.Temporary() {
//tempDelay为0,则赋值为5毫秒
//否则,乘2
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
//最大超时为1秒
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
//为下一个listener新创建一个连接
c := srv.newConn(rw)
//设置状态
c.setState(c.rwc, StateNew)
//新建一个线程(goroutine)处理该连接的一切请求
go c.serve(ctx)
}
}
关于context:
在 Go http包的Server中,每一个请求在都有一个对应的 goroutine 去处理。请求处理函数通常会启动额外的 goroutine 用来访问后端服务,比如数据库和RPC服务。用来处理一个请求的 goroutine 通常需要访问一些与请求特定的数据,比如终端用户的身份认证信息、验证相关的token、请求的截止时间。而context包则是为了让我们更好地处理goroutine之间的交互而实现的。
了解更多:Go语言并发模型:使用 context
关于select:
golang 的 select 的功能和 select, poll, epoll 相似, 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。
关于类型断言:
类型断言是interface的一种特殊用法。
5. c.serve
源码及分析如下:
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
//将请求作用域的数据与 Context 对象建立关系
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)
}
}()
//建立三次握手连接,构造w,r
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
}
}
//解析http1.1协议
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 {
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请求。
//由于http做了序列化处理,所以这里可以一次性处理完一个http请求
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{})
}
}
以上,便是golang web服务流程的分析了,希望能够让大家对go搭建服务器的流程有一个更加深入的了解。