深度优先

这个家伙好懒,除了文章什么都没留下

0%

在熟悉docker的情况下,这应该是你在网上能找到最简便的树莓派+rtmp视频监控教程吧。

下面主要罗列下重要的几个配置文件:

创建容器

1
2
3
4
5
6
7
docker run --name rtmp -d -p 8980:80 -p 8935:1935 \
-v /opt/docker/rtmp/website:/usr/local/nginx/html \
-v /opt/docker/rtmp/logs:/usr/local/nginx/logs \
-v /opt/docker/rtmp/static:/www/static \
-v /opt/docker/rtmp/nginx.conf:/etc/nginx/nginx.conf \
-v /opt/docker/rtmp/hls:/opt/data/hls \
andylippitt/nginx-rtmp

stat.xsl

/opt/docker/rtmp/static/stat.xsl

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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright (C) Roman Arutyunyan
-->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head>
<title>RTMP statistics</title>
</head>
<body>
<xsl:apply-templates select="rtmp"/>
<hr/>
Generated by <a href='https://github.com/arut/nginx-rtmp-module'>
nginx-rtmp-module</a>&#160;<xsl:value-of select="/rtmp/nginx_rtmp_version"/>,
<a href="http://nginx.org">nginx</a>&#160;<xsl:value-of select="/rtmp/nginx_version"/>,
pid <xsl:value-of select="/rtmp/pid"/>,
built <xsl:value-of select="/rtmp/built"/>&#160;<xsl:value-of select="/rtmp/compiler"/>
</body>
</html>
</xsl:template>
<xsl:template match="rtmp">
<table cellspacing="1" cellpadding="5">
<tr bgcolor="#999999">
<th>RTMP</th>
<th>#clients</th>
<th colspan="4">Video</th>
<th colspan="4">Audio</th>
<th>In bytes</th>
<th>Out bytes</th>
<th>In bits/s</th>
<th>Out bits/s</th>
<th>State</th>
<th>Time</th>
</tr>
<tr>
<td colspan="2">Accepted: <xsl:value-of select="naccepted"/></td>
<th bgcolor="#999999">codec</th>
<th bgcolor="#999999">bits/s</th>
<th bgcolor="#999999">size</th>
<th bgcolor="#999999">fps</th>
<th bgcolor="#999999">codec</th>
<th bgcolor="#999999">bits/s</th>
<th bgcolor="#999999">freq</th>
<th bgcolor="#999999">chan</th>
<td>
<xsl:call-template name="showsize">
<xsl:with-param name="size" select="bytes_in"/>
</xsl:call-template>
</td>
<td>
<xsl:call-template name="showsize">
<xsl:with-param name="size" select="bytes_out"/>
</xsl:call-template>
</td>
<td>
<xsl:call-template name="showsize">
<xsl:with-param name="size" select="bw_in"/>
<xsl:with-param name="bits" select="1"/>
<xsl:with-param name="persec" select="1"/>
</xsl:call-template>
</td>
<td>
<xsl:call-template name="showsize">
<xsl:with-param name="size" select="bw_out"/>
<xsl:with-param name="bits" select="1"/>
<xsl:with-param name="persec" select="1"/>
</xsl:call-template>
</td>
<td/>
<td>
<xsl:call-template name="showtime">
<xsl:with-param name="time" select="/rtmp/uptime * 1000"/>
</xsl:call-template>
</td>
</tr>
<xsl:apply-templates select="server"/>
</table>
</xsl:template>
<xsl:template match="server">
<xsl:apply-templates select="application"/>
</xsl:template>
<xsl:template match="application">
<tr bgcolor="#999999">
<td>
<b><xsl:value-of select="name"/></b>
</td>
</tr>
<xsl:apply-templates select="live"/>
<xsl:apply-templates select="play"/>
</xsl:template>
<xsl:template match="live">
<tr bgcolor="#aaaaaa">
<td>
<i>live streams</i>
</td>
<td align="middle">
<xsl:value-of select="nclients"/>
</td>
</tr>
<xsl:apply-templates select="stream"/>
</xsl:template>
<xsl:template match="play">
<tr bgcolor="#aaaaaa">
<td>
<i>vod streams</i>
</td>
<td align="middle">
<xsl:value-of select="nclients"/>
</td>
</tr>
<xsl:apply-templates select="stream"/>
</xsl:template>
<xsl:template match="stream">
<tr valign="top">
<xsl:attribute name="bgcolor">
<xsl:choose>
<xsl:when test="active">#cccccc</xsl:when>
<xsl:otherwise>#dddddd</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<td>
<a href="">
<xsl:attribute name="onclick">
var d=document.getElementById('<xsl:value-of select="../../name"/>-<xsl:value-of select="name"/>');
d.style.display=d.style.display=='none'?'':'none';
return false
</xsl:attribute>
<xsl:value-of select="name"/>
<xsl:if test="string-length(name) = 0">
[EMPTY]
</xsl:if>
</a>
</td>
<td align="middle"> <xsl:value-of select="nclients"/> </td>
<td>
<xsl:value-of select="meta/video/codec"/>&#160;<xsl:value-of select="meta/video/profile"/>&#160;<xsl:value-of select="meta/video/level"/>
</td>
<td>
<xsl:call-template name="showsize">
<xsl:with-param name="size" select="bw_video"/>
<xsl:with-param name="bits" select="1"/>
<xsl:with-param name="persec" select="1"/>
</xsl:call-template>
</td>
<td>
<xsl:apply-templates select="meta/video/width"/>
</td>
<td>
<xsl:value-of select="meta/video/frame_rate"/>
</td>
<td>
<xsl:value-of select="meta/audio/codec"/>&#160;<xsl:value-of select="meta/audio/profile"/>
</td>
<td>
<xsl:call-template name="showsize">
<xsl:with-param name="size" select="bw_audio"/>
<xsl:with-param name="bits" select="1"/>
<xsl:with-param name="persec" select="1"/>
</xsl:call-template>
</td>
<td>
<xsl:apply-templates select="meta/audio/sample_rate"/>
</td>
<td>
<xsl:value-of select="meta/audio/channels"/>
</td>
<td>
<xsl:call-template name="showsize">
<xsl:with-param name="size" select="bytes_in"/>
</xsl:call-template>
</td>
<td>
<xsl:call-template name="showsize">
<xsl:with-param name="size" select="bytes_out"/>
</xsl:call-template>
</td>
<td>
<xsl:call-template name="showsize">
<xsl:with-param name="size" select="bw_in"/>
<xsl:with-param name="bits" select="1"/>
<xsl:with-param name="persec" select="1"/>
</xsl:call-template>
</td>
<td>
<xsl:call-template name="showsize">
<xsl:with-param name="size" select="bw_out"/>
<xsl:with-param name="bits" select="1"/>
<xsl:with-param name="persec" select="1"/>
</xsl:call-template>
</td>
<td><xsl:call-template name="streamstate"/></td>
<td>
<xsl:call-template name="showtime">
<xsl:with-param name="time" select="time"/>
</xsl:call-template>
</td>
</tr>
<tr style="display:none">
<xsl:attribute name="id">
<xsl:value-of select="../../name"/>-<xsl:value-of select="name"/>
</xsl:attribute>
<td colspan="16" ngcolor="#eeeeee">
<table cellspacing="1" cellpadding="5">
<tr>
<th>Id</th>
<th>State</th>
<th>Address</th>
<th>Flash version</th>
<th>Page URL</th>
<th>SWF URL</th>
<th>Dropped</th>
<th>Timestamp</th>
<th>A-V</th>
<th>Time</th>
</tr>
<xsl:apply-templates select="client"/>
</table>
</td>
</tr>
</xsl:template>
<xsl:template name="showtime">
<xsl:param name="time"/>
<xsl:if test="$time &gt; 0">
<xsl:variable name="sec">
<xsl:value-of select="floor($time div 1000)"/>
</xsl:variable>
<xsl:if test="$sec &gt;= 86400">
<xsl:value-of select="floor($sec div 86400)"/>d
</xsl:if>
<xsl:if test="$sec &gt;= 3600">
<xsl:value-of select="(floor($sec div 3600)) mod 24"/>h
</xsl:if>
<xsl:if test="$sec &gt;= 60">
<xsl:value-of select="(floor($sec div 60)) mod 60"/>m
</xsl:if>
<xsl:value-of select="$sec mod 60"/>s
</xsl:if>
</xsl:template>
<xsl:template name="showsize">
<xsl:param name="size"/>
<xsl:param name="bits" select="0" />
<xsl:param name="persec" select="0" />
<xsl:variable name="sizen">
<xsl:value-of select="floor($size div 1024)"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$sizen &gt;= 1073741824">
<xsl:value-of select="format-number($sizen div 1073741824,'#.###')"/> T</xsl:when>
<xsl:when test="$sizen &gt;= 1048576">
<xsl:value-of select="format-number($sizen div 1048576,'#.###')"/> G</xsl:when>
<xsl:when test="$sizen &gt;= 1024">
<xsl:value-of select="format-number($sizen div 1024,'#.##')"/> M</xsl:when>
<xsl:when test="$sizen &gt;= 0">
<xsl:value-of select="$sizen"/> K</xsl:when>
</xsl:choose>
<xsl:if test="string-length($size) &gt; 0">
<xsl:choose>
<xsl:when test="$bits = 1">b</xsl:when>
<xsl:otherwise>B</xsl:otherwise>
</xsl:choose>
<xsl:if test="$persec = 1">/s</xsl:if>
</xsl:if>
</xsl:template>
<xsl:template name="streamstate">
<xsl:choose>
<xsl:when test="active">active</xsl:when>
<xsl:otherwise>idle</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="clientstate">
<xsl:choose>
<xsl:when test="publishing">publishing</xsl:when>
<xsl:otherwise>playing</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="client">
<tr>
<xsl:attribute name="bgcolor">
<xsl:choose>
<xsl:when test="publishing">#cccccc</xsl:when>
<xsl:otherwise>#eeeeee</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<td><xsl:value-of select="id"/></td>
<td><xsl:call-template name="clientstate"/></td>
<td>
<a target="_blank">
<xsl:attribute name="href">
http://apps.db.ripe.net/search/query.html&#63;searchtext=<xsl:value-of select="address"/>
</xsl:attribute>
<xsl:attribute name="title">whois</xsl:attribute>
<xsl:value-of select="address"/>
</a>
</td>
<td><xsl:value-of select="flashver"/></td>
<td>
<a target="_blank">
<xsl:attribute name="href">
<xsl:value-of select="pageurl"/>
</xsl:attribute>
<xsl:value-of select="pageurl"/>
</a>
</td>
<td><xsl:value-of select="swfurl"/></td>
<td><xsl:value-of select="dropped"/></td>
<td><xsl:value-of select="timestamp"/></td>
<td><xsl:value-of select="avsync"/></td>
<td>
<xsl:call-template name="showtime">
<xsl:with-param name="time" select="time"/>
</xsl:call-template>
</td>
</tr>
</xsl:template>
<xsl:template match="publishing">
publishing
</xsl:template>
<xsl:template match="active">
active
</xsl:template>
<xsl:template match="width">
<xsl:value-of select="."/>x<xsl:value-of select="../height"/>
</xsl:template>
</xsl:stylesheet>

