0%

文档主键

文档主键 _id

文档主键 _id 是每篇文档必备的字段,具有以下特性:

  • 文档主键的唯一性
  • 支持所有数据类型(数组除外)
  • 复合主键

对象主键 ObjectId

当我们不提供主键,MongoDB 自动为我们生成的默认对象主键 ObjectId

  • 默认的文档主键
  • 可以快速生成的 12 字节 id
  • 包含创建时间

ObjectId 默认值

1
2
test> ObjectId()
ObjectId("64eeed51b64b9d7e95e6b8ea")

手动设置 ObjectId 的值

1
2
test> ObjectId("123456789011123456789011")
ObjectId("123456789011123456789011")

提取 ObjectId 的创建时间

1
2
test> ObjectId("123456789011123456789011").getTimestamp()
ISODate("1979-09-05T22:51:36.000Z")
阅读全文 »

索引

  • 对文档部分内容进行排序的数据结构
  • 加快文档查询和文档排序的速度
  • 复合键索引只能支持前缀子查询

索引操作

创建索引

1
db.<collection>.createIndex(<keys>, <options>)
  • <keys> 文档指定了创建索引的字段
  • <options> 创建索引的参数和设定索引的特性

创建一个单键索引

1
db.accountWithIndex.createIndex({name: 1})

创建一个复合键索引

1
db.accountWithIndex.createIndex({name: 1, balance: -1})

创建一个多键索引,创建在数组字段上, 数组字段中的每一个元素都会在多键索引中创建一个键

1
db.accountWithIndex.createIndex({currency: 1})

列出集合中的索引

1
2
db.accountWithIndex.getIndexes()

阅读全文 »

macOS 上,动态链接器使用特定的路径变量来解析运行时的库位置。这些路径变量包括:绝对路径、 @executable_path@loader_path@rpath

绝对路径

对于安装在系统中共享位置的框架很有用,一般是 /Library/Frameworks/xxx/usr/lib/xxx, 但是查找嵌入在应用内部的动态库就很难使用,应用安装的位置都不固定,所以引出新的方式。

@executable path

@executable_path 是用于指代当前正在执行的程序或应用的路径。当你的应用程序或其动态库需要引用位于与可执行文件相同路径(或其子目录)下的其他动态库时,这会非常有用。

阅读全文 »

概述

libEvent, 一个事件通知库。有以下特点:

  • 事件驱动,高性能;
  • 轻量级,专注于网络;
  • 跨平台,支持 WindowsLinuxMac 等;
  • 支持多种 I/O 多路复用技术, epollpolldev/pollselectkqueue 等;
  • 支持 I/O ,定时器和信号等事件;

libevent接口分析

libevent 接口大概分为以下几类:

  • 环境配置和初始化
    • event_base_new
  • evutil socket 函数封装
    • evutil_make_socket_nonblocking
    • evutil_make_listen_socket_reuseable
      • set SO_REUSEADDR on Unix and does nothing on Windows
    • evutil_closesocket
  • 事件IO处理
    • event_new
  • 缓冲IO
    • bufferevent
  • 循环(Loop)
    • event_base_dispatch

Libevent API

libevent上下文创建

  • event_base *event_base_new(void)
  • event_base *event_base_new_with_config( const struct event_config *);
    • 配置参数
      • event_config *event_config_new(void);
      • void event_config_free(struct event_config * cfg);
  • event_reinit
    • int event_reinit(struct event_base *base); 调用 fork 之后可以正确工作
  • void event_base_free(struct event_base *); 释放event_base内部分配的空间及其本身对象的空间,不释放事件和socket和在回调函数中申请 的空间
  • event_config_set_flag
  • event_config_avoid_method(struct event_ config *cfg, const char *method);
  • event_config_require_features

