epoll 在 Golang net 库的使用

本文主要关注以下几个问题: Golang runtime 是怎么调用 epoll 的系统调用的 ? Golang net 库如何封装 epoll,使得开发者几乎不用直接操作 epoll ? C 如何调用 epoll 首先回顾一下用 C 语言怎么使用 epoll int s = socket(AF_INET, SOCK_STREAM, 0); bind(s...) listen(s...) int epfd = epoll_create(128); //创建eventpoll对象 ev.events = _EPOLLIN | _EPOLLOUT | _EPOLLRDHUP | _EPOLLET epoll_ctl(epfd, EPOLL_CTL_ADD, s, &ev);//注册事件 //轮询就绪事件 while(true){ //返回值n为就绪的事件数,events为事件列表 int n = epoll_wait(epfd, &events[0], len(events), 1000) for( i := 0; i < n; i++ ) { ev := &events[i] //处理事件 } } C 语言中调用 epoll 的方式比较底层,总的来说分下面三个步骤...

2021-01-31 · Me

gRPC-go Server 端实现

在上一篇文章中,介绍了 grpc 建立 TCP 连接的过程,侧重点在 Client 端,而关于 Server 端建立 TCP 的过程相对是比较简单的。 Server端 listen on 本地端口,并且接收来自 client 的连接请求,一旦建立 TCP 连接后,接下来的步骤是什么呢? 建立 HTTP2 server,并收发数据。 本文尝试回答一下几个问题: Server 怎么利用 http2 的 stream 传输数据? 从 stream 里读的数据存放在哪? Stream 读到的数据如何传给用户 Server 要发送的数据又是从哪发送的? 创建 http2Server 首先从用户的代码入手,用户的代码最后会调用 grpcServer.Serve(lis), 稍微追踪几个函数就能发现调用链是 handleRawConn() 到 serveStreams()。 从 handleRawConn() 中我们发现 newHTTP2Transport 会创建一个新的 http2Server。 serveStreams() 中的 HandleStreams() 是 type ServerTransport interface 的一个函数,而 type http2Server struct 实现了这个接口。 值得注意的是,有两个结构体实现了 ServerTransport,分别是 transport/handler_server.go 的 serverHandlerTransport transport/http2_server.go 的 http2Server 一般我们在 main 函数中调用 grpcServer....

2021-01-18 · Me

Bittorrent 协议及工作原理

在 2000 年左右开始接触互联网的同学都应该记得用 BT 种子下载电影和小电影那段的时光。之前只是大概知道 BT 的工作原理,但并没有仔细研究过,所以一直很好奇。 随便在网上搜索下,可以知道 BT 大概是这样工作的: BitTorrent 协议把提供下载的文件虚拟分成大小相等的块,块大小必须为 2k 的整数次方,并把每个块的索引信息和 Hash 验证码 写入 .torrent 文件(即种子文件,也简称为“种子”)中,作为被下载文件的“索引”。 下载者要下载文件内容,需要先得到相应的 .torrent 文件,然后使用 BT 客户端软件进行下载。 下载时,BT 客户端首先解析 .torrent 文件得到 Tracker 地址,然后连接 Tracker 服务器。Tracker 服务器回应下载者的请求,提供下载者其他下载者(包括发布者)的 IP。或者,BT客户端也可解析 .torrent 文件得到 nodes 路由表,然后连接路由表中的有效节点,由网络节点提供下载者其他下载者的 IP。 torrent 文件包含了什么 根据 bittorrent.org官方文档,种子文件也被称为metainfo files, 主要包含以下信息: announce, The URL of the tracker. info, This maps to a dictionary. 所以种子文件就是告诉你,去 announce 这个地址找文件,具体文件信息包含在 info 里面。 Info 结构体有以下基本内容: name key maps to a UTF-8 encoded string....

2020-10-24 · Me

Unicode 字符编码

ASCII 我们熟悉的 ASCII 码可以说是字符编码的始祖了。它规定了常用的数字、符号、英文字母与二进制之间的对应关系。 ASCII 的缺点是字符集太少了,只能表示英文和数字,无法表示像中文,日文这样的符号。因此人们就设计出了 Unicode 字符集,囊括了几乎所有人类语言文字的符号。 Unicode Unicode 是一个字符集,而不是一种编码方式。 Unicode 相当于是给人类所有的符号一个独一无二的 ID,只要大家都是用这个 ID 表示字符,就不会出现乱码的问题。 因为 Unicode 是一个字符集,因此它不存在所谓的 “用几个字节表示 unicode” 这样的问题,这是具体的编码方式需要处理的事。 Unicode 把 ID 划分成了 17 组 (Plane),每组有 65536 个字符,编号可以用 U+[XX]YYYY 这样的形式表示,每一位是一个十六进制数字,其中 XX 代表组编号,从 0 到 0x10,一共17个,YYYY 代表这一组中的字符编号,一共 65536 个。 其中第 0 组叫 Basic Multilingual Plane,简称 BMP,它是 Unicode 中最基础和最常用的一部分,码点范围是U+0000 ~ U+FFFF,包含了我们常用的英文和汉字。 UFT-8 UTF-8 是 Unicode 具体的编码方式,除此之外还要 UTF-16, UTF-32 等等。 为什么需要编码方式呢? 直接用 Unicode 的 ID 不就行了吗? 因为我们需要节省存储空间。 UTF-8 是一种变长的编码方式,它可以使用 1-4 个字节表示一个符号,编码规则如下...

