Go语言网络编程:HTTP服务端之底层原理与源码分析——http.HandleFunc()、http.ListenAndServe()

这篇具有很好参考价值的文章主要介绍了Go语言网络编程:HTTP服务端之底层原理与源码分析——http.HandleFunc()、http.ListenAndServe()。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、启动 http 服务

import (
    "net/http"
)
func main() {
    http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("ping...ping..."))
    })
    http.ListenAndServe(":8999", nil)
}

在 Golang只需要几行代码便能启动一个 http 服务,在上述代码中,完成了两件事:

  • 调用 http.HandleFunc 方法,注册了对应于请求路径 /ping 的 handler 函数
  • 调用 http.ListenAndServe,启动了一个端口为 8999 的 http 服务

二、server核心数据结构

2.1 server 结构
type Server struct {
    Addr string
    Handler Handler 
    mu         sync.Mutex
	ReadTimeout time.Duration
	WriteTimeout time.Duration
	IdleTimeout time.Duration
	TLSConfig *tls.Config
	ConnState func(net.Conn, ConnState)
	activeConn map[*conn]struct{}
	doneChan   chan struct{}
	listeners  map[*net.Listener]struct{}
    // ...还有其他变量
}
  1. Addr :表示服务器监听的地址。如":8080"表示服务器在本地主机上的8080端口上监听。
  2. Handler :用于处理HTTP请求的处理器。
  3. mu :用于保护共享资源的互斥锁。在对 Server 进行并发操作时,使用该互斥锁确保线程安全。
  4. ReadTimeout 、 WriteTimeout 、 IdleTimeout :分别表示服务器读取请求超时、写入响应超时和空闲超时的时间限制。
  5. TLSConfig :用于配置HTTPS连接的TLS例如证书、私钥等。
  6. ConnState :是一个回调函数,在每个网络连接状态变化时触发。可以用于跟踪连接状态的变化,如新建、活动、关闭等。
  7. activeConn :map类型,用于存储当前处于活动状态的连接。将每个活动的连接映射到一个空结构体。
  8. doneChan :channel类型,一个用于异步通知服务器已完成关闭的信道。当服务器关闭时,会向该信道发送信号。
  9. listeners :map类型,用于存储服务器正在侦听的网络。

这些变量在 Server 结构定义了服务器的配置和状态跟踪。它们用于控制服务器监听地址、处理请求、超时设置、连接状态跟踪等方面,并提供了并发安全性的保护。

2.2 Handler 接口
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Handler 是一个接口,定义了方法: ServeHTTP。任何结构只要实现了这个ServeHTTP方法,那么这个结构体就是一个Handler对象。
ServeHTTP方法的作用是,根据 http 请求 Request 中的请求路径 path 映射到对应的 handler 处理函数,对请求进行处理和响应。ServeHTTP方法有两个参数:ResponseWriter和*Request。

  • ResponseWriter是一个接口,用于向客户端发送HTTP响应;
  • *Request是一个指向Request结构体的指针,包含来自客户端的HTTP请求的各种信息,如请求方法、URL和请求头等。

通过实现 Handler 接口并定义自己的 ServeHTTP 方法,可以创建HTTP处理程序。当收到一个HTTP请求时,服务器会调用这个处理程序的 ServeHTTP 方法,并将适当的ResponseWriter和*Request给它,以便处理请求并生成响应。

2.3 muxEntry 具体路由
type muxEntry struct {
    h Handler
    pattern string 
}

muxEntry是一个结构体类型,代表具体的路由条目。每个路由条目包含一个路径模式pattern和关联的处理函数h,用于匹配和处理特定的URL路径。

  • h是一个 Handler 接口类型的变量,表示与该路由条目关联的处理程序。通过该处理程序可以执行特定的逻辑来处理HTTP请求并生成响应。
  • pattern是一个字符串类型的变量,表示该路由条目所匹配的URL模式或路径模式。这个模式用于将特定的HTTP请求与路由条目关联起来,以便选择正确的处理程序进行处理。它用于确定请求的URL是否与该路由条目匹配,并决定选择哪个处理程序来处理该请求。以下是一些pattern的匹配规则
    • 完全匹配:pattern可以是一个普通的字符串,比如 “/about”。这意味着只有当请求的URL路径与该字符串完全相同时,才会匹配该路由条目。
    • 模糊匹配:pattern以斜线 / 结尾,如 “/products/”。这表示当请求的URL路径只要以该前缀开头时,该路由条目将会匹配。例如,对于请求的URL路径为 “/products/123”、“/products/456”,这些路由条目都会与之匹配。
