ProjectEuler 74 题

https://projecteuler.net/problem=74

用rust写了一遍,觉得根据常识1e6数据量,应该1s内随便跑,结果5s+,

就换成C++写了一遍,发现是因为rust没开O2?开了以后还真就和C++差不多的时间

rust 版本

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use std::collections::HashMap;
const N:usize = 1_000_000;

fn next_value(mut value:usize,fac:&[usize]) -> usize{
let mut sum = 0;
while value != 0 {
sum += fac[value%10];
value/=10;
}
return sum;
}

fn dfs(
fac:&[usize],
ans:&mut HashMap<usize,usize>,
dep:&mut HashMap<usize,usize>,
value:usize
) -> (usize,usize) { // dep, len
let nv = next_value(value,fac);
let next_dep = dep.get(&value).unwrap() + 1;
match dep.get(&nv) {
Some(rs) => {
// println!("{} => {}",value,next_dep - rs);
ans.insert(value,next_dep - rs);
return (*rs,next_dep - rs);
},
None => {
match ans.get(&nv) {
Some(res) => {
let newres= (*res) + 1;
// println!("{} => {}",value,newres);
ans.insert(value,newres);
return (next_dep,newres);
},
None => {
dep.insert(nv,next_dep);
let mut ret = dfs(fac,ans,dep,nv);
if next_dep <= ret.0 {
ret.1 += 1;
}
// println!("{} => {}",value,ret.1);
ans.insert(value,ret.1);
return ret;
}
}
}
}
}

fn main() {
let mut fac = [0;11];
let mut ans = HashMap::new();
fac[0] = 1;
for i in 1..11{
fac[i] = fac[i-1]*i;
}
for i in 1..N+1{
if ans.contains_key(&i) {
continue;
}
let mut dep = HashMap::new();
dep.insert(i,0);
dfs(&fac,&mut ans,&mut dep,i);
}
let mut cnt = 0;
for (k, v) in &ans {
if *v >= 60 && *k <= N {
cnt+=1;
// println!("{}: \"{}\"", k, v);
}
}
println!("{}",cnt);
}

C++版本

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
#define ten5 100000+10
#define MOD 1000000007
#define rep(i,a,n) for (uint i=a;i<n;i++)
#define iif(c,t,f) ((c)?(t):(f))
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define mp make_pair
const double pi = acos(-1.0);

const uint N = 1'000'000;

uint next_value(uint value,uint *fac) {
uint sum = 0;
while(value != 0){
sum += fac[value%10];
value/=10;
}
return sum;
}

pair<uint,uint> dfs(
uint *fac,
unordered_map<uint,uint> &ans,
unordered_map<uint,uint> dep,
uint value
) { // dep, len
uint nv = next_value(value,fac);
uint next_dep = dep[value] + 1;
if(dep.count(nv)){
auto rs = dep[nv];
// printf("%d => %d\n",value,next_dep - rs);
ans[value] = next_dep - rs;
return {rs,next_dep - rs};
}else {
if(ans.count(nv)) {
auto res = ans[nv];
auto newres= res + 1;
// printf("%d => %d\n",value,newres);
ans[value] = newres;
return {next_dep,newres};
}else{
dep[nv] = next_dep;
auto ret = dfs(fac,ans,dep,nv);
if(next_dep <= ret.first){
ret.second += 1;
}
// printf("%d => %d\n",value,ret.second);
ans[value] = ret.second;
return ret;
}
}
}

int main() {
uint fac[11];
unordered_map<uint,uint> ans;
fac[0] = 1;
rep(i,1,11){
fac[i] = fac[i-1]*i;
}
rep(i,1,N+1){
if(ans.count(i)){
continue;
}
unordered_map<uint,uint> dep;
dep[i] = 0;
dfs(fac,ans,dep,i);
}
auto cnt = 0;
for (auto [k,v]: ans) {
if( v >= 60 && k <= N ){
cnt+=1;
// println!("{}: \"{}\"", k, v);
}
}
printf("%d",cnt);
return 0;
}

rust不带-O2 大概5s+,带了和C++相近都是0.3s左右, 真的快

env

Intel Core i7-7700HQ @ 8x 3.8GHz

!clang++ -o "%<" "%" -std=gnu++17 -O2 -g -Wall -Wcomma

1
2
3
clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
Target: x86_64-pc-linux-gnu
Thread model: posix