2020-08-16 · Me

用 Grafana 展示监控状态

运维或者 SRE 部门经常会弄一个大屏幕展示各种系统状态,看上去很好玩,于是我也用类似的开源软件监控一下家里的主机。 整个过程非常简单,主要是安装三个软件 Node exporter,Prometheus,Grafana。 Node exporter 既然要展示系统状态,那么第一步就是要获得系统的状态数据,比如 CPU 使用率,内存使用率,网络流量等。 Prometheus 官方提供了一个使用 go 语言编写的程序 node_exporter,直接下载项目主页上 release 里的二进制即可。node_exporter 最好直接安装在物理主机上,因为这样才能采集到最准确的数据。 运行 node_exporter 以后,会自动启动一个 http server 并且监听 9100 端口,如果有 client 过来访问, server 返回主机的监控信息。比如: $ curl http://localhost:9100/metrics node_network_transmit_packets_total{device="veth126cb08"} 28859 node_network_transmit_packets_total{device="veth1276a16"} 1383 node_network_transmit_packets_total{device="veth749c501"} 1.108492e+06 返回信息的格式是符合 Prometheus 定义的标准的,因此 Prometheus 能够处理并以简单的图标的形式展现这些数据。 看到这里大家应该不难想到,如果我自己写一个程序 HelloWorld,并且把程序的状态按照一定的格式导出,那么同样可以通过 Prometheus + Grafana 展现。 Prometheus Prometheus 是一个功能齐全的数据库,还提供了 PromSQL 语言方便用户查询,以及一个简单的网页前端。 最简单快捷的方式当然是启动一个容器,唯一需要注意的是把配置文件 prometheus.yml 挂载到容器的 /etc/prometheus/ 目录下。 $ docker run -d -p 9090:9090 \ -v /home/prometheus/:/etc/prometheus/ prom/prometheus 配置文件中需要在 scrape_configs 部分添加 noder exporter 的 IP 地址和端口。...

2020-02-02 · Me

CPU affinity

CPU affinity – CPU 亲和性,指进程更希望运行在哪个 CPU core 上。 指定 core 有什么好处呢? 比如,可以自己决定哪些程序可以独占 CPU 资源,保证这个程序性能的最大化; 指定 CPU 以后可以提高 Cache 的命中率,常用于一些对性能非常高要求的程序,例如 nginx。 命令行指令 taskset 在 Linux 系统中,我们可以用 taskset 命令指定一个进程运行在哪个核心上。 比如我们写一个程序用 while(1) 制造死循环,那么运行这个程序的时候 CPU 会飙到 100% 用以下这条命令运行这个程序 taskset -c 3 ./a.out 意思是把 a.out 运行在从 0 开始数起的第 3 个核心上。 于是,用 htop 命令查看,会看到第 4 个核 CPU 使用率是 100%。 编程的 API 那么在程序的代码里怎么用呢? 先来看看 glibc 提供的系统 API #include <sched.h> int sched_setaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask); int sched_getaffinity(pid_t pid, unsigned int cpusetsize, cpu_set_t *mask); void CPU_CLR(int cpu, cpu_set_t *set); int CPU_ISSET(int cpu, cpu_set_t *set); void CPU_SET(int cpu, cpu_set_t *set); void CPU_ZERO(cpu_set_t *set); nginx 的 config 文件中,可以为每个工作进程绑定CPU...

2019-09-15 · Me

Docker 中编译 vim8.0