2.4 ServeMux多路路由器
type ServeMux struct {
    mu sync.RWMutex			//允许多个 goroutine 并发地读取路由表,但在修改路由表时需要独占锁。
    m map[string]muxEntry	//用于将 所有的URL 模式(或路径模式)映射到 muxEntry 结构体
    es []muxEntry 			//只存储 "/" 结尾的路由,即模糊匹配patter的muxEntry,且切片内按从最长到最短的顺序排列
    hosts bool 				//表示是否有路由模式包含主机名
}

ServeMux是一个用于路由HTTP请求的多路复用器(multiplexer)。它可以将收到的请求根据路径匹配与注册的处理函数进行对应关联,并分发给相应的处理函数进行处理,实际上是一个实现了http.Handler接口的结构体。内部通过一个 map 维护了从 path 到 handler 的映射关系, 有效地管理和选择正确的处理程序来处理传入的HTTP请求。当收到一个请求时,ServeMux 会根据请求的URL路径进行匹配,并检查注册的路由模式pattern,然后选择适合的处理程序handler进行处理。
http.handlefunc,# Golang,# 网络编程,golang,http,源码

2.5 defaultServeMux的全局多路路由器
//全局变量
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

在http包的初始化程中,会创建一个名为defaultServeMux的全局ServeMux实例,然后通过&defaultServeMux将其地址赋值给DefaultServeMux。这样就得到了一个默认的全局变量,我们可以通过直接使用DefaultServeMux来进行请求路由和处理,而管理自己的多路复用器。当我们使用http.HandleFunc等函数来注册处理函数时,默认情况下它们会将路由信息添加到DefaultServeMux上。

三、http.HandleFunc()注册路由底层原理

http.handlefunc,# Golang,# 网络编程,golang,http,源码

3.1 http.HandleFunc()

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

如上所示,当调用http.HandleFunc()时,会直接调用全局变量DefaultServeMux.HandleFunc()。HandleFunc的第一个参数是路由表达式,也就是请求路径,第二个参数是一个函数类型,也就是真正处理请求的函数。

3.2 DefaultServeMux.HandleFunc()

type HandlerFunc func(ResponseWriter, *Request)

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

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    // ...
    mux.Handle(pattern, HandlerFunc(handler))
}

如上所示,在 ServeMux.HandleFunc 定义了接收一个 ResponseWriter 对象和一个 Request 对象的 HandlerFunc 类型。

  • HandlerFunc类型是一个适配器,并且这种类型实现了ServeHTTP方法,即是Handler接口的具体实现类型,并在ServeHTTP方法中又调用了被转换的函数自身,也就是说这个类型的函数其实就是一个Handler类型的对象,通过类型转换可以将一个具有func(ResponseWriter, *Request)签名的普通函数转换为一个Handler对象,而不需要再定义一个结构体,再让这个结构实现ServeHTTP方法,非常方便的将普通函数用作HTTP处理程序。

通过将处理函数转换为 HandlerFunc 类型,并将其作为参数传递给 ServeMux 的 Handle 方法,我们可以通过mux.Handle将处理函数注册到 ServeMux 的路由 map 中,以便在收到请求时能够正确匹配处理函数并处理该请求。这种设计模式使得我们可以将不同的处理函数注册到 ServeMux,并根据请求的路径来选择相应的处理函数进行处理。这样可以实现灵活的路由功能,使我们能够根据具体的业务逻辑来处理不同的请求。

3.3 mux.Handle()

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")
    }
    // 如果这个路由表达式已经注册过处理器函数,直接panic
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }
    // 如果 mux.m 为nil 通过 make 函数初始化 map
    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    // 用路由表达式和处理函数handler创建 muxEntry 对象
    e := muxEntry{h: handler, pattern: pattern}
    // 所有注册的路由都会存放到mux.m里面
    mux.m[pattern] = e
    // 如果路由以/做结尾,则在mux.es中存放一份规则,同时做好从长到短的排序
    if pattern[len(pattern)-1] == '/' {
        // 如果路由patterm以'/'结尾,则将对应的muxEntry对象加入到[]muxEntry中,路由长的位于切片的前面
        mux.es = appendSorted(mux.es, e)
    }
    // 如果该路由路径不以 "/" 开始,标记该 mux 中有路由的路径带有主机名
    if pattern[0] != '/' {
        mux.hosts = true
    }
}