crossdomain.xml

/opt/docker/rtmp/static/crossdomain.xml

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<site-control permitted-cross-domain-policies="all"/>
<allow-access-from domain="*" secure="false"/>
<allow-http-request-headers-from domain="*" headers="*" secure="false"/>
</cross-domain-policy>

/opt/docker/rtmp/website/index.html

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>树莓派-Live</title>
<link href="https://unpkg.com/video.js/dist/video-js.min.css" rel="stylesheet">
<script src="https://unpkg.com/video.js/dist/video.min.js"></script>
<style>
#roomVideo {
margin: 0 auto;
width: 600px;
height: 400px;
}
</style>
</head>
<body>
<video-js id="roomVideo" controls class="video-js vjs-default-skin vjs-big-play-centered" x-webkit-airplay="allow"
poster="" webkit-playsinline playsinline x5-video-player-type="h5" x5-video-player-fullscreen="true"
preload="auto">
<source src="https://live.bfsdfs.com/live/stream.m3u8" type="application/x-mpegURL">
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that
<a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
</video-js>
<script>
var vid = document.getElementById('roomVideo');
var player = videojs(vid, {
bigPlayButton: true,
textTrackDisplay: true,
posterImage: true,
errorDisplay: true,
controlBar: true
}, function () {
console.log(this)
this.on('loadedmetadata', function () {
console.log('loadedmetadata');
//加载到元数据后开始播放视频
startVideo();
})

this.on('ended', function () {
console.log('ended')
})
this.on('firstplay', function () {
console.log('firstplay')
})
this.on('loadstart', function () {
//开始加载
console.log('loadstart')
})
this.on('loadeddata', function () {
console.log('loadeddata')
})
this.on('seeking', function () {
//正在去拿视频流的路上
console.log('seeking')
})
this.on('seeked', function () {
//已经拿到视频流,可以播放
console.log('seeked')
})
this.on('waiting', function () {
console.log('waiting')
})
this.on('pause', function () {
console.log('pause')
})
this.on('play', function () {
console.log('play')
})
});
var isVideoBreak;
function startVideo() {
player.play();
//微信内全屏支持
// document.getElementById('roomVideo').style.width = window.screen.width + "px";
// document.getElementById('roomVideo').style.height = window.screen.height + "px";
//判断开始播放视频,移除高斯模糊等待层
// var isVideoPlaying = setInterval(function () {
// var currentTime = player.currentTime();
// if (currentTime > 0) {
// $('.vjs-poster').remove();
// clearInterval(isVideoPlaying);
// }
// }, 200)
//判断视频是否卡住,卡主3s重新load视频
var lastTime = -1,
tryTimes = 0;
clearInterval(isVideoBreak);
isVideoBreak = setInterval(function () {
var currentTime = player.currentTime();
console.log('currentTime' + currentTime + 'lastTime' + lastTime);
if (currentTime == lastTime) {
//此时视频已卡主3s
//设置当前播放时间为超时时间,此时videojs会在play()后把currentTime设置为0
player.currentTime(currentTime + 10000);
player.play();
//尝试5次播放后,如仍未播放成功提示刷新
if (++tryTimes > 5) {
alert('您的网速有点慢,刷新下试试');
tryTimes = 0;
}
} else {
lastTime = currentTime;
tryTimes = 0;
}
}, 3000)
}
</script>
</body>
</html>

