深入剖析ffplay.c(9)

get_clock 函数主要用于获取一个 Clock 结构体所表示的时钟时间值。它会根据时钟的不同状态(如是否暂停、序列号是否匹配等情况),通过不同的计算方式来返回对应的时间值,这个时间值常用于视频播放等场景中,例如用于同步音频、视频的播放进度,或者判断是否达到特定的播放时间点等,是实现时间相关控制和协调的关键函数。

  1. 检查序列号是否匹配

首先通过条件判断,比较 Clock 结构体中 queue_serial 指针所指向的值(这里 *c->queue_serial 表示取指针指向的实际值)与 c 结构体本身的 serial 值是否相等。如果不相等,说明可能存在队列顺序不一致或者相关数据更新不同步等问题(具体取决于程序中对 queue_serial 和 serial 的定义及使用场景,一般来说它们用于标识某种顺序或者版本信息),在这种情况下,直接返回 NAN(C 语言中表示不是一个数字的特殊值),表示当前获取的时钟时间无效,因为序列号不匹配可能意味着数据不可靠,无法准确获取时间信息。

  1. 根据时钟暂停状态返回相应时间值

这里通过判断 Clock 结构体中的 paused 成员变量的值来区分时钟的暂停与非暂停状态,进而决定如何计算并返回时间值:

  • 时钟暂停情况(c->paused 为 true):如果 Clock 结构体中的 paused 变量值为 true(这里假设非 0 值表示 true,具体取决于其数据类型定义),说明时钟处于暂停状态,此时直接返回 c->pts 的值作为当前时钟时间。c->pts 可能是记录了时钟暂停时的某个时间戳(具体取决于程序中对 pts 的定义,通常在视频播放等场景下可以表示播放到的某一帧对应的时间点等),在暂停期间时间不再变化,所以返回这个固定的时间戳值即可表示当前的时钟时间。
  • 时钟未暂停情况(c->paused 为 false):首先计算当前的系统相对时间,通过调用 av_gettime_relative 函数获取相对时间(这个函数通常返回以微秒为单位的时间值,不同系统下实现可能略有差异,但大致功能是获取一个相对的时间度量),然后将其除以 1000000.0,将时间单位转换为秒,存储在 double 类型的变量 time 中,得到当前的系统时间(以秒为单位)。接着进行时间计算,返回 c->pts_drift + time – (time – c->last_updated) * (1.0 – c->speed) 的值作为当前时钟时间。下面对这个计算表达式进行详细分析:c->pts_drift 可能是一个用于补偿时间偏差的变量(具体取决于程序中对其的定义和使用场景,比如在处理音视频同步等情况时,由于不同时钟源或者处理速度差异可能会产生时间偏差,通过这个变量来进行一定的调整),它先参与到时间值的计算中。time – (time – c->last_updated) * (1.0 – c->speed) 这部分表达式主要用于根据时钟的速度(c->speed,可能表示播放的快慢倍数等情况,例如 1.0 表示正常速度播放,大于 1.0 表示加速播放,小于 1.0 表示减速播放)以及上次更新时间(c->last_updated,记录了上一次时钟相关信息更新的时间点,同样单位应该也是秒,用于结合当前时间和播放速度等因素准确计算当前时钟时间)来调整当前系统时间 time,从而得到符合当前时钟状态(考虑了速度等因素)的准确时间值,最后将其与 c->pts_drift 相加,得到最终的当前时钟时间并返回。
  • 序列号相关的逻辑一致性:函数中依赖 queue_serial 和 serial 的比较来判断数据的有效性,在程序的其他部分,需要确保这两个变量的赋值和更新逻辑是一致且合理的。例如,它们应该在合适的时机(比如队列操作、数据同步操作等相关环节)进行更新,并且更新的规则要保证能够准确反映数据的顺序或者版本情况,不然可能会出现误判,导致在数据其实有效的情况下却返回 NAN,影响后续基于时钟时间的各种操作(如音视频同步判断等)的准确性。
  • 时间相关变量的准确性和单位一致性:在计算时钟时间时,涉及到多个时间相关的变量(如 pts、pts_drift、last_updated、speed 等),它们的赋值和含义需要明确且准确。特别是要注意各个变量所代表的时间单位要一致(这里都应该以秒为单位参与计算,虽然获取 time 时进行了单位转换,但其他变量也要与之匹配),如果单位不一致或者变量值被错误设置(比如 pts 记录的时间戳错误、speed 设置不符合实际播放速度要求等情况),会导致计算出的时钟时间错误,进而影响整个视频播放等场景下的时间同步和播放进度控制的准确性。
  • 多线程环境(如果涉及):如果该函数在多线程环境下被调用,要考虑线程安全性问题。由于函数涉及对 Clock 结构体中多个成员变量的读取操作(在计算时钟时间的过程中),多个线程同时访问这些变量可能会导致数据不一致的情况,比如一个线程正在更新 last_updated 等变量的值,另一个线程同时调用 get_clock 函数来获取时间,可能会获取到错误的中间状态值,影响时钟时间计算的准确性。所以在多线程场景下,可能需要通过合适的线程同步机制(如互斥锁等)来保证每次只有一个线程能够访问和操作 Clock 结构体中的变量进行时钟时间的获取操作,确保获取的时间值的一致性和可靠性。

以下是对 set_clock_at 函数的详细解释:

set_clock_at 函数主要用于设置 Clock 结构体中与时间相关的多个关键属性值。通过传入指定的时间戳(pts)、序列号(serial)以及时间(time)等参数,对 Clock 结构体内部的成员变量进行赋值操作,以此来更新时钟的状态,使其能准确反映当前期望的时间信息,常用于视频播放、音视频同步等场景中对时间控制相关的操作环节,例如在新的一帧数据到来或者特定时间节点时,更新时钟状态以用于后续的播放进度判断、同步操作等。

