0. 参考网站

1. 问题与笔记

  • 当使用百分比设置元素的长宽时,一般元素默认的参考是父元素对应的长宽。而像 marginpadding只会根据父元素的宽度 进行计算

  • 当设置 box-sizing: border-box 的时候,会使浏览器将元素的边距边框考虑在内,以防止超出父元素。而默认情况下使用的 box-sizingcontent-box ,即只考虑元素的内容部分进行布局。

  • CSS 中进行对齐

    • 一般的布局模式下对齐

      • margin: auto 会自动将块元素左右的 margin 设置为相同的值,达到居中的效果。

      • 如果单纯左右对齐,可以将元素设置为 position 或者 float 定位,并设置位置数值。

      • 加入是元素内的文本对齐可以对包含文本的元素使用 text-align 属性

        1
        2
        3
        4
        5
        text-align: 
        center # 靠中间对齐
        left, right # 靠左, 右对齐
        start, end # 根据文本的方向确定 start 为 left 或者 right
        justify # 文本向两边靠拢
    • flex 布局下 aligin-itemsjustify-content 分别控制 交叉轴主轴 方向的对齐。(这意味着如果改变flex-direction 的值会影响这两个字段的行为)。同时要注意 CSS 中有着 itemscontent 结尾的另外两个方法,结论上而言,items 结尾方法控制 flex 盒子中各个行单独的对齐行为,而 content 方法会把 flex 盒子内各个行看成一个整体进行对齐。

    • 除了上述,还有 place-items 可以同时控制主轴和交叉轴的对齐模式

  • 选择器的用法:CSS 中的选择器用于定位 HTML 中的不同标签或属性以便添加样式;选择器主要包括 类型ID、**属性 ** 和 关系 五种选择器 (但其实还有一个全局选择器)。另外逗号代表对多个元素应用同一个样式。

    • 类型ID 选择器

      1
      2
      3
      h1, span, div{} # 类型选择器就是使用标签作为目标进行匹配
      .box{} # 类选择器根据class中的值进行匹配,这里会匹配class带有box的标签
      #box{} # 类似于类选择器, id选择器根据标签的id中的值进行匹配
    • 属性 选择器

      1
      2
      3
      4
      5
      6
      7
      li[class]		# 匹配带有class属性的li标签
      li[class="a"] # 带有class属性且正好是"a"
      li[class~="a"] # 带有class属性且包含"a"
      li[class^="a"] # 属性值开头为"a"
      li[class$="a"] # 属性值结尾为"a"
      li[class*="a"] # 属性值出现了"a"
      li[class="a" i] # 添加i属性可以设置大小写不敏感
    • 关系 选择器

      1
      2
      3
      4
      div p 	# 用空格符代表pdiv的后代 (不一定是直接后代)
      div > p # 代表pdiv的直接后代
      div ~ p # divp属于同一元素的子代
      div + p # divp属于同一元素的子代且相邻
    • 全局 选择器,可以使用 * 符号作为通配符进行匹配

  • 伪类和伪元素,更多的伪类和伪元素可以参考 MDN 文档

    • 伪类:根据 MDN 文档,伪类是选择器的一种,它用于选择处于特定状态的元素,比如当它们是这一类型的第一个元素时,或者是当鼠标指针悬浮在元素上面的时候。它们表现得会像是你向你的文档的某个部分应用了一个类一样,帮你在你的标记文本中减少多余的类 。常见的伪类有 :hover , :focus:last-child

      1
      2
      3
      4
      # 匹配article后的p, 且为第一个子代
      article p:first-child{
      color: brown;
      }
    • 伪元素伪元素以类似方式表现,不过表现得是像你往标记文本中加入全新的 HTML 元素一样,而不是向现有的元素上应用类。伪元素开头为双冒号 。可以通过 ::before::after 这样的伪元素在标签中添加内容

      1
      2
      3
      4
      # 在box类的元素之前添加内容
      .box::before {
      content: "This should show before the other content. ";
      }
  • 在选择器中,元素之间的空格有特殊含义

    1
    2
    article p :last-child # 代表p的后代且是最后一个子代
    article p:last-child # 代表p, 且是最后一个子代
  • OOCSSBEM :都是 CSS 的组织方式,具有工程意义

    • OOCSS,面向对象 CSS 。对具有重复定义的 CSS 代码进行提炼,制造出继承关系。(参考来源)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      # 应用OOCSS前
      .box-1 {
      border: 1px solid #ccc;
      width: 200px;
      height: 200px;
      border-radius: 10px;
      }

      .box-2 {
      border: 1px solid #ccc;
      width: 120px;
      height: 120px
      border-radius: 10px;
      }

      # 应用OOCSS之后
      .box-border{
      border: 1px solid #CCC;
      border-radius: 10px;
      }

      .box-1 {
      width: 200px;
      height: 200px;
      }

      .box-2 {
      width: 120px;
      height: 120px;
      }
    • BEM (Block,Element,Modefier):BEM 是一个分层系统,它把我们的网站分为三层:块层、元素层、修饰符层。要体现到代码上,我们需要遵循三个原则(参考来源):

      • 使用 __ 两个下划线将块名称与元素名称分开
      • 使用 -- 两个破折号分隔元素名称及其修饰符
      • 一切样式都是一个类,不能嵌套
  • 描述颜色的三种方式

    • RGB
    • HSL:Hue (色相),Saturation,Lightness
    • HEX:即用 # 开头的十六进制字符表示颜色

