当我们输入URI,按下回车发生了什么?

本文详细介绍了从输入URL到浏览器最终展示页面的整个过程。包括通过DNS解析获取IP地址、建立TCP连接、发送HTTP请求、服务器处理请求并返回响应、浏览器解析渲染等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当我们输入URL,按下回车发生了什么? 这个题目很俗套- -但是是面试经常出现的题目了。今天听尼古拉斯•屌•大斌哥介绍关于从URI到浏览器呈现给我们页面发生了什么。感觉收获颇多。索性就翻阅了一些其他资料。在此总结下。这一晚上也算是没白白浪费。

当我们输入URL,发生了什么?

用一个例子来说明把。当我们打开https://datura900607.github.io/home-automation/ 这个网页的时候发生了什么?

通过URI定位到主机。

当我们在浏览器输入url后,浏览器通过DNS服务器,将网站中的URl中的主机域名解析为,web服务器中所对应的IP地址。顺序差不多是:

  • 先在浏览器缓存中查询,如果浏览器缓存中有就直接使用。
  • 浏览器缓存中找不到 在系统缓存中查询。
  • 系统缓存中没有在路由器缓存中查询。
  • 路由器找不到在web服务器中查询,直到找到这个IP地址。
打包HTTP请求

好了,经过一番查询,我们找到了我家居网站的IP地址:151.101.100.133:443 这是我们打包的HTTP请求:


通过TCP协议,向服务器发送请求

通过TCP协议与Web服务器创建连接。(俗称三次握手),向服务器发送请求

web服务器接收请求,交给相关进程处理

这里要根据后台语言不同而分情况处理,我这个是HTML类型文件。web服务器接收请求后 找到服务器上相对应的html文件(一般是index.html)
如果后台语言是PHP类型,则web服务器在接收到用户的请求后将请求转交给PHP应用服务器来处理,(web服务器本身不能处理PHP动态语言文件)

服务器响应请求,通过TCP协议回传响应

这里因为此家居网站就是一个静态HTML。就直接找到HTML文件就会通过TCP协议回传了。如果是php文件。则还需要PHP应用服务器将PHP文件,翻译成HTML静态代码,传回Web服务器,然后再通过TCP协议回传响应。这是回传的响应head截图


浏览器接收响应,开始下载并渲染,将页面呈现在我们面前
  1. 解析HTML生成DOM树,
  2. 解析CSS文件生成CSSOM树,并解析Javascript代码
  3. CSS和DOM组合形成渲染树,
  4. 进行布局,在浏览器中绘制渲染树中节点的属性(位置,宽度,大小等)
  5. 绘制元素,绘制各个节点的可视属性(颜色背景等,此时可能会形成多个图层)
  6. 合并图层,将页面呈现给用户面前
额外提一点

在服务器接收响应请求的时候,会因为一些不同的因素呈现出不同的页面主要是:
影响服务器结果的因素

  • 请求方式(例:get、post当然请求方式不止这两种。)的不同
  • 路径的不同:(url不同服务器结果当然不同!!)
  • query string 传参的不同
  • cookie ————用于用户身份识别的
  • 服务器自身的配置
  • 后台语言的逻辑(java和php)
    状态码
    状态码常见的大致可分为以下几种:
    200 (正常OK)
    304 (请求的网页与上次比没有更新)刷新的时候出现
    301 (重定向)换域名
    403 (配置服务器没权限)
    404 (没找到文件(找到了服务器))
    500 (没找到服务器)
    关于浏览器渲染页面的一些细节
    JS加载阻塞的原因
    JS加载阻塞主要是因为浏览器需要一个稳定的DOM树,而JS本质就是操作DOM元素,就很有可能改变DOM树的机构,为了防止出现修改DOM树的情况后重铸DOM树的情况,先等JS下载并解析完之后 再渲染DOM树
    CSS与JS一般情况下的放置位置
    一般来说css放置在Head,JS放置在底部主要原因有:
    上面提过到JS的加载阻塞
    因为HTML的下载和解析是从上到下的顺序解析的,如果CSS都不放在HEAD里面,那么当HTML代码加载完成之后也没有了样式的支持(需要等上一点时间才能将网页渲染出来,虽然指的是一点时间,但是对于用户来说一秒也不想等),就会让界面显得没有那么好看,而且全放在HEAD里面 CSS文件会先合并,只会形成CSSDOM,进行渲染。从性能和用户体验来看 一般CSS放置在HEAD,JS放置在底部