将传入的参数 pts(通常表示一个特定的时间戳,比如在视频播放场景下可能对应某一帧的显示时间戳,其单位一般与程序中整体的时间计量单位保持一致,这里具体取决于应用场景中对时间戳的定义和使用方式)赋值给 Clock 结构体中的 pts 成员变量。这样,Clock 结构体中的 pts 就记录了最新设定的这个时间戳值,后续在获取时钟时间(例如通过前面介绍的 get_clock 函数)或者进行其他与时间相关的比较、计算操作时,就会以这个新设置的 pts 值作为依据之一,用于反映当前对应的时间点情况。

  1. 设置上次更新时间(last_updated)

把传入的参数 time(同样是一个时间值,单位应该和其他时间相关变量保持一致,它代表的可能是当前进行时钟设置操作时对应的系统时间或者某个参考时间,具体取决于调用该函数时传入的实际情况)赋值给 Clock 结构体中的 last_updated 成员变量。last_updated 变量用于记录时钟上一次被更新的时间,在后续计算时钟的时间流逝、进行时间补偿等相关操作(比如在 get_clock 函数中结合当前时间和 last_updated 来计算实际时钟时间)时会起到关键作用,通过设置这个值能准确标记本次更新操作对应的时间位置,便于后续基于时间差等的计算。

  1. 计算并设置时间偏差(pts_drift)

通过用 Clock 结构体中刚刚设置好的 pts 值减去 time 值,计算得到两者的差值,并将这个差值赋值给 Clock 结构体中的 pts_drift 成员变量。pts_drift 通常用于表示时间戳与实际时间之间的偏差情况(例如在处理音视频同步过程中,由于不同的处理速度、时钟源差异等原因可能导致实际时间和期望的时间戳存在一定偏差,通过这个变量来记录并后续在时间计算中进行相应补偿),这样设置后,pts_drift 就准确反映了本次设置操作下时间戳和传入时间之间的偏差情况,在后续涉及到根据时钟时间进行调整、同步等操作时,可以利用这个偏差值来使计算出的时钟时间更符合实际情况,确保时间控制的准确性。

  1. 设置序列号(serial)

将传入的参数 serial(一般用于标识某种顺序或者版本信息,比如在队列操作、数据更新顺序等场景下,通过序列号来确保不同操作之间的顺序一致性和数据的同步性,具体取决于程序中对 serial 的定义和使用方式)赋值给 Clock 结构体中的 serial 成员变量。serial 值的设置可以帮助判断不同时刻或者不同操作下时钟数据的有效性和顺序关系,例如在 get_clock 函数中就会通过比较 serial 和 queue_serial(另一个相关的序列号变量,这里未在本函数中涉及修改,但其与 serial 存在关联用于数据有效性判断等操作)来决定是否返回有效的时钟时间,通过正确设置 serial 能保证时钟相关操作在整个程序流程中的逻辑一致性和数据准确性。

  • 参数的准确性和单位一致性:传入函数的 pts、serial 和 time 等参数需要保证其准确性以及和程序中整体时间计量、数据顺序标识等体系的一致性。例如,pts 的值必须是符合实际时间戳要求的合理数值(不能出现不符合时间先后顺序、超出合理范围等情况),time 的单位要和程序中其他时间相关变量(如后续在获取时钟时间等操作中涉及的变量)统一,不然会导致设置后的 Clock 结构体中各时间相关成员变量之间的关系混乱,影响后续基于时钟的各种时间计算、同步判断等操作的准确性。
  • 多线程环境(如果涉及):如果该函数在多线程环境下被调用,要考虑线程安全性问题。由于函数是对 Clock 结构体中的多个成员变量进行赋值操作,多个线程同时调用这个函数来修改这些变量,可能会导致数据不一致的情况,比如一个线程刚设置好 pts 值,另一个线程紧接着又修改了它,使得 Clock 结构体中的时间相关信息不符合任何一个线程的预期,影响整个时钟状态的准确性以及基于时钟的后续操作(如音视频同步控制等)。所以在多线程场景下,可能需要通过合适的线程同步机制(如互斥锁等)来保证每次只有一个线程能够调用该函数进行 Clock 结构体的时间相关属性设置操作,确保设置结果的一致性和稳定性。

以下是对 set_clock 函数的详细解释:

set_clock 函数的主要作用是更新 Clock 结构体中的时间相关属性。它先获取当前的系统相对时间(转换为秒为单位),然后调用 set_clock_at 函数,利用获取到的当前时间以及传入的时间戳(pts)和序列号(serial)等参数,对 Clock 结构体内部各个与时间相关的成员变量进行赋值操作,以此来准确设置时钟的状态,常用于视频播放、音视频同步等需要精准控制时间的场景中,按照当前实际时间情况来更新时钟相关信息,为后续的时间判断、播放进度控制等操作提供准确依据。

  1. 获取当前系统相对时间(并转换单位)

首先调用 av_gettime_relative 函数来获取系统的相对时间,该函数通常返回以微秒为单位的时间值(不同系统下其实现细节可能略有差异,但大致功能是获取一个相对的时间度量)。然后将获取到的微秒时间值除以 1000000.0,这一步操作是将时间单位从微秒转换为秒,把转换后的时间值存储在 double 类型的变量 time 中,这样 time 变量就代表了当前的系统时间(以秒为单位),后续将基于这个时间来更新 Clock 结构体中的相关属性。

  1. 调用 set_clock_at 函数设置 Clock 结构体属性