!rustc "%" -O

rustc 1.43.1 (8d69840ab 2020-05-04)

感受

rust写好的代码转换成C++是真的轻松,C++转换成rust就会很累(除非各种 as,unwrap

npm发布仓库

步骤

  1. 注册账户 https://www.npmjs.com/signup
  2. 命令行登录 npm login
  3. 建立github仓库
  4. 初始化仓库 npm init
  5. 编写代码
  6. 发布npm publish & github仓库更新

注意事项

如果是之前用taobao的镜像,记得发布时 更改设置为官方地址

个人建议一些“没有建设意义”的仓库,就不要去抢名字了,带上自己的scope发布

发布前** 记得清理敏感信息**,似乎npm虽然可以unpublish但是,不能重置版本号 https://www.npmjs.com/policies/unpublish

生成类发布

例如源码是用typescript写的,发布要发布编译后的文件,但是git中不会跟踪。

不建议使用.npmignore (会使.gitignore在publish时无效),建议的是在package.json中增加"files":["dist/**/*"]这样的白名单

本地调试

例如

在开发的库中构建后调用yarn link 会提示success Registered "@cromarmot/isNumberOne"

在用来测试的文件夹中 调用 yarn link "@cromarmot/isNumberOne" 即可使用

取消link:把上面link替换为unlink,使用对应的yarn unlink命令即可

link以后,代码更新只需要编译原来的库代码,不需要重新link

实例

一个基于莫比乌丝反演的数字1判断

https://www.npmjs.com/package/@cromarmot/is-number-one

refs

https://docs.npmjs.com/

起因

在业务页面 希望对输入框进行额外操作,例如 当输入长度大于 10则截取到前10位

BUG产生,业务页面写了如下的代码

1
2
3
4
5
6
7
8
9
10
data(){
return {
someInputValue:'',
}
},
watch:{
someInputValue(newVal){
this.someInputValue = newVal.substr(0,10);
}
}

然后 前技术经理写的输入框 却可以一直输入

我用render函数 写的没有问题

直接裸的input也没问题

定位

加了一堆console,基本确定bug的原理是

  1. 内部输入更新
  2. 触发组件emit
  3. 业务页面得到超过长度的值
  4. 修改为只有长度为10的值
  5. 通过props传递给组件
  6. 触发组件内对value的watch (这次能成功
  7. 用户继续输入
  8. 触发组件emit
  9. 业务页面得到超过长度的值
  10. 修改为只有长度为10的值
  11. 通过props传递给组件
  12. 没有触发组件内对value的watch

对比

官方直接input能用,但不知道是怎么封装的

我封装的是参考https://cn.vuejs.org/v2/guide/render-function.html#v-model

然而,如果你直接尝试上面所写的你会发现并不如愿

没有使用watch,而是使用domProps

我封装的是参考https://cn.vuejs.org/v2/guide/render-function.html#v-model的没有使用watch,而是使用domProps

解决

直接的话就是用domProps 但是有个问题,如果使用者

另外的话,我本来实现的是会提供一个接受过滤函数的props,业务层可以传递过滤函数而不是通过外部watch

结论

目前看起来,双向绑定就应该做全,不要单向使用。

有些时候不设置默认值?

代码

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
<template>
<div>
<h1>v-model</h1>
<div class="row">
<div> input(默认)<input v-model="x1"> {{ x1 }}</div>
<div>Input2(官方文档)<Input2 v-model="x2" /> {{ x2 }}</div>
<div>Input3(我)<Input3 v-model="x3" /> {{ x3 }}</div>
<div>Input4(历史遗留代码)<Input4 v-model="x4" /> {{ x4 }}</div>
</div>
<h1>@input only</h1>
<div class="row">
<div>input(默认)<input @input="(e)=>{input1 = e.target.value;}"> {{ input1 }}</div>
<div>Input2(官方)<Input2 @input="(v)=>{input2=v;}" /> {{ input2 }}</div>
<div>Input3(我)<Input3 @input="(v)=>{input3=v;}" /> {{ input3 }}</div>
<div>Input4(历史遗留代码)<Input4 @input="(v)=>{input4=v;}" /> {{ input4 }}</div>
</div>
</div>
</template>

<script>

export default {
components: {
Input3: {
name: 'FormInputJsx',
// inheritAttrs: false,
props: {
inputFilter: {
type: Function,
default: null
},
// 传入类型
value: String
},
data() {
return {
valueIn: this.value
}
},

methods: {
updateValue(value) {
let newVal = value
if (this.inputFilter) {
newVal = this.inputFilter(newVal)
}
// this.valueIn = newVal
// if (this.$refs.input.value !== this.valueIn) {
// this.$refs.input.value = this.valueIn
// }
this.$emit('input', newVal)
}
},
render(h) {
const inputConfig = {
ref: 'input',
// input 原始属性不改动 自动下发-
// attrs: Object.assign({ ...this.$attrs }),
on: Object.assign({ ...this.$listeners }, {
input: event => this.updateValue(event.target.value) // 输入框改变
})
}
if (typeof this.value !== 'undefined') {
inputConfig.domProps = {
value: this.value
}
}
return h('input', inputConfig)
}
},
Input4: {
render(h) {
const self = this
return h('input', {
domProps: {
value: self.value
},
on: {
input (event) {
self.Amount = event.target.value
}
}
})
},
props: {
value: {
type: String,
default: ''
}
},
data() {
return {
Amount: ''
}
},
watch: {
value(newVal) {
this.Amount = newVal
},
Amount(newVal) {
// 从前截取 不带$
this.Amount = newVal.match(/^\d*(\.?\d{0,2})/g)[0] || ''
if (isNaN(this.Amount)) {
this.Amount = ''
}
this.$emit('input', this.Amount)
}
}
},
Input2: {
props: ['value'],
render (createElement) {
const self = this
return createElement('input', {
domProps: {
value: self.value
},
on: {
input (event) {
self.$emit('input', event.target.value)
}
}
})
}
}
},
data() {
return {
x1: '',
x2: '',
x3: '',
x4: '',
input1: '',
input2: '',
input3: '',
input4: ''
}
},
watch: {
x1(nv) {
this.x1 = nv.substr(0, 3)
},
x2(nv) {
this.x2 = nv.substr(0, 3)
},
x3(nv) {
this.x3 = nv.substr(0, 3)
},
x4(nv) {
this.x4 = nv.substr(0, 3)
},
input1(nv) {
this.input1 = nv.substr(0, 3)
},
input2(nv) {
this.input2 = nv.substr(0, 3)
},
input3(nv) {
this.input3 = nv.substr(0, 3)
},
input4(nv) {
this.input4 = nv.substr(0, 3)
}
}

}
</script>

<style>
.row{
display:flex;
flex-direction:column;
}
</style>

refs

vue render v-model

vue pull requests 9331

起因

新做了个输入框,在三次封装时用 $attrs,本地一切正常,内部测试环境出现bug:输入不了值。

解决

版本都查过,一样的代码,最后猜是不是vue的问题。

查了一下在线的vue版本是2.6.11而内部发布版本是2.5.13

把调试的vue换成指定版本2.5.13重现了bug。

顺着官方https://github.com/vuejs/vue/releases

看到v2.6.1修复了v-model: add value to $attrs if not defined in props

pull requests 93319330似乎很多人也都遇到了

果然XD

内部测试发布的版本已经在生产使用,当然是锁版本不会改了,把开发用的vue调试版换回了2.5.13,然后对应组件增加propsvaluevalue的传递。// 还好是新组建

虽然听说并且实践了无数次锁版本,但亲身是第一次遇到版本问题!(之前几次其它第三方资源有bug 都是最新的官方资源也没修复的,例如swiper有个重复emit的bug放了很久也不知道现在修复没

结论

锁版本!!!!!

官方vue虽然说是开发环境是https://cdn.jsdelivr.net/npm/vue/dist/vue.js,但实际因为生产的vue 是锁了版本,所以为了保持和生产一致,如果要使用在线资源可以使用https://cdn.jsdelivr.net/npm/vue@版本号/dist/vue.js

refs

vue releases

vue pull requests 9331

Run

1
2
3
yarn global add jasmine-core karma karma-chrome-launcher karma-jasmine karma-jasmine-html-reporter
karma init # 一路默认
vim karma.conf.js

一些改动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@@ -21,8 +21,12 @@ module.exports = function (config) {

// list of files / patterns to load in the browser
files: [
+ 'src/**/*.js'
],

+ client: {
+ clearContext: false, // leave Jasmine Spec Runner output visible in browser
+ },


// list of files / patterns to exclude
@@ -37,7 +41,7 @@ module.exports = function (config) {
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
- reporters: ['progress'],
+ reporters: ['progress', 'kjhtml'],


// web server port

编写你的测试代码,语法见jasmine文档

启动

1
karma start

karma-jasmine-html-reporter 这个赞极少 却超高的周下载量 所以大多是连带下载的吧

版本兼容各种问题

目前一组能用的

1
2
3
4
5
6
7
{
"jasmine-core": "^3.5.0",
"karma": "^5.0.1",
"karma-chrome-launcher": "^3.1.0",
"karma-jasmine": "^3.1.1",
"karma-jasmine-html-reporter": "^1.5.3"
}

cnpm

不知道cnpm搞了什么私货,同样的命令 npm 能用 cnpm就是报错。目前采取全局yarn按加项目内karam.conf.js了 (感觉操作不科学啊

import

报错 大概意思不在module内

搜到很多加plugins或者在预处理调用webpack的,暂时没有采用

可行的一种方案,目前把files的配置写成

1
[{pattern:'src/**/*.js',type:'module'}]

files

需要包括源代码 和 测试代码

Repo

https://github.com/CroMarmot/karma.jasmine.demo

refs

http://karma-runner.github.io/4.0/config/files.html

http://karma-runner.github.io/4.0/intro/how-it-works.html

通用函数可变参数模板

// 迷,不是有处理args的办法吗?)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<iostream>
void showall() { return; }

template <typename R1 ,typename... Args>
void showall(R1 var, Args...args) {
std::cout << var << std::endl;
showall(args...);
}

int main(int argc, char * args[]) {
showall(1, 2, 3, 4, 5);
showall("gxjun","dadw","dasds");
showall(1.0,2,"3");
return 0;
}

仿函数

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
31
#include<iostream>
#include<functional>
using namespace std;
using namespace std::placeholders;

template <typename R1 , typename R2>
struct Calc
{
void add(R1 a) {
cout << a << endl;
};
void add_1(R1 a, R1 b) {
cout << a + b << endl;
}
};

int main(int argc, char * args[]) {

//函数指针
void(Calc<int, double>::*fc)(int a) = &Calc<int, double >::add;
// fc(25);
//显然上面的式子比较的麻烦

Calc < int, int> calc;
auto fun = bind(&Calc<int, int >::add, &calc, _1);
auto fun_2 = bind(&Calc<int, int >::add_1, &calc, _1,_2);
fun(123);
fun_2(12,24);
cin.get();
return 0;
}

refs

yield

yield*

function*

function*

demo

直接丢浏览器或者node里跑代码

函数的值产生和继续执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function* countAppleSales () {
let saleList = [3, 7, 5]
for (let i = 0; i < saleList.length; i++) {
console.log('in func before yield');
yield saleList[i];
console.log('in func after yield');
}
}

let appleStore = countAppleSales() // Generator { }
setInterval(()=>{
console.log(appleStore.next());
},2000);

嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function* g1() {
yield 2;
yield 3;
yield 4;
}

function* g2() {
yield 1;
yield* g1(); // here
yield 5;
}

const iterator = g2();

console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: 4, done: false}
console.log(iterator.next()); // {value: 5, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

遍历输出

1
2
3
4
5
6
7
8
9
10
11
12
function* foo() {
yield 'a';
yield 'b';
yield 'c';
}

let str = '';
for (const val of foo()) { // here
str = str + val;
}

console.log(str);

在函数调用是可以传参数,同时,在next调用也是可以传参数

问题

chrome里有命令行输出的时候,右侧会有VM:xxx或者源文件名:行数的一个跳转链接

然而如果封装了console函数或者例如用了vconsole之类的库,

它的错误输出,可能就不是你所期望的自己的代码

解决

如果只是简单的封装log

搜到的大多方案都是形如var newlogfunction = console.log.bind(window.console)

或者是原来的调用变成返回函数,然后再调用大概变成newconsole()()的写法

然而和我期望的需求有些不符,

目前期望的是一个通用的函数内,增加一点调试时的参数输出,希望能输出调用者的位置

也搜索了call stack相关,目前看来有些地方能用console.trace(),但在webpack里就不行了?

有搜到过用new Error()但它的stack字段是个string ,还真有人字符分割解析这个string

目前一个比较科学的方案是用chrome的blackbox文件功能,对webpack里的无效,但是可以对例如vconsole进行blackbox,这样就能展示到原来调用的位置。

同时根据这个思考,可以在调试的版本中对文件的分块进行指定,让公共文件和具体文件webpack打分开的chunk

参考

https://gist.github.com/paulirish/c307a5a585ddbcc17242

https://stackoverflow.com/questions/9559725/extending-console-log-without-affecting-log-line

https://stackoverflow.com/questions/13815640/a-proper-wrapper-for-console-log-with-correct-line-number/32928812

https://developer.mozilla.org/en-US/docs/Web/API/Console/trace

https://developers.google.com/web/tools/chrome-devtools/javascript/reference

https://developer.chrome.com/devtools/docs/blackboxing

ENOSPC

启动其它项目报错 ENOSPC: System limit for number of file watchers reached

也可能它自己报Visual Studio Code is unable to watch for file changes in this large workspace" (error ENOSPC)

多次尝试都是关掉vscode就好了,但之前一直没有更准确的定位问题,以及真的是它的锅吗

网上更多的说改改系统的最大限制吧。

先看看cat /proc/sys/fs/inotify/max_user_watches,大多都是8192,然后说改/etc/sysctl.conf改到fs.inotify.max_user_watches=524288

作为一个网易云音乐不用sudo启动不了就拒绝使用网易云音乐的人,又怎么会轻易该系统的东西//虽然linux是真的好改

我先是自己去看了 ps -aux | grep code/code | awk '{print $2}' | xargs -I {} ls -1 /proc/{}/fd | wc 占用不算多也不算少但是和8192还是差几个数量级(以2为基的数量级)

然后又搜了些资料,大概有anon_inodeinotify这两个关键字,但没具体说是怎么查看

最后是搜到了这个脚本

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
#!/bin/sh

# Get the procs sorted by the number of inotify watchers
#
# From `man find`:
# %h Leading directories of file's name (all but the last element). If the file name contains no slashes (since it
# is in the current directory) the %h specifier expands to `.'.
# %f File's name with any leading directories removed (only the last element).
lines=$(
find /proc/*/fd \
-lname anon_inode:inotify \
-printf '%hinfo/%f\n' 2>/dev/null \
\
| xargs grep -c '^inotify' \
| sort -n -t: -k2 -r \
)

printf "\n%10s\n" "INOTIFY"
printf "%10s\n" "WATCHER"
printf "%10s %5s %s\n" " COUNT " "PID" "CMD"
printf -- "----------------------------------------\n"
for line in $lines; do
watcher_count=$(echo $line | sed -e 's/.*://')
pid=$(echo $line | sed -e 's/\/proc\/\([0-9]*\)\/.*/\1/')
cmdline=$(ps --columns 120 -o command -h -p $pid)
printf "%8d %7d %s\n" "$watcher_count" "$pid" "$cmdline"
done

也就是 /proc/具体pid/fdinfo/具体文件 的以inotify开头的

这个也可以通过/proc/具体pid/fd/具体fd -> anon_inode:inotify查看有哪个symbolic link是指向anon_inode:inotify

可以看到其它的程序都用很少,就idea-IU-193.5662.53/bin/fsnotifier64/usr/share/code/code使用是在4000数量级的

然后启动一个node 又是1000+

所以最后还是改系统配置+改vsc的排除文件

inotify

TODO 记录的意义,和具体文件查看

1024 inotify wd:175 ino:a7cc6 sdev:800002 mask:fc6 ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:c67c0a00f48cb089

TODO 我记得最开始跑脚本有看到vscode用8000+ 的难道我看错了?反正后来暂时没有重现

vscode 的建议

一个也是改系统的

另一个是增加配置中files.watcherExclude的文件glob描述,增加以后 再启动似乎从8000+降低到1000+ (需要重启)

参考

https://howchoo.com/g/m2uzodviywm/node-increase-file-watcher-system-limit

https://unix.stackexchange.com/questions/15509/whos-consuming-my-inotify-resources/426001#426001

https://github.com/fatso83/dotfiles/blob/master/utils/scripts/inotify-consumers

https://code.visualstudio.com/docs/setup/linux#_visual-studio-code-is-unable-to-watch-for-file-changes-in-this-large-workspace-error-enospc

https://www.tldp.org/LDP/Linux-Filesystem-Hierarchy/html/proc.html

http://man7.org/linux/man-pages/man7/inotify.7.html

同步多路IO复用

CODE

五个子进程 2000本地端口SOCK_STREAM的demo

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <wait.h>
#include <signal.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

#include <poll.h>

#include <sys/epoll.h>

#define MAXBUF 256
#define CHILD 5

void child_process(void) {
sleep(2);
char msg[MAXBUF];
struct sockaddr_in addr = {0};
int sockfd,num=1;
srandom(getpid());
/* Create socket and connect to server */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");

connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));

printf("child {%d} connected \n", getpid());
while(true){
int sl = (random() % 10 ) + 1;
num++;
sleep(sl);
sprintf (msg, "Test message %d from client %d", num, getpid());
write(sockfd, msg, strlen(msg)); /* Send message -> 127.0.0.1:2000 */
}
}

void selectDemo(int sockfd){
int fds[CHILD];
fd_set rset;
socklen_t maxfd=0;
for (int i=0;i<CHILD;i++) {
fds[i] = accept(sockfd,NULL,NULL);
printf("fds[%d]=%d\n",i,fds[i]);
if(fds[i] > maxfd)
maxfd = fds[i];
}

while(true){
FD_ZERO(&rset);
for (int i = 0; i< CHILD; i++ ) {
FD_SET(fds[i],&rset);
}

puts("round again");
select(maxfd+1, &rset, NULL, NULL, NULL); // 返回值是 ready的个数 >= 0

for(int i=0;i<CHILD;i++) {
if (FD_ISSET(fds[i], &rset)){
char buffer[MAXBUF];
int n = read(fds[i], buffer, MAXBUF);
buffer[n] = '\0';
puts(buffer); // 主进程把子进程的内容打印
}
}
}
}

void pollDemo(int sockfd){
pollfd pollfds[CHILD]; // 这里变成了 数组,因此长度不再受到限制
for(int i=0;i<CHILD;i++){
pollfds[i].fd = accept(sockfd,NULL,NULL);
pollfds[i].events = POLLIN;
}
sleep(1);
while(true){
puts("round again");
poll(pollfds, CHILD, 50000);

for(int i=0;i<CHILD;i++) { // 但这里检查状态时 依然是 轮询
if (pollfds[i].revents & POLLIN){
pollfds[i].revents = 0; // 不需要每次 把所有的重设置到set里,只需要把已经就绪的状态恢复掉
char buffer[MAXBUF];
int n = read(pollfds[i].fd, buffer, MAXBUF);
buffer[n] = '\0';
puts(buffer);
}
}
}
}

void epollDemo(int sockfd){
int epfd = epoll_create(233); // create a context in the kernel 文档说 只要是正值就行 具体值被忽略了
for(int i=0;i<CHILD;i++) {
static struct epoll_event ev; // 注意这里是static
ev.data.fd = accept(sockfd,NULL,NULL); // 这也可以 自定义其它的data值
ev.events = EPOLLIN; // 这里还可以设置 EPOLLET 的bit位 启用edge-triggered
epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev); // add and remove file descriptors to/from the context using epoll_ctl
}
while(true){
puts("round again");
struct epoll_event events[CHILD];
int nfds = epoll_wait(epfd, events, CHILD, 50000); // 把就绪的写入到events中 写了nfds个

for(int i=0;i<nfds;i++) { // 遍历的都是就绪的
char buffer[MAXBUF];
int n = read(events[i].data.fd, buffer, MAXBUF);
buffer[n] = '\0';
puts(buffer);
}
}
}


int main() {
int sockfd;
struct sockaddr_in addr;
for(int i=0;i<CHILD;i++) {
if(fork() == 0) {
child_process(); // 子进程
exit(0);
}
}
// 主进程

sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof (addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd,(struct sockaddr*)&addr ,sizeof(addr));
listen (sockfd, CHILD);

// 三种 demo 解除注释 使用
// selectDemo(sockfd);
// pollDemo(sockfd);
// epollDemo(sockfd);
return 0;
}

select

man 2 select

1
2
3
4
5
6
7
8
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set); // 从set中移除fd
int FD_ISSET(int fd, fd_set *set); // 测试set中是否设置fd
void FD_SET(int fd, fd_set *set); // 在set中设置fd
void FD_ZERO(fd_set *set); // fd zero

int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);

nfds should be set to the highest-numbered file descriptor in any of the three sets, plus 1. The indicated file descriptors in each set are checked, up to this limit (but see BUGS).

Three independent sets of file descriptors are watched.

The file descriptors listed in readfds will be watched to see if characters become available for reading (more precisely, to see if a read will not block; in particular, a file descriptor is also ready on end-of-file).

The file descriptors in writefds will be watched to see if space is available for write (though a large write may still block).

The file descriptors in exceptfds will be watched for exceptional conditions. (For examples of some exceptional conditions, see the discussion of POLLPRI in poll(2).)

The time structures involved are defined in <sys/time.h> and look like

1
2
3
4
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};

