您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Go语言

golang如何将http请求流转到gin

时间:2022-03-14 14:41:40  来源:  作者:痞老板不要蟹堡王

gin是作为golang web开发中被广泛使用到的框架,了解其内部的实现有助于我们更好地理解gin的设计思想。

这篇文章主要探讨两个问题。

  • http请求如何流转到gin
  • gin为什么比golang的http路由寻找更快

开始之前我们先来看看分别用golang原生的http包实现一个http服务和使用gin实现的代码,先看看原生http包实现的http服务

package mAIn


import (
  ".NET/http"
)


func main() {
  http.HandleFunc("/ping", func(writer http.ResponseWriter, request *http.Request) {
    writer.Write([]byte(`{"message":"ok"}`))
  })
  http.ListenAndServe(":9090", nil)
}

这段代码做了两件事情,注册路由、启动服务监听9090端口。接下来我们对这段代码进一步分析,在第8行的地方是将路由/ping和对应的处理函数注册到http服务中,我们进入http.HandleFunc()函数看看该函数做了什么事情。

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  DefaultServeMux.HandleFunc(pattern, handler)
}

将路由和处理函数注册到了DefaultServeMux中,所以我们先看看DefaultServeMux的结构是什么。

type ServeMux struct {
  mu    sync.RWMutex
  m     map[string]muxEntry
  es    []muxEntry // slice of entries sorted from longest to shortest.
  hosts bool       // whether any patterns contain hostnames
}


type muxEntry struct {
  h       Handler
  pattern string
}


// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }


// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux


var defaultServeMux ServeMux

第17行代码就是刚刚用来注册http路由的服务,通过第19行代码知道了他是一个ServeMux类型。知道了DefaultServeMux的类型我们接着看具体的实现代码。

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  if handler == nil {
    panic("http: nil handler")
  }
  mux.Handle(pattern, HandlerFunc(handler))
}


// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
  mux.mu.Lock()
  defer mux.mu.Unlock()


  if pattern == "" {
    panic("http: invalid pattern")
  }
  if handler == nil {
    panic("http: nil handler")
  }
  if _, exist := mux.m[pattern]; exist {
    panic("http: multiple registrations for " + pattern)
  }


  if mux.m == nil {
    mux.m = make(map[string]muxEntry)
  }
  e := muxEntry{h: handler, pattern: pattern}
  mux.m[pattern] = e
  if pattern[len(pattern)-1] == '/' {
    mux.es = AppendSorted(mux.es, e)
  }


  if pattern[0] != '/' {
    mux.hosts = true
  }
}

主要的代码就是第29行,这里将路由和处理函数保存在了ServeMux的m中,通过前面的代码我们知道m是一个map,到这里路由注册的过程就分析完了。接下来我们来看看 http.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. After Shutdown or Close,
// the returned error is ErrServerClosed.
func (srv *Server) ListenAndServe() error {
  if srv.shuttingDown() {
    return ErrServerClosed
  }
  addr := srv.Addr
  if addr == "" {
    addr = ":http"
  }
  ln, err := net.Listen("tcp", addr)
  if err != nil {
    return err
  }
  return srv.Serve(ln)
}

第22行就是真正开始启动http服务,并接受请求的函数。第17行创建了主动套接字并监听套接字,接着我们进入Serve()函数。

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)
  }
}

比较关键的几行代码是第31行和第61行,他们做的事情分别是接收到请求并解析请求数据,使用新的goroutines处理该请求。接着我们需要看看golang具体是如何处理接收到的请求