nginx.conf

/opt/docker/rtmp/nginx.conf

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
daemon off;

error_log /dev/stdout info;

events {
worker_connections 1024;
}

rtmp {
server {
listen 1935;
chunk_size 4000;

application stream {
live on;

exec ffmpeg -i rtmp://localhost:1935/stream/$name
-c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 2500k -f flv -g 30 -r 30 -s 1280x720 -preset superfast -profile:v baseline rtmp://localhost:1935/hls/$name_720p2628kbs
-c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 1000k -f flv -g 30 -r 30 -s 854x480 -preset superfast -profile:v baseline rtmp://localhost:1935/hls/$name_480p1128kbs
-c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 750k -f flv -g 30 -r 30 -s 640x360 -preset superfast -profile:v baseline rtmp://localhost:1935/hls/$name_360p878kbs
-c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 400k -f flv -g 30 -r 30 -s 426x240 -preset superfast -profile:v baseline rtmp://localhost:1935/hls/$name_240p528kbs
-c:a libfdk_aac -b:a 64k -c:v libx264 -b:v 200k -f flv -g 15 -r 15 -s 426x240 -preset superfast -profile:v baseline rtmp://localhost:1935/hls/$name_240p264kbs;
}

application hls {
live on;
hls on;
hls_fragment_naming system;
hls_fragment 5;
hls_playlist_length 10;
hls_path /opt/data/hls;
hls_nested on;

hls_variant _720p2628kbs BANDWIDTH=2628000,RESOLUTION=1280x720;
hls_variant _480p1128kbs BANDWIDTH=1128000,RESOLUTION=854x480;
hls_variant _360p878kbs BANDWIDTH=878000,RESOLUTION=640x360;
hls_variant _240p528kbs BANDWIDTH=528000,RESOLUTION=426x240;
hls_variant _240p264kbs BANDWIDTH=264000,RESOLUTION=426x240;
}
}
}