1. 问题与笔记

  • 关于网页元素嵌入:
    • iframe 中的 allow-scriptsandbox :前者允许iframe中的脚本执行,后者能进行 iframe 中部分操作的限制
    • embedobject 标签:二者作用相似,用于嵌入多媒体元素。但是 embed 受制于浏览器插件,所以后者的兼容性更好
  • 创建一个表格,需要使用到 <th> 设定表头,<tr> 换行,<td> 设置表格内部数据;同时可以使用 col-span 等属性设置表格占据的大小;也可以用 col-groupcol 组合成的标签设置表格中元素的颜色,<col-group> 标签放在最开始的位置;同时可以使用 <thead>, <tbody> , <tfoot> 等标签将表格格式化以便于代码维护。

0. 随时可以访问官网查阅文档或者示例

1. 问题与笔记

  • undefinednull : 前者代表声明了但没有分配的值,后者代表一个分配了的空值

  • Promise 的用法

    • 首先根据 MDN 文档:

      Promise 有三种状态:

      • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。这是调用 fetch() 返回 Promise 时的状态,此时请求还在进行中。
      • 已兑现(fulfilled):意味着操作成功完成。当 Promise 完成时,它的 then() 处理函数被调用。
      • 已拒绝(rejected):意味着操作失败。当一个 Promise 失败时,它的 catch() 处理函数被调用。
    • 通过 Promise 对象传递异步函数运行的结果,对象拥有 then , catch 等方法

    • 任何一个 声明为 async 的函数都应该拥有 Promise 类型的返回值, 下文是一个 ‘烤鱼’ 的例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    async function cook(raw_food) {
    return new Promise((resolve, reject) => {
    if(raw_food === "fish") {
    resolve(`cooked ${raw_food}`) // return value
    } else {
    reject(`we do not cook ${raw_food}`)
    }
    })
    }

    cook("fish")
    .then((val) => {
    console.log(`you've got a ${val}`)
    })
    .catch((reason) => {
    console.log(`you've got nothing because ${reason}`)
    })
    // 输出:you've got a cooked fish
    • 同时可以使用 Promise.all 方法等待多个 Promise 执行完成,在上述例子的基础上换一个写法。注意这个方法会在所有 Promise 都成功后触发 then ,假如其中任意一个 Promise 失败则会触发 catch 返回触发该失败的原因

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
         var p_0 = cook("fish")
      var p_1 = cook("laptop")
      var p_2 = cook("meat")

      Promise.all([p_0, p_1, p_2])
      .then((vals) => {
      for(v of vals) {
      console.log(`you've got a ${v}`)
      }
      })
      .catch((reason) => {
      console.log(`you've got nothing because ${reason}`)
      })
      // 输出:you've got nothing because we do not cook laptop
  • gettersetter 的使用方法,注意不能看作普通函数调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Person {
    set setInfo(val) {
    this.age = val.age
    this.name = val.name
    }

    get getInfo() {
    return `My name is ${this.name}, I am ${this.age} `
    }
    }

    let p = new Person()

    p.setInfo = {
    age: 12,
    name: "Tommy"
    }

    console.log(p.getInfo)
  • 使用 new 调用构造函数:在 JavaScript 中除了类的构造函数,还可以将普通函数定义为构造函数并用 new 触发

    1
    2
    3
    4
    5
    6
    function Person(name, age) {
    this.name = name
    this.age = age
    }

    console.log(new Person("Tommy", 21))
  • for 中的 inof

    • 使用 in 用于遍历对象字段的键

      1
      2
      3
      4
      5
      6
      7
      8
      9
      let p = {
      name: "Tommy",
      age: 21,
      blood_type: "A"
      }

      for (const key in p) {
      console.log(`${key}: ${p[key]}`)
      }
    • of 一般用于遍历容器,比如数组等

      1
      2
      3
      4
      let arr = [1, 2, 5, 7]
      for (const val of arr) {
      console.log(val)
      }
  • 事件捕获和冒泡:一个事件的处理会先经过事件捕获阶段,再经过事件冒泡阶段。事件捕获阶段,事件从父元素逐层传递到子元素,事件冒泡阶段则反之。当我们在给元素添加监听器的时候 addEventListener() 的第三个参数如果为 true ,则会在事件捕获阶段进行监听,不写则默认为 false

  • 传入参数是值复制还是引用?一般来说可以理解为:基本类型复制值,其他对象传引用(细节可参考 [文章](JavaScript 是传值调用还是传引用调用? - 知乎 (zhihu.com)) )

  • 函数柯里化: 将函数的参数绑定已知的值返回一个新的函数,减少重复代码。比如下列例子可以生成对 add(1,4)(2)(5) 这样的函数的柯里化。需要注意的是 ,由于 JavaScript 独特的闭包机制 (Closure) , 变量可以访问作用域之外的值,同时作用域外的值会保存到不再被引用时。不像 C++ 这类函数变量压栈的语言,变量不会因为离开函数作用域就被直接释放。所以在下文的例子中 args 并不会在离开 add() 函数的作用域就被释放,而是可以被不断调用。(说实话听起来有点不靠谱。。。)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function add() {
    let args = Array.prototype.slice.call(arguments)
    let inner = function() {
    args.push(...arguments)
    let sum = 0
    for(val of args) {
    sum += val
    }
    return inner
    }
    return inner
    }

    console.log(add(1)(3))
  • JavaScript 中的 this 并不只限于类中,而可以出现在多个语境中。广义上讲, this 表示的是函数指向的某个数据。这个 this 一般指向的是调用当前函数的对象(不能是箭头函数,箭头函数中的 this 指代的是所属作用域的 this, 如果在全局则指代全局对象), 称之为隐式绑定。但也可以通过显式绑定设置为自定义的对象,见下一个条目的三个方法

  • 显式绑定 call, bindapply 。三者本质大差不差,用法略有不同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let tommy = {
    power: 100,
    charge: function (inc_0, inc_1) {
    this.power += (inc_0 + inc_1)
    }
    }

    let john = {
    power: 50
    }

    tommy.charge.call(john, 10, 10)
    tommy.charge.apply(john, [10, 10])
    tommy.charge.bind(john)(10, 0)

    console.log(john) // {power: 100}
  • 箭头函数与普通函数的不同

    • 箭头函数不能用作构造函数,所以 this 并不能指向构造的函数,而是等于所属的作用域的 this 的值
    • 函数没有 proyotypearguments 等内置对象
    • 不可用显式绑定修改绑定的对象
  • CommonJSECMAScript 模块(ESM)是两种不同的 JavaScript 模块化标准。它们之间有一些主要区别,包括:

    • 语法CommonJS 使用 requiremodule.exports 来导入和导出模块,而 ECMAScript 模块使用 importexport 关键字。
    • 运行时/编译时CommonJS 模块是在运行时动态加载的,这意味着您可以在运行时根据条件来决定是否导入某个模块。而 ECMAScript 模块是在编译时静态加载的,这意味着您必须在代码顶部显式地声明所有要导入的模块。
    • 环境支持CommonJS 最初是为 Node.js 设计的,因此它在 Node.js 环境中得到了广泛支持。而 ECMAScript 模块是 JavaScript 语言标准的一部分,因此它在现代浏览器中得到了广泛支持。不过,Node.js 也支持 ECMAScript 模块。

