当前位置: 首页 > news >正文

Go语言设计与实现 -- http服务器编程

Go http服务器编程

初始

http 是典型的 C/S 架构,客户端向服务端发送请求(request),服务端做出应答(response)。

golang 的标准库 net/http 提供了 http 编程有关的接口,封装了内部TCP连接和报文解析的复杂琐碎的细节,使用者只需要和 http.requesthttp.ResponseWriter 两个对象交互就行。也就是说,我们只要写一个 handler,请求会通过参数传递进来,而它要做的就是根据请求的数据做处理,把结果写到 Response 中。废话不多说,来看看 hello world 程序有多简单吧!

我们有两种写法,现在来看一下这两种写法:

type helloHandler struct {
}func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello, world!"))
}func main() {http.HandleFunc("/way1", func(writer http.ResponseWriter, request *http.Request) {writer.Write([]byte("Hello, world!"))})http.Handle("/way2", &helloHandler{})http.ListenAndServe("localhost:8080", nil)
}

我们先把注意力聚焦到/way2上,先暂时不看/way1

正如上面程序展示的那样,我们只要实现的一个 Handler,它的接口原型是(也就是说只要实现了 ServeHTTP 方法的对象都可以作为 Handler):

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

然后,注册到对应的路由路径上就 OK 了。

http.HandleFunc接受两个参数:第一个参数是字符串表示的 url 路径,第二个参数是该 url 实际的处理对象。

http.ListenAndServe 监听在某个端口,启动服务,准备接受客户端的请求(第二个参数这里设置为 nil,这里也不要纠结什么意思,后面会有讲解)。每次客户端有请求的时候,把请求封装成 http.Request,调用对应的 handler 的 ServeHTTP 方法,然后把操作后的 http.ResponseWriter 解析,返回到客户端。

封装

/way2没有什么问题,但是有一个不便:每次写 Handler 的时候,都要定义一个类型,然后编写对应的 ServeHTTP 方法,这个步骤对于所有 Handler 都是一样的。重复的工作总是可以抽象出来,net/http 也正这么做了,它提供了 http.HandleFunc 方法,允许直接把特定类型的函数作为 handler。于是/way2可以改成/way1的方法。

但是实际上/way1的本质还是/way2这种方法

我们看一下源码就可以知道了:

Handler是一个接口:

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

Handler 接口中声明了名为 ServeHTTP 的函数签名,也就是说任何结构只要实现了这个 ServeHTTP 方法,那么这个结构体就是一个 Handler 对象。其实 go 的 http 服务都是基于 Handler 进行处理,而 Handler 对象的 ServeHTTP 方法也正是用以处理 request 并构建 response 的核心逻辑所在。

我们现在回到上面的HandleFunc函数,注意一下这个代码:

mux.Handle(pattern, HandlerFunc(handler))

可能有人认为 HandlerFunc 是一个函数,包装了传入的 handler 函数,返回了一个 Handler 对象。然而这里 HandlerFunc 实际上是将 handler 函数做了一个类型转换,看一下 HandlerFunc 的定义:

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

HandlerFunc 是一个类型,只不过表示的是一个具有func(ResponseWriter, *Request)签名的函数类型,并且这种类型实现了 ServeHTTP 方法(在 ServeHTTP 方法中又调用了自身),也就是说这个类型的函数其实就是一个 Handler 类型的对象。利用这种类型转换,我们可以将一个 handler 函数转换为一个Handler 对象,而不需要定义一个结构体,再让这个结构实现 ServeHTTP 方法。读者可以体会一下这种技巧。

路由

虽然上面的代码已经工作,并且能实现很多功能,但是实际开发中,HTTP 接口会有许多的 URL 和对应的 Handler。这里就要讲 net/http 的另外一个重要的概念:ServeMuxMuxmultiplexor 的缩写,就是多路传输的意思(请求传过来,根据某种判断,分流到后端多个不同的地方)。ServeMux 可以注册多了 URL 和 handler 的对应关系,并自动把请求转发到对应的 handler 进行处理。我们还是来看例子吧:

func helloHandler(w http.ResponseWriter, r *http.Request) {io.WriteString(w, "Hello, world\n")
}func echoHandler(w http.ResponseWriter, r *http.Request) {io.WriteString(w, r.URL.Path)
}func main() {mux := http.NewServeMux()mux.HandleFunc("/hello", helloHandler)mux.HandleFunc("/", echoHandler)http.ListenAndServe("localhost:8080", mux)
}