http {
access_log /dev/stdout combined;

ssl_ciphers HIGH:!aNULL:!MD5;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

server {
listen 80;

# Uncomment these lines to enable SSL.
# Update the ssl paths with your own certificate and private key.
# listen 443 ssl;
# ssl_certificate /opt/certs/example.com.crt;
# ssl_certificate_key /opt/certs/example.com.key;

location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /opt/data;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}

location /live {
alias /opt/data/hls;
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}

location /stat {
rtmp_stat all;
rtmp_stat_stylesheet static/stat.xsl;
}

location /static {
alias /www/static;
}

location = /crossdomain.xml {
root /www/static;
default_type text/xml;
expires 24h;
}
}
}

推流命令

1
raspivid -w 640 -h 480 -b 15000000 -t 0 -a 12 -a 1024 -a "CAM-1 %Y-%m-%d %X" -ae 18,0xff,0x808000 -o - | ffmpeg -re -i - -s 640x480 -vcodec copy -acodec copy -b:v 800k -b:a 32k -f flv rtmp://192.168.8.100:8935/stream/stream

上个图

https://www.coder.work/article/1591477

我试图为一个if-else类型结构创建一个非常简单的解析器,它将构建并执行一个sql语句。
与其测试执行语句的条件,不如测试生成字符串的条件。
例如:

1
2
3
4
5
6
7
8
9
10
select column1
from
#if(VariableA = Case1)
table1
#else if(VariableA = Case2)
table2
#else
defaultTable
#end

如果variablea等于case1,则结果字符串应为:select column1 from table1
一个更复杂的例子是嵌套if语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
select column1
from
#if(VariableA = Case1)
#if(VariableB = Case3)
table3
#else
table4
#else if(VariableA = Case2)
table2
#else
defaultTable
#end

这是我真正遇到麻烦的地方,我想不出一个好的方法来正确识别每个if-else结束组。
另外,我不确定跟踪“else”子句中的字符串是否应该计算为true的好方法。
我一直在网上寻找不同类型的解析算法,它们看起来都非常抽象和复杂。
对于这个非计算机专业的学生,有什么好的开始的建议吗?

最佳答案

我编写了一个简单的解析器,并根据您提供的示例进行了测试。如果你想了解更多关于解析的知识,我建议你阅读niklaus wirth的Compiler Construction
第一步总是以适当的方式写下你的语言的语法。我选择了ebnf,这很容易理解。
|分离备选方案。
[]包含选项。
{}表示重复(零次、一次或多次)。
()组表达式(此处不使用)。
此描述不完整,但我提供的链接对其进行了更详细的描述。
ebnf语法

1
2
3
4
5
6
7
8
9
LineSequence = { TextLine | IfStatement }.
TextLine = <string>.
IfStatement = IfLine LineSequence { ElseIfLine LineSequence } [ ElseLine LineSequence ] EndLine.
IfLine = "#if" "(" Condition ")".
ElseLine = "#else".
ElseIfLine = "#else" "if" "(" Condition ")".
EndLine = "#end".
Condition = Identifier "=" Identifier.
Identifier = <letter_or_underline> { <letter_or_underline> | <digit> }.