0. 随时可以访问官网查阅文档或者示例

1. 基本环境配置

  • 通过 npm 包管理安装 typescript :

    1
    2
    3
    npm install typescript --save-dev
    # 使用 --save-dev 选项安装的包会限定在生产测试环境中而不会影响到部署环境
    # 如果想全局安装请使用 -g 选项
  • 为了更好的管理工程,我们应该使用 tsconfig.json 文件配置环境

    1
    2
    # 这将自动生成一个tsconfig.json文件
    tsc --init
    • tsconfig.json 中的 rootDir 项设置为 ts 源文件的文件夹

    • tsconfig.json 末尾添加 include 项,以防将与 tsconfig.json 同文件夹的文件编译

      1
      "include": ["[SRC_DIR]"] # 将[SRC_DIR]设置为源文件文件夹	
    • outDir 项设置为 js 文件的输出文件夹

  • 接着就可以使用 npx 运行编译器将 .ts(x) 文件编译成 .js(x) 文件

    1
    npx tsc # 假如不想设置tsconfig.json, 这条指令后面应该指定ts源文件,即可跳过上述第二步
  • 使用 -watch 选项可以让 tsc 进程在后台自动编译文件 ( 或者 -w )

    1
    npx tsc -w

2. 问题与笔记

  1. 编译选项 --noEmitOnError 的作用:该选项默认为 false ,代表源文件编译错误时仍然会输出 js 文件
  1. instanceoftypeof : 前者用于判断实例与某个类的关系 (可以判断继承关系),但是不能用于接口。后者可以用于类与接口,可以判断实例的基本类型,这两个方法属于 javascript 方法

  2. in , isas

  • in 用于类型判断,可以判断某个字段是否存在于类型中

    1
    2
    3
    4
    let p:Person = {gender:"male", swim: () => {}};
    if("gender" in p){
    console.log("It has member gender")
    }
  • is 用于定义类型检查函数,写在返回 bool 的函数返回值中,编译器根据布尔值确定变量类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function isString(p: any): p is string {
    return true
    }

    if(true) {
    let s: any = "Hello"
    if(isString(s)){
    console.log(typeof s)
    }
    }
  • as 用于告诉编译器某个变量的类型以通过编译

    1
    2
    let s: unknown = "HELLO"
    console.log((s as string).toLowerCase())
  1. keyof: 用于返回一个类型中的字段的类型的 Union

  2. Meta Types :在 typescript 中操作类型的不同方法(用于操纵 typeinterface):

    • Mapped Types:意指用现有的类型生成新的类型。如下所示,根据原来的 Person 类型生成一个只读的 Readonly<Person> 类型

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      type Readonly<T> = {
      readonly [P in keyof T]: T[P];
      };

      type Person = {
      name: string;
      age: number;
      };

      type ReadonlyPerson = Readonly<Person>;

    • Conditional Types:用 ? 结合 extends 关键字生成类型

      1
      2
      3
         type T = String extends Object ? never: String
      # 也可以结合泛型
      type Ty<T> = T extends Object ? never : String
    • Indexed Types:可以用下标运算符操作某个类型以获得某个字段的类型

      1
      2
      3
      4
      interface Test {
      name: string
      }
      type IDXType = Test["name"]
    • Discriminate Types:允许用键值对临时定义一个类型

      1
      2
      3
      let a : {name: string} = {
      name: "Tommy"
      }
  3. 函数签名 :

    • Construct Signatures:用于描述构造函数类型信息的语法 (在这里只要是生成新对象的函数就是构造函数), 在 Call Signatures 之前使用 new 关键字即可完成一个构造签名

      1
      2
      3
      4
      5
      6
      7
      8
      9
      type SomeConstructor = {
      new (s: string) : String;
      }

      function fn_(ctor: SomeConstructor) {
      return new ctor("hello");
      }

      fn_(String) // 传入 String 的构造函数也就是 String
    • Call Signatures:用于描述函数类型信息的语法, 写法类似于 lambda

      1
      2
      3
      4
      5
      function f(v: number): void {
      console.log('do nothing')
      }

      let func: (val: number) => void = f
  4. .d.ts 文件一般用于描述 javascript 库的类型信息,指导编译器更好地工作

  5. unknownneverany :

    • never 表示一个永远不会发生或者用到的类型,比如一个函数永远不会正常返回时,可以将返回值设置为 never
    • unknown 代表任何值,但是需要进行类型检查或者断言才能保证通过编译,推荐使用 unknown 作为 any 的代替
    • any 代表任何值,可以躲过 typescript 的检查
  6. 文档中 argument 与 parameter 的区别:前者代表函数传入的实际参数,后者代表函数定义中的参数