// 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: %vn%s", c.remoteAddr, err, buf)
    }
    if !c.hijacked() {
      c.close()
      c.setState(c.rwc, StateClosed, runHooks)
    }
  }()


  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.HandshakeContext(ctx); err != nil {
      // If the handshake failed due to the client not speaking
      // TLS, assume they're speaking plaintext HTTP and write a
      // 400 response on the TLS conn's underlying net.Conn.
      if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
        io.WriteString(re.Conn, "HTTP/1.0 400 Bad RequestrnrnClient sent an HTTP request to an HTTPS server.n")
        re.Conn.Close()
        return
      }
      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; validNextProto(proto) {
      if fn := c.server.TLSNextProto[proto]; fn != nil {
        h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
        // Mark freshly created HTTP/2 as active and prevent any server state hooks
        // from being run on these connections. This prevents closeIdleConns from
        // closing such connections. See issue https://golang.org/issue/39776.
        c.setState(c.rwc, StateActive, skipHooks)
        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, runHooks)
    }
    if err != nil {
      const errorHeaders = "rnContent-Type: text/plain; charset=utf-8rnConnection: closernrn"


      switch {
      case 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


      case isUnsupportedTEError(err):
        // Respond as per RFC 7230 Section 3.3.1 which says,
        //      A server that receives a request message with a
        //      transfer coding it does not understand SHOULD
        //      respond with 501 (Unimplemented).
        code := StatusNotImplemented


        // We purposefully aren't echoing back the transfer-encoding's value,
        // so as to mitigate the risk of cross side scripting by an attacker.
        fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)
        return


      case isCommonNetReadError(err):
        return // don't reply


      default:
        if v, ok := err.(statusError); ok {
          fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s: %s%s%d %s: %s", v.code, StatusText(v.code), v.text, errorHeaders, v.code, StatusText(v.code), v.text)
          return
        }
        publicErr := "400 Bad Request"
        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}
        w.canWriteContinue.setTrue()
      }
    } 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 {
      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, runHooks)
    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{})
  }
}

关键的代码是第137行将需要返回的response和reques

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{}
  }


  if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") {
    var allowQuerySemicolonsInUse int32
    req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() {
      atomic.StoreInt32(&allowQuerySemicolonsInUse, 1)
    }))
    defer func() {
      if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 {
        sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192")
      }
    }()
  }


  handler.ServeHTTP(rw, req)
}

在这里我们终于又和注册路由时候使用的DefaultServeMux见面了,因为在启动服务的时候handler传入的是nil,所以这里默认的使用DefaultServeMux,然而此时的DefaultServeMux已经包含了注册的路由。接下来我们来看看DefaultServeMux的ServeHTTP()是如何实现的。

// 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)
}

第11行就是通过通过请求中的路由再返回路由对应的处理函数

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {


  // CONNECT requests are not canonicalized.
  if r.Method == "CONNECT" {
    // If r.URL.Path is /tree and its handler is not registered,
    // the /tree -> /tree/ redirect applies to CONNECT requests
    // but the path canonicalization does not.
    if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
      return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
    }


    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 the given path is /tree and its handler is not registered,
  // redirect for /tree/.
  if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
    return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
  }


  if path != r.URL.Path {
    _, pattern = mux.handler(host, path)
    u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}
    return RedirectHandler(u.String(), StatusMovedPermanently), pattern
  }


  return mux.handler(host, r.URL.Path)
}

第32行然后接着往下走

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
}

第7行

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.  mux.es contains all patterns
  // that end in / sorted from longest to shortest.
  for _, e := range mux.es {
    if strings.HasPrefix(path, e.pattern) {
      return e.h, e.pattern
    }
  }
  return nil, ""
}

第3~5行,如请求的路由有对应的处理函数则返回对应的处理函数。得到了对应的处理函数,然后调用处理函数实现的ServeHTTP()的逻辑

 

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)


// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
  f(w, r)
}

通过刚开始注册路由的时候我们传入的处理函数是HandlerFunc类型,而且对应的ServeHTTP()逻辑是运行处理函数,所以到这里逻辑就走到了我们的业务逻辑了,这就是使用golang原生http包实现的http服务具体的实现过程。

接着我们来看看gin的http服务有什么不同,gin中匹配路由和处理函数的的数据结构是Radix Tree,这是前缀树的优化方案