这个服务器的功能也很简单:如果在请求的 URL 是 /hello,就返回 hello, world!;否则就返回 URL 的路径,路径是从请求对象 http.Requests 中提取的。

这段代码和之前的代码有两点区别:

  1. 通过 NewServeMux 生成了 ServerMux 结构,URL 和 handler 是通过它注册的
  2. http.ListenAndServe 方法第二个参数变成了上面的 mux 变量

还记得我们之前说过,http.ListenAndServe 第二个参数应该是 Handler 类型的变量吗?这里为什么能传过来 ServeMux?嗯,估计你也猜到啦:ServeMux 也是是 Handler 接口的实现,也就是说它实现了 ServeHTTP 方法,我们来看一下:

type ServeMux struct {// contains filtered or unexported fields
}func NewServeMux() *ServeMux
func (mux *ServeMux) Handle(pattern string, handler Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

哈!果然,这里的方法我们大都很熟悉,除了 Handler() 返回某个请求的 Handler。HandleHandleFunc 这两个方法 net/http 也提供了,后面我们会说明它们之间的关系。而 ServeHTTP 就是 ServeMux 的核心处理逻辑:**根据传递过来的 Request,匹配之前注册的 URL 和处理函数,找到最匹配的项,进行处理。**可以说 ServeMux 是个特殊的 Handler,它负责路由和调用其他后端 Handler 的处理方法。

  • URL 分为两种,末尾是 /:表示一个子树,后面可以跟其他子路径; 末尾不是 /,表示一个叶子,固定的路径
  • / 结尾的 URL 可以匹配它的任何子路径,比如 /images 会匹配 /images/cute-cat.jpg
  • 它采用最长匹配原则,如果有多个匹配,一定采用匹配路径最长的那个进行处理
  • 如果没有找到任何匹配项,会返回 404 错误
  • ServeMux 也会识别和处理 ...,正确转换成对应的 URL 地址

你可能会有疑问?我们之间为什么没有使用 ServeMux 就能实现路径功能?那是因为 net/http 在后台默认创建使用了 DefaultServeMux

深入

Server

首先来看 http.ListenAndServe():

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

这个函数其实也是一层封装,创建了 Server 结构,并调用它的 ListenAndServe 方法,那我们就跟进去看看:

// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {Addr           string        // TCP address to listen on, ":http" if emptyHandler        Handler       // handler to invoke, http.DefaultServeMux if nil......
}// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.  If
// srv.Addr is blank, ":http" is used.
func (srv *Server) ListenAndServe() error {addr := srv.Addrif addr == "" {addr = ":http"}ln, err := net.Listen("tcp", addr)if err != nil {return err}return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

Server 保存了运行 HTTP 服务需要的参数,调用 net.Listen 监听在对应的 tcp 端口,tcpKeepAliveListener 设置了 TCP 的 KeepAlive 功能,最后调用 srv.Serve()方法开始真正的循环逻辑。我们再跟进去看看 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.
func (srv *Server) Serve(l net.Listener) error {defer l.Close()var tempDelay time.Duration // how long to sleep on accept failure// 循环逻辑,接受请求并处理for {// 有新的连接rw, e := l.Accept()if e != nil {if ne, ok := e.(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", e, tempDelay)time.Sleep(tempDelay)continue}return e}tempDelay = 0// 创建 Conn 连接c, err := srv.newConn(rw)if err != nil {continue}c.setState(c.rwc, StateNew) // before Serve can return// 启动新的 goroutine 进行处理go c.serve()}
}

最上面的注释也说明了这个方法的主要功能:

  • 接受 Listener l 传递过来的请求
  • 为每个请求创建 goroutine 进行后台处理
  • goroutine 会读取请求,调用 srv.Handler
func (c *conn) serve() {origConn := c.rwc // copy it before it's set nil on Close or Hijack...for {w, err := c.readRequest()if c.lr.N != c.server.initialLimitedReaderSize() {// If we read any bytes off the wire, we're active.c.setState(c.rwc, StateActive)}...// 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.serverHandler{c.server}.ServeHTTP(w, w.req)w.finishRequest()if w.closeAfterReply {if w.requestBodyLimitHit {c.closeWriteAndWait()}break}c.setState(c.rwc, StateIdle)}
}

看到上面这段代码 serverHandler{c.server}.ServeHTTP(w, w.req)这一句了吗?它会调用最早传递给 Server 的 Handler 函数:

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {handler := sh.srv.Handlerif handler == nil {handler = DefaultServeMux}if req.RequestURI == "*" && req.Method == "OPTIONS" {handler = globalOptionsHandler{}}handler.ServeHTTP(rw, req)
}

哇!这里看到 DefaultServeMux 了吗?如果没有 handler 为空,就会使用它。handler.ServeHTTP(rw, req),Handler 接口都要实现 ServeHTTP 这个方法,因为这里就要被调用啦。

也就是说,无论如何,最终都会用到 ServeMux,也就是负责 URL 路由的家伙。前面也已经说过,它的 ServeHTTP 方法就是根据请求的路径,把它转交给注册的 handler 进行处理。这次,我们就在源码层面一探究竟。

ServeMux

我们已经知道,ServeMux 会以某种方式保存 URL 和 Handlers 的对应关系,下面我们就从代码层面来解开这个秘密:

type ServeMux struct {mu    sync.RWMutexm     map[string]muxEntry  // 存放路由信息的字典!\(^o^)/hosts bool // whether any patterns contain hostnames
}type muxEntry struct {explicit boolh        Handlerpattern  string
}

没错,数据结构也比较直观,和我们想象的差不多,路由信息保存在字典中,接下来就看看几个重要的操作:路由信息是怎么注册的?ServeHTTP 方法到底是怎么做的?路由查找过程是怎样的?

// 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 " + pattern)}if handler == nil {panic("http: nil handler")}if mux.m[pattern].explicit {panic("http: multiple registrations for " + pattern)}// 创建 `muxEntry` 并添加到路由字典中mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}if pattern[0] != '/' {mux.hosts = true}// 这是一个很有用的小技巧,如果注册了 `/tree/`, `serveMux` 会自动添加一个 `/tree` 的路径并重定向到 `/tree/`。当然这个 `/tree` 路径会被用户显示的路由信息覆盖。// Helpful behavior:// If pattern is /tree/, insert an implicit permanent redirect for /tree.// It can be overridden by an explicit registration.n := len(pattern)if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {// If pattern contains a host name, strip it and use remaining// path for redirect.path := patternif pattern[0] != '/' {// In pattern, at least the last character is a '/', so// strings.Index can't be -1.path = pattern[strings.Index(pattern, "/"):]}mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently), pattern: pattern}}
}