event_base_config_flag

  • EVENT_BASE_FLAG_NOLOCK:

    • 不要为event_base 分配锁。设置这个选项可以为event_base 节省一点用于锁定和解锁的时间,但是让在多个线程中访问event_base 成为不安全的。
  • EVENT_BASE_FLAG_IGNORE_ENV

    • 选择使用的后端时,不要检测EVENT_*环境变量。使用这个标志需要三思:这会让用户更难 调试你的程序与libevent 的交互。
  • EVENT_BASE_FLAG_STARTUP_IOCP

    • 仅用于Windows,启用任何必需的IOCP 分发逻辑
    • iocp
      • event_config_set_num_cpus_hint
      • event_config_set_flag(cfg, EVENT_BASE_ FLAG_STARTUP_IOCP)
      • evthread_use_windows_threads();
  • EVENT_BASE_FLAG_NO_CACHE_TIME

    • 不是在事件循环每次准备执行超时回调时检测当前时间,而是在每次超时回调后进行检 测。注意:这会消耗更多的CPU 时间。
  • EVENT_BASE_FLAG_EPOLL_USE_ CHANGELIST

    • epoll下有效,防止同一个fd多次激发事件,fd如果做复制会有bug
  • EVENT_BASE_FLAG_PRECISE_TIMER

    • 默认使用系统最快的记时机制,如果系统有较慢 且更精确的则采用

event_method_feature

  • EV_FEATURE_ET = 0x01
    • 边沿触发的后端
  • EV_FEATURE_O1 = 0x02,
    • 要求添加、删除单个事件,或者确定哪个事件激 活的操作是O(1)复杂度的后端
  • EV_FEATURE_FDS = 0x04,
    • 要求支持任意文件描述符,而不仅仅是套接字的 后端
  • EV_FEATURE_EARLY_CLOSE = 0x08
    • 检测连接关闭事件。您可以使用它来检测连接何时关闭,而不必从连接中读取所有挂起的数据。 并非所有后端都支持EV_CLOSED。允许您使用EV_CLOSED,而不需要读取所有挂起的数据。无法在所有内核版本上

事件Event处理

循环(loop)

缓冲 bufferevent

libevent http

select

select 实现多路复用的方式是,将已连接的 socket 都放到一个文件描述符集合,然后调用 select函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 socket,然后再对其处理。

所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。

select 函数原型

1
2
3
#include <sys/select.h>

int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);
  • 返回值
    • 若有就绪描述符则为其数目,若超时则为 0,若出错则为 -1
  • 参数
    • maxfd: 待测试的描述符基数,它的值是待测试的最大描述符加 1
    • readfds:读描述符集合
    • writefds:写描述符集合
    • errorfds:异常描述符集合
    • timeout: 超时设置
阅读全文 »

TCP 基本认识

TCP面向连接的、可靠的、基于字节流的传输层通信协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

源端口(Source Port)

源端口,16

目标端口(Destination Port)

目标端口,16

序列号 (Sequence Number)

TCP 是面向字节流的协议,通过 TCP 传输的字节流的每个字节都分配了序列号,序列号(Sequence number)指的是本报文段第一个字节的序列号。

序列号加上报文的长度,就可以确定传输的是哪一段数据。序列号是一个 32 位的无符号整数,达到 2^32-1 后循环到 0

SYN 报文中,序列号用于交换彼此的初始序列号,在其它报文中,序列号用于保证包的顺序。

因为网络层(IP 层)不保证包的顺序,TCP 协议利用序列号来解决网络包乱序、重复的问题,以保证数据包以正确的顺序组装传递给上层应用。

