docker镜像是如何导入的?
镜像导入是由image/tarexport/load.go#tarexporter.Load()完成的
以下代码参考github.com/docker/docker版本v0.0.0-20181129155816-baab736a3649
主要是注册镜像信息以及解包镜像tar流到新root
导出和保存的区别在于
- 导出(export): 仅导出文件结构
- 保存(save): 保存镜像历史和元数据
这意味着导出将不会包含USER、EXPOSE等Dockerfile里面的命令,也就无法转移镜像到另一台机器上了
func (l *tarexporter) Load(inTar io.ReadCloser, outStream io.Writer, quiet bool) error {var progressOutput progress.Outputif !quiet {progressOutput = streamformatter.NewJSONProgressOutput(outStream, false)}outStream = streamformatter.NewStdoutWriter(outStream)// 1. 创建docker-import的临时目录tmpDir, err := ioutil.TempDir("", "docker-import-")if err != nil {return err}defer os.RemoveAll(tmpDir)// 2. 解包tar流到临时目录if err := chrootarchive.Untar(inTar, tmpDir, nil); err != nil {return err}// 3. 打开manifest文件,并解析manifestPath, err := safePath(tmpDir, manifestFileName)if err != nil {return err}manifestFile, err := os.Open(manifestPath)if err != nil {if os.IsNotExist(err) {return l.legacyLoad(tmpDir, outStream, progressOutput)}return err}defer manifestFile.Close()var manifest []manifestItemif err := json.NewDecoder(manifestFile).Decode(&manifest); err != nil {return err}var parentLinks []parentLinkvar imageIDsStr stringvar imageRefCount int// 4. 从manifest中读取并解析到imagefor _, m := range manifest {configPath, err := safePath(tmpDir, m.Config)if err != nil {return err}config, err := ioutil.ReadFile(configPath)if err != nil {return err}img, err := image.NewFromJSON(config)if err != nil {return err}if err := checkCompatibleOS(img.OS); err != nil {return err}rootFS := *img.RootFSrootFS.DiffIDs = nil// 若image rootFS diffID数量与manifest中记录的层数不一致,则报错if expected, actual := len(m.Layers), len(img.RootFS.DiffIDs); expected != actual {return fmt.Errorf("invalid manifest, layers length mismatch: expected %d, got %d", expected, actual)}// On Windows, validate the platform, defaulting to windows if not present.os := img.OSif os == "" {os = runtime.GOOS}if runtime.GOOS == "windows" {if (os != "windows") && (os != "linux") {return fmt.Errorf("configuration for this image has an unsupported operating system: %s", os)}}// 5. 注册层for i, diffID := range img.RootFS.DiffIDs {layerPath, err := safePath(tmpDir, m.Layers[i])if err != nil {return err}r := rootFSr.Append(diffID)newLayer, err := l.lss[os].Get(r.ChainID())if err != nil {// 如果没有注册,那就注册layernewLayer, err = l.loadLayer(layerPath, rootFS, diffID.String(), os, m.LayerSources[diffID], progressOutput)if err != nil {return err}}defer layer.ReleaseAndLog(l.lss[os], newLayer)// 若manifest与缓存中layer diffID不一致,则报错if expected, actual := diffID, newLayer.DiffID(); expected != actual {return fmt.Errorf("invalid diffID for layer %d: expected %q, got %q", i, expected, actual)}rootFS.Append(diffID)}// 6. 缓存该层镜像配置imgID, err := l.is.Create(config)if err != nil {return err}imageIDsStr += fmt.Sprintf("Loaded image ID: %s\n", imgID)imageRefCount = 0for _, repoTag := range m.RepoTags {named, err := reference.ParseNormalizedNamed(repoTag)if err != nil {return err}ref, ok := named.(reference.NamedTagged)if !ok {return fmt.Errorf("invalid tag %q", repoTag)}// 设置已加载的id、referencel.setLoadedTag(ref, imgID.Digest(), outStream)outStream.Write([]byte(fmt.Sprintf("Loaded image: %s\n", reference.FamiliarString(ref))))imageRefCount++}parentLinks = append(parentLinks, parentLink{imgID, m.Parent})l.loggerImgEvent.LogImageEvent(imgID.String(), imgID.String(), "load")}for _, p := range validatedParentLinks(parentLinks) {if p.parentID != "" {if err := l.setParentID(p.id, p.parentID); err != nil {return err}}}if imageRefCount == 0 {outStream.Write([]byte(imageIDsStr))}return nil
}
Untar
主要过程是将tar流解包到新root
untar操作实际由chrootarchive/archive_unix.go untar()执行
// untar is the entry-point for docker-untar on re-exec. This is not used on
// Windows as it does not support chroot, hence no point sandboxing through
// chroot and rexec.
func untar() {runtime.LockOSThread()flag.Parse()var options *archive.TarOptions//read the options from the pipe "ExtraFiles"if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil {fatal(err)}// Linux上的Chroot使用pivot_root,而不是Chroot。 pivot_root需要一个新根和一个旧根。旧根必须是新根的子目录,它是调用pivot_root后当前rootfs驻留的位置。New root是新rootfs设置的位置。在调用pivot_root之后,旧根会被移除,因此在新根下不再可用。这类似于libcontainer设置容器rootfs的方式// 在这里是以前面创建的临时目录作为新root,并在其下创建privot_root作为老root,最后切换到新rootif err := chroot(flag.Arg(0)); err != nil {fatal(err)}// 将tar流解包到新rootif err := archive.Unpack(os.Stdin, "/", options); err != nil {fatal(err)}// fully consume stdin in case it is zero paddedif _, err := flush(os.Stdin); err != nil {fatal(err)}os.Exit(0)
}
loadLayer
注册镜像层以及加载层tar流到对应目录下
image/tarexport/load.go#tarexpoter.loadLayer()
func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string, os string, foreignSrc distribution.Descriptor, progressOutput progress.Output) (layer.Layer, error) {// We use system.OpenSequential to use sequential file access on Windows, avoiding// depleting the standby list. On Linux, this equates to a regular os.Open.rawTar, err := system.OpenSequential(filename)if err != nil {logrus.Debugf("Error reading embedded tar: %v", err)return nil, err}defer rawTar.Close()var r io.Readerif progressOutput != nil {fileInfo, err := rawTar.Stat()if err != nil {logrus.Debugf("Error statting file: %v", err)return nil, err}r = progress.NewProgressReader(rawTar, progressOutput, fileInfo.Size(), stringid.TruncateID(id), "Loading layer")} else {r = rawTar}inflatedLayerData, err := archive.DecompressStream(r)if err != nil {return nil, err}defer inflatedLayerData.Close()if ds, ok := l.lss[os].(layer.DescribableStore); ok {return ds.RegisterWithDescriptor(inflatedLayerData, rootFS.ChainID(), foreignSrc)}// 到这里是去注册层tar流和本层镜像的chainIDreturn l.lss[os].Register(inflatedLayerData, rootFS.ChainID())
}
func (ls *layerStore) registerWithDescriptor(ts io.Reader, parent ChainID, descriptor distribution.Descriptor) (Layer, error) {// err is used to hold the error which will always trigger// cleanup of creates sources but may not be an error returned// to the caller (already exists).var err errorvar pid stringvar p *roLayer// 1. 从缓存中获取到给定chainID的层信息if string(parent) != "" {p = ls.get(parent)if p == nil {return nil, ErrLayerDoesNotExist}pid = p.cacheID// Release parent chain if errordefer func() {if err != nil {ls.layerL.Lock()ls.releaseLayer(p)ls.layerL.Unlock()}}()if p.depth() >= maxLayerDepth {err = ErrMaxDepthExceededreturn nil, err}}// 2. 创建新的只读层layer := &roLayer{parent: p,cacheID: stringid.GenerateRandomID(),referenceCount: 1,layerStore: ls,references: map[Layer]struct{}{},descriptor: descriptor,}// 3. 准备文件系统(overlay2)文件目录结构if err = ls.driver.Create(layer.cacheID, pid, nil); err != nil {return nil, err}tx, err := ls.store.StartTransaction()if err != nil {return nil, err}defer func() {if err != nil {logrus.Debugf("Cleaning up layer %s: %v", layer.cacheID, err)if err := ls.driver.Remove(layer.cacheID); err != nil {logrus.Errorf("Error cleaning up cache layer %s: %v", layer.cacheID, err)}if err := tx.Cancel(); err != nil {logrus.Errorf("Error canceling metadata transaction %q: %s", tx.String(), err)}}}()// 4. 从给定读写层流中提取变化的内容到镜像层挂载点if err = ls.applyTar(tx, ts, pid, layer); err != nil {return nil, err}// 5. 若本层无父层,那么chainID就是自己的diffID。否则从parent和自己的diffID中生成if layer.parent == nil {layer.chainID = ChainID(layer.diffID)} else {layer.chainID = createChainIDFromParent(layer.parent.chainID, layer.diffID)}// 6. 储存层diffID、size、cacheID、descriptor、parent、os等信息if err = storeLayer(tx, layer); err != nil {return nil, err}ls.layerL.Lock()defer ls.layerL.Unlock()if existingLayer := ls.getWithoutLock(layer.chainID); existingLayer != nil {// Set error for cleanup, but do not return the errorerr = errors.New("layer already exists")return existingLayer.getReference(), nil}if err = tx.Commit(layer.chainID); err != nil {return nil, err}ls.layerMap[layer.chainID] = layerreturn layer.getReference(), nil
}
driver.Create
为镜像层创建diff、work、lower目录,并写入镜像层tar流lower内容到对应lower目录
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr error) {dir := d.dir(id)// 1. 获取当前用户在宿主机对应的userID、groupIDrootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)if err != nil {return err}root := idtools.Identity{UID: rootUID, GID: rootGID}// 2. 为当前用户创建镜像目录if err := idtools.MkdirAllAndChown(path.Dir(dir), 0700, root); err != nil {return err}if err := idtools.MkdirAndChown(dir, 0700, root); err != nil {return err}defer func() {// Clean up on failureif retErr != nil {os.RemoveAll(dir)}}()// 3. 解析储存选项if opts != nil && len(opts.StorageOpt) > 0 {driver := &Driver{}if err := d.parseStorageOpt(opts.StorageOpt, driver); err != nil {return err}// 4. 设置储存配额if driver.options.quota.Size > 0 {// Set container disk quota limitif err := d.quotaCtl.SetQuota(dir, driver.options.quota); err != nil {return err}}}// 5. 创建镜像diff目录if err := idtools.MkdirAndChown(path.Join(dir, "diff"), 0755, root); err != nil {return err}// 6. 创建指向diff目录的链接lid := generateID(idLength)if err := os.Symlink(path.Join("..", id, "diff"), path.Join(d.home, linkDir, lid)); err != nil {return err}// 7. 将链接id写入链接文件if err := ioutil.WriteFile(path.Join(dir, "link"), []byte(lid), 0644); err != nil {return err}// 8. 父层不存在就直接返回if parent == "" {return nil}// 9. 创建镜像work目录作为overlay2内部使用if err := idtools.MkdirAndChown(path.Join(dir, "work"), 0700, root); err != nil {return err}// 10. 找到父层(也就是tar中的镜像层)lower文件,并写入到当前层lower文件中lower, err := d.getLower(parent)if err != nil {return err}if lower != "" {if err := ioutil.WriteFile(path.Join(dir, lowerFile), []byte(lower), 0666); err != nil {return err}}return nil
}
applyTar
将层tar流解包到层挂载点
func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, diff io.Reader) (size int64, err error) {driver := gdw.ProtoDriver// Mount the root filesystem so we can apply the diff/layer.// 返回由id引用的分层文件系统的挂载点layerRootFs, err := driver.Get(id, "")if err != nil {return}defer driver.Put(id)layerFs := layerRootFs.Path()options := &archive.TarOptions{UIDMaps: gdw.uidMaps,GIDMaps: gdw.gidMaps}start := time.Now().UTC()logrus.WithField("id", id).Debug("Start untar layer")// 将层tar流解包到层挂载点if size, err = ApplyUncompressedLayer(layerFs, diff, options); err != nil {return}logrus.WithField("id", id).Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())return
}
Create
创建就是在缓存中添加镜像信息,保存配置
func (is *store) Create(config []byte) (ID, error) {var img Imageerr := json.Unmarshal(config, &img)if err != nil {return "", err}// Must reject any config that references diffIDs from the history// which aren't among the rootfs layers.rootFSLayers := make(map[layer.DiffID]struct{})for _, diffID := range img.RootFS.DiffIDs {rootFSLayers[diffID] = struct{}{}}// 如果记录的创建历史非空层大于rootFS层数,报错layerCounter := 0for _, h := range img.History {if !h.EmptyLayer {layerCounter++}}if layerCounter > len(img.RootFS.DiffIDs) {return "", errors.New("too many non-empty layers in History section")}// 将解析配置写入content目录dgst, err := is.fs.Set(config)if err != nil {return "", err}imageID := IDFromDigest(dgst)is.Lock()defer is.Unlock()// 若镜像已经存在镜像元数据缓存中,就直接返回if _, exists := is.images[imageID]; exists {return imageID, nil}layerID := img.RootFS.ChainID()var l layer.Layer// 获取镜像只读层,并缓存if layerID != "" {if !system.IsOSSupported(img.OperatingSystem()) {return "", system.ErrNotSupportedOperatingSystem}l, err = is.lss[img.OperatingSystem()].Get(layerID)if err != nil {return "", errors.Wrapf(err, "failed to get layer %s", layerID)}}imageMeta := &imageMeta{layer: l,children: make(map[ID]struct{}),}is.images[imageID] = imageMeta// 添加reference和id缓存if err := is.digestSet.Add(imageID.Digest()); err != nil {delete(is.images, imageID)return "", err}return imageID, nil
}
创建容器时是如何使用image的?
- 从缓存获取镜像配置进行校验以及合并容器配置
- 以镜像chainID作为容器挂载层(也是读写层)的parent
- 复制镜像目录内容到容器目录
Ref
- https://stackoverflow.com/questions/22655867/what-is-the-difference-between-save-and-export-in-docker
相关文章:
docker镜像是如何导入的?
镜像导入是由image/tarexport/load.go#tarexporter.Load()完成的 以下代码参考github.com/docker/docker版本v0.0.0-20181129155816-baab736a3649 主要是注册镜像信息以及解包镜像tar流到新root 导出和保存的区别在于 导出(export): 仅导出文件结构保存(save): 保存镜像历史和元…...
四川大学874考研真题00-23
22, 2022年硕士学位研究生入学考试试题回忆版 数据结构 1.一个时间复杂度为n2 的算法运行,m1算n个问题用时1秒,m2处理器是m1效率的64倍,则m2每秒能计算()个问题。 A. 64n B. 8n …...
openGauss学习笔记-58 openGauss 高级特性-资源池化
文章目录 openGauss学习笔记-58 openGauss 高级特性-资源池化58.1 特性简介58.2 架构介绍58.3 功能特点58.4 适用场景与限制58.5 手动安装示例58.6 OCK RDMA使用示例58.7 OCK SCRLock使用示例 openGauss学习笔记-58 openGauss 高级特性-资源池化 58.1 特性简介 资源池化特性主…...
centos升级cmake之相关问题解决
1. yum安装(仓库默认版本) # 查看可安装版本 yum --showduplicates list xxxx# 安装指定版本 yum install xxxx-1.23.1 2. 安装高版本cmake 2.1 一开始下载的是cmake-xxx.zip,在 ./bootstrap 一直会报错“missing terminating " character”,后来…...
vcs仿真教程(查看断言)
VCS是在linux下面用来进行仿真看波形的工具,类似于windows下面的modelsim以及questasim等工具,以及quartus、vivado仿真的操作。 1.vcs的基本指令 vcs的常见指令后缀 sim常见指令 2.使用vcs的实例 (1)新建文件夹: …...
2023开学礼新疆石河子大学图书馆藏八一新书《乡村振兴战略下传统村落文化旅游设计》许少辉新财经理工
2023开学礼新疆石河子大学图书馆藏八一新书《乡村振兴战略下传统村落文化旅游设计》许少辉新财经理工...
javaee spring aop 切入点表达式
1、切入点表达式:对指定的方法进行拦截,并且生成代理表达式。 表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))execution( public void com.test.service.impl.UsersService.add() )2、表达式不同写法 1.匹配指定方法 …...
js 获得元素的offsetLeft
要获得元素相对于其offsetParent元素左侧边缘的距离(即offsetLeft),可以使用如下代码: var el document.getElementById(your-element-id); var offsetLeft el.offsetLeft;其中,el为要获取offsetLeft的元素对象&…...
【Spring面试题】IOC控制反转和DI依赖注入(详解)
IOC Inversion of Control 控制反转,是一种面向对象的思想。 控制反转就是把创建和管理 bean 的过程转移给了第三方。而这个第三方,就是 Spring IoC Container,对于 IoC 来说,最重要的就是容器。 通俗点讲,因为项目…...
LeetCode 2511. 最多可以摧毁的敌人城堡数目
【LetMeFly】2511.最多可以摧毁的敌人城堡数目 力扣题目链接:https://leetcode.cn/problems/maximum-enemy-forts-that-can-be-captured/ 给你一个长度为 n ,下标从 0 开始的整数数组 forts ,表示一些城堡。forts[i] 可以是 -1 ,…...
bazel远程缓存(Remote Cache)
原理 您可以将服务器设置为构建输出(即这些操作输出)的远程缓存。这些输出由输出文件名列表及其内容的哈希值组成。借助远程缓存,您可以重复使用其他用户的 build 中的构建输出,而不是在本地构建每个新输出。 增量构建极大的提升…...
算法竞赛入门经典习题2-6 排列(permutation)
排列(permutation)——算法竞赛入门经典_还记得樱花正开~的博客-CSDN博客 上面的代码很厉害,学习...我的代码水平就比较差了... #include <cstdio> #include <set>int main(){for(int i 123; i < 329; i){std::set<int&…...
队列的链表实现 题目(难度1/10)
C数据结构与算法 目录 队列介绍 队列这种容器,就像大家排队上公交车一样。 第一个来到的人排在最前面; 最后来的排在最后面; 第一个先上车(离开队列); 队列的接口 队列是有如下接口的容器࿱…...
SpringMVC常用的三种获取请求参数的方式
在Spring MVC中,可以使用多种方式来获取请求参数。下面我将介绍常用的几种方式,并提供相关的示例代码。 1. 使用RequestParam注解获取请求参数 RequestParam注解用于从请求中获取指定名称的参数值,并将其绑定到方法参数上。如果请求中没有找…...
2023开学礼新疆理工学院图书馆藏八一新书《乡村振兴战略下传统村落文化旅游设计》许少辉新财经理工
2023开学礼新疆理工学院图书馆藏八一新书《乡村振兴战略下传统村落文化旅游设计》许少辉新财经理工...
数据结构----结构--线性结构--字符串
数据结构----结构–线性结构–字符串 一.字符串的定义方式 第一种: char* str1"Hello"第二种: char str2[]"Hello";区别 1.所在区域不同 //str1在常量区//str2在这里的写法是在栈区2.元素是否可改 //str1中的元素不可改//st…...
数据工厂-生成接口通用用例
章节目录: 一、背景介绍二、前置准备三、设计思路四、代码具体实现五、执行效果六、其他说明七、结束语 一、背景介绍 有哪些用例是可以通用且固定的? 针对之前提到的接口用例设计思路,拆分为三个切入点: 举个例子: {…...
N 字形变换
N 字形变换 题目: 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:P A H N A P L S I I G Y I R 之后,你的输…...
STM32+RTThread配置以太网无法ping通,无法获取动态ip的问题
记录一个非常蠢的问题,今天在移植rtthread的以太网驱动的时候出现无法获取动态ip的问题,问题如下: 设置为动态ip时不管是连接路由器还是电脑主机都无法ping通,也无法获取dns地址。 设置为静态ip时无法ping通主机。 使用wireshark…...
python编写MQTT订阅程序
Download | Eclipse Mosquitto 1、下载: https://mosquitto.org/files/binary/win64/mosquitto-2.0.17-install-windows-x64.exe 2、安装: 3、conf配置 1)使用notepad打开“C:\Program Files\mosquitto\mosquitto.conf”另存为c:\myapp\msquitto\mo…...
mysql 中 cast 函数用法
在 MySQL 中,CAST() 函数用于将一个表达式转换为指定的数据类型。它可以用于多种场景,例如将字符串转换为数字,或者将日期时间转换为特定格式。 以下是 CAST() 函数的基本语法: CAST(expression AS datatype) 其中,…...
MongoDB 的简介
MongoDB 趋势 对于 MongoDB 的认识 Q&A QA什么是 MongoDB? 一个以 JSON 为数据模型的文档数据库一个以 JSON 为数据模型的文档数据库文档来自于“JSON Document”,并非我们一般理解的 PDF,WORD谁开发 MongDB? 上市公司 MongoD…...
是否在业务中使用大语言模型?
ChatGPT取得了巨大的成功,在短短一个月内就获得了1亿用户,并激发了企业和专业人士对如何在他们的组织中利用这一工具的兴趣和好奇心。 但LLM究竟是什么,它们如何使你的企业受益?它只是一种炒作,还是会长期存在? 在这篇文章中我…...
37. 交换字符(第三期模拟笔试)
题目: 给定一个01串(仅由字符0和字符1构成的字符串)。每次操作可以交换两个相邻的字符。 例如:对于字符串"001110"来说, 可以交换第二个字符0和第三个字符1,交换之后的字符串变成了"0101…...
git 查看当前分支最近一次提交的commit SHA
获取当前分支最近一次commit SHA (长度为40个16进制数字的字符)命令如下: git rev-parse HEAD 获取简写(短) commit SHA git rev-parse --short HEAD...
LuatOS 开发指南
NDK 开发 官方教程 官方例程 API 下载软件 下载官方NDK例程压缩包到本地,并解压。可以看到目录如下: doc: 文档教程 env: 编译环境 example: NDK示例 platform: 需要编译的平台(air72x/air8xx) tools: 其他辅助软件 VSCode 使…...
maven推包The environment variable JAVA_HOME is not correctly set
解决办法: 打开idea查看jdk安装位置 1.在/etc下面创建(如果存在就是更新)launchd.conf。里面添加一行: setenv JAVA_HOME /Library/Java/JavaVirtualMachines/jdk1.8.0_351.jdk/Contents/Home #JAVA_HOME后面是我的java安装路径…...
Python VScode 配置
在上一章节中我们已经安装了 Python 的环境,本章节我们将介绍 Python VScode 的配置。 准备工作: 安装 VS Code安装 VS Code Python 扩展安装 Python 3 安装 VS Code VSCode(全称:Visual Studio Code)是一款由微软…...
【vue2第九章】组件化开发和根组件以及style上的scoped作用
组件化开发和根组件 什么是组件化开发? 一个页面可以拆分为多个组件,每个组件有自己的样式,结构,行为,组件化开发的好处就是,便于维护,利于重复利用,提升开发的效率。 便于维护&…...
从零开始的Hadoop学习(五)| HDFS概述、shell操作、API操作
1. HDFS 概述 1.1 HDFS 产出背景及定义 1)HDFS 产生背景 随着数据量越来越大,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中,但是不方便管理和维护,迫切 需要一种系统来管理多台机器上的…...
阿里巴巴网站域名建设/深圳网络推广
1. 需要使用svnant,从SVN中获取源码 需要使用的扩展包:svnant-1.3.1.zip里所有的jar 下载地址:http://subclipse.tigris.org/files/documents/906/49042/svnant-1.3.1.zip build.xml中的写法 <!--定义SVN地址--><property name"…...
南通个人网站制作/整站seo
在《通过一个模拟程序让你明白ASP.NET MVC是如何运行的》一文中我通过一个普通的ASP.NET Web程序模拟了ASP.NET MVC的执行流程,现在我们通过类似的原理创建一个用于模拟WCF服务端和客户端工作原理的模拟程序。[源代码从这里下载] 目录 一、基本的组件和执行流程 二、…...
建设网站的网页设计/自己做网站如何赚钱
[翻译] ASP.NET MVC Framework控制器操作安全性 原文地址:http://gridviewguy.com/Articles/385_ASP_NET_MVC_Framework_Controller_Action_Security.aspx 翻译:Anders Liu 摘要:ASP.NET MVC Framework允许开发者使用更为灵活的方式创建Web应…...
免费做直播网站/企业网站模板下载
Mysql字符串截取函数SUBSTRING的用法说明 函数: 1、从左开始截取字符串 left(str, length) 说明:left(被截取字段,截取长度) 例:select left(content,200)…...
凯里网站设计公司/网络营销案例分析论文
关于节点的兼容性: 1:获取元素的子节点 a: childNodes:获取元素的子节点,空文本,非空文本,注释,获取的比较全面, 如果只是想获取元素的子节点,请用…...
建设微网站的特色/外贸网络营销平台
问题: 我的的phantomjs的版本是2.0,是否是这个问题,但如果是这个问题的话,怎么解决,我看到其他有些人,下载了1.版本的,phantomjs,好像也出现这个问题,大神们这个怎么解决。 另外…...