路由注册没有什么特殊的地方,很简单,也符合我们的预期,注意最后一段代码对类似 /tree URL 重定向的处理。

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

好吧,ServeHTTP 也只是通过 mux.Handler(r) 找到请求对应的 handler,调用它的 ServeHTTP 方法,代码比较简单我们就显示了,它最终会调用 mux.match() 方法,我们来看一下它的实现:

// Does path match pattern?
func pathMatch(pattern, path string) bool {if len(pattern) == 0 {// should not happenreturn false}n := len(pattern)if pattern[n-1] != '/' {return pattern == path}// 匹配的逻辑很简单,path 前面的字符和 pattern 一样就是匹配return len(path) >= n && path[0:n] == pattern
}// Find a handler on a handler map given a path string
// Most-specific (longest) pattern wins
func (mux *ServeMux) match(path string) (h Handler, pattern string) {var n = 0for k, v := range mux.m {if !pathMatch(k, path) {continue}// 最长匹配的逻辑在这里if h == nil || len(k) > n {n = len(k)h = v.hpattern = v.pattern}}return
}

match 会遍历路由信息字典,找到所有匹配该路径最长的那个。路由部分的代码解释就到这里了,最后回答上面的一个问题:http.HandleFuncServeMux.HandlerFunc 是什么关系?

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }// 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 调用对应的方法,到这里上面的一切都串起来了!

相关文章:

Go语言设计与实现 -- http服务器编程

Go http服务器编程 初始 http 是典型的 C/S 架构,客户端向服务端发送请求(request),服务端做出应答(response)。 golang 的标准库 net/http 提供了 http 编程有关的接口,封装了内部TCP连接和…...

MySQL-视图

视图是什么? 一张虚表,和真实的表一样。视图包含一系列带有名称的行和列数据。视图是从一个或多个表中导出来的,我们可以通过insert,update,delete来操作视图。当通过视图看到的数据被修改时,相应的原表的数…...

都工作3年了,怎么能不懂双亲委派呢?(带你手把手断点源码)

💗推荐阅读文章💗 🌸JavaSE系列🌸👉1️⃣《JavaSE系列教程》🌺MySQL系列🌺👉2️⃣《MySQL系列教程》🍀JavaWeb系列🍀👉3️⃣《JavaWeb系列教程》…...

Hive 运行环境搭建

文章目录Hive 运行环境搭建一、Hive 安装部署1、安装hive2、MySQL 安装3、Hive 元数据配置到 Mysql1) 拷贝驱动2) 配置Metastore 到 MySQL3) 再次启动Hive4) 使用元数据服务的方式访问Hive二、使用Dbaver连接HiveHive 运行环境搭建 HIve 下载地址:http://archive.a…...