在 C++ 中,static_castdynamic_cast 和普通类型转换(C 风格的类型转换)都可以用来进行类型转换,但它们之间有一些重要的区别。

static_cast 用于在相关类型之间进行转换,例如基类和派生类之间的指针或引用,或者隐式转换。它在编译时执行,不执行运行时检查。这意味着如果您尝试执行无效的转换,编译器可能不会报错,但运行时行为是未定义的。

dynamic_cast 用于在多态类型之间进行安全的向下转换。它在运行时执行类型检查,以确保转换是有效的。如果转换失败,则返回空指针(对于指针)或抛出 std::bad_cast 异常(对于引用)。由于 dynamic_cast 需要运行时类型信息(RTTI),因此它比 static_cast 更慢。

普通类型转换(C 风格的类型转换)是 C++ 中最原始的类型转换方式。它可以执行多种类型转换,包括 static_castconst_castreinterpret_cast。然而,由于它不执行运行时检查,并且容易滥用,因此不建议在 C++ 程序中使用。

总之,当您需要在相关类型之间进行转换时,应该使用 static_cast;当您需要在多态类型之间进行安全的向下转换时,应该使用 dynamic_cast;而普通类型转换(C 风格的类型转换)应该尽量避免使用。

希望这些信息对您有所帮助!

背景

  • UNIX 是一种操作系统,它最初由贝尔实验室的 Ken Thompson、Dennis Ritchie 和其他人在 1969 年开发。BSD(Berkeley Software Distribution)是 UNIX 的一个分支,它源自加州大学伯克利分校对贝尔实验室 UNIX 的一系列修改。BSD 最终发展成一个完整的操作系统,现在有多个不同的 BSD 分支。

  • Linux 是由 Linus Torvalds 在芬兰上大学时开发的。它是一个类似于 UNIX 的操作系统,但并不是 UNIX 的一个分支。Linux 是一个独立开发的操作系统,它试图与 UNIX 兼容。Linux 和 BSD 都是类 UNIX 操作系统,但它们之间并没有直接的关系。Linux 是一个独立开发的操作系统,它试图与 UNIX 兼容。虽然 Linux 模仿了 UNIX 的许多特性,但它并没有抄袭 UNIX 的源代码,因此 Linux 内核与 Unix 内核有所不同。

  • 简而言之,BSD从UNIX继承而来。而Linux是独立开发,但模仿了UNIX大部分特性的操作系统。

UNIX标准

可以仔细展开UNIX标准统一的过程,size_t的由来 –> UNIX网络编程第一章节

IETF , RFC与网际协议

POSIX (Portable Operating System Interface)

1. Socket地址 API

1.1 地址结构