调用 set_clock_at 函数,并传入 Clock 结构体指针 c、时间戳参数 pts、序列号参数 serial 以及刚刚获取并转换好单位的当前系统时间 time。set_clock_at 函数内部会按照如下步骤对 Clock 结构体的成员变量进行赋值操作(如前面介绍 set_clock_at 函数时所述):

  • 将传入的 pts 参数赋值给 c->pts,更新 Clock 结构体中的时间戳值,使其反映最新设定的对应时间点情况。
  • 把 time 参数赋值给 c->last_updated,记录本次时钟更新操作对应的系统时间,便于后续基于时间差等的时间计算操作。
  • 通过计算 c->pts – time,将差值赋值给 c->pts_drift,确定时间戳与当前系统时间之间的偏差情况,用于后续在时间调整、音视频同步等相关操作中对时间进行补偿,保证时间计算的准确性。
  • 将 serial 参数赋值给 c->serial,更新序列号信息,用于标识本次时钟操作的顺序或者版本等情况,确保在与其他相关操作配合时(比如在判断时钟数据有效性等场景下通过序列号对比来进行相关判断)能保证逻辑的一致性和数据的准确性。

通过 set_clock_at 函数完成对 Clock 结构体中多个关键时间相关属性的更新操作,从而实现了整个 set_clock 函数对时钟状态进行设置更新的功能。

  • 时间获取函数的可靠性与系统差异:函数依赖 av_gettime_relative 函数来获取系统相对时间,虽然它在多数常见系统中有相对稳定的功能表现,但不同操作系统、不同运行环境下其具体实现和精度等可能会有一定差异。在跨平台应用或者特殊的嵌入式等系统环境中使用时,需要充分测试该函数获取时间的准确性以及是否符合程序对时间精度等方面的要求,避免因时间获取不准确导致 Clock 结构体中时间相关属性设置错误,进而影响后续基于时钟的各种操作(如音视频同步出现偏差等问题)。
  • 多线程环境(如果涉及):和 set_clock_at 函数类似,在多线程环境下调用 set_clock 函数时要考虑线程安全性问题。由于函数涉及获取当前系统时间以及对 Clock 结构体中多个成员变量的更新操作,多个线程同时执行这些操作可能会引发数据不一致的情况。比如多个线程几乎同时调用 set_clock 函数,可能会导致获取到的系统时间混乱,或者对 Clock 结构体中各变量的赋值相互干扰,使得 Clock 结构体最终的状态不符合预期,影响整个时钟控制以及后续基于时钟的时间相关操作的准确性。所以在多线程场景下,通常需要采用合适的线程同步机制(如互斥锁等)来保证每次只有一个线程能够调用该函数进行时钟设置操作,确保操作的稳定性和结果的一致性。
  • 参数的准确性与合理性:传入的 pts 和 serial 参数需要保证其准确性和合理性,符合程序中对时间戳以及序列号相关的定义和使用要求。例如,pts 的值要符合时间先后顺序以及实际的时间计量逻辑(不能出现时间戳错乱、超出合理时间范围等情况),serial 的值要能准确反映操作的顺序或者版本信息,否则会导致 Clock 结构体中设置的属性不符合实际情况,影响后续基于时钟状态进行的各种时间判断、同步操作等的正确性。

set_clock_speed 函数主要用于设置 Clock 结构体所代表时钟的速度,并在设置速度前确保时钟的当前时间状态得到更新。它先调用 set_clock 函数来依据当前时钟时间(通过 get_clock 函数获取)和已有的序列号更新 Clock 结构体中的时间相关属性,然后再将传入的表示速度的参数赋值给 Clock 结构体中的 speed 成员变量,以此来改变时钟运行的快慢程度,常用于视频播放等场景中实现播放速度调整(如快进、慢放等)的功能,保证在调整速度时时钟能基于准确的当前时间状态进行后续计时等相关操作。

  1. 更新时钟的时间相关属性

这一行代码调用了 set_clock 函数来更新 Clock 结构体中的时间相关属性。在调用 set_clock 时,传入了三个参数:

  • 首先传入 Clock 结构体指针 c,指明要操作的目标时钟结构体对象。
  • 接着通过调用 get_clock 函数来获取当前时钟的时间值,并将其作为 set_clock 函数所需的时间戳参数(pts)传入。get_clock 函数会根据时钟当前的状态(如是否暂停、序列号是否匹配等情况,前面已详细介绍过其内部逻辑)返回对应的时间值,如果序列号匹配且时钟未暂停,会结合当前系统时间、上次更新时间、速度以及时间偏差等因素计算出当前时钟时间;若序列号不匹配则返回 NAN(表示时间无效),若时钟暂停则返回当前记录的时间戳 pts 值。通过获取这个当前时间值并传入 set_clock 函数,能保证在后续设置时钟速度前,时钟的时间戳等时间相关属性是基于最新的实际时间情况进行更新的。
  • 最后传入 c->serial,也就是 Clock 结构体自身的序列号,确保在更新时钟时间属性时保持序列号的一致性,符合整个时钟数据管理中对于顺序、版本等信息的要求,使得更新操作在逻辑上是连贯且正确的。

set_clock 函数内部会获取当前系统相对时间(转换为秒为单位),然后结合传入的时间戳和序列号,对 Clock 结构体中的 pts(时间戳)、last_updated(上次更新时间)、pts_drift(时间偏差)以及 serial(序列号)等成员变量进行赋值更新操作(如前面介绍 set_clock 函数时所述),从而使时钟的时间相关属性处于最新且准确的状态,为接下来设置速度做好准备。

  1. 设置时钟速度