通过以上代码,Handle 方法实现了将传进来的pattern和handler保存在muxEntry结构中,并且pattern作为key,把muxEntry装入到DefaultServeMux的Map里面。简单来说就是保存当前路由和自己定义的那个处理函数。在收到请求时,ServeMux 可以根据请求的 URL 查找到对应的处理函数,并进行处理。同时,将以“/”结尾的模糊匹配路由添加到 es 切片中,同时在es切片中通过appendSorted对路由按从长到短进行排序,代码如下所示。

func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
    n := len(es)
    i := sort.Search(n, func(i int) bool {
        return len(es[i].pattern) < len(e.pattern)
    })
    if i == n {
        return append(es, e)
    }
    es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
    copy(es[i+1:], es[i:])      // Move shorter entries down
    es[i] = e
    return es
}

四、http.ListenAndServe()监听与服务底层原理

http.handlefunc,# Golang,# 网络编程,golang,http,源码

4.1 Http.ListenAndServe()启动 server

在上述http启动过程中,我们通过调用 net/http 包下的公开方法 http.ListenAndServe,实现了对服务端的一键启动。
而在http.ListenAndServe 内部会声明一个新的 Server 对象,嵌套执行 Server.ListenAndServe 方法。我们使用Http包启动web服务时Handler一般设置为nil,所以使用的就是默认的DefaultServeMux。

//The handler is typically nil, in which case the DefaultServeMux is used.
//Handler通常是nil,此时,会默认使用DefaultServeMux。
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}
   

4.2 server.ListenAndServe()

func (srv *Server) ListenAndServe() error {
    // ...
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    // ...    
    return srv.Serve(ln)
}

Server.ListenAndServe 方法中,根据用户传入的地址,申请到一个监听器TCPListener 监听地址addr,继而调用 Server.Serve 方法为这个连接提供服务

4.3 Server.Serve()

var ServerContextKey = &contextKey{"http-server"}

type contextKey struct {
    name string
}

func (srv *Server) Serve(l net.Listener) error {
   // ...
   ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
    	//rw可视为tcp连接
        rw, err := l.Accept()
        // ...
        connCtx := ctx
        // ...
        //c为http连接
        c := srv.newConn(rw)
        // ...
        go c.serve(connCtx)
    }
}

http.handlefunc,# Golang,# 网络编程,golang,http,源码

Server.Serve 方法体现了典型的服务端的运行架构,使用For + Listener.Accept的模式来接受并处理客户端请求。这种模式是一种常见的服务器设计模式,通常用于监听网络连接请求的处理。

在这个方法中,通过循环调用listener.Accept()方法来接受客户端连接。每当有新的连接建立时,将 server 封装成一组 valueContext,并开启一个新的goroutine调用 conn.serve 来处理该连接并传入server 封装后的valueContext,这确保了可以同时处理多个同时到达的连接请求。

4.4 *conn.serve()

*conn.serve()代码很长,核心是下面几行代码,简化之后如下:

func (c *conn) serve(ctx context.Context) {
    // ...
    c.r = &connReader{conn: c}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
    for {
        w, err := c.readRequest(ctx)
        // ...
        serverHandler{c.server}.ServeHTTP(w, w.req)
        w.cancelCtx()
        // ...
    }
}

*conn.serve 是响应客户端连接的核心方法,用于处理HTTP连接客户端发送的请求,实现了HTTP连接的处理逻辑。它通过不断循环读取和处理客户端请求,根据请求调用相应的处理函数,并最终取消请求的上下文。

  • 首先,该方法初始化了读取器c.bufr与带有缓冲的写入器c.bufw。这些缓冲区可以提高性能,减少对底层网络的读写次数。
  • 接着,通过一个for循环来处理客户端请求。
    • 传入的ctx 为valueContext,其key为ServerContextKey 即“http-server”,其value为server结构
    • 在每次迭代中,调用c.readRequest(ctx)方法来读取客户端的请求,并返回一个response类型的写入器w和一个错误err。
    • 然后,通过serverHandler{c.server}.ServeHTTP(w, w.req)调用服务器的处理器函数ServeHTTP来处理请求。该处理器函数负责根据请求路径和其他特定规则来处理请求并生成相应的响应,会去匹配在http.HandleFunc()中注册的路由,找到对应的处理函数。
    • 随后,调用w.cancelCtx()方法取消当前请求的上下文。

4.5 serverHandler.ServeHTTP()