SAP ABAP 深度解析Smartform打印特殊符号等功能

ABAP 开发人员可以在 Smartform 输出上显示 SAP 图标或 SAP 符号。例如,需要在 SAP Smart Forms 文档上显示复选框形状的输出。SAP Smartform 文档上可以轻松显示空复选框、标记复选框以及 SAP 图标等特殊符号。 在 SAP Smartform 文档中添加一个新的文本节点。 1. 单击“更…...

React17+React Hook+TS4 最佳实践仿 Jira 企业级项目笔记

前言 个人笔记,记录个人过程,如有不对,敬请指出React17React HookTS4 最佳实践仿 Jira 企业级项目项目完成到第十章,剩下后面就没有看了,说的不是特别好 github地址:https://github.com/superBiuBiuMan/React-jira husky方便我们管理git hooks的工具 REST-API风格 https://zh…...

35- tensorboard的使用 (PyTorch系列) (深度学习)

知识要点 FashionMNIST数据集: 十种产品的分类. # T-shirt/top, Trouser, Pullover, Dress, Coat,Sandal, Shirt, Sneaker, Bag, Ankle Boot.writer SummaryWriter(run/fashion_mnist_experiment_1) # 网站显示一 tensorboard的使用 在网站显示pytorch的架构:1.1 …...

ChatGPT在工业领域的用法

在工业数字化时代,我们需要怎么样的ChatGPT? 近日,ChatGPT热度高居不下,强大的人机交互能力令人咋舌,在国内更是掀起一股讨论热潮。一时间,这场由ChatGPT引起的科技飓风,使得全球最顶尖科技力量…...

使用Chakra-UI封装简书的登录页面组件(React)

要求:使用chakra ui和react 框架将简书的登录页面的表单封装成独立的可重用的组件使用到的API:注册API请求方式:POST 请求地址:https://conduit.productionready.io/api/users请求数据: {"user":{ "username&quo…...

Three.js初试——基础概念(二)

前言 姊妹篇:Three.js初试——基础概念 介绍了 Three.js 的一些核心要素概念,这篇文章会讲一下它的关键要素概念。 之前我们了解到展示一个3D图像,必须要有场景、相机、渲染器这些核心要素,仅仅这些还不够,我们还需要…...

Qt音视频开发21-mpv内核万能属性机制

一、前言 搞过vlc内核后又顺带搞了搞mpv内核,mpv相比vlc,在文件数量、sdk开发便捷性方面绝对占优势的,单文件(可能是静态编译),不像vlc带了一堆插件,通过各种属性来set和get值,后面…...

C语言学生随机抽号演讲计分系统

6.学生随机抽号演讲计分系统(★★★★) 设计一款用于课程大作业检查或比赛计分的软件,基本功能: (1)设置本课程的学生总数 (2)根据本次参与的学生总数,随机抽取一个还未汇报演讲的学生的学号。 (3)每个学生汇报演讲完毕,输入该学生…...

Spring Boot 3.0系列【12】核心特性篇之任务调度

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot版本3.0.3 源码地址:https://gitee.com/pearl-organization/study-spring-boot3 文章目录 前言Spring Scheduler1. 单线程任务2. 自动配置3. 多线程异步任务Quartz1. 简介2. 核心组件2.1 Job(任务)2.2 Trigger(…...