​ 地址结构定义在<netinet/in.h>

  • IPv4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct sockaddr_in {
    uint8_t sin_len;
    sa_family_t sin_family;
    in_addr sin_addr;
    char sin_zero[8];
    }

    struct in_addr {
    in_addr_t s_addr; // 32-bit IPv4 地址
    }

    1)注意 in_addr 被定义为结构体却只有一个字段是由于历史原因:在子网划分技术出现之前,过去的 struct in_addr 存储着四个8位值或者两个16位值,便于划分 A类、B类、C类地址,而现在已经不需要。

    2) sin_zero 字段作为结构体填充,一般置为零。

  • IPv6, 以及 UNIX 本地域地址( 即在本机的 socket 之间进行通信,不用经过协议栈 )

    1
    2
    struct sockaddr_in6{}
    struct sockaddr_un{}
  • 通用Socket地址结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
       struct sockaddr {
    uint8_t sa_len;
    sa_family_t sa_family;
    char sa_data[14];
    }

    struct sockaddr_storage {
    sa_family_t sa_family;
    unsigned long int __ss_align; // 内存对齐
    char __ss_padding[128 - sizeof(__ss_align)];
    }

    1)事实上调用套接字相关 API 的时候,函数定义的地址参数为 struct sockaddr* , 需要将上述地址结构指针强制转换再传入。

    2)由于 sockaddr 并不能放下 IPv6 或者 UNIX 本地域地址,所以有了第二种地址结构 sockaddr_storgae

1.2 字节排序函数

  1. 在计算机存储中,需要区分不同的字节存储顺序,以使机器能够按照正确的逻辑顺序读取数据。字节序分为 大端序小端序 ,不同的主机可能拥有不同的 主机字节序 。例如某个十六进制值 0x1234567 的每个字节按照不同顺序的排列:
address 0x100 0x101 0x102 0x103
big endian 01 23 45 67
little endian 67 45 23 01
  1. 网际协议提倡按 大端序 作为 网络字节序 。同时为了保证网络传输中的各个数据是正确的字节序,UNIX 提供字节顺序转换函数。
1
2
3
4
5
6
// h: host, n: net
// l: long, s: short
uint16_t htons(uint16_t);
uint16_t htonl(uint16_t);
uint16_t ntohs(uint16_t);
uint16_t ntohl(uint16_t);

1.3 IP 地址转换函数

  • 下列三个函数,将 IPv4 数据在 char* 和网络字节序整数之间进行转换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <arpa/inet.h>

    // 将点十分制的地址转换为 in_addr_t, 失败则返回宏 INADDR_NONE
    in_addr_t inet_addr(const char* inp);

    // 与 inet_addr 作用相同, 成功返回 true, 否则 false
    int inet_aton(const char* cp, struct in_addr* inp);

    // 将网络字节序整数转换为 char*, 注意返回的指针指向的是内部的一个静态变量
    char* inet_ntoa(struct inaddr_t in);
  • 下面的新函数完成同样的功能,同时适用于 IPv6 地址

    1
    2
    3
    4
    5
    6
    7
       #include <arpa/inet.h>

    // 成功返回1, 若失败返回 0 且设置 errno
    int inet_pton(int af, const char* src, void* dst);

    // 成功返回存储地址的地址, 失败则返回 NULL 并设置 errno
    const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt);
  • 另外新函数中指定目标存储单元大小的大小可以根据提供的宏进行定义

    1
    2
    3
    #include <netinet/in.h>
    #define INET_ADDRSTRLEN 16
    #define INET6_ADDRSTRLEN 46

2. 基本的网络连接

2.1 初始化 socket

  • 返回值:成功则返回 fd, 否则返回 -1 并设置 errno
  • domain : 指定 IP 协议或者 UNIX 本地域协议
  • type : 用于指定 运输层 协议。自 Linux 内核 2.6.17 开始可以将这个值同 SOCK_NONBLOCK 或者 SOCK_CLOEXEC 相与 ,以获得 fcntl 的功能。其中 SOCK_CLOEXEC 用于在子进程中关闭该 socket
  • protocol : 通常设置为 0,除非还需要选择更具体的协议
1
2
3
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

2.2 命名 socket

为一个 socket 绑定地址被称为 socket 的命名。这个操作在客户端并不是必须的,默认情况下系统会自动为客户端的 socket 分配通信端口。

  • 返回值:成功返回 0, 否则返回 -1 并设置 errnoerrno 有以下两种:

    • EACCES : 被绑定地址是受保护地址,比如除了 root 用户以外访问 0 - 1024 端口设置这个 errno
    • EADDRINUSE :端口正在被使用
  • addr :如前文所述,应该将专用的地址结构在这里进行类型转换

  • addrlen : 因为不同专用地址结构的大小不同,需要传入专用地址的大小。可以通过 sizeof 实现。另外 socklen_t 其实是一个 unsigned int

1
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);

2.3 监听端口与接受连接

  • listen 。我们把调用过 listen 处于 LISTEN 状态的 socket 称之为 监听socket 。将 ESTABLISHED 状态的 socket 称之为 连接socket,它们会被放入监听队列中等待下文 accept 函数处理

    • 返回值:成功返回 0, 否则返回 -1,并设置 errno
    • backlog : 提示内核最大的连接数量。在内核 2.2 版本之前,这个连接数包括 SYN_RCVDESTBLISHED 两种状态的连接,现在只包括后者。
    1
    2
    #include <sys/socket>
    int listen(int sockfd, int backlog);
  • accept , 从监听队列取出一个 socket 进行处理

    • 返回值:成功则返回 fd, 否则返回 -1 并设置 errno

    • addr : 连接socket 的地址,监听 socketIP 地址可以设置为 INETADDR_ANY 代表监听某端口任何地址的连接

    1
    2
    #include <sys/socket>
    int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);