作者:datura_lj
链接:http://www.jianshu.com/p/5b069ae8c4f2
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
<template> <view class="content"> <button @click="selectFile()">打开安卓文件管理器</button> <view> <text>单选URL</text> {{ filePath }} </view> <view><text>多选</text></view> <view v-for="(item, index) in fileList">{{ item }}</view> </view> </template> <script> export default { data() { return { filePath: '', fileList: [] }; }, onLoad() {}, methods: { selectFile() { let that = this; let main = plus.android.runtimeMainActivity(); let Intent = plus.android.importClass('android.content.Intent'); let intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType('*/*'); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); //关键!多选参数 intent.addCategory(Intent.CATEGORY_OPENABLE); main.startActivityForResult(intent, 200); // 获取回调 main.onActivityResult = function(requestCode, resultCode, data) { let Activity = plus.android.importClass('android.app.Activity'); let ContentUris = plus.android.importClass('android.content.ContentUris'); let Cursor = plus.android.importClass('android.database.Cursor'); let Uri = plus.android.importClass('android.net.Uri'); let Build = plus.android.importClass('android.os.Build'); let Environment = plus.android.importClass('android.os.Environment'); let DocumentsContract = plus.android.importClass('android.provider.DocumentsContract'); let InputStream = plus.android.importClass('java.io.InputStream'); let contentResolver = main.getContentResolver(); plus.android.importClass(contentResolver); if (resultCode == Activity.RESULT_OK) { if (data.getData() != null) { let uri = data.getData(); let path = getPath(this, uri); that.filePath = path; // 获取文件大小 let fileSize = getFileSize(contentResolver, uri); console.log("文件大小: " + fileSize + " bytes"); // 获取文件内容 let fileContent = getFileContent(contentResolver, uri); console.log("文件内容: " + fileContent); } else { try { let ClipData = plus.android.importClass('android.content.ClipData'); let clipData = new ClipData(); clipData = data.getClipData(); if (clipData != null) { for (let i = 0; i < clipData.getItemCount(); i++) { let item = clipData.getItemAt(i); let uri = item.getUri(); let path = getPath(this, uri); that.fileList.push(path); // 获取文件大小 let fileSize = getFileSize(contentResolver, uri); console.log("文件大小: " + fileSize + " bytes"); // 获取文件内容 let fileContent = getFileContent(contentResolver, uri); console.log("文件内容: " + fileContent); } } } catch (e) { console.log(e); } } } function getFileSize(contentResolver, uri) { let cursor = contentResolver.query(uri, null, null, null, null); let size = 0; if (cursor != null && cursor.moveToFirst()) { let sizeIndex = cursor.getColumnIndex("_size"); if (sizeIndex != -1) { size = cursor.getLong(sizeIndex); } cursor.close(); } return size; } function getFileContent(contentResolver, uri) { try { // 打开输入流 let inputStream = contentResolver.openInputStream(uri); let ByteArrayOutputStream = plus.android.importClass('java.io.ByteArrayOutputStream'); let byteArrayOutputStream = new ByteArrayOutputStream(); // 创建一个缓冲区(使用标准 JavaScript 数组) let buffer = new Uint8Array(1024); // 每次读取 1024 字节 let bytesRead; // 循环读取文件内容 while ((bytesRead = inputStream.read(buffer.buffer)) !== -1) { console.log('bytesRead', bytesRead); byteArrayOutputStream.write(buffer.subarray(0, bytesRead)); } // 关闭输入流 inputStream.close(); // 获取完整的字节数组 let byteArray = byteArrayOutputStream.toByteArray(); byteArrayOutputStream.close(); console.log(`文件读取完成,共 ${byteArray.length} 字节`); return byteArray; // 返回字节数组 } catch (e) { console.error("读取文件内容时发生错误:", e); return null; } } function getPath(context, uri) { try { let isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider //一些三方的文件浏览器会进入到这个方法中,例如ES //QQ文件管理器不在此列 if (isExternalStorageDocument(uri)) { let docId = DocumentsContract.getDocumentId(uri); let split = docId.split(':'); let type = split[0]; // 如果是手机内部存储 if ('primary' == type.toLowerCase()) { return Environment.getExternalStorageDirectory() + '/' + split[1]; } else { return '/storage/' + type + '/' + split[1]; } } // DownloadsProvider else if (isDownloadsDocument(uri)) { let id = DocumentsContract.getDocumentId(uri); if (id.indexOf('raw:') > -1) { id = id.replace('raw:', ''); } // 不同系统获取的id开头可能不一样,在这后面便是真实的地址 if (id.substring(0, 5) == 'msf:') { return id.substring(5, id.length); } let contentUri = ContentUris.withAppendedId(Uri.parse( 'content://downloads/public_downloads'), ContentUris.parseId(uri)); return getDataColumn(context, contentUri, null, null); } // MediaProvider else if (isMediaDocument(uri)) { let MediaStore = plus.android.importClass('android.provider.MediaStore'); let docId = DocumentsContract.getDocumentId(uri); let split = docId.split(':'); let type = split[0]; let contentUri = null; if ('image' == type.toLowerCase()) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ('video' == type.toLowerCase()) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ('audio' == type.toLowerCase()) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } let selection = '_id=?'; let selectionArgs = [split[1]]; return getDataColumn(context, contentUri, selection, selectionArgs); } } // MediaStore (and general) else if ('content' == uri.getScheme().toLowerCase()) { if (isGooglePhotosUri(uri)) { return uri.getLastPathSegment(); } else if (isQQMediaDocument(uri)) { let paths = uri.getPath(); let Environment = plus.android.importClass('android.os.Environment'); let fileDir = Environment.getExternalStorageDirectory(); let files = plus.android.importClass('java.io.File'); let file = new files(fileDir, paths.substring('/QQBrowser'.length, paths .length)); return file.getPath(); } return getDataColumn(context, uri, null, null); } // File else if ('file' == uri.getScheme().toLowerCase()) { return uri.getPath(); } return null; } catch (e) { console.log(e); return null; } } // 通过uri 查找出绝对路径 function getDataColumn(context, uri, selection, selectionArgs) { try { let MediaStore = plus.android.importClass('android.provider.MediaStore'); let cursor = null; let column = MediaStore.MediaColumns.DATA; let projection = [column]; cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { let column_index = cursor.getColumnIndexOrThrow(column); return cursor.getString(column_index); } } catch (e) { console.log(e); return null; } } function isExternalStorageDocument(uri) { return 'com.android.externalstorage.documents' == uri.getAuthority() ? true : false; } function isDownloadsDocument(uri) { return 'com.android.providers.downloads.documents' == uri.getAuthority() ? true : false; } function isMediaDocument(uri) { return 'com.android.providers.media.documents' == uri.getAuthority() ? true : false; } /** * 使用第三方qq文件管理器打开 * * @param uri * @return */ function isQQMediaDocument(uri) { return 'com.tencent.mtt.fileprovider' == uri.getAuthority() ? true : false; } /** * @param uri The Uri to check. * @return Whether the Uri authority is Google Photos. */ function isGooglePhotosUri(uri) { return 'com.google.android.apps.photos.content' == uri.getAuthority() ? true : false; } }; }, } }; </script> <style></style> 获取文件大小正确,但是我怎末获取文件的二进制内容从而符合以上文档的发送格式 附录1:Ymodem OTA 升级流程 1. YMODEM协议概述 YMODEM是一种基于XMODEM的改进文件传输协议,支持批处理传输和更大文件。133字节版本是YMODEM的标准实现,使用128字节数据块加5字节头信息。 发送方和接收方都需支持YMODEM协议。 确定使用CRC-16校验(YMODEM标准)。 2. 传输流程详解 2.1 初始化阶段 • 接收方发送"C" ASCII字符0x43,表示接收方准备就绪,请求使用CRC校验,连续发送直到收到第一个数据包或超时 • 发送方响应 发送第一个数据块(序号0),包含文件名和文件信息 2.2 发送文件信息包(第0号包) 项 说明 SOH (0x01) 起始头 00 包序号0 FF 包序号的补码(255-0=255) 文件名 以NULL(0x00)结尾的ASCII字符串 文件大小 ASCII字符串表示的文件大小,以NULL结尾 修改时间 可选,ASCII格式的时间戳 填充 用0x00填充至128字节 CRC-16 2字节校验码 2.3 数据传输阶段 • 接收方ACK(0x06) 正确接收文件信息包后确认 • 发送方开始发送数据包: 项 说明 STX (0x02) 表示133字节数据包 序号 从1开始的包序号(1-255) 补码 255-序号 数据 实际文件数据,不足128字节用EOF或填充字符补全 STX (0x02) 表示133字节数据包 • 接受方响应 项 说明 ACK(0x06) 成功接收 NAK(0x15) 请求重传 CAN(0x18) 取消传输 2.4 传输结束阶段 • 发送方发送EOT(0x04) 文件传输结束 • 接收方ACK(0x06) 确认结束 • 最终结束 发送方发送全零数据包表示批传输结束 接收方ACK确认 3. 错误处理机制 3.1 重传机制 • 接收方检测到错误时发送NAK • 发送方重传当前数据包 3.2 超时处理 • 接收方等待数据包超时(通常0.5秒)发送NAK • 发送方等待响应超时重传当前包 3.3 重新传输 • 发送方发送: stop=1\r\n 其中\r\n是ascii表示换行回符号,占2个字节 设备会在收到该指令后重新进行升级流程 4. 校验方式 • 多项式:x^16 + x^12 + x^5 + 1 (0x1021) • 初始值:0x0000 • 计算范围:以133字节为例,CRC计算从第1个字节到第131个字节,132和133分别保存CRC值的高低位 • CRC-16校验算法 5. 注意事项 • 包序号从0开始(文件信息),数据包从1开始 • 包序号达到255后回绕到0 • 文件名和文件信息必须使用ASCII字符
最新发布
05-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值