- A+
1. C/S 与 B/S
C/S结构系统是什么
Client/Server结构(C/S结构)是大家熟知的客户机和服务器结构。它是软件系统体系结构,通过它可以充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统的通讯开销
B/S结构系统是什么
B/S结构(Browser/Server,浏览器/服务器模式),是WEB兴起后的一种网络结构模式,WEB浏览器是客户端最主要的应用软件。这种模式统一了客户端,将系统功能实现的核心部分集中到服务器上,简化了系统的开发、维护和使用。客户机上只要安装一个浏览器,就可以使用B/S结构的系统。其实B/S结构的系统也可以看做是一种特殊的C/S结构。
C/S结构的优点
1.能充分发挥客户端的处理能力,可控性强 2.形式多样,可以充分满足客户自身的个性化要求 3.容易保证安全性
C/S结构的缺点
1.用户群固定。由于程序需要安装才可使用,因此不适合面向一些不可知的用户 2.维护成本高
B/S结构的优点
1.分布性强,客户端零维护。只要有网络、浏览器,就可以随时随地进行使用系统 2.业务扩展简单方便,维护简单方便。只需要在服务器端做相应的修改,客户端就会在下次访问获取最新版本
B/S结构的缺点
1.个性化特点明显降低,无法实现具有个性化的功能要求。集成诸如指纹仪、摄像头、调用播放器变得困难 2.在跨浏览器上,BS架构不尽如人意 3.请求/响应模式带来的性能问题 4.安全性上需要花费巨大的设计成本。因为B/S客户端是基于浏览器的,通过简单修改URL参数、篡改POST字段值就会产生安全性方面的问题。
常用的B/S应用程序
1.MVC 2.WebForms 3.WebAPI
常用的C/S应用程序
1.Windows窗体应用程序 2.WPF应用程序 3.控制台应用程序
2. 为什么需要Http协议
客户端与服务端之间的通讯是否也需要某种协议?
答:http 协议. http协议是一种未进行加密处理,由服务器传输超文本到本地浏览器传输协议。
特点:
-
基于TCP/IP的高级协议 (Socket)
-
默认端口号:80
-
基于请求/响应模型的:一次请求对应一次响应
-
无状态的:每次请求之间相互独立,不能交互数据
3. Http的前世今生
1960年美国人Ted Nelson构思了一种通过计算机处理文本信息的方法,并称之为超文本(hypertext),这成为了HTTP超文本传输协议标准架构的发展根基。Ted Nelson组织协调万维网协会(World Wide Web Consortium)和互联网工程工作小组(Internet Engineering Task Force )共同合作研究,最终发布了一系列的RFC,其中著名的RFC 2616定义了HTTP 1.1。
-
HTTP协议在应用层
-
最初的目的是为了提供一种发布和接收HTML页面的方法
-
规定了客户端和服务器之间通信格式
4. Http 的通信流程
-
建立TCP连接
-
客户端向服务端发送命令
-
客户端发送请求头信息
-
服务器应答
-
服务器发送应答头信息
-
服务器向客户端发送数据(静态资源,html/css/js)
-
服务器关闭TCP连接
一般情况下,一旦Web服务器向浏览器发送了请求数据,它就要关闭TCP连接,然后如果浏览器或者服务器在其头信息加入了这行代码: Connection:keep-alive TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。
5. URL与URI的区别
什么是URI
URI 统一资源标识符(Uniform Resource Identifiers, URI),用来唯一识别一个资源,可以把它理解为你的身份证号。
作用:Web上可用的资源如HTML文档,图像,视频等都是以URI来定位的。
什么是URL
URL 统一资源定位符( Uniform Resource Locator ),可以把它理解为你身份证上地址。 是互联网上用来标识某一处资源的地址。
作用:
-
可以用来标识一个资源,而且还指明了如果定位这个资源
-
URL是internel 上用来描述信息资源的字符串,主要用在各种www 程序上。
区别
-
URI是一种抽象的,高层次概念定义统一资源标识
-
每个URL都是一个URI,但每一个URI并不一定是URL,因为URI 还包括另外一个子类URN(统一资源命名),它命名资源但不负责定位资源(姓名+你的地址 = 你的身份证号)
-
URL就是通过定位的方式来实现URI的。
6. 消息数据格式
请求消息格式
-
请求行
请求方式 请求url 请求协议/版本 GET /login.html HTTP/1.1
-
请求方式:
HTTP协议有8中请求方式:
① GET:请求获取Request-URI所标识的资源。
② POST:在Request-URI所标识的资源后附加新的数据。
③ HEAD:请求获取由Request-URI所标识的资源的响应消息报头。
④ PUT:请求服务器存储一个资源,并用Request-URI作为其标识。
⑤ DELETE:请求服务器删除Request-URI所标识的资源。
⑥ TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断。
⑦ CONNECT:HTTP 1.1协议中预留给能够将连接改为管道方式的代理服务器。
⑧ OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项和需求。
常用的有2种
-
GET:
-
请求参数在请求行中,在url后。
-
请求的url长度有限制的
-
不太安全
-
可被收藏到书签,也可被缓存
-
-
POST:
-
请求参数在请求体中
-
请求的url长度没有限制的
-
相对安全
-
-
-
请求头:客户端浏览器告诉服务器一些信息
请求头名称: 请求头值
常见的请求头:
-
User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息
作用: 可以在服务器端获取该头的信息,解决浏览器的兼容性问题
-
Referer:http://localhost/login.html ,告诉服务器,我(当前请求)从哪里来。
作用:
-
防盗链:
-
统计工作:
-
-
-
请求空行 :就是用于分割POST请求的请求头,和请求体的。
-
请求体(正文): 封装POST请求消息的请求参数的
字符串格式:
POST /login.html HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://localhost/login.html Connection: keep-alive Upgrade-Insecure-Requests: 1 username=zhangsan
响应数据格式
响应消息:服务器端发送给客户端的数据
响应行
协议/版本 响应状态码 状态码描述
响应状态码:服务器告诉客户端浏览器本次请求和响应的一个状态,状态码都是3位数字。
分类
-
1xx:服务器接收客户端消息,但没有接受完成,等待一段时间后,发送1xx状态码
-
2xx:成功。代表:200
-
3xx:重定向。代表:302(重定向),304(访问缓存)
-
4xx:客户端错误。代表:404(请求路径没有对应的资源)405:请求方式没有对应的方法, 401 未授权,403?
-
5xx:服务器端错误。代表:500(服务器内部出现异常),503:网关出现问题
响应头
格式:
头名称: 值
常见的响应头:
-
Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式
-
Content-disposition:服务器告诉客户端以什么格式打开响应体数据值
-
attachment;filename=xxx:以附件形式打开响应体。文件下载
响应体
服务器返回的数据
响应字符串格式:
HTTP/1.1 200 OK Content-Type: text/html;charset=UTF-8 Content-Length: 101 Response对象 Date: Wed, 06 Jun 2018 07:08:42 GMT <html> <head> <title></title> </head> <body> hello , response </body> </html>
7. HTTP 各版本简介
HTTP 1.0
: 规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求 。连接无法复用
HTTP1.1
:
-
复用连接(keep-alive)
-
缓存处理
-
身份认证, 状态管理
HTTP 1.1状态代码及其含义
状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx
:指示信息--表示请求已接收,继续处理
2xx
:成功--表示请求已被成功接收、理解、接受
3xx
:重定向--要完成请求必须进行更进一步的操作
4xx
:客户端错误--请求有语法错误或请求无法实现
5xx
:服务器端错误--服务器未能实现合法的请求
HTTP2.0
:
-
多路复用 (Multiplexing)
即连接共享,即每一个request都是是用作连接共享机制的。一个request 对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的id将request再归属到各自不同的服务端请求里面。多路复用原理和keep alive区别如下图:
-
二进制分帧
HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
-
首部压缩(Header Compression)
如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份headerfields表,既避免了重复header的传输,又减小了需要传输的大小。
-
服务端推送(Server Push)
服务端推送是一种在客户端请求之前发送数据的机制。在 HTTP/2 中,服务器可以对客户端的一个请求发送多个响应。Server Push 让
HTTP1.x
时代使用内嵌资源的优化手段变得没有意义;如果一个请求是由你的主页发起的,服务器很可能会响应主页内容、logo 以及样式表,因为它知道客户端会用到这些东西。这相当于在一个 HTML 文档内集合了所有的资源,不过与之相比,服务器推送还有一个很大的优势:可以缓存!也让在遵循同源的情况下,不同页面之间可以共享缓存资源成为可能。
HTTP 3.0
HTTP3.0,也称作HTTP over QUIC。HTTP3.0的核心是QUIC(读音quick)协议,由Google在 2015年提出的SPDY v3演化而来的新协议,传统的HTTP协议是基于传输层TCP的协议,而QUIC是基于传输层UDP上的协议,可以定义成:HTTP3.0基于UDP的安全可靠的HTTP2.0协议。
QUIC 协议针对基于TCP和TLS的HTTP2.0协议解决了下面的问题。
-
1.1 减少了TCP三次握手及TLS握手时间 不管是HTTP1.0/1.1还是HTTPS,HTTP2.0,都使用了TCP进行传输。HTTPS和HTTP2还需要使用TLS协议来进行安全传输。这就出现了两个握手延迟,而基于UDP协议的QUIC,因为UDP本身没有连接的概念,连接建立时只需要一次交互,半个握手的时间。区别如下图:
-
1.2 多路复用丢包的线头阻塞问题 QUIC保留了HTTP2.0多路复用的特性,在之前的多路复用过程中,同一个TCP连接上有多个stream,假如其中一个stream丢包,在重传前后的stream都会受到影响,而QUIC中一个连接上的多个stream之间没有依赖。所以当发生丢包时,只会影响当前的stream,也就避免了线头阻塞问题。
-
1.3 优化重传策略 以往的TCP丢包重传策略是:在发送端为每一个封包标记一个编号(sequence number),接收端在收到封包时,就会回传一个带有对应编号的ACK封包给发送端,告知发送端封包已经确实收到。当发送端在超过一定时间之后还没有收到回传的ACK,就会认为封包已经丢失,启动重新传送的机制,复用与原来相同的编号重新发送一次封包,确保在接收端这边没有任何封包漏接。这样的机制就会带来一些问题,假设发送端总共对同一个封包发送了两次(初始+重传),使用的都是同一个sequence number:编号N。之后发送端在拿到编号N封包的回传ACK时,将无法判断这个带有编号N的ACK,是接收端在收到初始封包后回传的ACK。这就会加大后续的重传计算的耗时。QUIC为了避免这个问题,发送端在传送封包时,初始与重传的每一个封包都改用一个新的编号,unique packet number,每一个编号都唯一而且严格递增,这样每次在收到ACK时,就可以依据编号明确的判断这个ACK是来自初始封包或者是重传封包。
-
1.4 流量控制 通过流量控制可以限制客户端传输资料量的大小,有了流量控制后,接收端就可以只保留相对应大小的接收 buffer ,优化记忆体被占用的空间。但是如果存在一个流量极慢的stream ,光一个stream就有可能估用掉接收端所有的资源。QUIC为了避免这个潜在的HOL Blocking,采用了连线层(connection flow control)和Stream层的(streamflow control)流量控制,限制单一Stream可以占用的最大buffer size。
-
1.5 连接迁移 TCP连接基于四元组(源IP、源端口、目的IP、目的端口),切换网络时至少会有一个因素发生变化,导致连接发生变化。当连接发生变化时,如果还使用原来的TCP连接,则会导致连接失败,就得等原来的连接超时后重新建立连接,所以我们有时候发现切换到一个新网络时,即使新网络状况良好,但内容还是需要加载很久。如果实现得好,当检测到网络变化时立刻建立新的TCP连接,即使这样,建立新的连接还是需要几百毫秒的时间。QUIC的连接不受四元组的影响,当这四个元素发生变化时,原连接依然维持。QUIC连接不以四元组作为标识,而是使用一个64位的随机数,这个随机数被称为Connection lD,对应每个stream,即使IP或者端口发生变化,只要Connection ID没有变化,那么连接依然可以维持。
8 . 什么是HTTPS
HTTP协议传输的数据都是未加密的.为了保证这些隐私数据能加密传输,于是网景公司设计了SSL
(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS
。现在的HTTPS
都是用的TLS
协议,但是由于SSL
出现的时间比较早,并且依旧被现在浏览器所支持,因此SSL
依然是HTTPS
的代名词。
HTTPS
默认端口号是443.(http协议默认端口号是80)
HTTPS与HTTP的一些区别
-
HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。
-
HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的TLS加密传输协议。
-
HTTP和HTTPS使用的是完全不同的连接方式,用的默认端口也不一样,前者是80,后者是443.
-
HTTPS的连接很简单,HTTPS协议是由TLS+HTTP协议构建的 可进行加密传输、身份认证的网络协议,比HTTP协议安全。
9. HttpContext上下文
1. 什么是应用程序上下文
我们先举个例子:
小张,我现在有点口渴,帮我去倒杯水来。
虽然只有短短几个字,却清楚的交待了请求的“来龙” 与 “去脉” 。口渴是你的上文,小张把水给你递来了就是你的下文。如果你只是说“小张,我现在有点口渴” 你只交待了上文却没有了下文,或者 你突然来了句 “帮我去倒杯水来” 没有交待上文,只有下文也是不对的,没有上文确实会让人觉得很懵B的。
在此跟各位吐槽一下没有上文的苦水,属实让人很难受(5555..。。。。)。。。。
很多同学找我帮忙找bug的时候,直接给我截了一张报错的图给我,然后报错的图也不全,然后就没有然后了。小伙子真把我们当会算命的神了,看一眼报错就知道哪儿报错。首先你得交待一下你的上文是啥(干嘛用的),就是代码的来龙去脉,最好把相关的代码都截图给我们看,这样我们才能有把握解决掉这个bug.
所谓的应用程序的上下文:其实就是交待了当前请求的环境信息,当前上下文中包含了你的请求信息(Request) 与 请求响应(Response) 。 也就是当前请求的来龙去脉
2. IHttpContextAccessor 接口
提供对当前 HttpContext的访问权限(如果有)。 应谨慎使用此接口。 它依赖于 AsyncLocal 对异步调用产生负面影响的性能。 它还会创建一个依赖于“环境状态”的依赖项,这使得测试更加困难。
解决办法,将 IHttpContextAccessor 设置为单例。
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); // 或者 builder.Services.AddHttpContextAccessor(); // 同样也是单例
属性
HttpContext | 获取或设置当前 HttpContext。 如果没有活动HttpContext状态,则返回 null 。 |
---|---|
HttpContext 上下文中包含了一些非常重要的对象信息:
-
Request 属性
HttpRequest获取此请求的对象, 表示单个 HTTP 请求的传入端。
-
RequestServices
获取或设置 IServiceProvider 提供对请求的服务容器的访问权限。
-
Response 对象
HttpResponse获取此请求的对象,表示单个 HTTP 请求的传出端。
-
Session 对象
获取或设置用于管理此请求的用户会话数据的对象。
-
User 对象
获取或设置此请求的用户。
3. HttpRequest 请求
表示单个 HTTP 请求的传入端。
Body | 获取或设置请求正文 Stream。 |
---|---|
BodyReader | 获取请求正文 PipeReader。 |
ContentLength | 获取或设置 Content-Length 标头。 |
ContentType | 获取或设置 Content-Type 标头。 |
Cookies | 获取此请求的 Cookie 集合。 |
Form | 获取或设置请求正文作为Form表单。 |
HasFormContentType | 检查Form表单类型的 Content-Type 标头。 |
Headers | 获取请求标头。 |
Host | 获取或设置 Host 标头。 可以包含端口。 |
HttpContext | 获取 HttpContext 此请求。 |
IsHttps | 如果 RequestScheme 为 https,则返回 true。 |
Method | 获取或设置 HTTP 方法。 |
Path | 获取或设置 RequestPath 中的请求路径。 |
PathBase | 获取或设置请求的基本路径。 路径基不应以尾部斜杠结尾。 |
Protocol | 获取或设置请求协议 (,例如 HTTP/1.1) 。 |
Query | 获取从 Request.QueryString 分析的查询值集合。 |
QueryString | 获取或设置用于在 Request.Query 中创建查询集合的原始查询字符串。 |
RouteValues | 获取此请求的路由值的集合。 |
Scheme | 获取或设置 HTTP 请求方案。 |
<form method="get" action="/home/query"> <label for="username">姓名:</label><input name="username" id="username"/> <label for="studentNo">学号:</label><input name="studentno" id="studentNo"/> <input type="submit" value="查询"/> </form>
public IActionResult Query() { var username = HttpContext.Request.Query["username"]; var studentNo = HttpContext.Request.Query["studentNo"]; _logger.LogInformation($"姓名:{username}"); _logger.LogInformation($"学号:{studentNo}"); // 获取所有的请求头 foreach (var header in Request.Headers) { Console.WriteLine($"头名:{header.Key},值:{header.Value}"); } return Content("查询完毕"); } info: WebApplication2.Controllers.HomeController[0] 姓名:任我行 info: WebApplication2.Controllers.HomeController[0] 学号:1310734881 头名:Accept,值:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 头名:Host,值:localhost:7096 头名:User-Agent,值:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77 头名::method,值:GET 头名:Accept-Encoding,值:gzip, deflate, br 头名:Accept-Language,值:zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 头名:Cookie,值:.AspNetCore.Session=CfDJ8JezZrvMRBVHqdn1GbTI%2B3bdZuPW2P971ifEekAO%2BfcIEIYo4vpUwD5bHRtEspZHmgyzMYyNAp8u5r8PZaPdwiij2jjPmksoigF8yIwuEuJBGe5zmq1zN0gqgGwSaYmIBw328xN5fzrxbkl92Xo5te4cOHy6GRwKKZMd4YjAbRlk,.AdventureWorks.Session=CfDJ8JezZrvMRBVHqdn1GbTI%2B3ZqwisKw1CqoPT5%2Fbb6V8VD%2BYE%2B7ytbOjCy1%2BB%2BqkuaWL3%2B4GpuM2%2BACge3ahqhKRfp7utYMtYdsICYKzEM7o5qzNQkdv1U5JnLbbvZlJM2MDp6GFkjfeAQFae%2FB29PeYYM3tfhIGu0wNmAtdDZTIkg 头名:Referer,值:https://localhost:7096/ 头名:Upgrade-Insecure-Requests,值:1 头名:sec-ch-ua,值:" Not;A Brand";v="99", "Microsoft Edge";v="103", "Chromium";v="103" 头名:sec-ch-ua-mobile,值:?0 头名:sec-ch-ua-platform,值:"Windows" 头名:sec-fetch-site,值:same-origin 头名:sec-fetch-mode,值:navigate 头名:sec-fetch-user,值:?1 头名:sec-fetch-dest,值:document
4. HttpResponse 响应
表示单个 HTTP 请求的传出端。
Body | 获取或设置响应正文 Stream。 |
---|---|
BodyWriter | 获取响应正文 PipeWriter |
ContentLength | 获取或设置 Content-Length 响应标头的值。 |
ContentType | 获取或设置 Content-Type 响应标头的值。 |
Cookies | 获取可用于管理此响应的 Cookie 的对象。 |
HasStarted | 获取一个值,该值指示是否已将响应标头发送到客户端。 |
Headers | 获取响应标头。 |
HttpContext | HttpContext获取此响应。 |
StatusCode | 获取或设置 HTTP 响应代码。 |
一、Header属性
属性 | 备注 | 例如 |
---|---|---|
Access-Control-Allow-Origin | 该站点可以被哪些网站进行 跨域资源共享 | Access-Control-Allow-Origin: http://example.com:8080 http://foo.example.com Access-Control-Allow-Origin:* |
Accept-Ranges | 服务器是否支持资源范围请求,资源范围请求:指按byte为单位,请求资源的某一段数据 | 例如请求一个文件的200byte—400byte的数据 Accept-Ranges:bytes 表示该资源支持byte形式资源范围请求 Accept-Ranges:none则表示不支持 |
Content-Range | 如果当前这个响应数据是整个资源的一部分时,是具体的哪一部分(从第几byte到第几byte)。在请求中,客户端可以通过设定”Range”头域来通知服务器其只想请求整个资源中某一段数据,而对应的,当服务器响应这种请求,并发送某一段数据到客户端的时候,必须通过Content-Range头来告诉客户端当前的响应数据是整个资源的第几byte到第几byte。这个在资源的分段下载和续点下载应用中很有用。 | Content-Range:500-900 |
Allow | 一个资源允许哪些HTTP方法进行请求 | Allow: GET, HEAD Allow:* |
Connection | 连接方式 | Connection:keep-alive Connection:close |
Content-Encoding | 服务器对响应数据的编码方式,但这里的编码方式不同于编码字符集(GB2312,UTF-8等),而是(通常)指压缩方式 | Content-Encoding:gzip |
Content-Language | 响应数据的自然语言 | Content-Language:ZH-CN、 en-US |
Content-Length | 响应数据的数据长度,单位是byte | Content-Length:1024 |
Content-Disposition | 当客户端请求的资源是一个可下载的资源(这里的“可下载”是指浏览器会弹出下载框或者下载界面)时,对这个可下载资源的描述(例如下载框中的文件名称)就是来源于该头域。 | Content-Disposition: attachment; filename=”some_app.exe” |
Server | 服务器的名称 | Server: Kestrel |
Content-Type | 服务器告诉浏览器它发送的数据属于什么文件类型,也就是响应数据的MIME类型 | Content-Type: text/html; charset=utf-8,让浏览器把接收到的实体内容以HTML格式解析 Content-Type: text/plain; charset=utf-8,让浏览器把接收到的实体内容以普通文本解析 octet-stream 响应流 |
date | 响应消息发送的GMT格式日期 | Date: Tue, 15 Nov 1994 08:12:31 GMT |
-
设置响应语言
HttpContext.Response.Headers.Add("Content-Language","zh-CN");
-
下载文件
public async Task Download() { // HttpUtility 为了解决中文文件名问题 Response.Headers.Add("Content-Disposition", $"attachment;filename={HttpUtility.UrlEncode("文件下载",Encoding.Default)}.txt"); Response.ContentType = "application/octet-stream;"; // MIME 类型为下载流 var buffer = Encoding.UTF8.GetBytes("响应头下载测试"); await Response.Body.WriteAsync(buffer); // 将内容写入 await Response.Body.FlushAsync(); }
配套视频链接:什么是Mvc (cctalk.com)