语言Web Server实现

Go的第三方Web库

在Go语言当中有很多知名的Web服务,例如ginbeego,在它们的描述中都提到了high-performance,我们的文章会从最原始的socket来实现一个简单的web服务器。实现的服务与gin、beego做一次性能对比,对比工具使用ab(我们只做了一个不严肃的性能测试,我们只能去做个人能做的事情)。

Go语言TCP服务的原理

一般语言实现

c 语言去实现一个简单的http server并不难,但如果要保持高性能,例如nginx自己做了所有的一切,并不是很容易。你需要去管理连接,并采用合理的io模型和策略。下面是一个最基本的流程。

服务器端的工作主要是listen()和accept()连接,然后进入到交互过程,最终close()连接。

go语言实现

服务器端和c语言差不多,客户端省掉了一个bind()过程,合并成Dial(),翻译成中文为拨号。

实现代码

gin 和 beego 是怎样实现的

我们先看看gin 和 beego都做了什么 它们都调用了net库下的net/http/server下的Serve()方法实现HTTP服务器,因此如果我们的代码与该方法一致或者做了精简,那么理论上性能是一致的。并没有做更多的额外的优化和处理。

net/http/server 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.
//
// HTTP/2 support is only enabled if the Listener returns *tls.Conn
// connections and they were configured with "h2" in the TLS
// Config.NextProtos.
//
// Serve always returns a non-nil error and closes l.
// After Shutdown or Close, the returned error is ErrServerClosed.
func (srv *Server) Serve(l net.Listener) error {
	if fn := testHookServerServe; fn != nil {
		fn(srv, l) // call hook with unwrapped listener
	}

	origListener := l
	l = &onceCloseListener{Listener: l}
	defer l.Close()

	if err := srv.setupHTTP2_Serve(); err != nil {
		return err
	}

	if !srv.trackListener(&l, true) {
		return ErrServerClosed
	}
	defer srv.trackListener(&l, false)

	baseCtx := context.Background()
	if srv.BaseContext != nil {
		baseCtx = srv.BaseContext(origListener)
		if baseCtx == nil {
			panic("BaseContext returned a nil context")
		}
	}

	var tempDelay time.Duration // how long to sleep on accept failure

	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, err := l.Accept()
		if err != nil {
			select {
			case <-srv.getDoneChan():
				return ErrServerClosed
			default:
			}
			if ne, ok := err.(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", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return err
		}
		connCtx := ctx
		if cc := srv.ConnContext; cc != nil {
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
				panic("ConnContext returned nil")
			}
		}
		tempDelay = 0
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
		go c.serve(connCtx)
	}
}

实现一个 http 服务器

client

server

server端与net/http/server下的Serve()方法几乎相同,只是做了更大的精简,核心是for + Accept() + goroutine。

go语言的goroutine本身就有极高的性能,因此替代了c语言自己去实现connections pool的麻烦,简单调用就可以实现很高的性能。

性能测试

使用ab做一个简单的性能测试,不是很严谨,但足够说明问题。我们看一个最终结果的对比图,没有本质上的差别大概都在每秒3万次左右。(并发20,总请求是10000次)

server
gin server
beego server

Requests per second

30682.19

33626.22

32875.27

测试在实现的 web server

测试 gin web server

测试 beego web server

配套代码和视频

代码地址 https://github.com/langwan/chihuo/blob/main/go%E8%AF%AD%E8%A8%80/Go%E8%AF%AD%E8%A8%80Web%20Server%E5%AE%9E%E7%8E%B0

视频地址

https://www.bilibili.com/video/BV1Jd4y147hi/?share_source=copy_web&vd_source=934149a9d52713966455c630fcbcc964

最后更新于