解析器严格遵循语法,即将重复翻译成循环,将替代翻译成if-else语句,依此类推。

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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace Example.SqlPreprocessor
{
class Parser
{
enum Symbol
{
None,
LPar,
RPar,
Equals,
Text,
NumberIf,
If,
NumberElse,
NumberEnd,
Identifier
}

List<string> _input; // Raw SQL with preprocessor directives.
int _currentLineIndex = 0;

// Simulates variables used in conditions
Dictionary<string, string> _variableValues = new Dictionary<string, string> {
{ "VariableA", "Case1" },
{ "VariableB", "CaseX" }
};

Symbol _sy; // Current symbol.
string _string; // Identifier or text line;
Queue<string> _textQueue = new Queue<string>(); // Buffered text parts of a single line.
int _lineNo; // Current line number for error messages.
string _line; // Current line for error messages.

/// <summary>
/// Get the next line from the input.
/// </summary>
/// <returns>Input line or null if no more lines are available.</returns>
string GetLine()
{
if (_currentLineIndex >= _input.Count) {
return null;
}
_line = _input[_currentLineIndex++];
_lineNo = _currentLineIndex;
return _line;
}

/// <summary>
/// Get the next symbol from the input stream and stores it in _sy.
/// </summary>
void GetSy()
{
string s;
if (_textQueue.Count > 0) { // Buffered text parts available, use one from these.
s = _textQueue.Dequeue();
switch (s.ToLower()) {
case "(":
_sy = Symbol.LPar;
break;
case ")":
_sy = Symbol.RPar;
break;
case "=":
_sy = Symbol.Equals;
break;
case "if":
_sy = Symbol.If;
break;
default:
_sy = Symbol.Identifier;
_string = s;
break;
}
return;
}

// Get next line from input.
s = GetLine();
if (s == null) {
_sy = Symbol.None;
return;
}

s = s.Trim(' ', '\t');
if (s[0] == '#') { // We have a preprocessor directive.
// Split the line in order to be able get its symbols.
string[] parts = Regex.Split(s, @"\b|[^#_a-zA-Z0-9()=]");
// parts[0] = #
// parts[1] = if, else, end
switch (parts[1].ToLower()) {
case "if":
_sy = Symbol.NumberIf;
break;
case "else":
_sy = Symbol.NumberElse;
break;
case "end":
_sy = Symbol.NumberEnd;
break;
default:
Error("Invalid symbol #{0}", parts[1]);
break;
}

// Store the remaining parts for later.
for (int i = 2; i < parts.Length; i++) {
string part = parts[i].Trim(' ', '\t');
if (part != "") {
_textQueue.Enqueue(part);
}
}
} else { // We have an ordinary SQL text line.
_sy = Symbol.Text;
_string = s;
}
}

void Error(string message, params object[] args)
{
// Make sure parsing stops here
_sy = Symbol.None;
_textQueue.Clear();
_input.Clear();

message = String.Format(message, args) +
String.Format(" in line {0}\r\n\r\n{1}", _lineNo, _line);
Output("------");
Output(message);
MessageBox.Show(message, "Error");
}

/// <summary>
/// Writes the processed line to a (simulated) output stream.
/// </summary>
/// <param name="line">Line to be written to output</param>
void Output(string line)
{
Console.WriteLine(line);
}

/// <summary>
/// Starts the parsing process.
/// </summary>
public void Parse()
{
// Simulate an input stream.
_input = new List<string> {
"select column1",
"from",
"#if(VariableA = Case1)",
" #if(VariableB = Case3)",
" table3",
" #else",
" table4",
" #end",
"#else if(VariableA = Case2)",
" table2",
"#else",
" defaultTable",
"#end"
};

// Clear previous parsing
_textQueue.Clear();
_currentLineIndex = 0;

// Get first symbol and start parsing
GetSy();
if (LineSequence(true)) { // Finished parsing successfully.
//TODO: Do something with the generated SQL
} else { // Error encountered.
Output("*** ABORTED ***");
}
}

// The following methods parse according the the EBNF syntax.

bool LineSequence(bool writeOutput)
{
// EBNF: LineSequence = { TextLine | IfStatement }.
while (_sy == Symbol.Text || _sy == Symbol.NumberIf) {
if (_sy == Symbol.Text) {
if (!TextLine(writeOutput)) {
return false;
}
} else { // _sy == Symbol.NumberIf
if (!IfStatement(writeOutput)) {
return false;
}
}
}
return true;
}

bool TextLine(bool writeOutput)
{
// EBNF: TextLine = <string>.
if (writeOutput) {
Output(_string);
}
GetSy();
return true;
}

bool IfStatement(bool writeOutput)
{
// EBNF: IfStatement = IfLine LineSequence { ElseIfLine LineSequence } [ ElseLine LineSequence ] EndLine.
bool result;
if (IfLine(out result) && LineSequence(writeOutput && result)) {
writeOutput &= !result; // Only one section can produce an output.
while (_sy == Symbol.NumberElse) {
GetSy();
if (_sy == Symbol.If) { // We have an #else if
if (!ElseIfLine(out result)) {
return false;
}
if (!LineSequence(writeOutput && result)) {
return false;
}
writeOutput &= !result; // Only one section can produce an output.
} else { // We have a simple #else
if (!LineSequence(writeOutput)) {
return false;
}
break; // We can have only one #else statement.
}
}
if (_sy != Symbol.NumberEnd) {
Error("'#end' expected");
return false;
}
GetSy();
return true;
}
return false;
}

bool IfLine(out bool result)
{
// EBNF: IfLine = "#if" "(" Condition ")".
result = false;
GetSy();
if (_sy != Symbol.LPar) {
Error("'(' expected");
return false;
}
GetSy();
if (!Condition(out result)) {
return false;
}
if (_sy != Symbol.RPar) {
Error("')' expected");
return false;
}
GetSy();
return true;
}

private bool Condition(out bool result)
{
// EBNF: Condition = Identifier "=" Identifier.
string variable;
string expectedValue;
string variableValue;

result = false;
// Identifier "=" Identifier
if (_sy != Symbol.Identifier) {
Error("Identifier expected");
return false;
}
variable = _string; // The first identifier is a variable.
GetSy();
if (_sy != Symbol.Equals) {
Error("'=' expected");
return false;
}
GetSy();
if (_sy != Symbol.Identifier) {
Error("Value expected");
return false;
}
expectedValue = _string; // The second identifier is a value.

// Search the variable
if (_variableValues.TryGetValue(variable, out variableValue)) {
result = variableValue == expectedValue; // Perform the comparison.
} else {
Error("Variable '{0}' not found", variable);
return false;
}

GetSy();
return true;
}

bool ElseIfLine(out bool result)
{
// EBNF: ElseIfLine = "#else" "if" "(" Condition ")".
result = false;
GetSy(); // "#else" already processed here, we are only called if the symbol is "if"
if (_sy != Symbol.LPar) {
Error("'(' expected");
return false;
}
GetSy();
if (!Condition(out result)) {
return false;
}
if (_sy != Symbol.RPar) {
Error("')' expected");
return false;
}
GetSy();
return true;
}
}
}