将传入的参数 speed(通常表示一个倍数关系,比如 1.0 表示正常速度,大于 1.0 表示加速播放,小于 1.0 表示减速播放,具体取决于程序中对播放速度控制的定义和使用方式)赋值给 Clock 结构体中的 speed 成员变量。这样就改变了时钟运行的速度,后续在获取时钟时间(例如通过 get_clock 函数)或者进行其他与时钟时间相关的计算、操作(如在视频播放中基于时钟时间来控制播放进度、进行音视频同步等)时,就会根据这个新设置的速度来相应地调整时间的变化情况,实现播放速度调整的功能。

  • 时钟时间更新的准确性:在调用 set_clock 函数更新时钟时间相关属性时,依赖于 get_clock 函数能准确返回当前时钟时间。如果 get_clock 函数由于序列号不匹配、时钟状态异常等原因返回了不准确的时间值(比如返回 NAN 或者错误的时间戳),那么 set_clock 函数基于这个错误时间值进行的更新操作会导致 Clock 结构体中的时间相关属性出现偏差,进而影响后续整个时钟的计时准确性以及基于时钟的各种操作(如播放速度调整后的时间计算、音视频同步等)的正确性。
  • 多线程环境(如果涉及):和前面多个涉及对 Clock 结构体操作的函数类似,在多线程环境下调用 set_clock_speed 函数时要考虑线程安全性问题。由于函数既涉及获取当前时钟时间(通过 get_clock 函数)、更新时钟时间属性(通过 set_clock 函数),又涉及对 Clock 结构体中 speed 成员变量的赋值操作,多个线程同时执行这些操作可能会导致数据不一致的情况。例如,一个线程正在获取时钟时间准备更新,另一个线程却同时修改了 speed 或者其他相关时间属性,就会使得 Clock 结构体的最终状态不符合预期,影响时钟功能以及后续基于时钟的播放速度调整等操作的准确性。所以在多线程场景下,通常需要采用合适的线程同步机制(如互斥锁等)来保证每次只有一个线程能够调用该函数进行时钟速度设置操作,确保操作的稳定性和结果的一致性。
  • 速度参数的合理性:传入的 speed 参数需要保证其合理性,要符合程序中对时钟速度控制的定义和范围要求。例如,不能传入不合理的极小值(可能导致时间计算异常、播放卡顿等问题)或极大值(可能超出系统处理能力、导致程序崩溃等),而且其值应该在逻辑上能够准确表示期望的播放速度变化情况(如正常的倍数关系等),否则设置的时钟速度不符合实际需求,无法正确实现播放速度调整的功能。

init_clock 函数主要用于对 Clock 结构体进行初始化操作。它会将时钟相关的关键属性设置为初始值,包括将时钟速度设置为正常速度(1.0)、设置时钟为非暂停状态(0 表示非暂停)、关联对应的队列序列号指针,以及调用 set_clock 函数来进一步设置初始的时间戳等时间相关属性,以此确保 Clock 结构体在后续使用时处于一个合理的初始状态,常用于视频播放、音视频同步等涉及时间控制的系统初始化阶段。

  1. 设置时钟速度和暂停状态
  • 将 Clock 结构体中的 speed 成员变量赋值为 1.0,这表示将时钟的初始速度设置为正常速度,意味着在后续基于该时钟进行计时或者控制相关操作(比如视频播放进度控制等)时,时间会按照正常的速率进行推进,符合一般情况下系统启动时的默认运行速度要求。
  • 接着把 c->paused 成员变量设置为 0,这里假设非 0 值表示暂停状态,那么 0 就代表时钟初始时处于非暂停状态,即从一开始时钟就可以正常计时并进行相应的时间更新操作,保证系统启动后时间相关功能能够正常运转起来。
  1. 关联队列序列号指针

将传入的 queue_serial 指针(int * 类型,通常在程序中用于标识某种队列的顺序或者版本信息,可能在多数据处理、同步等场景下与时钟的操作配合使用,以确保数据的一致性和顺序性)赋值给 Clock 结构体中的 queue_serial 成员变量,使得 Clock 结构体能够通过这个指针与对应的队列序列号建立关联。后续在判断时钟数据的有效性(比如在 get_clock 函数中会对比 queue_serial 和 serial 的值来确定是否返回有效的时钟时间)等操作时,可以依据这个关联来保证操作的逻辑连贯性和数据准确性。

  1. 设置初始时间相关属性

调用 set_clock 函数来进一步初始化 Clock 结构体中的时间相关属性,传入 Clock 结构体指针 c、特殊值 NAN(在 C 语言中表示不是一个数字,这里用于表示初始时时间戳的一种特殊、无效状态)以及 -1(可能作为一种特殊的序列号初始值,用于区分尚未正式开始有效操作的阶段,同样是一种标记初始状态的用法,具体含义取决于程序中对序列号的整体定义和使用逻辑)。

set_clock 函数内部会先获取当前系统相对时间(转换为秒为单位),然后结合传入的时间戳(这里是 NAN)和序列号(-1),对 Clock 结构体中的 pts(时间戳)、last_updated(上次更新时间)、pts_drift(时间偏差)以及 serial(序列号)等成员变量进行赋值更新操作。由于传入的时间戳是 NAN,这实际上是将时钟初始的时间相关属性设置为一种特殊的、待正式更新的状态,后续在真正开始计时或者有有效时间数据到来时,再通过合适的调用其他时间设置函数(如 set_clock_at 等)来更新这些时间属性为实际有效的值,使得时钟能准确反映相应的时间情况。

  • 参数的合法性与一致性:传入的 queue_serial 指针需要保证其合法性,即它应该指向有效的内存区域,存储的是符合程序中对队列序列号定义要求的数值。如果指针为 NULL 或者指向的数据不符合预期,可能会导致后续在依赖 queue_serial 进行数据有效性判断等操作时出现错误,影响时钟功能的正常运行。同时,整个程序中对于队列序列号的使用逻辑(包括初始值 -1 的定义和后续更新规则等)要保持一致性,不然可能会出现对时钟数据有效性的误判等情况。
  • 多线程环境(如果涉及):在多线程环境下,调用 init_clock 函数进行初始化操作时同样要考虑线程安全性问题。虽然初始化一般在程序启动阶段进行,但如果存在多个线程可能同时访问或依赖这个正在初始化的 Clock 结构体(比如其他线程尝试获取时钟时间等操作,尽管初始时时间相关属性是特殊的无效状态,但可能存在并发访问的逻辑冲突),就可能导致数据不一致或者未完全初始化就被使用等问题。所以在多线程场景下,可能需要通过合适的线程同步机制(如互斥锁等)来保证 init_clock 函数的初始化操作能完整、正确地执行,避免被其他线程干扰,确保 Clock 结构体初始状态的准确性和稳定性。
  • 时间相关属性的后续更新要求:由于初始化时通过 set_clock 函数将时间戳等属性设置为特殊的无效状态(使用 NAN 等),在后续实际应用中,必须要通过合适的时机和正确的函数调用(如 set_clock_at 等函数在有有效时间数据时进行更新操作)来及时更新这些时间属性,使其变为有效的、符合实际时间情况的值,否则基于这个时钟进行的任何与时间相关的操作(如获取时钟时间、进行音视频同步判断等)都可能出现错误或者无法正常进行,因为初始的无效状态不能反映真实的时间进展情况。

