http 库的服务端实现

news/2024/5/19 1:34:02/文章来源:https://blog.csdn.net/upstream480/article/details/128429477

前言

net/http 库的客户端实现(上)

net/http 库的客户端实现(下)

net/http 库的服务端实现

上两篇文章介绍了 http 客户端的实现,这篇文章看一下服务端的实现

服务端

使用 net/http 库可以快速搭建HTTP服务,HTTP服务端主要包含两部分:

  • 注册处理器:net/http.HandleFunc函数用于注册处理器
  • 监听端口:net/http.ListenAndServe用于处理请求
package mainimport ("fmt""net/http"
)func hello(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "Hello World")
}func main() {http.HandleFunc("/hello", hello)http.ListenAndServe(":8080", nil)
}

注册处理器

直接调用net/http.HandleFunc可以注册路由和处理函数:

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

我们调用http.HandleFunc("/hello", hello)注册路径处理函数,这里将路径/hello的处理函数设置为hello。处理函数的类型必须是:

func (http.ResponseWriter, *http.Request)

它调用HTTP服务起的DefaultServeMux处理请求,DefaultServeMux本质是ServeMux

type ServeMux struct {mu    sync.RWMutex  		// 读写锁,保证并发安全,注册处理器时会加写锁做保护m     map[string]muxEntry 	// 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式es    []muxEntry 			// slice of entries sorted from longest to shortest.hosts bool       			// whether any patterns contain hostnames
}
  • **mu**:需要加读写锁保证并发安全,注册处理器时会加写锁保证写map的数据正确性,这个map就是patternhandler
  • **m**:存储路由规则,key就是patternvaluemuEntry实体,muEntry实体中包含:patternhandler
  • **es**:存储的也是muxEntry实体,因为我们使用map存储路由和handler的对应关系,所以只能索引静态路由,并不支持[path_param],所以这块的作用是当在map中没有找到匹配的路由时,会遍历这个切片进行前缀匹配,这个切片按照路由长度进行排序;
  • **hosts**:这个也是用来应对特殊case,如果我们注册的路由没有以/开始,那么就认为我们注册的路由包含host,所以路由匹配时需要加上host
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}// map存储路由和处理函数的映射mux.m[pattern] = e// 如果路由最后加了`/`放入到切片后在路由匹配时做前缀匹配if pattern[len(pattern)-1] == '/' {mux.es = appendSorted(mux.es, e)}// 如果路由第一位不是/,则认为注册的路由加上了host,所以在路由匹配时使用host+path进行匹配;if pattern[0] != '/' {mux.hosts = true}
}

监听端口

net/http库提供了ListenAndServe()用来监听TCP连接并处理请求:

func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}return server.ListenAndServe()
}

在这里初始化Server结构,然后调用ListenAndServe:

func (srv *Server) ListenAndServe() error {if srv.shuttingDown() {return ErrServerClosed}addr := srv.Addrif addr == "" {addr = ":http"}// 调用 net 进行 tcp 连接ln, err := net.Listen("tcp", addr)if err != nil {return err}return srv.Serve(ln)
}

这里调用net网络库进行tcp连接,包含了创建socketbind绑定socket与地址,listen端口的操作,最后调用Serve方法循环等待客户端的请求:
image.png
可以看到,每个HTTP请求服务端都会单独创建一个goroutine来处理请求,我们看一下处理过程:

func (c *conn) serve(ctx context.Context) {c.remoteAddr = c.rwc.RemoteAddr().String()ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())var inFlightResponse *responsedefer func() {// 添加recover函数防止panic引发主程序挂掉;if err := recover(); err != nil && err != ErrAbortHandler {const size = 64 << 10buf := make([]byte, size)buf = buf[:runtime.Stack(buf, false)]c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)}}()// HTTP/1.x from here on.ctx, cancelCtx := context.WithCancel(ctx)c.cancelCtx = cancelCtxdefer cancelCtx()c.r = &connReader{conn: c}c.bufr = newBufioReader(c.r)c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)for {// 读取请求,从连接中获取HTTP请求并构建一个实现了`net/http.Conn.ResponseWriter`接口的变量`net/http.response`w, err := c.readRequest(ctx)if c.r.remain != c.server.initialReadLimitSize() {c.setState(c.rwc, StateActive, runHooks)}if err != nil {}// 处理请求serverHandler{c.server}.ServeHTTP(w, w.req)}
}

继续跟踪ServeHTTP方法,ServeMux是一个HTTP请求的多路复用器,在这里可以根据请求的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)// 这块就是执行我们注册的handler,也就是例子中的getProfile()h.ServeHTTP(w, r)
}