vim 的最新版 vim8.0 提供了很多新的特性,而且一些流行的 vim 插件很多功能也依赖于 8.0 版本,如果你要使用 vim8.0,那么最好的办法当然是使用操作系提供的软件包管理器一键安装,省时省力。 但是总有那么一些蛋疼的情况 ——你需要自己编译 vim。 本文就是记录下具体的步骤,并且把编译源码时需要安装的依赖软件全部做成 docker 镜像。 事情起因 某台服务器上我要用 vim8.0 的新特性,但是在服务器上我没有任何超级权限,只能读写我自己的 home 目录。 所以没法直接安装vim,只能从源码编译。 制作编译 vim 的docker image 编译 vim 需要系统中安装很多依赖软件,比如 vim 最基本的要包括 python2.7,luajit 等。 都 2019 年了,最好的方式当然是制作一个 docker 镜像,具体的步骤就不一一解释,贴上 Dockerfile 以示诚意。 FROM ubuntu:18.04 RUN apt-get update && apt-get install -y \ liblua5.1-dev \ luajit \ libluajit-5.1 \ python-dev \ ruby-dev \ libperl-dev \ libncurses5-dev \ libatk1.0-dev \ libx11-dev \ libxpm-dev \ libxt-dev \ gnupg2 \ curl \ && gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB \ && curl -sSL https://get....

2019-08-16 · Me

How TCP backlog works in Linux

原文: http://veithen.io/2014/01/01/how-tcp-backlog-works-in-linux.html 当一个应用程序使用 listen() 系统调用把一个 socket fd 设置成 LISTEN 状态时,也需要指定一个 backlog 值。通常我们可以认为这个 backlog 代表这个 socket fd 可以接受最大的连接请求数。 #include <sys/types.h> #include <sys/socket.h> int listen(int sockfd, int backlog); 因为 TCP 的三次握手,在 server 端 accept() 系统调用返回,并且tcp 状态在变成 ESTABLISHED 之前,会有一个短暂的 SYN RECEIVED 状态。那么这个状态的 tcp 链接应该放在哪个 queue 里面呢? 单个 queue,其大小就是 listen() 参数 backlog。当一个 SYN 包到达时,server 返回一个 SYN/ACK 给 client,并且把这个链接放入 queue。当 client 的 ACK 到达时,TCP 的状态变成 ESTABLISHED。这就意味着这一个 queue 有两种不同的状态:SYN RECEIVED 和 ESTABLISHED。只有在 ESTABLISHED 状态的链接才能被 accept() 返回给用户程序。...

2019-08-15 · Me

Golang 操作共享内存

前言 进程间通信的方式有很多种,如果两个进程分别在不同的机器上,那么使用 socket 通信;如果在同一台机器上,共享内存机制是一种快速高效的方式。 本文实现一个 go 语言二进制程序和 C 语言二进制程序通过共享内存交换数据。 提到共享内存主要有两种: System V 标准的 shmget/shmdt 等接口 POSIX 标准的 shm_open 等接口 另外 Linux 下 mmap() 匿名映射也是最常用的进程间共享内存方法。 创建了共享内存以后,一般会显示在系统的 /dev/shm 目录下。Linux 默认 /dev/shm 为实际物理内存的1/2, 比如我的机器上物理内存为 16G,运行 df 命令后可以看到 /dev/shm 的大小为 7.8G 。 $ df -h Filesystem Size Used Avail Use% Mounted on tmpfs 1.6G 3.2M 1.6G 1% /run tmpfs 7.8G 4.0K 7.8G 1% /dev/shm tmpfs, ramfs 和 ramdisk tmpfs是一个虚拟内存文件系统,在Linux内核中,虚拟内存资源由物理内存(RAM)和交换分区组成,Tmpfs可以使用物理内存,也可以使用交换分区。 ramdisk 是一个块设备,只不过它是存在于内存上的。 ramfs 也是文件系统,不过已经被 tmpfs 替代了。...

2019-08-02 · Me

ASLR 内核虚拟地址随机化

ASLR 全称 Address Space Layout Randomization,是一项 Linux 内核的安全措施,使应用程序每次加载到内存后,函数地址都不同。 试用一下 先来直观的感受下什么是 ASLR。目前大多数 linux 系统都默认开启了这个选项,可以用一下两个命令确认一下系统是否支持 ASLR。 $ cat /proc/sys/kernel/randomize_va_space 2 $ sysctl kernel.randomize_va_space kernel.randomize_va_space = 2 其中 0 表示关闭,1 表示有约束的随机,2 表示完全随机化。 然后随便找一个可执行程序,用 ldd 命令显示它加载的动态链接库,可以看到两次运行 ldd 结果各个库的地址不一样。 $ ldd /bin/sleep linux-vdso.so.1 (0x00007ffd49764000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f02783ae000) /lib64/ld-linux-x86-64.so.2 (0x00007f02789a8000) $ ldd /bin/sleep linux-vdso.so.1 (0x00007ffc10996000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f12c3534000) /lib64/ld-linux-x86-64.so.2 (0x00007f12c3b2e000) 应用程序如何使用 ASLR 在这篇文章中提到,除了 kernel 开启以外,应用程序在编译的时候也必须添加编译选项 gcc -fPIE -pie test.c 。 但是在我的实际测试中,似乎并不需要额外添加编译选项,看来 gcc 默认开启了 ASLR。...

2019-07-07 · Me