一篇文章带你开启嵌入式编程—C语言的学习

C 语言作为一种古老而强大的编程语言,是许多程序员学习编程的起点。它不仅为学习其他高级语言奠定了基础,还在系统编程、嵌入式开发等领域有着广泛的应用。如果你已经报名我们嵌入式课程,那么就现在就和小编一起学习起来吧。

  1. 对底层操作更直接它可以直接操作内存,让程序员对程序的运行有更多的控制。
  2. 可移植性强C 语言的代码可以在不同的操作系统和硬件平台上运行,只需要进行少量的修改。
  3. 功能强大C 语言可以进行底层的系统编程,也可以进行高级的应用开发。它支持指针、结构体、联合体等复杂的数据类型,让程序员可以更加灵活地处理数据。
  1. 数据类型C 语言中有多种数据类型,包括整型、浮点型、字符型等。不同的数据类型占用不同的内存空间,并且有不同的取值范围。
  1. 变量和常量变量是在程序运行过程中可以改变的值,而常量是在程序运行过程中不能改变的值。在 C 语言中,变量和常量都需要先声明后使用。
  2. 运算符C 语言中有丰富的运算符,包括算术运算符、关系运算符、逻辑运算符等。这些运算符可以对数据进行各种运算操作。

(图片来源于网络)

(图片来源于网络)

(图片来源于网络)

  1. 控制语句C 语言中的控制语句包括条件语句(if-else、switch-case)、循环语句(for、while、do-while)等。这些控制语句可以让程序根据不同的条件执行不同的代码。

下面是一个简单的 C 语言程序,用于计算两个整数的和:

在这个程序中,我们首先定义了两个整数变量 a 和 b,并分别赋值为 10 和 20。然后,我们使用加法运算符 + 计算两个变量的和,并将结果存储在变量 sum 中。最后,我们使用 printf 函数输出结果。

最后给大家分享一下,如何才能掌握好C语言呢?

  1. 多写代码学习 C 语言最好的方法就是多写代码。通过实践,你可以更好地理解 C 语言的语法和编程思想。
  2. 阅读优秀的代码阅读优秀的 C 语言代码可以帮助你学习到更多的编程技巧和经验。你可以在网上搜索一些开源的 C 语言项目,或者阅读一些经典的 C 语言书籍中的代码示例。
  3. 编程交流比如在松勤学习嵌入式的同学,就可以就编程在学习群内友好交流。彼此分享自己的编程代码,对于自己的练习和理解都非常有助力的。

如果你还想了解更多关于嵌入式的学习,可以联系下方二维码免费领取哦~~

C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (2)

一个只返回单一纯文本消息的网站并不是很有用!

至少,它应该返回静态 HTML 页面、网页将用于样式的 CSS 以及任何其他静态资源,例如图像和视频。

根据惯例,这些文件应存储在名为 wwwroot 的目录中,以将它们与您网站项目的动态执行部分分开。

您现在将为您的静态网站资源创建一个文件夹,并创建一个使用 Bootstrap 进行样式设计的基本索引页面:

  1. In the Northwind.Web project/folder, create a folder named wwwroot. Note that Visual Studio recognizes it as a special type of folder by giving it a globe icon, .
  2. wwwroot 文件夹中,添加一个名为 index.xhtml 的新文件。(在 Visual Studio 中,项目项模板名为 HTML 页面。)
  3. index.xhtml 中,修改其标记以链接到 CDN 托管的 Bootstrap 进行样式设置,并使用现代良好实践,例如设置视口,如以下标记所示:
  1. 良好实践:请访问以下链接查看最新版本:https://getbootstrap.com/docs/versions/。点击最新版本以进入其“开始使用 Bootstrap”页面。向下滚动页面到第 2 步,找到最新的 <link> 元素(以及在本章后面的 <script> 元素),您可以复制并粘贴。

Bootstrap 是全球最受欢迎的响应式、移动优先网站构建框架。您可以在以下链接阅读一个仅在线的部分,介绍 Bootstrap 的一些重要功能:https://github.com/markjprice/cs13net9/blob/main/docs/ch13-bootstrap.md。

  1. wwwroot 文件夹中,添加一个名为 site.css 的文件并修改其内容,如下所示:
  1. wwwroot 文件夹中,添加一个名为 categories.jpeg 的文件。您可以从以下链接下载它:https://github.com/markjprice/cs13net9/blob/main/code/images/Categories/categories.jpeg。
  2. wwwroot 文件夹中,添加一个名为 about.xhtml 的文件并修改其内容,如下所示:

如果您现在启动网站并在地址框中输入 http://localhost:5130/index.xhtmlhttps://localhost:5131/index.xhtml ,网站将返回一个 404 Not Found 错误,表示未找到网页。为了使网站能够返回静态文件,例如 index.xhtml ,我们必须明确配置该功能。

即使我们启用了存储在 wwwroot 中的静态文件,如果您启动网站并在地址框中输入 http://localhost:5130/https://localhost:5131/ ,网站仍然会返回 404 Not Found 错误,因为如果没有请求命名文件,web 服务器不知道默认返回什么。