初始序列号 (Initial Sequence Number, ISN

在建立连接之初,通信双方都会各自选择一个序列号,称之为初始序列号。在建立连接时,通信双方通过 SYN 报文交换彼此的 ISN

序列号回绕

确认号 (Acknowledgment Number)

TCP 使用确认号(Acknowledgment number, ACK)来告知对方下一个期望接收的序列号,小于此确认号的所有字节都已经收到

头部长度 (Data Offset ):

只有 4 位, 取值范围 0~15, 表示 TCP头长度为 32位(4字节)的数量。TCP 头(甚至包括选项)的长度是 32 位的整数倍。例如 DOffset的值为 0111, 则该TCP包头的长度为 7 * 4 = 28

TCP Flags

  • SYN(Synchronize):用于发起连接数据包同步双方的初始序列号
  • ACK(Acknowledge):确认数据包
  • RST(Reset):这个标记用来强制断开连接,通常是之前建立的连接已经不在了、包不合法、或者实在无能为力处理
  • FIN(Finish):通知对方我发完了所有数据,准备断开连接,后面我不会再发数据包给你了。
  • PSH(Push):告知对方这些数据包收到以后应该马上交给上层应用,不能缓存起来

窗口大小 (Window Size)

窗口大小字段是接收方用来控制发送方数据流量的机制。它表示接收方当前允许发送方发送但尚未确认的数据字节数。通过动态调整窗口大小,TCP 可以有效地进行流量控制,防止发送方数据发送过快导致接收方缓存溢出。

TCP 协议窗口大小只有 16 位,最大窗口大小为 65535 字节(64KB), 在今天显然不够用,所以 TCP 协议引入了 TCP 窗口缩放 选项,作为窗口缩放的比例因子,比利因子的值在 0~14,其中 0 表示不缩放,最大值为 14。比例因子可以将窗口扩大到原来的 2n 次方,比如窗口大小缩放前为 1050,缩放因子为 7,则真正的窗口大小为 1050 * 128 = 134400

窗口缩放选项

由于窗口大小字段只有 16 位,最大值为 65535 字节,这对于高带宽-延迟产品 (BDP) 的网络可能不够。为了解决这个问题,TCP 窗口缩放选项 (Window Scale Option) 被引入,用于扩展窗口大小的范围。

窗口缩放选项在 TCP 连接建立时通过 SYN 包交换协商。窗口缩放因子 (Window Scale Factor) 表示允许窗口大小值左移的位数,从而有效地扩展窗口大小的范围。

计算实际窗口大小

实际的窗口大小计算如下:

$$Actual Window Size = Window Size \times 2 ^{Window Scale Factor}$$

示例(带窗口缩放)

假设窗口大小字段的值是 8192(十进制),窗口缩放因子是 3

$Actual Window Size = 8192 \times 2 ^ 3 = 8192 \times 8 = 65536 $ bytes

这表示接收方可以接受最多 65536 字节的未确认数据。

可选项 (Options)

只有 (DOffset > 5) 才有此数据,

可选项的格式:

  • 一个字节的 Kind

    • No-Operation: 无操作。
  • 一个字节的 Kind, 一个字节的 option-length,和真实的 option data

    • MSS:最大段大小选项,是 TCP 允许的从对方接收的最大报文段
    • SACK:选择确认选项
    • Window Scale:窗口缩放选项
      阅读全文 »

SO_REUSEADDR

  • TCP 服务器能够在杀掉或崩溃后快速重启
  • 也适用 fork-per-connection 服务器模型。
1
2
int optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

忽略 SIGPIPE

程序向对方已经关闭的管道,写数据,会收到 SIGPIPE 信号。write 系统调用返回 -1收到 errono EPIPESIGPIPE 信号默认行为终止进程。我们应该忽略 SIGPIPE 信号。

1
2
3
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
return 1;
}
阅读全文 »

1. socket地址结构

sockaddr_in

ipv4 协议的地址结构是 sockaddr_inipv6 的地址结构是sockaddr_in6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <arpa/inet.h>
//#include<netinet/in.h>