and

1
2
3
4
struct timespec {
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};

我们可以看到有时 会多个fd同时 就绪,

并且每次select后需要 再走一边FD_ZERO -> FD_SET

当就绪(或者超时)的时候,需要for所有的fd用FD_ISSET来判断就绪状态

单个进程能够监视的文件描述符的数量存在最大限制,它由FD_SETSIZE设置通常为1024,最大数量可以通过修改宏定义甚至重新编译内核的方式来满足。

select.h

内核/用户空间的拷贝问题,select需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。

轮询扫描: 也就是for+FD_ISSET

水平触发:应用程序如果没有完成对一个已经就绪的文件描述符进行IO,那么之后再次select调用还是会将这些文件描述符通知进程。

poll

1
2
3
4
5
6
7
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};

采用数组指针+长度的参数形式

返回值

1
2
3
4
5
On success, a positive number is returned; this is the number of
structures which have nonzero revents fields (in other words, those
descriptors with events or errors reported). A value of 0 indicates
that the call timed out and no file descriptors were ready. On
error, -1 is returned, and errno is set appropriately.

水平触发

其和select不同的地方:采用数组的方式替换原有fd_set数据结构,而使其没有连接数的限制。

虽然也是轮询,但是假设是单个fd,但fd的值很大的情况下,poll就会比select效率好