Java操作XML

Java操作XML XML语法 一个XML文件分为文档声明、元素、属性、注释、CDATA区、特殊字符、处理指令。 转义字符 对于一些单个字符&#xff0c;若想显示其原始样式&#xff0c;也可以使用转义的形式予以处理。 & > &amp; < > < > > > " &g…...

女神节灯笼祝福【HTML+CSS】

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…...

CUDA并行计算基础知识

1、相关缩写术语 显卡:GPU 显卡驱动:驱动软件 GPU架构: 硬件的设计方式,例如是否有L1 or L2缓存 CUDA: 一种编程语言像C++, Python等,只不过它是专门用来操控GPU的 cudnn: 一个专门为深度学习计算设计的软件库,里面提供了很多专门的计算函数 CUDAToolkit:所谓的装cuda首先…...

88. 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。注意&#xff1a;最终&#xff0c;合并后数组不应…...

卢益贵(码客):软件开发团队的管理要素

卢益贵&#xff08;码客&#xff09;&#xff1a;软件开发团队的管理要素 最好的范例是领导 无论个人素养、技术水平和代码风格&#xff0c;管理者应该起到典范的作用。 最高的权力是威望 管理者的威望比手中权力更有信服力。在处处倚仗权力施压的团队中&#xff0c;高压必有…...

中小企业的TO B蓝海,如何「掘金」?

中国中小企业的数字化转型土壤&#xff0c;如今究竟成长到了哪一步&#xff1f;对一众数字服务厂商而言&#xff0c;在另一个付费群体出现的当下&#xff0c;产品形态是否应该进行微调&#xff1f; 作者|皮爷 出品|产业家 中国市场存在一个黄金定律&#xff1a;二八法则。 这…...

C++ 算法主题系列之集结0-1背包问题的所有求解方案

1. 前言 背包问题是类型问题&#xff0c;通过对这一类型问题的理解和掌握&#xff0c;从而可以归纳出求解此类问题的思路和模板。 背包问题的分类有&#xff1a; 0-1背包问题&#xff0c;也称为不可分割背包问题。无限背包问题。判定性背包问题.带附属关系的背包问题。双背包…...

【Vue】Vue常见的6种指令

Vue的6种指令-前言指令&#xff08;Directives&#xff09;是vue 为开发者提供的模板语法&#xff0c;用于辅助开发者渲染页面的基本结构。vue 中的指令按照不同的用途可以分为如下6 大类① 内容渲染指令 ② 属性绑定指令 ③ 事件绑定指令 ④ 双向绑定指令 ⑤ 条件渲染指令 ⑥ …...

计算机科学与技术(嵌入式)四年学习资料_文件目录树

说明&#xff1a; 资料内容主要包括&#xff1a;计嵌专业2019级大学四年主要科目的各种电子资料&#xff0c;有电子实验报告、课程设计报告、课程设计项目、整理复习笔记、电子书、ppt、练习题、期末试卷、部分课程软件资源、科创项目&#xff0c;职业生涯规划书&#xff0c;大…...

【java】Java 继承

文章目录继承的概念生活中的继承&#xff1a;类的继承格式为什么需要继承公共父类&#xff1a;继承类型继承的特性继承关键字extends关键字implements关键字super 与 this 关键字final 关键字构造器继承的概念 继承是java面向对象编程技术的一块基石&#xff0c;因为它允许创建…...

自媒体账号数据分析从何入手?

账号的数据可以直接反应这个账号的好坏&#xff0c;数据越高收益就会越好&#xff0c;数据越差收益自然高不了。 新手要从哪些方面入手见效更快呢&#xff1f;今天大周就来把自己的经验分享给粉丝们&#xff01; 1、账号定位 &#xff08;1&#xff09;账号所创作的领域 &a…...

Clickhouse新版本JSON字段数据写入方式

Clickhouse新版本JSON字段数据写入方式 在Clickhouse版本22.3.1版本以上&#xff0c;提供了针对JSON格式数据的新的数据类型&#xff1a;JSON&#xff0c;从而实现了存储此类数据由原先的结构化表结构&#xff0c;更新为现在的半结构化表存储。对于新增字段&#xff0c;某些同…...