struct sockaddr_in
{
sa_family_t sin_family;
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */

/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
  • sin_family:表示地址簇,ipv4: AF_INET, ipv6: AF_INET6
  • sin_port:16位的端口号
  • sin_addr:点分十进制。

通用地址结构

结构体是 sockaddr,方便可以接受 ipv4/ipv6 的地址结构。之所以采用 sockaddr,而不采用 void* 是因为 BSD 设计套接字的时候大约是 1982 年,那个时候的 C 语言还没有void * 的支持。

1
2
3
4
struct sockaddr {
sa_family_t sa_family;
char ss_data[14];
};
阅读全文 »

tcpdump 命令行抓包工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
tcpdump [ -AbdDefhHIJKlLnNOpqStuUvxX# ] [ -B buffer_size ]
[ -c count ] [ --count ] [ -C file_size ]
[ -E spi@ipaddr algo:secret,... ]
[ -F file ] [ -G rotate_seconds ] [ -i interface ]
[ --immediate-mode ] [ -j tstamp_type ] [ -k (metadata_arg) ]
[ -m module ]
[ -M secret ] [ --number ] [ --print ]
[ -Q packet-metadata-filter ] [ -Q in|out|inout ]
[ -r file ] [ -s snaplen ] [ -T type ] [ --version ]
[ -V file ] [ -w file ] [ -W filecount ] [ -y datalinktype ]
[ -z postrotate-command ] [ -Z user ]
[ --time-stamp-precision=tstamp_precision ]
[ --micro ] [ --nano ]
[ expression ]

需要 root 权限运行:

1
2
3
4
5
6
7
8
9
10
11
12
> sudo tcpdump -i any
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
15:01:28.921972 ens33 Out IP ept.55660 > 123.208.120.34.bc.googleusercontent.com.https: Flags [P.], seq 1706429942:1706429981, ack 1072465052, win 63000, length 39
15:01:28.922423 ens33 In IP 123.208.120.34.bc.googleusercontent.com.https > ept.55660: Flags [.], ack 39, win 64240, length 0
15:01:28.971505 lo In IP localhost.33425 > localhost.domain: 48367+ [1au] PTR? 123.208.120.34.in-addr.arpa. (56)
15:01:28.971785 ens33 Out IP ept.50072 > _gateway.domain: 32506+ PTR? 123.208.120.34.in-addr.arpa. (45)
15:01:28.977216 ens33 In IP _gateway.domain > ept.50072: 32506 1/0/0 PTR 123.208.120.34.bc.googleusercontent.com. (98)
15:01:28.977492 lo In IP localhost.domain > localhost.33425: 48367 1/0/1 PTR 123.208.120.34.bc.googleusercontent.com. (109)
15:01:28.977796 lo In IP localhost.45140 > localhost.domain: 38317+ [1au] PTR? 168.14.168.192.in-addr.arpa. (56)
15:01:28.978066 ens33 Out IP ept.50288 > _gateway.domain: 21988+ PTR? 168.14.168.192.in-addr.arpa. (45)

-i 表示指定哪一个网卡,any 表示任意。有哪些网卡可以用 ifconfig 来查看,在我的虚拟机上,ifconfig 输出结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-> ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.14.168 netmask 255.255.255.0 broadcast 192.168.14.255
inet6 fe80::a421:b6c8:4404:333d prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:cc:cd:fb txqueuelen 1000 (Ethernet)
RX packets 4548 bytes 5135525 (5.1 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2897 bytes 1689085 (1.6 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 5307 bytes 391240 (391.2 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5307 bytes 391240 (391.2 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

如果只想查看 ens33 网卡经过的数据包,就可以使用 tcpdump -i ens33来指定。

阅读全文 »

HTTP 是什么?

HTTP 是超文本传输协议,也就是 HyperText Transfer Protocol

URI 的完整图解

1
2
3
4
5
6
7
  foo://example.com:8042/over/there?name=ferret#nose
\_/ \______________/\_________/ \_________/ \__/
| | | | |
scheme authority path query fragment
| _____________________|__
/ \ / \
urn:example:animal:ferret:nose

消息格式

1
2
3
4
start-line
*( header-field CRLF )
CRLF
[ message-body ]
阅读全文 »