2.4 发起连接

  • connect

    • 返回值:成功返回 0,失败返回 -1 并设置 errno ,分为以下两种
      • ECONNREFUSED
      • ETIMEOUT
    1
    int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);

2.5 关闭连接

  • 可以通过通用的文件接口关闭 socket

    1
    int close(int fd);
  • 或者 socket 专用的函数,howto 可以选择性关闭 socket 的读写端,有以下三种宏可以选择

    • SHUT_RD
    • SHUT_WR
    • SHUT_RDWR
    1
    int shutdown(int fd, int howto);
  • 注意 close 只会把 fd 上的引用减 1 , 只有对文件的引用为 0 时,才会释放资源。而后者会直接释放资源。

2.6 socket 选项

对于文件可以通过 fcntl 设置文件描述符的属性,而对于 socket 也有专门的函数。

  • level 指定选项属于的协议 (也可以是协议无关的通用选项 SOl_SOCKET), 例如 IPv4IPv6 分别为 IPPROTO_IPIPPROTO_IPV6
  • option_name 即为具体的选项名称
  • option_valueoption_len 根据不同的选项有不同的设置方法
1
2
3
4
int getsockopt(int fd, int level, int option_name, 
void* option_value, socklen_t* restrict option_len);
int setsockopt(int fd, int level, int option_name,
const void* option_value, socklen_t* restrict option_len);

接下来我们讨论几个重要的 socket 选项

  • SO_REUSEADDR : 该选项使处于 TIME_WAIT 状态的 socket 的端口能被立即重用
  • SO_REVBUFSO_SNDBUF : 设置 TCP 接收缓冲区和发送缓冲区的大小。不过最终的值可能会受到系统的调度
  • SO_RCVLOWATSO_SNDLOWATI/O 复用系统通过低水位标记 (一般为一个字节)来判断缓冲区是否可读,一般情况下低水位指的都是一个字节
  • SO_LINGER : 用于控制 close 在关闭 TCP 连接时的行为,具体的讨论会放在之后的文章中。

3. 数据读写

3.1 专用读写函数

对于 socket 上的数据读写同样可以用普通文件的读写函数,但是专门的 socket 读写函数带有对数据读写的控制

  • 对于 TCP 数据流的读写

    • 其中 flags 有几个典型值:
      • MSG_MORE : 提示内核还有更多数据要写入,于是内核会等待数据写入后再一并发送
      • MSG_OOB : 专门接收带外数据,带外数据一般处于与普通数据不同的缓冲区中
    1
    2
    3
    4
    #include <sys/types>
    #include <sys/socket.h>
    ssize_t recv(int fd, void* buf, size_t len, int flags);
    ssize_t send(int fd, const void* buf, size_t len, int flags);
  • 对于 UDP 数据

    可以看到函数命名上都带上了介词 fromto,暗示 UDP 协议没有源地址和源端口,所以对于连接好的 fd ,我们必须手动指定目标 socket 地址结构。实际上如果将地址结构置为 NULL ,同样也可以读写流式数据。

    1
    2
    3
    4
    5
    6
    #include <sys/types>
    #include <sys/socket.h>
    ssize_t recvfrom(int scokfd, void* buf, size_t len, int flags,
    struct sockaddr* addr, socklen_t* len);
    ssize_t sendto(int scokfd, const void* buf, size_t len, int flags,
    struct sockaddr* addr, socklen_t* len);

3.2 通用读写函数

  • socket 编程接口还提供了一组适用于 TCPUDP 的系统调用

    1
    2
    3
    #include <sys/socket.h>
    ssize_t recvmsg(int fd, struct msghdr* msg, int flag);
    ssize_t sendmsg(int fd, struct msghdr* msg, int flag);

    其中 struct msghdr 是一个专门的数据结构,注意在 TCPmsg_name 这个字段没有任何意义,应当设置为 NULL 。其中struct iovec* 类型的指针指向一个 iovec 数组,而每个 iovec 结构都指向一片内存。它们代表着若干块分散的内存,使用上述函数进行读写分别成为 分散读集中写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    struct msghdr {
    void* msg_name; // socket 地址结构
    socklen_t msg_namelen;
    struct iovec* msg_iov;
    int msg_iovlen;
    void* msg_control; // 辅助数据
    socklen_t msg_controllen;
    int msg_flags; // 根据 flag 参数自动设定
    }

    struct iovec {
    void* iov_base;
    size_t iov_len;
    }

4. 获取网络信息

4.1 静态

  • 可以根据一个连接的 socket 的文件描述符获取 socket 结构的地址。peersock 分别代表 对端 和和 本端 的地址。

    1
    2
    int getsockname(int fd, struct sockaddr* addr, socklen_t* len);
    int getpeername(int fd, struct sockaddr* addr, socklen_t* len);

4.2 动态