上面看到了只需要一次初始化,和恢复已经就绪的fd,不需要每次初始化

可移植性:select( ) is more portable, as some Unix systems do not support poll( )

epoll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int epoll_create(int size); // Since Linux 2.6.8, the size argument is ignored, but must be greater than zero;
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;

struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};

前面两种,都是 user space设置,要用时调用 select/poll 进入 kernel 态

  • create a context in the kernel using epoll_create
  • add and remove file descriptors to/from the context using epoll_ctl
  • wait for events in the context using epoll_wait ,据说这里做了内存映射优化

epoll_ctl这里要有fd参数,epoll_event中也有epoll_data 中有fd

然而 里面的epoll_data是个union也就是调用者自己喜欢放什么就放什么,不论是下面的fd还是

Level-triggered(默认) and edge-triggered

LT模式:若就绪的事件一次没有处理完要做的事件,就会一直去处理。即就会将没有处理完的事件继续放回到就绪队列之中(即那个内核中的链表),一直进行处理。 

ET模式:就绪的事件只能处理一次,若没有处理完会在下次的其它事件就绪时再进行处理。而若以后再也没有就绪的事件,那么剩余的那部分数据也会随之而丢失。 

由此可见:ET模式的效率比LT模式的效率要高很多。只是如果使用ET模式,就要保证每次进行数据处理时,要将其处理完,不能造成数据丢失,这样对编写代码的人要求就比较高。 
注意:ET模式只支持非阻塞的读写:为了保证数据的完整性。

总结

上面的函数都有一些保证原子性的操作函数,例如pselect,epoll_pwait

例如epoll_pwait()等价于

1
2
3
4
5
sigset_t origmask;

pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
ready = epoll_wait(epfd, &events, maxevents, timeout);
pthread_sigmask(SIG_SETMASK, &origmask, NULL);

有的地方说

表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调

reference

man 2 listen

man 2 read

man 2 select

man 2 poll

man 7 epoll

man 2 epoll_create

man 2 epoll_ctl

http://www.ulduzsoft.com/2014/01/select-poll-epoll-practical-difference-for-system-architects/

https://devarea.com/linux-io-multiplexing-select-vs-poll-vs-epoll/

Using poll() instead of select()

Example: Using asynchronous I/O

Example: Nonblocking I/O and select()

The method to epoll’s madness

0%