您现在将启用静态文件,显式配置默认文件,如 index.xhtml ,并更改注册的返回纯文本响应的 URL 路径:

  1. Program.cs 中,在启用 HTTPS 重定向后添加语句以启用静态文件和默认文件。同时,修改将 GET 请求映射为返回包含环境名称的纯文本响应的语句,使其仅响应 URL 路径 /env ,如以下代码中突出显示的内容所示:
  1. ASP.NET Core 9 引入了 MapStaticAssets 方法,该方法自动压缩静态文件,从而减少带宽需求。对于 ASP.NET Core 8 及更早版本,您必须改为调用 UseStaticFiles 方法。您可以在以下链接中了解更多信息: https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-9.0#optimizing-static-web-asset-delivery。

警告!对 UseDefaultFiles 的调用必须在对 MapStaticAssetsUseStaticFiles 的调用之前,否则将无法正常工作!您将在本章末尾链接的在线部分中了解有关中间件和端点路由的顺序的更多信息。

  1. 启动网站。
  2. 启动 Chrome 并显示开发者工具。
  3. 在 Chrome 中输入 http://localhost:5130/ 。请注意,您被重定向到端口 5131 上的 HTTPS 地址,并且 index.xhtml 文件现在通过该安全连接返回,因为它是该网站的可能默认文件之一,并且是 wwwroot 文件夹中找到的第一个匹配项。

警告!如果您仍然看到环境名称为纯文本,请确保您将相对路径 /env 映射到该端点,而不仅仅是 / ,否则这将覆盖对 index.xhtml 文件的默认请求。

  1. 在 Chrome 中,输入 http://localhost:5131/about.xhtml ,注意网页被返回,并且它请求 site.css 文件,该文件应用了额外的样式。
  2. 在开发者工具中,注意对 Bootstrap 样式表的请求。
  3. 在 Chrome 中,输入 http://localhost:5130/env ,注意它返回的环境名称与之前的纯文本相同。
  4. 关闭 Chrome 并关闭网络服务器。

在.NET 9 中引入的 MapStaticAssets 通过整合构建和发布时的过程来收集应用程序中所有静态资源的数据。然后,运行时库使用这些数据高效地将这些文件提供给浏览器。

虽然 MapStaticAssets 通常可以直接替代 UseStaticFiles ,但它特别针对在构建和发布时已知的应用程序资产进行了优化。对于从其他位置提供的资产,例如磁盘或嵌入资源,仍应使用 UseStaticFiles

MapStaticAssets 相较于 UseStaticFiles 提供以下好处:

  • 为应用中的所有资产构建时间压缩:gzip 在开发期间和 gzip + brotli 在发布期间。所有资产都经过压缩,目的是将资产的大小减少到最小。
  • 每个资源的 ETag 是内容的 SHA-256 哈希的 Base64 编码字符串。这确保浏览器仅在文件内容发生变化时重新下载该文件。

作为一个例子,表 13.1 显示了使用 Fluent UI Blazor 组件库的原始和压缩大小,总共从 478 KB 未压缩到 84 KB 压缩:

表 13.1:MapStaticAssets 如何压缩 Fluent UI Blazor 组件

如果所有网页都是静态的(也就是说,它们仅由网页编辑手动更改),那么我们的网站编程工作就完成了。但几乎所有网站都需要动态内容,这意味着通过执行代码在运行时生成的网页。

最简单的方法是使用 ASP.NET Core 的一个功能,名为 Blazor staticSSR。但在此之前,让我们了解一下为什么您可能会在开发者工具等工具中看到您不期望的额外请求。

在开发者工具中,我们可以看到浏览器发出的所有请求。有些请求是你所期望的,例如:

  • localhost : 这是网站项目中主页的请求。对于我们当前的项目,地址将是 http://localhost:5130/https://localhost:5131/
  • bootstrap.min.css : 这是对 Bootstrap 样式的请求。我们在主页上添加了对此的引用,因此浏览器随后发出了对样式表的请求。

某些请求仅在开发期间发出,并由您使用的代码编辑器确定。如果您在开发者工具中看到它们,通常可以忽略它们。例如:

  • browserLinkaspnetcore-browser-refresh.js : 这些是 Visual Studio 发出的请求,用于将浏览器连接到 Visual Studio 进行调试和热重载。例如, https://localhost:5131/_vs/browserLinkhttps://localhost:5131/_framework/aspnetcore-browser-refresh.js
  • negotiate?requestUrl , connect?transport , abort?Transport , 等等:这些是用于将 Visual Studio 与浏览器连接的附加请求。
  • Northwind.Web/ : 这是一个与 SignalR 相关的安全 WebSockets 请求,用于将 Visual Studio 连接到浏览器: wss://localhost:44396/Northwind.Web/ .

现在您已经了解了如何设置一个基本的网站,并支持静态文件,如 HTML 网页和 CSS,让我们通过添加对动态生成的静态网页的支持来使其更有趣(当网页到达浏览器客户端时,它是静态的)。

ASP.NET Core 有多种技术用于动态生成静态网页,包括 Razor Pages 和模型视图控制器(MVC)Razor 视图。动态网页的最现代技术是 Blazor 静态 SSR 页面。但让我们首先回顾一下 Blazor 及其创建原因。

Blazor 让您使用 C# 而不是 JavaScript 构建交互式网页 UI 组件。Blazor 在所有现代浏览器上都受支持。

传统上,任何需要在网页浏览器中执行的代码必须使用 JavaScript 编程语言或一种更高级的技术编写,该技术会转译(转换或编译)为 JavaScript。这是因为所有浏览器已经支持 JavaScript 超过二十年,因此它是实现客户端业务逻辑的最低共同分母。

JavaScript 确实存在一些问题。然而,尽管它与 C#和 Java 等 C 风格语言有表面上的相似之处,但一旦深入探讨,它实际上是非常不同的。它是一种动态类型的伪函数式语言,使用原型而不是类继承来实现对象重用。它可能看起来像人类,但当它被揭示为斯克鲁尔时,你会感到惊讶。

如果我们在浏览器中能够使用与服务器相同的语言和库,那就太好了。