func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
  assert1(path[0] == '/', "path must begin with '/'")
  assert1(method != "", "HTTP method can not be empty")
  assert1(len(handlers) > 0, "there must be at least one handler")


  debugPrintRoute(method, path, handlers)


  root := engine.trees.get(method)
  if root == nil {
    root = new(node)
    root.fullPath = "/"
    engine.trees = append(engine.trees, methodTree{method: method, root: root})
  }
  root.addRoute(path, handlers)


  // Update maxParams
  if paramsCount := countParams(path); paramsCount > engine.maxParams {
    engine.maxParams = paramsCount
  }


  if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
    engine.maxSections = sectionsCount
  }
}

第14行向该树添加节点,gin中每一个http请求方法都单独维护了一棵Radix Tree。接着我们看Run()函数做了什么事情

func (engine *Engine) Run(addr ...string) (err error) {
  defer func() { debugPrintError(err) }()


  if engine.isUnsafeTRustedProxies() {
    debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.n" +
      "Please check https://pkg.go.dev/Github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
  }


  address := resolveAddress(addr)
  debugPrint("Listening and serving HTTP on %sn", address)
  err = http.ListenAndServe(address, engine)
  return
}

第11行将我们将建的gin实例作为handler传入ListenAndServe,之后的逻辑就是http包原生的逻辑,唯一不同的是最后调用的ServeHTTP是gin的实现而不是DefaultServeMux的实现接下来我们看看gin的ServeHTTP实现

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  c := engine.pool.Get().(*Context)
  c.writermem.reset(w)
  c.Request = req
  c.reset()


  engine.handleHTTPRequest(c)


  engine.pool.Put(c)
}

gin将请求包装成Context然后调用handleHTTPRequest在Radix Tree找到路由对应的处理函数,并调用该函函数。

func (engine *Engine) handleHTTPRequest(c *Context) {
  httpMethod := c.Request.Method
  rPath := c.Request.URL.Path
  unescape := false
  if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
    rPath = c.Request.URL.RawPath
    unescape = engine.UnescapePathValues
  }


  if engine.RemoveExtraSlash {
    rPath = cleanPath(rPath)
  }


  // Find root of the tree for the given HTTP method
  t := engine.trees
  for i, tl := 0, len(t); i < tl; i++ {
    if t[i].method != httpMethod {
      continue
    }
    root := t[i].root
    // Find route in tree
    value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
    if value.params != nil {
      c.Params = *value.params
    }
    if value.handlers != nil {
      c.handlers = value.handlers
      c.fullPath = value.fullPath
      c.Next()
      c.writermem.WriteHeaderNow()
      return
    }
    if httpMethod != "CONNECT" && rPath != "/" {
      if value.tsr && engine.RedirectTrailingSlash {
        redirectTrailingSlash(c)
        return
      }
      if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
        return
      }
    }
    break
  }


  if engine.HandleMethodNotAllowed {
    for _, tree := range engine.trees {
      if tree.method == httpMethod {
        continue
      }
      if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
        c.handlers = engine.allNoMethod
        serveError(c, http.StatusMethodNotAllowed, default405Body)
        return
      }
    }
  }
  c.handlers = engine.allNoRoute
  serveError(c, http.StatusNotFound, default404Body)
}

第22~31行获取处理函数,并执行中间件和处理函数。

到这里我们就一起知道了http请求是如何从golang流转到gin的,只要我们自己定义的结构体实现了ServeHTTP函数并在启动服务使用我们自己实现的handler类型的结构体,那么最后就会流转的自定义的http handler。

通过分析我们知道原生的DefaultServeMux路由和处理函数对应关系使用的是map,而gin使用的是Radix Tree,所以gin比原生http快的原因就是这两种数据结构的的性能差别,map在最糟糕的条件下时间复杂度会变成O(n)也就是所有的key hash只有相同,最后变成链表。而且由于map的性质,所有的key不是很可能不是连续的,所有可能造成空间浪费。

关于gin的学习今天就到这里,有什么错误的地方希望指正。