路由匹配

mux.Handler()中是路由匹配的代码

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 onesif mux.hosts {h, pattern = mux.match(host + path)}if h == nil {h, pattern = mux.match(path)}if h == nil {h, pattern = NotFoundHandler(), ""}return
}func (mux *ServeMux) match(path string) (h Handler, pattern string) {// 先从map中查找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, ""
}

总结

服务端的代码看主逻辑主要是看两部分,

  • 一个是注册处理器,标准库使用map进行存储,本质是一个静态索引,同时维护了一个切片,用来做前缀匹配,只要以/结尾的,都会在切片中存储;
  • 服务端监听端口本质也是使用net网络库进行TCP连接,然后监听对应的TCP连接,每一个HTTP请求都会开一个goroutine去处理请求,所以如果有海量请求,会在一瞬间创建大量的goroutine,这个是一个性能瓶颈点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_239692.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【圣诞特辑】码一个漂漂亮亮的圣诞树(Single Dog版)

目录 前言 一、C语言版圣诞树 1.代码实现 2.效果图 二、python版圣诞树 1.代码实现 2.效果图​ 三、html5版圣诞树 1.代码实现 2.效果图 总结 前言 圣诞节即将来临&#xff0c;圣诞树也是必不可少的装饰之一。圣诞树是一棵绿叶繁茂的树&#xff0c;上面挂满了彩色的灯…

Python pandas有好几百个库函数,你都用过吗(4)

上一篇链接&#xff1a; https://blog.csdn.net/boysoft2002/article/details/128428569 S~W&#xff1a; Function46~56 Types[Function][45:] [set_eng_float_format, show_versions, test, timedelta_range, to_datetime, to_numeric, to_pickle, to_timedelta, unique,…

VSCode 最全实用插件

一、必备插件 &#x1f33e;Chinese&#xff08;中文&#xff09; Settings Sync&#xff08;配置同步到云端&#xff09; 可以让我们的vscode配置同步到云端&#xff0c;当我们跟换电脑或者再次安装vscode的时候&#xff0c;只需要登录账号即可同步配置了 wakatime&#xf…

技术分享 Oracle下启用块跟踪

创建存放块跟踪文件目录 [oraclehost01 ~]$ cd /u01/app [oraclehost01 app]$ mkdir BCT 启用块跟踪 SQL> alter database enable block change tracking using file /u01/app/BCT/rman.bct; 检查块跟踪状态 SQL> col filename for a22 SQL> select filename, status,…

RabbitMQ——延迟队列

目录 一、延迟队列的应用场景 1. 场景&#xff1a;"订单下单成功后&#xff0c;15分钟未支付自动取消" ① 传统处理超时订单 ② RabbitMQ延时队列方案 二、延迟队列中的消息投递和消息消费 1.TTL 和 DLX ① TTL ② DLX和死信队列 ③ 延迟队列 ④ 开发步骤 …

get/post/put/delete请求头说明

目录 1.请求头说明 2.get 3.delete 4.post 5.put 6. 说明 7.Content-Type说明 1.请求头说明 前端发出的请求通过浏览器进行查看&#xff0c;可以发现分为四个部分。常规信息(General)&#xff0c;请求头信息(Request Headers)&#xff0c;响应头信息(Response Headers)…

ConvLSTM时空预测实战代码详解

写在前面 时空预测是很多领域都存在的问题&#xff0c;不同于时间序列&#xff0c;时空预测不仅需要探究时间的变化&#xff0c;也需要关注空间的变化。许多预测问题都只片面的关注时间问题&#xff0c;如预测某人未来3年患某种病的概率&#xff0c;食堂就餐人数等&#xff0c…

35. 池化层 / 汇聚层 代码实现

1. 池化层 在下面的代码中的pool2d函数&#xff0c;我们实现汇聚层的前向传播。 这类似于之前文章中的的corr2d函数。 然而&#xff0c;这里我们没有卷积核&#xff0c;输出为输入中每个区域的最大值或平均值。 from torch import nn from d2l import torch as d2l# X是输入&…

springcloud-gateway简介

目录 1. gateway简介 1.1 是什么 1.2 作用 1.3 主要特征 1.4 与zuul的主要区别 1.5 主要组件 1.6 架构图 2. 开发示例 2.1 创建一个gateway模块 2.2 与nacos结合使用 2.2.1 默认规则 2.2.2 通过配置文件配置路由 2.2.3 动态路由 1. gateway简介 1.1 是什么 SpringC…