HNU-电路与电子学-实验2

实验二 模型机组合部件的实现&#xff08;一&#xff09; 班级 计XXXXX 姓名 wolf 学号 2021080XXXXX 一、实验目的 1&#xff0e;了解简易模型机的内部结构和工作原理。 2&#xff0e;熟悉译码器、运算器的工作原理。 3&#xff0e;分析模型机的功…...

从0开始学python -49

Python MySQL - mysql-connector 驱动 -2 插入数据 插入数据使用 “INSERT INTO” 语句&#xff1a; demo_mysql_test.py: 向 sites 表插入一条记录。 import mysql.connectormydb mysql.connector.connect(host"localhost",user"root",passwd"…...

Spring MVC 详解(连接、获取参数、返回数据)

在之前我们先简单那谈谈Spring、SpringBoot以及Spring MVC框架之间有什么关系&#xff1f;首先Spring是一个框架&#xff0c;SpringBoot脚手架是为了快速开发Spring框架而创造的技术。可以理解为SpringBoot又在Spring上面包了一层壳子&#xff0c;是基于Spring的&#xff0c;是…...

IT女神节(致敬中国IT界永远的女神严蔚敏-数据结构)

我们都知道程序数据结构算法。相信很多人都学过严蔚敏的数据结构的课程。作为一个码农&#xff0c;在这不管是3.7女神节&#xff0c;还是3.8妇女节。我觉得都有必要向这些教育界的老前辈致敬。今天我就梳理梳理&#xff0c;最经典的数据结构教材。 严蔚敏介绍&#xff08;来自…...

Java 集合分页

一、前言 在Java开发中&#xff0c;若单次展示的数据量太大&#xff0c;会造成程序响应缓慢&#xff0c;就需要用到 分页 功能&#xff0c;每一页展示一定量的数据&#xff0c;分多次展示 ... 那么在List集合中&#xff0c;如何实现 分页 功能呢&#xff1f; 本文将以3种方式&a…...

uncode wordpress主题/广州新闻24小时爆料热线

如大家所期待&#xff0c;flow.ci 现已支持开源中国的代码仓库 &#xff0d; 码云&#xff0c;可以直接构建 GitOSC 的项目了&#xff0c;点击创建项目&#xff0d;选择代码仓库&#xff0d;选择码云&#xff0d;绑定 OSChina 账户&#xff0d;选择要构建项目&#xff0c;教程看…...

在哪个网站做服装代理批发/百度新闻app

第1关:代换-置换网络 任务描述 在密码学中,代换-置换网络(或译作置换排列网络,英语:Substitution-Permutation Network,缩写作 SP-network 或 SPN)是乘积密码和分组加密的一种,美国数学家克劳德香农在1949年为了找到利用简单的代换-置换方式进行加密的安全加密方式,发…...

wordpress Apache升级/广州网站外包

IOS用CGContextRef画各种图形(文字、圆、直线、弧线、矩形、扇形、椭圆、三角形、圆角矩形、贝塞尔曲线、图片)转载于:https://www.cnblogs.com/hl666/p/3800052.html...

景安网络网站建设/免费网站推广网站破解版

2019独角兽企业重金招聘Python工程师标准>>> 一、 基本介绍 Celery是一个专注于实时处理和任务调度的分布式任务队列。所谓任务就是消息&#xff0c;消息中的有效载荷中包含要执行任务需要的全部数据。 使用Celery常见场景&#xff1a; Web应用。当用户触发的一个操…...

春风家教营销型网站建设/长春seo培训

1. 先从 http://download.csdn.net/detail/dingyuming1991/9618125 下载反编译工具dex2jar和jd-gui&#xff08;当然也可以google搜索下载&#xff09;&#xff1b; 2. 将要反编译的apk包改扩展名为zip(apk包实则为zip压缩包)&#xff0c;解压zip文件&#xff1b; 3. 在解压出来…...

做外贸业务去哪些网站/电商培训心得体会

2019独角兽企业重金招聘Python工程师标准>>> sql语句查询在实际开发中,是一个比较重要的环节,好的sql&#xff0c;优良的sql的会加快程序的运行,减少服务器的压力.下面就以报表为原型. 请看如下场景: 一个OA系统需求要统计每个部门的用户 假设有下面三张表 用户表 …...