以下介绍的函数可以根据主机名字的信息获取 IP 和端口信息,或者反过来查询。系统会在本地 /etc/services/etc/hosts 进行查询,若查不到,则会向 DNS 服务器发起请求

  • gethostbynamegethostbyaddr ,这两个函数根据主机名字或者地址获取主机的更多信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <netdb.h>
    struct hostent* gethostbyname(const char* name);
    struct hostent* gethostbyaddr(const void* addr, size_t len, itn type);

    struct hostent {
    char* h_name;
    char** h_aliases; // 主机别名列表
    int h_addrtype; // 地址族
    int h_length;
    char** h_addr_list; // ip地址列表
    }
  • getservbynamegetservbyport , 这两个函数通过本地的 /etc/services 文件进行查询。

  • 实际上存在 getaddrinfogetnameinfo 这两个函数,但功能大致类似,不多讨论。

5. 更多思考

  1. 注意到 accept 函数和 bind 函数中传入地址的方式一个是指针,一个是传值。

1. 表达式语法

参考网站:

http://regexr.com/

http://www.regexpal.com/

:heart:regex101:heart:

2. C++11 Regex基本API

​ **注:**以下函数声明并非官方原型,仅是为了方便理解使用而产生形式。详细可以查询 cppreference.com

  • 引用头文件#include <regex>

  • std::regex

    • s: 字符串形式的正则表达式
    1
    2
    // 初始化一个std::regex对象作为匹配模板	
    std::regex(std::string s);
  • std::regex_match

    • src: 待匹配字符串
    • m: 传出参数包含了匹配的结果信息,匹配的字符串也会保存在这里, 注意std::match_result作为基类,实际使用的时候需要调用子类std::cmatch或者std::smatch, 代表结果的储存是char*或者std::string::const_iterator
    • r: 上文构建的std::regex对象
    1
    2
    3
    // 将匹配结果放在m中
    // 返回值代表匹配是否成功
    bool std::regex_match(std::string src, std::match_result m, std::regex r);
  • std::regex_search

    1
    bool std::regex_match(std::string src, std::match_result m, std::regex r);
  • std::regex_iterator

3. 注解

Because regex_match only considers full matches, the same regex may give different matches between regex_match and std::regex_search:

1
2
3
4
5
6
std::regex re("Get|GetValue");
std::cmatch m;
std::regex_search("GetValue", m, re); // returns true, and m[0] contains "Get"
std::regex_match ("GetValue", m, re); // returns true, and m[0] contains "GetValue"
std::regex_search("GetValues", m, re); // returns true, and m[0] contains "Get"
std::regex_match ("GetValues", m, re); // returns false

regex_search 会在字符串中查找与正则表达式模式匹配的子串,而 regex_match 则要求整个被匹配字符串都与正则表达式模式匹配。另外regex_search只成功匹配一次就会返回,意味着得到所有匹配的结果需要迭代查找。

  • 如何使用匹配结果?

前言

MDN有关响应式web的文章中,有过以下描述:

如果你要支持多分辨率显示,但希望每个人在屏幕上看到的图片的实际尺寸是相同的,你可以使用 srcset 结合 x 语法——一种更简单的语法——而不用 sizes,来让浏览器选择合适分辨率的图片。你可以参考这个示例 srcset-resolutions.html(或查看源代码):

1
2
3
4
5
<img
srcset="elva-fairy-320w.jpg, elva-fairy-480w.jpg 1.5x, elva-fairy-640w.jpg 2x"
src="elva-fairy-640w.jpg"
alt="Elva dressed as a fairy" />

其中1x代表用一个CSS像素表示一个设备像素(默认所以不用写出来),2x代表用一个CSS像素表示两个设备像素

Viewport

视口(viewport)是指浏览器窗口中用于显示网页内容的区域。它通常与浏览器窗口的大小相同,但也可以通过设置来改变。

单位

在CSS中,您可以使用不同的单位来指定长度和尺寸。这些单位包括像素(px)、em、rem、百分比(%)、视口宽度(vw)和视口高度(vh)等。

  • px:像素(px)是一种绝对单位,它表示屏幕上的一个点。使用px可以保证元素在不同设备上具有一致的大小,但可能会影响响应式设计

  • em:em是一种相对单位,它相对于父元素的字体大小。使用em可以使元素的大小随父元素的字体大小而改变,有助于实现响应式设计。

  • rem:rem也是一种相对单位,它相对于根元素(即元素)的字体大小。与em不同,rem不受父元素字体大小的影响,因此可以更好地控制元素的大小。注意本身没有字体大小,但是可以设置font-size的值:

    1
    2
    3
    html {
    font-size: 16px;
    }
  • %:百分比(%)是一种相对单位,它相对于父元素的尺寸。使用%可以使元素的大小随父元素的尺寸而改变,有助于实现响应式设计。

  • vw:视口宽度(vw)是一种相对单位,它相对于视口的宽度。1vw等于视口宽度的1%。使用vw可以使元素的大小随视口宽度而改变,有助于实现响应式设计。

  • vh:视口高度(vh)也是一种相对单位,它相对于视口的高度。1vh等于视口高度的1%。使用vh可以使元素的大小随视口高度而改变,有助于实现响应式设计。