Tags:golang   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
慢聊Golang协程池Ants实现原理
大家都知道goroutine 是 Go语言中的轻量级线程实现,由 Go 运行时(runtime)管理,Go 程序会智能地将 goroutine 中的任务合理地分配给每个 CPU。创建一个goroutine大小大概在2k左...【详细内容】
2023-12-27  Search: golang  点击:(96)  评论:(0)  加入收藏
基于Go-Kit的Golang整洁架构实践
简介Go是整洁架构(Clean Architecture)的完美选择。整洁架构本身只是一种方法,并没有告诉我们如何构建源代码,在尝试用新语言实现时,认识到这点非常重要。自从我有了使用Ruby o...【详细内容】
2023-12-25  Search: golang  点击:(167)  评论:(0)  加入收藏
Golang清晰代码指南
发挥易读和易维护软件的好处 - 第一部分嗨,开发者们,清晰的代码是指编写易于阅读、理解和维护的软件代码。它是遵循一组原则和实践,优先考虑清晰性、简单性和一致性的代码。清...【详细内容】
2023-12-18  Search: golang  点击:(133)  评论:(0)  加入收藏
如何优雅的组织Golang项目结构
一个Go项目的结构设计始终遵循Go语言的简洁高效理念。一个合理和良好的布局可以提高代码的可读性,简化依赖管理,并优化编译过程。像cmd、internal和docs这样的目录是标准Go项...【详细内容】
2023-12-06  Search: golang  点击:(173)  评论:(0)  加入收藏
从 Discord 的做法中学习 — 使用 Golang 进行请求合并
正如你可能之前看到的,Discord去年发布了一篇有价值的文章,讨论了他们成功存储了数万亿条消息。虽然有很多关于这篇文章的YouTube视频和文章,但我认为这篇文章中一个名为“数据...【详细内容】
2023-11-24  Search: golang  点击:(258)  评论:(0)  加入收藏
使用Golang进行自动化的20个库
Golang,也被称为Go,是一种静态类型的编译型编程语言,由Robert Griesemer,Rob Pike和Ken Thompson在Google设计。它在2009年推出,旨在解决其他编程语言在并发编程、垃圾回收和代码...【详细内容】
2023-11-23  Search: golang  点击:(206)  评论:(0)  加入收藏
Golang 中的 Bytes 包详解之 Bytes.Buffer
上篇文章详细讲解了一次性密码 OTP 相关的知识,基于时间的一次性密码 TOTP 是 OTP 的一种实现方式。这种方法的优点是不依赖网络,因此即使在没有网络的情况下,用户也可以生成密...【详细内容】
2023-11-07  Search: golang  点击:(264)  评论:(0)  加入收藏
聊聊Golang饱受争议的Error
一、error是什么?在C中,返回错误通过errno.h中的错误代码来表示,比如0代表No error,也就是没有错误;2代表No such file or directory,也就是找不到指定路径的文件或文件夹;5代表Inp...【详细内容】
2023-11-06  Search: golang  点击:(257)  评论:(0)  加入收藏
使用示例和应用程序全面了解高效数据管理的Golang MySQL数据库
Golang,也被称为Go,已经成为构建强大高性能应用程序的首选语言。在处理MySQL数据库时,Golang提供了一系列强大的库,简化了数据库交互并提高了效率。在本文中,我们将深入探讨一些...【详细内容】
2023-10-29  Search: golang  点击:(308)  评论:(0)  加入收藏
Golang中的强大Web框架
揭示Fiber在Go Web开发中的特点和优势在不断发展的Web开发领域中,选择正确的框架可以极大地影响项目的效率和成功。介绍一下Fiber,这是一款令人印象深刻的Golang(Go语言)Web框架...【详细内容】
2023-10-26  Search: golang  点击:(261)  评论:(0)  加入收藏
▌简易百科推荐
宝藏级Go语言开源项目——教你自己动手开发互联网搜索引擎
DIYSearchEngine 是一个能够高速采集海量互联网数据的开源搜索引擎,采用 Go 语言开发。Github 地址:https://github.com/johnlui/DIYSearchEngine运行方法首先,给自己准备一杯...【详细内容】
2024-03-12  OSC开源社区    Tags:Go语言   点击:(29)  评论:(0)  加入收藏
Go Gin框架实现优雅地重启和停止
在Web应用程序中,有时候我们需要重启或停止服务器,无论是因为更新代码还是进行例行维护。在这种情景下,我们需要保证应用程序的可用性和数据的一致性。这就需要优雅地关闭和重...【详细内容】
2024-01-30  源自开发者  微信公众号  Tags:Go   点击:(70)  评论:(0)  加入收藏
如何让Go程序以后台进程或daemon方式运行
本文探讨了如何通过Go代码实现在后台运行的程序。最近我用Go语言开发了一个WebSocket服务,我希望它能在后台运行,并在异常退出时自动重新启动。我的整体思路是将程序转为后台...【详细内容】
2024-01-26  Go语言圈  微信公众号  Tags:Go程序   点击:(61)  评论:(0)  加入收藏
深入Go底层原理,重写Redis中间件实战
Go语言以其简洁、高效和并发性能而闻名,深入了解其底层原理可以帮助我们更好地利用其优势。在本文中,我们将探讨如何深入Go底层原理,以及如何利用这些知识重新实现一个简单的Re...【详细内容】
2024-01-25  547蓝色星球    Tags:Go   点击:(74)  评论:(0)  加入收藏
Go 内存优化与垃圾收集
Go提供了自动化的内存管理机制,但在某些情况下需要更精细的微调从而避免发生OOM错误。本文将讨论Go的垃圾收集器、应用程序内存优化以及如何防止OOM(Out-Of-Memory)错误。Go...【详细内容】
2024-01-15  DeepNoMind  微信公众号  Tags:Go   点击:(64)  评论:(0)  加入收藏
Go函数指针是如何让你的程序变慢的?
导读Go 语言的常规优化手段无需赘述,相信大家也能找到大量的经典教程。但基于 Go 的函数值问题,业界还没有太多深度讨论的内容分享。本文作者根据自己对 Go 代码的使用与调优...【详细内容】
2024-01-15  腾讯云开发者  微信公众号  Tags:Go函数   点击:(91)  评论:(0)  加入收藏
Go编程中调用外部命令的几种场景
在很多场合, 使用Go语言需要调用外部命令来完成一些特定的任务, 例如: 使用Go语言调用Linux命令来获取执行的结果,又或者调用第三方程序执行来完成额外的任务。在go的标准库...【详细内容】
2024-01-09  suntiger    Tags:Go编程   点击:(117)  评论:(0)  加入收藏
Go 语言不支持并发读写 Map,为什么?
Go语言的map类型不支持并发读写的主要原因是并发读写会导致数据竞态(data race),这意味着多个 goroutine 可能同时访问并修改同一个 map,从而引发不确定的结果。在Go语言的设计...【详细内容】
2024-01-05  Go语言圈  微信公众号  Tags:Go 语言   点击:(83)  评论:(0)  加入收藏
Go微服务入门到容器化实践
Go微服务入门到容器化实践Go 是一门高效、现代化、快速增长的编程语言,非常适合构建 Web 应用程序。而 Docker 是一种轻量级的容器化技术,能够使得您的应用程序在任何地方运行...【详细内容】
2024-01-01  大雷家吃饭    Tags:Go微服务   点击:(69)  评论:(0)  加入收藏
你是否想知道如何应对高并发?Go语言为你提供了答案!
并发编程是当前软件领域中不可忽视的一个关键概念。随着CPU等硬件的不断发展,我们都渴望让我们的程序运行速度更快、更快。而Go语言在语言层面天生支持并发,充分利用现代CPU的...【详细内容】
2023-12-29  灵墨AI探索室  微信公众号  Tags:Go语言   点击:(114)  评论:(0)  加入收藏
站内最新
站内热门
站内头条