即使是 Blazor 也无法完全取代 JavaScript。例如,浏览器的某些部分仅可通过 JavaScript 访问。Blazor 提供了一种互操作服务,以便您的 C# 代码可以调用 JavaScript 代码,反之亦然。您将在第 14 章《使用 Blazor 构建交互式 Web 组件》的仅在线 JavaScript 互操作部分看到这一点。

微软之前曾尝试通过一种名为 Silverlight 的技术来实现这一目标。当 Silverlight 2 于 2008 年发布时,C# 和 .NET 开发人员可以利用他们的技能构建库和可视组件,这些组件通过 Silverlight 插件在网页浏览器中执行。

到 2011 年和 Silverlight 5,苹果在 iPhone 上的成功以及史蒂夫·乔布斯对像 Flash 这样的浏览器插件的厌恶,最终导致微软放弃 Silverlight,因为像 Flash 一样,Silverlight 在 iPhone 和 iPad 上被禁止。

另一个网页浏览器的发展使微软有机会再次尝试。2017 年,WebAssembly 共识完成,所有主要浏览器现在都支持它:Chromium(Chrome、Edge、Opera 和 Brave)、Firefox 和 WebKit(Safari)。

WebAssembly(Wasm)是一种用于虚拟机的二进制指令格式,提供了一种在网络上以接近本地速度运行用多种语言编写的代码的方法。Wasm 被设计为高层语言(如 C#)编译的可移植目标。

Blazor 是一个单一的编程或应用模型。对于 .NET 7 及更早版本,开发者必须为每个项目选择一个托管模型:

  • Blazor Server 项目运行在服务器端,因此 C# 代码可以完全访问业务逻辑可能需要的所有资源,而无需提供身份验证凭据。它使用 SignalR 将 UI 更新传递给客户端。服务器必须与每个客户端保持一个实时的 SignalR 连接,并跟踪每个客户端的当前状态。这意味着如果需要支持大量客户端,Blazor Server 的扩展性较差。它于 2019 年 9 月作为 ASP.NET Core 3 的一部分首次发布。
  • Blazor Wasm 项目在客户端运行,因此 C# 代码只能访问浏览器中的资源。在访问服务器上的资源之前,它必须进行 HTTP 调用(这可能需要身份验证)。它于 2020 年 5 月作为 ASP.NET Core 3.1 的扩展首次发布,并被标记为 3.2,因为它是当前版本,因此不在 ASP.NET Core 3.1 的长期支持范围内。Blazor Wasm 3.2 版本使用了 Mono 运行时和 Mono 库。 .NET 5 及更高版本使用 Mono 运行时和 .NET 库。
  • 一个 .NET MAUI Blazor 应用程序,也称为 Blazor Hybrid 项目,通过使用本地互操作通道将其网页 UI 渲染到网页视图控件,并托管在 .NET MAUI 应用程序中。它在概念上类似于 Electron 应用程序。

随着 .NET 8 的发布,Blazor 团队创建了一个统一的托管模型,其中每个单独的组件都可以设置为使用不同的渲染模型执行:

  • SSR:在服务器端执行代码,类似于 Razor Pages 和 MVC。然后将完整的响应发送到浏览器,以便向访问者显示,直到浏览器发出新的 HTTP 请求,服务器和客户端之间没有进一步的交互。就浏览器而言,网页是静态的,就像任何其他 HTML 文件一样。
  • 流式渲染:在服务器端执行代码。HTML 标记可以返回并在浏览器中显示,并且在连接仍然打开时,任何异步操作可以继续执行。当所有异步操作完成后,最终的标记由服务器发送以更新页面内容。这改善了访客的体验,因为他们在等待其余内容时可以看到一些内容,比如“加载中…”的消息。
  • 交互式服务器渲染:在实时交互过程中在服务器端执行代码,这意味着代码可以完全且轻松地访问服务器端资源,如数据库。这可以简化功能的实现。交互请求使用 SignalR 进行,这比完整请求更高效。浏览器和服务器之间需要保持一个永久连接,这限制了可扩展性。这是一个适合内部网网站的好选择,因为客户端数量有限且网络带宽高。
  • 交互式 Wasm 渲染:在客户端执行代码,这意味着代码只能访问浏览器内的资源。这可能会使实现变得复杂,因为每当需要新数据时必须回调服务器。对于潜在有大量客户端且其中一些连接带宽较低的公共网站来说,这是一个不错的选择。
  • 交互式自动渲染:首先在服务器上渲染以实现更快的初始显示,后台下载 Wasm 组件,然后切换到 Wasm 以进行后续交互。

这个统一模型意味着,通过精心规划,开发者可以一次编写 Blazor 组件,然后选择在 Web 服务器端、Web 客户端,或动态切换运行。这提供了最佳的解决方案。

重要的是要理解,Blazor 用于创建 UI 组件。组件定义了如何渲染 UI 和响应用户事件,并且可以组合、嵌套,并编译成 Razor 类库以便打包和分发。

例如,为了在电商网站上提供产品的星级评分 UI,您可以创建一个名为 Rating.razor 的组件,如下所示的标记:

您可以在网页上使用该组件,如以下标记所示:

创建组件实例的标记看起来像一个 HTML 标签,其中标签的名称是组件类型。组件可以通过元素嵌入到网页中,例如 <Rating Value=\”5\” /> ,或者可以像映射的端点一样进行路由。

代码可以存储在一个名为 Rating.razor.cs 的单独代码隐藏文件中,而不是一个包含标记和 @code 块的单一文件。此文件中的类必须是 partial 并且与组件同名。

内置了许多 Blazor 组件,包括用于在网页的 <head> 部分设置像 <title> 这样的元素的组件,还有许多第三方会出售用于常见目的的组件。

您可能会想知道为什么 Blazor 组件使用 .razor 作为其文件扩展名。Razor 是一种模板标记语法,允许混合 HTML 和 C#。支持 Razor 语法的旧技术使用 .cshtml 文件扩展名来表示 C# 和 HTML 的混合。

Razor 语法用于:

  • ASP.NET Core MVC 视图和使用 .cshtml 文件扩展名的部分视图。业务逻辑被分离到一个控制器类中,该类将视图视为一个模板,以将视图模型推送到该模板,然后将其输出到网页。
  • 使用 .cshtml 文件扩展名的 Razor 页面。业务逻辑可以嵌入或分离到使用 .cshtml.cs 文件扩展名的文件中。输出是一个网页。
  • 使用 .razor 文件扩展名的 Blazor 组件。输出作为网页的一部分进行渲染,尽管可以使用布局来包装组件,使其作为网页输出,并且可以使用 @page 指令来分配一个路由,定义检索组件作为页面的 URL 路径。

现在您了解了 Blazor 的背景,让我们来看一些更实际的内容:如何将 Blazor 支持添加到现有的 ASP.NET Core 项目中。

ASP.NET Core Blazor 静态 SSR 允许开发者轻松地将 C# 代码语句与 HTML 标记混合,以使生成的网页动态。

以下是您必须完成的任务摘要,以在现有的 ASP.NET Core 项目中启用 Blazor 及其静态 SSR 功能:

  1. 创建一个 Components 文件夹以包含您的 Blazor 组件。
  2. 创建一个 Components\\Pages 文件夹以包含您的 Blazor 页面组件。
  3. Components 文件夹中,创建三个 .razor 文件:_Imports.razor : 此文件导入所有 .razor 文件的命名空间,以便您无需在每个 .razor 文件的顶部导入它们。至少,您需要导入 Blazor 路由和您本地项目 Blazor 组件的命名空间。App.razor : 此文件包含将包含您所有 Blazor 组件的网页的 HTML。它还需要在网页的 <body> 中某处引用您的 Blazor Routes 组件。Routes.razor : 此文件定义了一个 <Router> 组件,该组件扫描当前程序集以查找页面组件及其注册的路由。
  4. Components\\Pages 中,创建一个 Index.razor 文件,这是一个 Blazor 页面组件,将作为 Blazor 路由器默认显示的主页。文件顶部需要一个指令来定义根路径的路由: @page \”/\”
  5. Program.cs 中,您必须调用 AddRazorComponents() 来将 Blazor 或 Razor 组件 ( *.razor ) 文件注册到 ASP.NET Core 的依赖服务集合中,然后调用 MapRazorComponents<App>() 来映射所有找到的 Blazor 路由的端点。您还必须调用 UseAntiforgery() ,因为 Blazor 组件会自动检查防伪令牌;因此,HTTP 管道必须启用中间件以支持它们。

您现在将添加并启用 Blazor 静态 SSR 服务,然后将静态 HTML 页面复制并更改为 Blazor 静态 SSR 文件:

  1. Northwind.Web 项目文件夹中,创建一个名为 Components 的文件夹。
  2. Components 文件夹中,创建一个名为 Pages 的文件夹。
  3. Components 文件夹中,创建一个名为 _Imports.razor 的文件。
  4. _Imports.razor 中,添加语句以导入 Blazor 组件路由的命名空间,以及您的 Northwind.Web 项目及其组件,如下所示的标记:
  1. Components 文件夹中,创建一个名为 Routes.razor 的文件。
  2. Routes.razor 中,添加语句以定义一个路由器,该路由器扫描当前程序集以查找已注册路由的 Blazor 页面组件,如下标记所示:
  1. index.xhtml 文件复制到 Components\\Pages 文件夹中。(在 Visual Studio 或 Rider 中,按住 Ctrl 键同时拖放。)
  2. 对于 Components\\Pages 文件夹中的文件(不是原始文件),将文件扩展名从 index.xhtml 重命名为 Index.razor 。确保“I”是大写的。(Blazor 组件必须以大写字母开头,否则会出现编译错误!)
  3. Components 文件夹中,创建一个名为 App.razor 的文件。
  4. App.razor 中,从 Index.razor 中剪切并粘贴根标记,包括头部和主体元素,然后添加元素以使用 <HeadOutlet /> 插入页面标题,并为您的 <Routes> 组件,如以下标记所示:

Index.razor 中,将 @page 指令添加到文件顶部,并将其路由设置为 / ,添加一个 <PageTitle> 组件以设置网页 <title> ,删除表示这是一个静态 HTML 页面的 <h2> 元素,并注意结果,如以下标记所示:

警告!如果您已为 Visual Studio 安装了 ReSharper,或者使用 Rider,那么它们可能会在您的 Razor 页面、Razor 视图和 Blazor 组件中发出“无法解析符号”的警告。这并不总是意味着存在实际问题。如果文件可以编译,则可以忽略它们的错误。有时这些工具会感到困惑,毫无必要地让开发者担忧。

  1. Program.cs 中,在创建 builder 的语句后,添加一条语句以添加 ASP.NET Core Blazor 组件及其相关服务,并可选择性地定义一个 #region ,如下代码所示:

Program.cs 中,在调用方法以使用 HTTPS 重定向后,添加一条语句以使用反伪造中间件,如以下代码中突出显示的内容所示:

Program.cs 中,在文件顶部添加一条语句以导入您的项目组件,如以下代码所示:

Program.cs 中,在映射 HTTP GET 请求到路径 /env 的语句之前,添加一个调用 MapRazorComponents 方法的语句,如以下代码中突出显示的那样:

  1. 使用 https 启动配置开始网站项目。
  2. 在 Chrome 中,输入 https://localhost:5131/ ,注意到显示这是一个静态 HTML 页面的元素消失了。如果它仍然存在,那么您可能需要清空浏览器缓存。查看开发者工具,点击并按住重新加载此页面按钮,然后选择清空缓存并强制重新加载,如图 13.4 所示:

图 13.4:查看开发者工具,然后点击并按住重新加载此页面按钮以查看更多命令

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。