type serverHandler struct {
    srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    // 此handler即为http.ListenAndServe 中的第二个参数
    // 获取Server对应的Handler 封装结构体的时候传入的是nil所以使用默认的DefaultServeMux
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    // 如果是 OPTIONS Method 请求且 URI 是 *,就使用 globalOptionsHandler 
    // Method == "OPTIONS" Preflighted Request(带预检的跨域请求) 
    // Preflighted Request在发送真正的请求前,会先发送一个方法为OPTIONS的预请求(Preflighted Request)
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    // handler传的是nil就执行 DefaultServeMux.ServeHTTP() 方法
    handler.ServeHTTP(rw, req)
}

在serverHandler实现的ServeHTTP()方法里的sh.srv.Handler就是我们最初在http.ListenAndServe()中传入的Handler参数。
如果该Handler对象为nil,则会使用默认的DefaultServeMux,否则使用我们自定义的ServeMux对象。最后调用handler的具体实现结构ServeMux的ServeHTTP()方法匹配当前路由对应的handler方法。

4.6 ServeMux.ServeHTTP()

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    // 如果RequestURI为 "*" 判断是不是HTTP/1.1 然后关闭长连接 响应 BadRequest
    //....代码省略
    
    // 如果是一个正常的 GET POST 请求 执行ServeMux.Handler() 方法 寻找匹配的路由
    h, _ := mux.Handler(r)
    // 执行匹配到的路由的ServeHTTP方法
    h.ServeHTTP(w, r)
}

ServeMux.ServeHTTP()方法主要代码可以分为两步:

  1. 通过 ServerMux.Handler() 方法获取到匹配的处理函数 h,
  2. 调用 Handler.ServeHTTP() 执行该处理函数来处理请求。
    http.handlefunc,# Golang,# 网络编程,golang,http,源码
4.6.1 ServerMux.Handler()
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    // ...代码省略
    
	//在mux.m和mux.es中根据host/url.path寻找对应的handler
    return mux.handler(host, r.URL.Path)
}

在 ServeMux.Handler() 方法内部,会调用 ServerMux.handler(host, r.URL.Path) 方法来查找匹配的处理函数。

4.6.2 ServerMux.handler()
// handler is the main implementation of Handler.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    mux.mu.RLock()
    defer mux.mu.RUnlock()

    // 若当前 mux 中注册有带主机名的路由,就用"主机名+路由路径"去匹配
    // 也就是说带主机名的路由优先于不带的
    if mux.hosts {
        h, pattern = mux.match(host + path)
    }
    
    // 若没有匹配到,就直接把路由路径拿去匹配
    if h == nil {
        h, pattern = mux.match(path)
    }
    
    // 如果还没有匹配到,就默认返回 NotFoundHandler,该 Handler 会往 响应里写上 "404 page not found"
    if h == nil {
        h, pattern = NotFoundHandler(), ""
    }
    // 返回获得的 Handler 和路由路径
    return
}

  • 首先在 mux.m 中根据主机和 URL 路径进行精确匹配,如果找到匹配的处理函数,则直接返回。
  • 如果在 mux.m 中找不到匹配的处理函数,会在 mux.es 中按照 URL 模式长度从长到短的顺序进行模糊匹配,找到第一个匹配的处理函数并返回。
  • 如果没有匹配就返回NotFoundHandler
4.6.3 ServeMux.match()
func (mux *ServeMux) match(path string) (h Handler, pattern string) {

    // 若 mux.m 中已存在该路由映射,直接返回该路由的 Handler,和路径
    v, ok := mux.m[path]
    if ok {
        return v.h, v.pattern
    }
    
    // 找到路径能最长匹配的路由。
    for _, e := range mux.es {
        if strings.HasPrefix(path, e.pattern) {
            return e.h, e.pattern
        }
    }
    return nil, ""
}

ServeMux.match()方法用于具体实现根据给定的路径 path 找到最佳匹配的路由。

  • 首先,方法会从 mux.m 字段中查找是否已经存在与 path 完全匹配的路由映射。如果存在,就直接返回该路由的 Handler 和路径。
  • 如果 mux.m 中不存在与 path 完全匹配的路由映射,接下来会遍历 mux.es 字段中保存的模糊匹配路由。模糊匹配时,会找到一个与请求路径 path 前缀完全匹配且长度最长的 pattern,其对应的handler 会作为本次请求的处理函数。
  • 如果 path 无法匹配到任何路由,则返回 nil 和空字符串。
4.6.4 handler.ServeHTTP()
type HandlerFunc func(ResponseWriter, *Request)

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

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    // ...
    mux.Handle(pattern, HandlerFunc(handler))
}