像素

  • CSS像素(CSS Pixel)是适用于Web编程的逻辑像素,指的是我们在样式代码中使用到的逻辑像素,是一个抽象概念,实际并不存在。**设备独立像素(Device Independent Pixel)**是与设备无关的逻辑像素,代表 可以通过程序控制使用的虚拟像素,是一个总体概念,包括了CSS像素。

  • 设备像素(Device Pixel)也称为物理像素,是设备能控制显示的最小单位。我们常说的1920×1080像素分辨率就是用的设备像素单位。设备独立像素就是在设备像素的基础上人为定义的一层逻辑像素。举个例子,一个屏幕的 物理像素是2560×1440,但是我们可以人为定义这个屏幕就是1280×720,所以1个设备独立像素就用4个设备像素显示。在前言的例子中可以写为4x

  • 简而言之,CSS像素和设备独立像素都是逻辑上的概念,它们与设备无关,可以通过程序控制使用。而设备像素则是物理上的概念,它与设备相关,表示设备能控制显示的最小单位。希望这些信息能够帮助您了解CSS像素和设备像 素之间的区别。

杂谈

在 HTML 文件中的head 标签里,你将会找到这一行代码 <meta name="viewport" content="width=device-width">:这行代码会强制让手机浏览器采用它们真实可视窗口的宽度来加载网页(有些手机浏览器会提供不真实的可视窗口宽度,用比真实视口更大的宽度加载网页,然后再缩小加载的页面,这样的做法对响应式图片或其他响应式设计,没有任何帮助)。

基本操作流程

  • 设置服务器地址、用户名、密码和远程路径
1
2
3
4
SERVER="your_server"
USERNAME="your_username"
PASSWORD="your_password"
REMOTE_PATH="/path/to/remote/folder"
  • 设置本地文件夹路径
1
LOCAL_FOLDER_PATH="/path/to/local/folder"
  • 使用 scp 命令上传文件夹
1
sshpass -p "$PASSWORD" scp -r "$LOCAL_FOLDER_PATH""$USERNAME@$SERVER:$REMOTE_PATH"
  • 您可以使用 GitHub Secrets 来安全地存储敏感信息,例如密码、密钥或访问令牌等。下面是一个简单的例子,它演示了如何在工作流中使用 GitHub Secrets 来存储和使用 SSH 密码。
  1. 在您的 GitHub 仓库中,导航到 Settings 选项卡,然后单击 Secrets
  2. 单击 New repository secret 按钮,然后输入一个名称(例如 SSH_PASSWORD)和您的 SSH 密码。单击 Add secret 按钮保存您的密钥。
  3. 在您的工作流文件中,您可以使用 ${{ secrets.SSH_PASSWORD }} 来引用您刚才存储的密钥。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
jobs:
upload:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Upload folder to server
run: |
# 设置服务器地址、用户名和远程路径
SERVER="your_server"
USERNAME="your_username"
REMOTE_PATH="/path/to/remote/folder"

# 设置本地文件夹路径
LOCAL_FOLDER_PATH="/path/to/local/folder"

# 安装 sshpass
sudo apt-get update
sudo apt-get install -y sshpass

# 使用 scp 命令上传文件夹
sshpass -p ${{ secrets.SSH_PASSWORD }} scp -r "$LOCAL_FOLDER_PATH" "$USERNAME@$SERVER:$REMOTE_PATH"

注解

  • 在上面的示例中,我们使用 ${{ secrets.SSH_PASSWORD }} 来引用存储在 GitHub Secrets 中的 SSH 密码。这样,我们就可以在工作流中安全地使用这个密码,而不必担心泄露敏感信息。actions/checkout@v2 是一个 GitHub Action,它可以检出您的仓库代码。它的工作原理是使用 git 命令来克隆您的仓库到工作流运行环境中。默认情况下,它只会获取触发工作流的那个 ref/SHA 的单个提交。但您也可以通过设置 fetch-depth: 0 来获取所有分支和标签的所有历史记录¹。

  • 此外,这个 Action 还会将身份验证令牌持久化到本地 git 配置中。这样,您的脚本就可以运行经过身份验证的 git 命令。在后续步骤中,该令牌会在后置作业清理期间被删除。您可以通过设置 persist-credentials: false 来选择退出¹。简而言之,actions/checkout@v2 能够检出您的代码,是因为它使用了 git 命令来克隆您的仓库,并将身份验证令牌持久化到本地 git 配置中。希望这对您有所帮助!

  • 在上面给出的 shell 脚本示例中,我们使用了 sshpass 命令来非交互式地提供 SSH 密码。这样,我们就可以在脚本中使用 scp 命令来上传文件夹,而不需要手动输入密码。如果您直接使用 scp 命令,那么在执行命令时,系统会提示您输入密码。这在交互式环境中是可以的,但在脚本中就不太方便了。为了避免这种情况,我们使用了 sshpass 命令来非交互式地提供密码。当然,除了使用密码登录,您还可以使用 SSH 密钥来进行身份验证。这种方法更安全,也更方便。如果您希望使用 SSH 密钥来进行身份验证,那么您就不需要使用 sshpass 命令了。

0%