注意,嵌套的if语句是以非常自然的方式自动处理的。首先,语法是递归表达的。aLineSequence可以包含IfStatments,IfStatments包含LineSequences。其次,这会导致语法处理方法以递归方式相互调用。因此,语法元素的嵌套被转换为递归方法调用。

https://www.cjavapy.com/article/398/

SSH.NET是用于.NET的Secure Shell(SSH-2)库,sftp是通过ssh实现的,本文主要介绍.Net(C#)中,使用SSH.NET对sftp常用操作及工具类代码,包括sftp服务器的连接,文件上传,下载文件,文件删除,文件的位置的移动等操作。

1、SSH.NET安装引用

使用Nuget管理工具搜索 SSH.NET =>找到选择安装

2、SSH.NET操作代码

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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
using NLog;
using Renci.SshNet;
using Renci.SshNet.Sftp;
using ShellProgressBar;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;

namespace PiSftpClient
{
public class SFtpHelper
{
private SftpClient sftp;

private static readonly Logger logger = LogManager.GetLogger("SFtpHelper");

/// <summary>
/// 连接状态
/// </summary>
public bool Connected
{
get
{
return this.sftp.IsConnected;
}
}

/// <summary>
///
/// </summary>
/// <param name="host">sftp的IP</param>
/// <param name="port">sftp的端口</param>
/// <param name="username">sftp的帐户</param>
/// <param name="password">sftp的密码</param>
public SFtpHelper(string host, int port, string username, string password)
{
this.sftp = new SftpClient(host, port, username, password);
}

/// <summary>
/// 连接sftp服务器
/// </summary>
/// <returns>连接状态</returns>
public bool Connect()
{
bool result;
try
{
bool flag = !this.Connected;
if (flag)
{
this.sftp.Connect();
}
result = true;
}
catch (Exception ex)
{
result = false;
logger.Error(string.Format("连接SFTP失败,原因:{0}", ex.Message));
}
return result;
}

/// <summary>
/// 断开连接
/// </summary>
public void Disconnect()
{
try
{
bool flag = this.sftp != null && this.Connected;
if (flag)
{
this.sftp.Disconnect();
}
}
catch (Exception ex)
{
logger.Error(string.Format("断开SFTP失败,原因:{0}", ex.Message));
}
}

/// <summary>
/// 上传文件
/// </summary>
/// <param name="localPath">本地文件路径</param>
/// <param name="remotePath">服务器端文件路径</param>
public void Put(string localPath, string remotePath)
{
try
{
using (FileStream fileStream = File.OpenRead(localPath))
{
this.sftp.UploadFile(fileStream, remotePath, null);
}
}
catch (Exception ex)
{
logger.Error(string.Format("SFTP文件上传失败,原因:{0}", ex.Message));
}
}

/// <summary>
/// 上传文字字节数据
/// </summary>
/// <param name="fileByteArr">文件内容字节</param>
/// <param name="remotePath">上传到服务器的路径</param>
public void Put(byte[] fileByteArr, string remotePath)
{
try
{
Stream input = new MemoryStream(fileByteArr);
this.sftp.UploadFile(input, remotePath, null);
}
catch (Exception ex)
{
logger.Error(string.Format("SFTP文件上传失败,原因:{0}", ex.Message));
}
}

/// <summary>
/// 将sftp服务器的文件下载本地
/// </summary>
/// <param name="remotePath">服务器上的路径</param>
/// <param name="localPath">本地的路径</param>
public void DownloadFile(string remotePath, string localPath)
{
try
{
this.Connect();
MemoryStream stream = new MemoryStream();
this.sftp.DownloadFile(remotePath, stream, UpdateProgress);
File.WriteAllBytes(localPath, stream.ToArray());
}
catch (Exception ex)
{
logger.Error(string.Format("SFTP文件获取失败,原因:{0}", ex.Message));
}
}

private void UpdateProgress(ulong obj)
{
}

/// <summary>
/// 删除ftp服务器上的文件
/// </summary>
/// <param name="remoteFile">服务器上的路径</param>
public void Delete(string remoteFile)
{
try
{
this.sftp.Delete(remoteFile);
}
catch (Exception ex)
{
logger.Error(string.Format("SFTP文件删除失败,原因:{0}", ex.Message));
}
}

/// <summary>
/// 获取ftp服务器上指定路径上的指定前缀的文件名列表
/// </summary>
/// <param name="remotePath">服务器上的路径</param>
/// <param name="fileSuffix">文件名后缀</param>
/// <returns></returns>
public List<string> GetFileList(string remotePath, string fileSuffix)
{
List<string> result;
try
{
IEnumerable<SftpFile> enumerable = this.sftp.ListDirectory(remotePath, null);
result = new List<string>();
foreach (SftpFile current in enumerable)
{
if (current.Name.EndsWith(fileSuffix))
{
result.Add(current.Name);
}
}
}
catch (Exception ex)
{
result = new List<string>();
logger.Error(string.Format("SFTP文件列表获取失败,原因:{0}", ex.Message));
}
return result;
}

/// <summary>
/// ftp服务器端文件移动
/// </summary>
/// <param name="oldRemotePath">原来服务器上路径</param>
/// <param name="newRemotePath">移动后服务器上新路径</param>
public void Move(string oldRemotePath, string newRemotePath)
{
try
{
this.sftp.RenameFile(oldRemotePath, newRemotePath);
}
catch (Exception ex)
{
logger.Error(string.Format("SFTP文件移动失败,原因:{0}", ex.Message));
}
}
}
}

3、例子从树莓派上下载照片

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
using NLog;
using PiSftpClient.Mail;
using ShellProgressBar;
using System;
using System.IO;
using System.Linq;

namespace PiSftpClient
{
internal class Program
{
private static readonly Logger logger = LogManager.GetLogger("PiSftpClient");

private static void Main(string[] args)
{
var servicePath = "/opt/docker/camera/pictures/";
var localhostPath = @"F:\pi\pictures\";

var dateStr = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd");
logger.Info($"开始下载 {dateStr} 的照片文件!");

SFtpHelper ftp = new SFtpHelper("192.168.8.100", 22, "root", "123456");

ftp.Connect();
var servicePathDate = $"{servicePath}/{dateStr}/";
var localhostPathDate = $"{localhostPath}/{dateStr}";

if (!Directory.Exists(localhostPathDate))
{
Directory.CreateDirectory(localhostPathDate);
}

var childOptions = new ProgressBarOptions
{
ProgressCharacter = '#',
ForegroundColor = ConsoleColor.Yellow,
ForegroundColorDone = ConsoleColor.DarkGreen,
BackgroundColor = ConsoleColor.DarkGray,
ProgressBarOnBottom = true,
};

var imageFiles = ftp.GetFileList(servicePathDate, ".jpg").OrderBy(p => p).ToList();
logger.Info($"服务器上总共 {imageFiles.Count} 个文件!");
using var pbar = new ProgressBar(imageFiles.Count, "download", childOptions);

for (int i = 0; i < imageFiles.Count; i++)
{
var newName = $"{imageFiles[i].Replace("-", "").Replace(".jpg", "")}{i.ToString().PadLeft(4, '0')}.jpg";

ftp.DownloadFile($"{servicePathDate}/{imageFiles[i]}", $"{localhostPathDate}/{newName}");
pbar.Message = $"[{DateTime.Now:HH:mm:ss}] ({i}/{imageFiles.Count}) downloading {imageFiles[i]}";
pbar.Tick();

logger.Info($"下载成功 ({i}/{imageFiles.Count}) downloading {imageFiles[i]} ");
}

ftp.Disconnect();
logger.Info($"下载完成!");

SendMail();
}

private static void SendMail()
{
string filePath = $"./logs/{DateTime.Now:yyyy-MM-dd}.log";
MailHelper mail = new MailHelper();
MailObject mailObject = new MailObject()
{
toMails = "bfsdfs@yeah.net",
ccMails = "1439084907@qq.com",
Subject = "【树莓派】消息通知",
MailBody = "ok...日志信息见附件!",
FilePaths = filePath
};
var result = mail.Send(mailObject);
}
}
}