获取到handler之后,又调用了handler.ServeHTTP(w ResponseWriter, r *Request)处理请求,这实际上是http.HandleFunc中注册的路由对应的处理函数,通过HandlerFunc可以将具有func(ResponseWriter, *Request)签名的普通函数转换为一个Handler对象,从而调用 handler.ServeHTTP()进行处理。如示例中在 http.HandleFunc 中注册了 ping 路由对应的处理函数为 ping 函数,那么在调用 handler.ServeHTTP(w, r) 时实际上是调用了 ping(w, r) 函数。文章来源地址https://www.toymoban.com/news/detail-720203.html

到了这里,关于Go语言网络编程:HTTP服务端之底层原理与源码分析——http.HandleFunc()、http.ListenAndServe()的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • GO语言网络编程(并发编程)select

    1.1.1 select多路复用 在某些场景下我们需要同时从多个通道接收数据。通道在接收数据时,如果没有数据可以接收将会发生阻塞。你也许会写出如下代码使用遍历的方式来实现: 这种方式虽然可以实现从多个通道接收值的需求,但是运行性能会差很多。为了应对这种场景,G

    2024年02月09日
    浏览(90)
  • Go语言网络编程(socket编程)WebSocket编程

    WebSocket是一种在单个TCP连接上进行全双工通信的协议 WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据 在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输 需要安装第

    2024年02月09日
    浏览(77)
  • Go语言网络编程(socket编程)TCP

    TCP协议 TCP/IP(Transmission Control Protocol/Internet Protocol) 即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议,因为是面向连接的协议,数据像水流一样传输,会存在黏包问题。 TCP服务端 一个TCP服务端可以同时连接很

    2024年02月09日
    浏览(58)
  • GO语言网络编程(并发编程)runtime包

    1.1.1. runtime.Gosched() 让出CPU时间片,重新等待安排任务(大概意思就是本来计划的好好的周末出去烧烤,但是你妈让你去相亲,两种情况第一就是你相亲速度非常快,见面就黄不耽误你继续烧烤,第二种情况就是你相亲速度特别慢,见面就是你侬我侬的,耽误了烧烤,但是还馋就

    2024年02月09日
    浏览(71)
  • Go语言网络编程(socket编程)TCP粘包

    服务端代码如下: 客户端代码如下: 将上面的代码保存后,分别编译。先启动服务端再启动客户端,可以看到服务端输出结果如下: 收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you? 收到client发来的数

    2024年02月09日
    浏览(66)
  • GO语言网络编程(并发编程)并发介绍,Goroutine

    进程和线程 并发和并行 协程和线程 协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。 线程:一个线程上可以跑多个协程,协程是轻量级的线程。 goroutine 只是由官方实现的超级\\\"线程池\\\"。 每个

    2024年02月09日
    浏览(58)
  • Go语言网络编程介绍以及案例运用

    1. 基本概念 TCP 和 UDP : Go语言支持TCP(传输控制协议)和UDP(用户数据报协议)。TCP提供可靠的、面向连接的通信,而UDP提供无连接的快速数据传输。 并发 : Go语言的并发模型是通过goroutines实现的。每个网络请求都可以在自己的goroutine中处理,实现高效的并发。 Channels : 用于

    2024年01月25日
    浏览(71)
  • Go语言的网络编程与TCP_IP

    Go语言是一种现代的编程语言,由Google的Robert Griesemer、Rob Pike和Ken Thompson在2009年开发。Go语言的设计目标是简单、高效、可扩展和易于使用。它具有弱类型、垃圾回收、并发性和原生支持的网络编程。Go语言的网络编程是其强大功能之一,它使得开发者可以轻松地编写高性能的

    2024年02月22日
    浏览(75)
  • Go语言实战:网络编程与TCP_UDP

    Go语言是一种现代的编程语言,由Google的Robert Griesemer、Rob Pike和Ken Thompson于2009年开发。Go语言的设计目标是简单、高效、可扩展和易于使用。它具有垃圾回收、类型安全、并发性能等优点。Go语言的网络编程库 net 包提供了TCP/UDP的实现,使得开发者可以轻松地编写网络应用程序

    2024年02月21日
    浏览(66)
  • Java 网络编程 —— 创建非阻塞的 HTTP 服务器

    HTTP 客户程序必须先发出一个 HTTP 请求,然后才能接收到来自 HTTP 服器的响应,浏览器就是最常见的 HTTP 客户程序。HTTP 客户程序和 HTTP 服务器分别由不同的软件开发商提供,它们都可以用任意的编程语言编写。HTTP 严格规定了 HTTP 请求和 HTTP 响应的数据格式,只要 HTTP 服务器

    2024年02月06日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包