VuePress初学之利用模板theme创建一个个人博客网站

目录前言官方文档创建项目创建目录安装VuePress初始化项目创建文档修改package.json运行项目修改README.md的编码显示官方默认主题创建.vuepress文件创建config.js修改README.md补充logo资源运行效果更多默认主题配置开源主题vuepress-theme-reco安装脚手架初始化项目安装npm运…

10.2、Django入门--前台管理

文章目录1、URLconf 路由管理展示首页2、视图函数处理业务逻辑展示书籍的详细页3、模板管理实现好看的HTML页面3.1 模板引擎配置3.2 模板语法&#xff1a;变量3.3 模板语法: 常用标签3.4 主页与详情页前端HTML设计常用的HTML编写基础标题标签列表标签图片标签链接标签表格标签表…

RabbitMQ 第一天 基础 6 SpringBoot 整合RabbitMQ

RabbitMQ 【黑马程序员RabbitMQ全套教程&#xff0c;rabbitmq消息中间件到实战】 文章目录RabbitMQ第一天 基础6 SpringBoot 整合RabbitMQ6.1 SpringBoot 整合 RabbitMQ【生产者】6.1.1 生产者6.2 SpringBoot 整合 RabbitMQ【消费者】6.2.1 消费者6.3 小结第一天 基础 6 Spri…

day3-javascript

HTML的注释 CSS的注释 Javascript的注释是不一样的 JQuery JQuery是一个JavaScript的第三方模块 基于JQuery&#xff0c;自己开发一个功能。 现成的工具 依赖jQuery&#xff0c;例如BootStrap动态效果

Vue事件处理的基本使用

前言 事件处理在vue中也是非常重要的一项技术&#xff0c;它类似于js的事件处理&#xff0c;但是也有不同&#xff0c;下面就简单介绍一下在vue中如何进行事件使用以及一些要点 1 事件基本使用 在这里我们使用单击事件为例&#xff0c;简单讲讲在vue中单击事件的编写以及细节…

Spring Bean作用域

目录 什么是作用域呢 ? 那什么又是Spring Bean的作用域呢 ? Spring框架默认Bean作用域是什么呢 ? Spring Bean的作用域都有哪些呢 ? 如何设置Bean作用域 什么是作用域呢 ? 在JavaSE中,作用域就是指一个变量可生效的范围. 就比如一个变量的作用域是方法的代码块的范围…

将单向链表按照目标值value 划分成左边小,中间等,右边大的形式,给定一个单链表,判断单链表的值是否是回文结构【图文解释包你看懂】

将单向链表按照目标值value 划分成左边小&#xff0c;中间等&#xff0c;右边大的形式 例如 1 -> 3 -> 5-> 3 -> 7 按照value 3划分 1-> 3-> 3 -> 5 -> 7 解题思路&#xff1a;给定值为 value 用6个变量&#xff0c;分别表示 小于value 的Head sH &…

第11章_数据库的设计规范(理论了解)

第11章_数据库的设计规范 范式 2.3键和相关属性的概念 范式的定义会使用到主键和候选键&#xff0c;数据库中的键(Key)由一个或者多个属性组成。数据表中常用的几种键和属性的定义: 超键︰能唯─标识元组的属性集叫做超键。候选键︰如果超键不包括多余的属性&#xff0c;那…

WEB1.0起源:全球首个网站info.cern.ch

伯纳斯李&#xff08;图&#xff09;1990年创立第一个网站。 info.cern.ch是世上第一个网站&#xff0c;提供有关万维网的资料。 info.cern.ch这个网站依然运作如常。 英国科学家蒂姆伯纳斯-李 (Tim Berners-Lee) 于 1989 年在 CERN 工作期间发明了万维网 (WWW)。Web 最初的构思…

mqtt的使用与二次封装

前提&#xff1a;先安装Mosquitto并启动服务&#xff0c;可使用mqttx进行接收发送的测试。 Mosquitto以配置启动命令 mosquitto -c mosquitto.conf -v原文链接&#xff1a;mqtt的使用 本文为测试使用固无账号密码&#xff0c;可在原文查看 封装后实现效果&#xff0c;加入一个…

耗时二周,万字总结Maven简明教程,与君共勉!

什么是Mavne Maven 是一个项目管理工具&#xff0c;它包含了一个项目对象模型 (POM&#xff1a;Project Object Model)&#xff0c;一组标准集合。由于 Maven 使用标准目录布局和默认构建生命周期&#xff0c;开发团队几乎可以立即自动化项目的构建基础设施。在多个开发团队环…