Chemmy's Blog

chengming0916@outlook.com

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
sudo vim /etc/sysctl.d/99-sysctl.conf:     #重启后生效
fs.inotify.max_user_watches = 600000
dev.i915.perf_stream_paranoid = 0
vm.swappiness = 1
net.ipv6.conf.all.accept_ra = 2
fs.file-max = 6553560
net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.core.rmem_default = 65536
net.core.wmem_default = 65536
net.core.optmem_max = 10000000
net.core.netdev_max_backlog = 8096
net.core.somaxconn = 8096
net.ipv4.ip_default_ttl = 128
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.route.gc_timeout = 100
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_ecn = 1
net.ipv4.tcp_sack = 1
net.ipv4.tcp_fack = 1
net.ipv4.tcp_low_latency = 0
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_frto = 2
net.ipv4.tcp_frto_response = 0
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_max_syn_backlog = 30000
net.ipv4.tcp_max_orphans = 262114
net.ipv4.netfilter.ip_conntrack_max = 204800
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# for high-latency network Google BBR #个人PC不建议开启BBR,对网速不会有提升,还会降低wifi吞吐量;还是等BBR2正式版出了再开启BBR2吧,BBR2对wifi就没有影响了
#net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq_codel
net.ipv4.tcp_congestion_control = cubic
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.default.disable_ipv6 = 0
net.ipv6.conf.lo.disable_ipv6 = 0
net.ipv6.ip_default_ttl = 128
1
2
3
4
5
sudo vim /etc/security/limits.conf:
* soft nofile 65536
* hard nofile 65536
* soft noproc 65536
* hard noproc 65536

概述

netfilter/iptables 为 Linux 内核级有状态防火墙。支持连接跟踪,包含四种有效状态:ESTABLISHEDINVALIDNEWRELATED


一、安装与检测

  • 检测安装:whereis iptables
  • 安装命令:apt-get install iptables
  • 加载内核模块:modprobe ip_tables

二、规则配置

编辑策略文件:vi /etc/iptables.rules

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
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]

# 允许特定IP访问8080
-A INPUT -s 59.109.149.221 -p tcp -m tcp --dport 8080 -j ACCEPT

# 常用业务端口
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 7080 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 7180 -j ACCEPT

# 状态追踪(允许已建立和相关连接)
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Loopback接口(保障本地通信与DNS)
-A OUTPUT -o lo -p all -j ACCEPT
-A INPUT -i lo -p all -j ACCEPT

# SSH连接(端口需与 /etc/ssh/sshd_config 一致)
-A INPUT -p tcp --dport 22 -j ACCEPT
-A OUTPUT -p tcp --sport 22 -j ACCEPT

# ICMP控制(允许ping,丢弃时间戳等探测包)
-I INPUT -p icmp --icmp-type echo-request -j ACCEPT
-A INPUT -p ICMP --icmp-type timestamp-request -j DROP
-A INPUT -p ICMP --icmp-type timestamp-reply -j DROP
-A INPUT -p ICMP --icmp-type time-exceeded -j DROP
-A INPUT -p icmp --icmp-type 8 -s 0/0 -j DROP
-A OUTPUT -p ICMP --icmp-type time-exceeded -j DROP

# 默认拒绝策略
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
-A INPUT -p tcp -m tcp --dport 8080 -j DROP
COMMIT

[!NOTE]
INPUT 为入口,OUTPUT 为出口,REJECT 为禁止。若需完全开放出口,将默认策略改为 -A OUTPUT -j ACCEPT


三、加载与持久化

  • 加载生效:iptables-restore < /etc/iptables.rules
  • 查看规则:iptables -L -n
  • 开机自启配置:
    • 创建软链:ln -s /lib/systemd/system/rc-local.service /etc/systemd/system/
    • 编辑服务:vi /lib/systemd/system/rc-local.service,末尾追加 [Install]
    • 创建脚本:vi /etc/rc.local,写入 iptables-restore < /etc/iptables.rules
    • 赋权:chmod +x /etc/rc.local

四、常用命令速查

4.1 基础管理

1
2
3
4
5
6
7
8
# 查看现有规则
iptables -L -n
# 临时放行所有(防误锁)
iptables -P INPUT ACCEPT
# 清空规则
iptables -F # 清空默认规则
iptables -X # 清空自定义规则
iptables -Z # 计数器归零

4.2 端口与协议

1
2
3
4
5
6
7
# 开放常用端口
iptables -A INPUT -p tcp --dport 22 -j ACCEPT # SSH
iptables -A INPUT -p tcp --dport 21 -j ACCEPT # FTP
iptables -A INPUT -p tcp --dport 80 -j ACCEPT # HTTP
iptables -A INPUT -p tcp --dport 443 -j ACCEPT # HTTPS
# 允许ping
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT

4.3 策略与黑白名单

1
2
3
4
5
6
7
8
9
# 设置默认策略
iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP
# 信任内网IP(放行所有TCP)
iptables -A INPUT -p tcp -s 45.96.174.68 -j ACCEPT
# 封停/解封IP
iptables -I INPUT -s ***.***.***.*** -j DROP
iptables -D INPUT -s ***.***.***.*** -j DROP

4.4 保存规则

1
service iptables save

五、核心概念对照

方向 说明
INPUT 入站 目标为本机的数据包
OUTPUT 出站 源为本机的数据包
FORWARD 转发 经本机路由转发的数据包
状态 说明
ESTABLISHED 已建立的连接
NEW 新连接请求
RELATED 与已有连接关联的数据包
INVALID 无法识别或无效的数据包

一、 概述

TDengine 是一款开源、高性能、云原生的时序数据库(Time Series Database, TSDB),专为物联网、车联网、工业互联网、金融、IT 运维监控等场景设计。

  • 官方文档https://docs.taosdata.com/
  • 核心组件(容器内包含):
    • taosd: 数据库核心服务。
    • taosAdapter: 提供 RESTful 和 WebSocket 接口。
    • taosKeeper: 系统监控指标收集。
    • taosExplorer: Web 管理控制台。

二、 单节点部署(快速体验)

1. 使用 Docker Compose 启动

创建 docker-compose.yml 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.9'
services:
tdengine:
image: tdengine/tdengine:3.3.4.3 # 可使用最新稳定版标签
container_name: tdengine
ports:
- '6030:6030' # taosd 核心服务端口
- '6041:6041' # taosAdapter Restful API
- '6043:6043' # taosKeeper 指标监控
- '6060:6060' # taosExplorer Web控制台
- '6044-6049:6044-6049' # 第三方数据接入
- '6044-6045:6044-6045/udp' # UDP 接入
volumes:
- './data:/var/lib/taos' # 数据持久化
- './log:/var/log/taos' # 日志持久化

启动服务:docker-compose up -d

2. 连接测试与基本操作
  1. 进入容器
    1
    docker exec -it tdengine bash
  2. 生成测试数据(使用官方工具):
    1
    taosBenchmark -y
    此命令会在 test 数据库下创建一张名为 meters 的超级表,包含 10,000 张子表,每表 10,000 条记录。
  3. 使用 CLI 连接数据库
    1
    taos
    1
    2
    3
    -- 在 taos 提示符下执行
    USE test;
    SELECT COUNT(*) FROM meters;
  4. 修改默认密码
    1
    ALTER USER root PASS 'YourNewPassword';
  5. 使用密码登录
    1
    taos -uroot -pYourNewPassword
  6. 访问 Web 控制台
    浏览器访问 http://<服务器IP>:6060,使用数据库用户名密码登录 taosExplorer 进行可视化管理。

三、 生产集群部署

1. 集群规划

假设使用 3 台服务器,所有节点均部署 TDengine。

服务器 IP 地址 部署服务 对外代理端口 (Nginx)
node0 192.168.4.211 TDengine -
node1 192.168.4.115 TDengine -
node2 192.168.0.37 TDengine, Nginx 16041, 16043, 16060

端口说明:每个 TDengine 容器需开放与单节点相同的端口(6030, 6041, 6043, 6060, 6044-6049)。

2. 部署步骤

(1)所有节点:准备配置文件

1
2
3
4
5
6
7
8
9
10
# 1. 启动临时容器获取默认配置
docker run --rm -it --name=tdengine-temp tdengine/tdengine:3.3.4.3 taos

# 2. 另开终端,拷贝配置
docker cp tdengine-temp:/etc/taos ./conf

# 3. 修改配置中的主机名(替换为当前节点IP)
CURRENT_IP=$(YOUR_NODE_IP) # 请替换为实际IP,如 192.168.4.211
cd conf
sed -i "s/buildkitsandbox/$CURRENT_IP/g" explorer.toml taosadapter.toml taos.cfg taoskeeper.toml

(2)所有节点:配置集群首个节点 (firstEp)
编辑 conf/taos.cfg 文件,所有节点的此配置必须相同

1
2
# 指向规划中第一个启动的节点(例如 node0)
firstEp 192.168.4.211:6030

(3)所有节点:启动 TDengine 服务
创建 docker-compose.yml,使用 host 网络模式以便节点间通信。

1
2
3
4
5
6
7
8
9
10
version: '3.9'
services:
tdengine:
image: tdengine/tdengine:3.3.4.3
container_name: tdengine
network_mode: host # 关键:使用主机网络
volumes:
- './data:/var/lib/taos'
- './log:/var/log/taos'
- './conf:/etc/taos' # 挂载自定义配置

启动:docker-compose up -d

启动顺序建议:先启动 firstEp 指向的节点(node0),再启动其他节点。

3. 集群验证与管理
  1. 查看数据节点
    1
    docker exec -it tdengine taos
    1
    SHOW DNODES;
    输出应显示 3 个 statusready 的节点。
  2. 添加管理节点(Mnode)以实现高可用
    首个启动的 dnode 自动成为 Mnode(管理节点)。为防止单点故障,需添加冗余 Mnode。
    1
    2
    3
    4
    -- 在 dnode 2 (node1) 上创建第二个 Mnode
    CREATE MNODE ON DNODE 2;
    -- 查看 Mnode 状态
    SHOW MNODES;
    输出应显示一个 leader 和一个 follower Mnode。

四、 高可用与负载均衡(Nginx 代理)

node2 上部署 Nginx,对外提供统一的访问入口,实现 taosAdapter (REST API)、taosKeeper (监控) 和 taosExplorer (Web) 的负载均衡。

1. 创建 Nginx 配置文件 (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
user root;
worker_processes auto;
events {
use epoll;
worker_connections 1024;
}
http {
access_log off;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

# 1. 代理 taosAdapter (REST API, 6041)
server {
listen 16041;
location / {
proxy_pass http://dbserver;
proxy_read_timeout 600s;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}

# 2. 代理 taosKeeper (监控, 6043)
server {
listen 16043;
location / {
proxy_pass http://keeper;
}
}

# 3. 代理 taosExplorer (Web控制台, 6060)
server {
listen 16060;
location / {
proxy_pass http://explorer;
# 处理 CORS 头部(如果前端需要)
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}

# 上游服务器定义
upstream dbserver {
least_conn; # 最少连接负载均衡
server 192.168.4.211:6041 max_fails=0;
server 192.168.4.115:6041 max_fails=0;
server 192.168.0.37:6041 max_fails=0;
}
upstream keeper {
ip_hash; # 保持会话
server 192.168.4.211:6043;
server 192.168.4.115:6043;
server 192.168.0.37:6043;
}
upstream explorer {
ip_hash; # 保持会话
server 192.168.4.211:6060;
server 192.168.4.115:6060;
server 192.168.0.37:6060;
}
}
2. 使用 Docker Compose 启动 Nginx

创建 docker-compose-nginx.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3.9'
services:
nginx:
restart: always
image: nginx:1.23.1
container_name: nginx-tdengine
ports:
- '16041:16041'
- '16043:16043'
- '16060:16060'
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./nginx-logs:/var/log/nginx

启动:docker-compose -f docker-compose-nginx.yml up -d

3. 验证代理

通过 Nginx 代理访问 REST API:

1
2
3
4
curl -L 'http://192.168.0.37:16041/rest/sql/test' \
-H 'Content-Type: text/plain' \
-H 'Authorization: Basic cm9vdDp0YW9zZGF0YQ==' \ # Base64编码的 root:taosdata
-d 'SELECT * FROM meters LIMIT 1'

应返回包含测试数据的 JSON 响应。

五、 关键注意事项

  1. 版本一致性:集群所有节点必须使用相同版本的 TDengine 镜像。
  2. 网络:集群部署必须使用 host 网络或确保容器间在 6030-6049 端口上能直接通信。
  3. 防火墙:确保服务器防火墙开放相关端口。
  4. 数据持久化:务必挂载 /var/lib/taos 目录以防止数据丢失。
  5. **firstEp**:此配置是集群组建的关键,必须正确且一致。
  6. Mnode:生产环境至少配置 2 个 Mnode 以实现管理层面的高可用。

OpenCV进行RTSP推流

1 概述

将 OpenCV 处理后的图像直接进行 RTSP 推流,支持多种实现方案。


2 测试命令

2.1 推流本地文件

1
gst-launch-1.0 filesrc location=D:\5.mp4 ! decodebin ! videoconvert ! x264enc ! rtspclientsink location=rtsp://127.0.0.1:8554/live

2.2 播放推流内容

1
gst-launch-1.0 rtspsrc location=rtsp://127.0.0.1:8554/live ! rtph264depay ! h264parse ! avdec_h264 ! autovideosink

3 方案一:GStreamer推流

3.1 核心代码

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
#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
#include <opencv2/opencv.hpp>

#define RTSP_SERVER_URL "rtsp://127.0.0.1:8554/live"
#define CAPTURE_WIDTH 640
#define CAPTURE_HEIGHT 480
#define CAPTURE_FPS 30
#define FRAME_INTERVAL_MS (1000 / CAPTURE_FPS)

int main(int argc, char* argv[])
{
// 初始化GStreamer
gst_init(&argc, &argv);
std::cout << "3e" << std::endl;

// 创建管道
GstElement* pipeline = gst_pipeline_new("rtsp-pipeline");
GstElement* appsrc = gst_element_factory_make("appsrc", "app-source");
GstElement* videoconvert = gst_element_factory_make("videoconvert", "convert");
GstElement* x264enc = gst_element_factory_make("x264enc", "encoder");
GstElement* rtph264pay = gst_element_factory_make("rtph264pay", "payloader");
GstElement* rtspclientsink = gst_element_factory_make("rtspclientsink", "sink");

// 配置appsrc
g_object_set(G_OBJECT(appsrc), "is-live", TRUE, NULL);
g_object_set(G_OBJECT(appsrc), "format", GST_FORMAT_TIME, NULL);
g_object_set(G_OBJECT(appsrc), "caps", gst_caps_new_simple("video/x-raw",
"format", G_TYPE_STRING, "BGR",
"width", G_TYPE_INT, CAPTURE_WIDTH,
"height", G_TYPE_INT, CAPTURE_HEIGHT,
"framerate", GST_TYPE_FRACTION, CAPTURE_FPS, 1,
NULL), NULL);

// 添加元素到管道
gst_bin_add_many(GST_BIN(pipeline), appsrc, videoconvert, x264enc, rtph264pay, rtspclientsink, NULL);

// 连接元素
gst_element_link_many(appsrc, videoconvert, x264enc, rtph264pay, rtspclientsink, NULL);

// 配置RTSP目标地址
g_object_set(G_OBJECT(rtspclientsink), "location", RTSP_SERVER_URL, NULL);

// 启动管道
gst_element_set_state(pipeline, GST_STATE_PLAYING);

// 打开摄像头
cv::VideoCapture cap(0);
if (!cap.isOpened()) {
std::cerr << "Error: Unable to open camera." << std::endl;
return -1;
}

// 推流循环
while (true)
{
cv::Mat frame;
cap >> frame;
if (frame.empty()) {
std::cerr << "Warning: Empty frame." << std::endl;
continue;
}

// 打印时间戳(精确到毫秒)
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
auto t = std::chrono::system_clock::to_time_t(now);
auto tp = std::chrono::system_clock::from_time_t(t);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - tp).count();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::cout << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %X") << "." << ms << std::endl;

// 创建GStreamer缓冲区
GstBuffer* buffer = gst_buffer_new_allocate(NULL, frame.total() * frame.elemSize(), NULL);
GstMapInfo map;
gst_buffer_map(buffer, &map, GST_MAP_WRITE);
memcpy(map.data, frame.data, frame.total() * frame.elemSize());
gst_buffer_unmap(buffer, &map);

// 推送缓冲区
GstFlowReturn ret;
g_signal_emit_by_name(appsrc, "push-buffer", buffer, &ret);
gst_buffer_unref(buffer);

// 控制帧率
g_usleep(FRAME_INTERVAL_MS * 1000);
}

// 清理资源
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);

return 0;
}

3.2 流程说明

步骤 操作
1 初始化 GStreamer
2 创建管道和元素(appsrc → videoconvert → x264enc → rtph264pay → rtspclientsink)
3 配置 appsrc 参数(分辨率、帧率、格式)
4 启动管道
5 循环读取摄像头帧并推送

4 方案二:FFmpeg推流

4.1 头文件

1
2
3
#include "rtspencoder.h"
#include <QCoreApplication>
#include <QDebug>

4.2 类定义

1
2
3
4
5
6
7
8
9
10
RTSPEncoder::RTSPEncoder(const char* rtspUrl, int outputWidth, int outputHeight, QObject* parent)
: QThread(parent), m_rtspUrl(rtspUrl), m_outputWidth(outputWidth), m_outputHeight(outputHeight)
{

}

RTSPEncoder::~RTSPEncoder()
{
cleanup();
}

4.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
int RTSPEncoder::initialize()
{
avformat_network_init();

// 初始化输出上下文
m_outFormatCtx = nullptr;
avformat_alloc_output_context2(&m_outFormatCtx, nullptr, "rtsp", m_rtspUrl.c_str());
if (!m_outFormatCtx)
{
std::cerr << "Error: Failed to allocate output context\n";
return -1;
}

// 查找H264编码器
m_codec = (AVCodec*)avcodec_find_encoder(AV_CODEC_ID_H264);
if (!m_codec) {
std::cerr << "Error: Failed to find H264 encoder\n";
return -1;
}

// 添加视频流
m_outStream = avformat_new_stream(m_outFormatCtx, m_codec);
if (!m_outStream) {
std::cerr << "Error: Failed to create new stream\n";
return -1;
}

// 初始化编码器上下文
m_codecCtx = avcodec_alloc_context3(m_codec);
if (!m_codecCtx) {
std::cerr << "Error: Failed to allocate codec context\n";
return -1;
}
m_codecCtx->codec_id = AV_CODEC_ID_H264;
m_codecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
m_codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
m_codecCtx->width = m_outputWidth;
m_codecCtx->height = m_outputHeight;
m_codecCtx->time_base = { 1, 25 }; // 25fps
m_codecCtx->bit_rate = 700000;
m_codecCtx->gop_size = 10;
m_codecCtx->max_b_frames = 0;
m_codecCtx->flags = AV_CODEC_FLAG_GLOBAL_HEADER | AV_CODEC_FLAG_LOW_DELAY;

// 配置编码器选项
AVDictionary* codecOptions = nullptr;
av_dict_set(&codecOptions, "preset", "ultrafast", 0);
av_dict_set(&codecOptions, "tune", "zerolatency", 0);

// 打开编码器
if (avcodec_open2(m_codecCtx, m_codec, &codecOptions) < 0) {
std::cerr << "Error: Failed to open encoder\n";
return -1;
}

// 复制编码器参数到输出流
avcodec_parameters_from_context(m_outStream->codecpar, m_codecCtx);

// 写入流头部
avformat_write_header(m_outFormatCtx, nullptr);

m_frame.create(m_outputHeight, m_outputWidth, CV_8UC3);
m_frame.setTo(0);
m_frameCount = 0;

m_avFrame = av_frame_alloc();

return 0;
}

4.4 编码与发送

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
void RTSPEncoder::encodeFrameAndSend()
{
while (1)
{
// 阻塞等待新帧
{
std::unique_lock<std::mutex> lock(mtx);

while (!m_isReady) {
m_cv.wait(lock);
}
m_isReady = false;

// 转换颜色空间 BGR → I420
cv::cvtColor(m_frame, m_im420, cv::COLOR_RGB2YUV_I420);

// 配置AVFrame
m_avFrame->data[0] = m_im420.data;
m_avFrame->data[1] = m_avFrame->data[0] + m_codecCtx->width * m_codecCtx->height;
m_avFrame->data[2] = m_avFrame->data[1] + m_codecCtx->width * m_codecCtx->height / 4;
m_avFrame->linesize[0] = m_codecCtx->width;
m_avFrame->linesize[1] = m_codecCtx->width / 2;
m_avFrame->linesize[2] = m_codecCtx->width / 2;
m_avFrame->width = m_codecCtx->width;
m_avFrame->height = m_codecCtx->height;
m_avFrame->format = AV_PIX_FMT_YUV420P;
m_avFrame->pts = m_frameCount++;

// 发送帧到编码器
auto ret = avcodec_send_frame(m_codecCtx, m_avFrame);
if (ret < 0)
{
std::cerr << "Error: Failed to send frame for encoding\n";
return;
}

// 接收编码后的数据包
AVPacket pkt;
av_init_packet(&pkt);
while (ret >= 0)
{
ret = avcodec_receive_packet(m_codecCtx, &pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if (ret < 0)
{
std::cout << "Error: Failed to receive encoded packet\n";
continue;
}
pkt.stream_index = m_outStream->index;
av_packet_rescale_ts(&pkt, m_codecCtx->time_base, m_outStream->time_base);
ret = av_interleaved_write_frame(m_outFormatCtx, &pkt);
av_packet_unref(&pkt);

if (ret < 0)
{
std::cout << "Error: Failed to write packet to output stream\n";
continue;
}
}
}
}
}

4.5 添加帧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void RTSPEncoder::AddFrame(cv::Mat& imGBR, int nFrame)
{
if (imGBR.cols != m_outputWidth || imGBR.rows != m_outputHeight)
return;

// 阻塞等待编码
{
std::lock_guard<std::mutex> lock(mtx);
memcpy(m_frame.data, imGBR.data, imGBR.cols * imGBR.rows * 3);
m_frameCount = nFrame;
m_isReady = true;
}
m_cv.notify_one();
}

4.6 资源清理

1
2
3
4
5
6
7
8
9
10
11
12
13
void RTSPEncoder::cleanup()
{
// 写入尾部
av_write_trailer(m_outFormatCtx);

// 释放资源
if(m_codecCtx)
avcodec_free_context(&m_codecCtx);
if(m_avFrame)
av_frame_free(&m_avFrame);
if(m_outFormatCtx)
avformat_free_context(m_outFormatCtx);
}

5 方案三:OpenCV GStreamer后端

5.1 推流代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void push()
{
int codec = cv::VideoWriter::fourcc('H', 'E', 'V', 'C');
m_writer.open(push_str, codec, fps, cv::Size(width, height));

cv::Mat tmp;
while (true)
{
{
std::unique_lock<std::mutex> lk(proc2push_mtx);
proc2push_cv.wait(lk);
tmp = img_push.clone();
}

if (!m_writer.isOpened())
{
int codec = cv::VideoWriter::fourcc('H', 'E', 'V', 'C');
m_writer.open(push_str, codec, fps, cv::Size(width, height));
}

m_writer.write(tmp);
}
}

5.2 优缺点

方案 优点 缺点
GStreamer 灵活性高,延迟低 代码复杂
FFmpeg 功能强大,兼容性好 学习成本高
OpenCV GStreamer 实现简单 延迟较高

6 测试代码

6.1 视频源测试

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
int main1()  
{
// USB摄像头(GStreamer格式)
std::string pipeline = "v4l2src device=/dev/video0 ! image/jpeg,width=1920,height=1080,framerate=30/1 ! nvv4l2decoder mjpeg=1 ! nvvidconv ! appsink";
cv::VideoCapture capture(pipeline, cv::CAP_GSTREAMER);

if (!capture.isOpened())
{
std::cout << "Read video Failed !" << std::endl;
return -1;
}

cv::Mat frame;
cv::namedWindow("video test");

int frame_num = capture.get(cv::CAP_PROP_FRAME_COUNT);
std::cout << "total frame number is: " << frame_num << std::endl;

while(1)
{
capture >> frame;
imshow("video test", frame);
if (cv::waitKey(30) == 'q')
{
break;
}
}

cv::destroyWindow("video test");
capture.release();
return 0;
}

6.2 GStreamer命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 文件推流
gst-launch-1.0 -v filesrc location=D:/1.mp4 ! decodebin ! x264enc ! rtph264pay ! udpsink host=127.0.0.1 port=8554

# RTSP播放
gst-launch-1.0 -v rtspsrc location=rtsp://127.0.0.1:10054/live/Fs4FsmKSR ! rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! autovideosink

# 测试源
gst-launch-1.0 -v videotestsrc ! video/x-raw, format=BGRx ! autovideosink

# AppSource推流
gst-launch-1.0 -v appsrc name=mysource is-live=true block=true format=GST_FORMAT_TIME \
! videoconvert \
! video/x-raw,format=NV12 \
! nvv4l2h264enc \
! video/x-h264,stream-format=byte-stream \
! h264parse \
! rtspclientsink location=rtsp://localhost:8554/mystream

7 总结

方案 适用场景
GStreamer 低延迟、高性能推流
FFmpeg 复杂编码需求、跨平台
OpenCV GStreamer 简单场景、快速实现

本文档整合了多种SOCKS5代理服务器的搭建方法,包括SS5DanteSquidMicrosocks,旨在为不同需求和场景提供选择。

一、 方案概览与选择

方案 特点 适用场景 复杂度
SS5 经典SOCKS5服务器,功能全面,支持用户认证。 需要稳定、功能完整的SOCKS5代理。 中等
Dante 轻量级、高性能的SOCKS4/5服务器,配置灵活。 对性能和资源占用有要求的生产环境。 中等
Squid 功能强大的HTTP/HTTPS缓存代理,也支持SOCKS。 主要需求为Web缓存,同时需要SOCKS功能。 较高
Microsocks 极致轻量、内存占用极小的SOCKS5服务器。 资源受限的VPS、临时或简单代理需求。

环境建议:CentOS 7.x, Debian 7+, Ubuntu 14.04+。避免使用CentOS 8.x(兼容性问题)。确保服务器防火墙/安全组已开放代理端口。


二、 SS5 搭建方案

SS5是一个功能完整的SOCKS5代理服务器。

1. 安装依赖与源码

1
2
3
4
5
6
7
yum -y install gcc gcc-c++ automake make pam-devel openldap-devel cyrus-sasl-devel openssl openssl-devel
wget http://nchc.dl.sourceforge.net/project/ss5/ss5/3.8.9-8/ss5-3.8.9-8.tar.gz
tar -zxvf ss5-3.8.9-8.tar.gz
cd ss5-3.8.9/
./configure
make
make install

2. 服务注册与自启

1
2
3
chmod +x /etc/init.d/ss5
chkconfig --add ss5
chkconfig ss5 on

3. 核心配置

  • 认证配置 (/etc/opt/ss5/ss5.conf):
    1
    2
    auth    0.0.0.0/0       -       u
    permit u 0.0.0.0/0 - 0.0.0.0/0 - - - - -
  • 用户密码 (/etc/opt/ss5/ss5.passwd):
    1
    username password
  • 修改端口 (/etc/sysconfig/ss5):
    1
    SS5_OPTS=" -u root -b 0.0.0.0:8899"

4. 启动与测试

1
service ss5 start  # 或 restart

使用Proxifier等客户端工具,配置代理服务器地址和端口进行连接测试。


三、 Dante 搭建方案

Dante是一个高效、配置灵活的SOCKS服务器。

1. 安装 (CentOS 7)

1
2
rpm -Uvh http://mirror.ghettoforge.org/distributions/gf/gf-release-latest.gf.el7.noarch.rpm
yum --enablerepo=gf-plus install dante-server -y

2. 基础配置 (/etc/sockd.conf)
以下是一个允许所有连接(无认证)的简单配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
errorlog: /var/log/sockd.errlog
logoutput: /var/log/sockd.log

internal: eth0 port = 1080 # 监听端口
external: eth0

method: none # 无认证

client pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
}

socks pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
}

注意:需创建日志目录 mkdir /var/run/sockd

3. 启动与管理

1
2
systemctl start sockd
systemctl enable sockd

4. 高级配置

  • 用户认证:将 method: none 改为 method: username,并使用系统用户登录。
  • 访问控制:在 client passsocks pass 块中,通过 from: / to: 字段精细控制源IP和目标地址。

四、 Squid 搭建方案 (支持正向/透明代理)

Squid主要用作HTTP代理,但也可配置为SOCKS代理,并支持强大的访问控制。

1. 安装

1
2
3
sudo apt-get install squid -y  # Ubuntu/Debian
# 或安装支持SSL的版本以启用HTTPS透明代理
sudo apt-get install squid-openssl -y

2. 基础正向代理配置

  • 编辑 /etc/squid/squid.conf,添加允许的客户端网段:
    1
    acl localnet src 172.28.0.0/16
  • /etc/squid/conf.d/debian.conf 中取消注释:
    1
    http_access allow localnet
  • 重启服务:sudo service squid restart
  • 客户端在浏览器或系统设置中配置HTTP代理为Squid服务器IP:3128。

3. 强大的访问控制示例 (在squid.conf中配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 禁止特定IP
acl badhost src 172.28.18.7
http_access deny badhost

# 2. 禁止特定时间段
acl worktime time MTWHF 17:00-18:00
http_access deny worktime

# 3. 禁止特定域名
acl baddomain dstdomain .taobao.com .pinduoduo.com
http_access deny baddomain

# 4. 禁止访问所有IP地址(防域名绕过)
acl ipurl url_regex ^http://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/
http_access deny ipurl

4. 透明代理配置 (需双网卡)

  • 配置Squid:在 squid.conf 中添加拦截端口。
    1
    2
    http_port 3129 intercept  # HTTP透明代理
    https_port 3130 ssl-bump cert=/etc/squid/certs/myCA.pem transparent # HTTPS透明代理
  • 生成自签名CA证书 (用于HTTPS解密):
    1
    openssl req -new -newkey rsa:4096 -sha256 -days 3650 -nodes -x509 -keyout myCA.pem -out myCA.pem
  • 配置iptables规则,将客户端的80/443流量重定向到Squid。
  • 客户端需将网关和DNS指向Squid服务器内网IP,并导入生成的CA证书。

五、 Microsocks 搭建方案 (极致轻量)

适用于内存资源紧张的VPS。

1. 安装

  • Debian/Ubuntu新版本sudo apt install microsocks
  • 旧版本或从源码安装
    1
    2
    3
    4
    wget http://ftp.barfooze.de/pub/sabotage/tarballs/microsocks-1.0.4.tar.xz
    tar -Jxf microsocks-1.0.4.tar.xz
    cd microsocks-*/
    make && sudo make install

2. 直接运行

1
microsocks -1 -u USERNAME -P PASSWORD
  • -1: 认证一次后IP加入白名单。
  • -u / -P: 用户名和密码。
  • 默认监听 0.0.0.0:1080

3. 配置为Systemd服务 (推荐)

  • 创建服务文件 /etc/systemd/system/microsocks.service
  • 创建配置文件 /etc/microsocks.conf,定义 MICROSOCKS_LOGINMICROSOCKS_PASSW
  • 启用服务:sudo systemctl enable --now microsocks

六、 客户端连接与测试

  1. **命令行测试 (Linux/Mac)**:
    1
    curl --socks5 USER:PASS@SERVER_IP:PORT https://www.google.com/
  2. 浏览器设置:在网络设置中手动配置SOCKS5代理。
  3. 专业工具:使用 Proxifier (Windows/macOS) 或 Proxy SwitchyOmega (浏览器插件) 进行全局或按规则代理。
  4. **环境变量 (Linux)**:
    1
    2
    export http_proxy=socks5://SERVER_IP:PORT
    export https_proxy=socks5://SERVER_IP:PORT

七、 故障排查

  1. 连接失败
    • 检查服务器防火墙/安全组规则是否放行代理端口。
    • 确认代理服务正在运行 (systemctl status xxx)。
    • 查看服务日志 (journalctl -xe, /var/log/xxx.log)。
  2. 认证失败:检查配置文件的用户名/密码部分,确保无拼写错误。
  3. Squid HTTPS代理失败:确保使用了 squid-openssl,正确生成并配置了CA证书,且客户端已导入该证书。

八、 总结与建议

  • 追求轻量与简单:首选 Microsocks
  • 需要稳定完整的SOCKS5服务:选择 SS5Dante
  • 主要需求是Web缓存和复杂的内容过滤:选择 Squid
  • 生产环境:务必配置用户认证和基于IP的**访问控制列表(ACL)**,并定期检查日志。

请根据您的具体需求(性能、功能、易用性、资源占用)选择最合适的方案进行部署。

一、开启 Docker Compatibility 模式

在 Podman Desktop 设置中启用 **Experimental (Docker Compatibility)**:

1
2
3
4
5
6
# 启用后即可使用 docker CLI 语法
[user@kkbruce ~]$ docker version
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
Client: Podman Engine
Version: 5.2.3
...

二、独立安装 Docker CLI

因未安装 Docker Desktop,需通过 winget 独立安装:

1
PS C:\> winget install Docker.DockerCLI

安装完成后重启 PowerShell,验证连接:

1
2
3
4
5
6
7
8
9
PS C:\> docker version
Client:
Version: 27.3.1
Context: default

Server: linux/amd64/fedora-40
Podman Engine:
Version: 5.2.3
...

三、验证 Docker CLI 与 Podman 整合

1
2
3
4
5
6
7
8
9
# 拉取镜像
PS C:\> docker pull nginx

# 确认镜像列表
PS C:\> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 60c8a892f36f 6 weeks ago 196MB
mcr.microsoft.com/dotnet/aspnet 8.0 6d7e7cb08845 5 days ago 221MB
webapplication1 dev e6090be04289 2 minutes ago 221MB

四、Visual Studio Container Tools 正常运行

创建 ASP.NET Core MVC 项目并启用容器支持,确认:

  • 无错误信息
  • Container Tools 正常运行
  • 可成功构建镜像、运行容器

关键技术点

项目 说明
Podman Desktop Windows 环境运行 WSL2,后端为 podman-machine
Docker CLI Context npipe:////./pipe/docker_engine 与 Podman 通信
Experimental Compatibility 启用后 docker CLI 可兼容操作 Podman
独立 Docker CLI 通过 winget 安装 Docker.DockerCLI

待补充

  • Podman Compose 替代 Docker Compose
  • Kubernetes 集群整合验证
  • 多阶段构建 Dockerfile 优化

一、准备工作

1.1 获取服务器

本文使用阿里云 ECS 服务器(香港节点,CentOS 7.9 64位)进行演示。学生用户可通过阿里云学生计划免费领取服务器。

重要信息记录

  • 公网 IP 地址:在服务器控制台查看并记录(后续配置需要)
  • 操作系统:CentOS 7.9 64位

1.2 安装 Docker(如未安装)

1.2.1 更新系统并安装依赖

1
2
sudo yum update -y
sudo yum install -y yum-utils device-mapper-persistent-data lvm2

1.2.2 添加 Docker 官方仓库

1
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

1.2.3 安装 Docker

1
sudo yum install -y docker-ce docker-ce-cli containerd.io

1.2.4 启动并设置开机自启

1
2
sudo systemctl start docker
sudo systemctl enable docker

1.2.5 验证安装

1
2
3
4
5
# 查看 Docker 版本
docker --version

# 测试 Docker 运行
sudo docker run hello-world

验证成功标志:能看到 hello-world 镜像拉取成功并运行。


二、配置 VPN 服务器

2.1 创建配置目录

1
2
mkdir -p /vpn
cd /vpn

2.2 创建环境配置文件

创建并编辑 .env 文件:

1
2
3
nano /vpn/.env
# 或使用 vim
# vim /vpn/.env

2.3 配置文件内容

将以下配置粘贴到 .env 文件中,根据实际情况修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# IPsec 预共享密钥(重要!)
VPN_IPSEC_PSK=AKIDkQD7!

# 主 VPN 账号和密码
VPN_USER=test
VPN_PASSWORD=vpn1234

# 服务器公网 IP(必须修改为你的服务器 IP)
VPN_PUBLIC_IP=36.111.xxx.xxx

# 额外 VPN 用户(用户名用空格分隔)
VPN_ADDL_USERS=vpn1 vpn2

# 对应额外用户的密码(顺序与用户名对应)
VPN_ADDL_PASSWORDS=vpn11234 pass21234

# DNS 服务器配置
VPN_DNS_SRV1=8.8.8.8
VPN_DNS_SRV2=114.114.114.114

配置说明

  • VPN_PUBLIC_IP:必须替换为你的服务器公网 IP
  • VPN_IPSEC_PSK:建议使用强密码,包含大小写字母、数字和特殊字符
  • 多个用户配置:确保用户名和密码顺序对应

三、部署 VPN 容器

3.1 首次运行容器

1
2
3
4
5
6
7
8
docker run \
--name ipsec-vpn-server \
--env-file /vpn/.env \
--restart=always \
-p 500:500/udp \
-p 4500:4500/udp \
-v /lib/modules:/lib/modules:ro \
-d hwdsl2/ipsec-vpn-server

3.2 处理特权模式错误

如果出现错误提示需要特权模式,按以下步骤操作:

停止并删除现有容器

1
2
docker stop ipsec-vpn-server
docker rm ipsec-vpn-server

重新以特权模式运行

1
2
3
4
5
6
7
8
9
docker run \
--name ipsec-vpn-server \
--env-file /vpn/.env \
--restart=always \
--privileged \
-p 500:500/udp \
-p 4500:4500/udp \
-v /lib/modules:/lib/modules:ro \
-d hwdsl2/ipsec-vpn-server

3.3 检查容器状态

1
2
3
4
5
# 查看容器运行状态
docker ps

# 查看容器日志
docker logs ipsec-vpn-server

成功标志:日志中显示 IPsec VPN server started 或类似信息。


四、配置服务器防火墙

4.1 开放必要端口

VPN 服务需要以下 UDP 端口:

  • 500:ISAKMP(IKE 协商)
  • 4500:NAT-T(NAT 穿越)
  • 1701:L2TP(可选)

方法一:使用 iptables(命令行)

1
2
3
4
5
6
7
8
9
10
11
# 开放 UDP 500 端口
sudo iptables -A INPUT -p udp --dport 500 -j ACCEPT

# 开放 UDP 4500 端口
sudo iptables -A INPUT -p udp --dport 4500 -j ACCEPT

# 开放 UDP 1701 端口(L2TP)
sudo iptables -A INPUT -p udp --dport 1701 -j ACCEPT

# 保存规则(CentOS 7)
sudo service iptables save

方法二:使用云控制台(推荐)

以阿里云为例:

  1. 登录阿里云控制台
  2. 进入 ECS 实例详情页
  3. 找到「安全组」配置
  4. 点击「配置规则」
  5. 手动添加以下入方向规则:
授权策略 协议类型 端口范围 授权对象 描述
允许 UDP 500/500 0.0.0.0/0 IPsec ISAKMP
允许 UDP 4500/4500 0.0.0.0/0 IPsec NAT-T
允许 UDP 1701/1701 0.0.0.0/0 L2TP(可选)

4.2 重启容器使配置生效

1
docker restart ipsec-vpn-server

五、Windows 客户端连接配置

5.1 创建 VPN 连接

  1. 打开「设置」→「网络和 Internet」→「VPN」
  2. 点击「添加 VPN 连接」
  3. 填写以下信息:
    • VPN 提供商:Windows(内置)
    • 连接名称:自定义(如 MyVPN)
    • 服务器名称或地址:你的服务器公网 IP
    • VPN 类型:使用预共享密钥的 L2TP/IPsec
    • 预共享密钥.env 文件中的 VPN_IPSEC_PSK
    • 登录信息的类型:用户名和密码
    • 用户名.env 文件中的 VPN_USER
    • 密码.env 文件中的 VPN_PASSWORD

5.2 修改注册表(解决连接问题)

以管理员身份打开 PowerShell 或命令提示符,执行:

1
REG ADD HKLM\SYSTEM\CurrentControlSet\Services\PolicyAgent /v AssumeUDPEncapsulationContextOnSendRule /t REG_DWORD /d 0x2 /f

5.3 连接 VPN

  1. 执行上述命令后重启计算机
  2. 在系统托盘点击网络图标
  3. 选择创建的 VPN 连接
  4. 点击「连接」
  5. 输入用户名和密码(如已保存则自动连接)

六、Android 客户端连接配置

6.1 创建 VPN 连接

  1. 打开「设置」→「更多连接」→「VPN」
  2. 点击「添加 VPN」或「+」
  3. 填写以下信息:
    • 名称:自定义(如 MyVPN)
    • 类型:L2TP/IPSec PSK
    • 服务器地址:你的服务器公网 IP
    • L2TP 密钥:留空
    • IPSec 标识符:留空
    • IPSec 预共享密钥.env 文件中的 VPN_IPSEC_PSK
    • 用户名.env 文件中的 VPN_USER
    • 密码.env 文件中的 VPN_PASSWORD

6.2 保存并连接

  1. 点击「保存」
  2. 在 VPN 列表中选择刚创建的连接
  3. 点击「连接」

七、故障排除

7.1 常见问题及解决

问题1:容器启动失败,提示特权模式错误

解决:使用 --privileged 参数重新运行容器(见3.2节)

问题2:客户端连接超时

检查步骤

  1. 确认服务器公网 IP 正确
  2. 检查防火墙端口是否开放
  3. 验证容器是否正常运行
    1
    docker logs ipsec-vpn-server

问题3:Windows 连接错误 789

解决

  1. 确保已执行注册表修改命令(5.2节)
  2. 重启计算机
  3. 检查预共享密钥是否正确

问题4:能连接但无法上网

检查

  1. 确认 DNS 配置正确
  2. 检查服务器是否开启 IP 转发
    1
    echo 1 > /proc/sys/net/ipv4/ip_forward

7.2 查看连接状态

1
2
3
4
5
# 查看容器实时日志
docker logs -f ipsec-vpn-server

# 查看当前连接用户
docker exec ipsec-vpn-server ipsec status

八、安全建议

8.1 密码安全

  1. 使用强密码:预共享密钥和用户密码应包含大小写字母、数字和特殊字符
  2. 定期更换:建议每月更换一次密码
  3. 最小权限:只为必要用户创建 VPN 账号

8.2 服务器安全

  1. 限制访问:在安全组中限制源 IP(如只允许特定国家或 IP 段)
  2. 监控日志:定期检查 VPN 连接日志
  3. 更新维护:定期更新 Docker 镜像和系统

8.3 备份配置

1
2
3
4
5
# 备份 .env 配置文件
cp /vpn/.env /vpn/.env.backup.$(date +%Y%m%d)

# 导出容器配置
docker export ipsec-vpn-server > /vpn/vpn-backup.tar

九、总结

通过以上步骤,你已成功在 CentOS 7 服务器上使用 Docker 搭建了 IPsec VPN 服务器。关键要点:

  1. 环境准备:确保 Docker 正确安装
  2. 配置正确.env 文件中的公网 IP 必须准确
  3. 端口开放:确保 UDP 500 和 4500 端口对外开放
  4. 客户端配置:注意 Windows 需要修改注册表

优势

  • 使用 Docker 部署,简单快速
  • 支持多用户同时连接
  • 跨平台客户端支持(Windows、Android、iOS、macOS)
  • 配置易于备份和迁移

后续维护

  • 定期检查容器日志
  • 关注安全更新
  • 监控服务器资源使用情况

现在你可以安全地通过 VPN 访问你的服务器资源,或用于保护公共 Wi-Fi 下的网络通信。

1 概述

Android中的SlidingMenu组件允许用户通过侧滑来展示或隐藏侧边栏,通常用于导航选项或额外功能的展示。

核心特性

  • 由Jeff Gilfelt开发
  • 提供易于使用的API
  • 支持与Fragment集成
  • 可自定义滑动方向和模式

虽然新材料倾向于使用NavigationView,SlidingMenu仍然在旧版本或需要定制的项目中有其应用价值。


2 组件集成

2.1 添加依赖

build.gradle 中添加:

1
2
3
dependencies {
implementation 'com.jeremyfeinstein.slidingmenu:library:1.3@aar'
}

2.2 布局配置

1
2
3
4
<com.jeremyfeinstein.slidingmenu.lib.SlidingMenu
android:id="@+id/slidingmenulayout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

2.3 Activity初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends SlidingFragmentActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 配置SlidingMenu
setBehindContentView(R.layout.menu_frame);
getSlidingMenu().setMode(SlidingMenu.LEFT);
getSlidingMenu().setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
getSlidingMenu().setShadowWidthRes(R.dimen.shadow_width);
getSlidingMenu().setShadowDrawable(R.drawable.shadow_left);
getSlidingMenu().setBehindOffsetRes(R.dimen.slidingmenu_offset);
getSlidingMenu().setFadeDegree(0.35f);
}
}

3 常用配置

3.1 菜单属性设置

方法 说明
setMenuWidth(int) 设置菜单宽度
setShadowWidthRes(int) 设置阴影宽度
setShadowDrawable(int) 设置阴影图片
setTouchModeAbove(int) 设置触摸模式
setFadeDegree(float) 设置渐变程度

3.2 触摸模式

1
2
3
4
5
6
7
8
// 仅在菜单显示时允许滑动
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_NONE);

// 全屏可滑动
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);

// 边缘滑动
slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_MARGIN);

3.3 滑动方向

1
2
3
4
5
6
7
8
// 左侧滑动
slidingMenu.setMode(SlidingMenu.LEFT);

// 右侧滑动
slidingMenu.setMode(SlidingMenu.RIGHT);

// 双侧滑动
slidingMenu.setMode(SlidingMenu.LEFT_RIGHT);

4 Fragment集成

4.1 创建菜单Fragment

1
2
3
4
5
6
7
8
9
public class MenuFragment extends Fragment {

@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_menu, container, false);
}
}

4.2 加载Fragment

1
2
3
4
5
6
7
8
9
10
11
// 在Activity中加载菜单Fragment
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.menu_frame, new MenuFragment())
.commit();

// 加载内容Fragment
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content_frame, new HomeFragment())
.commit();

4.3 布局适配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<!-- 菜单区域 -->
<FrameLayout
android:id="@+id/menu_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#FF0000"/>

<!-- 主内容区域 -->
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@id/menu_frame"
android:background="#00FF00"/>

</RelativeLayout>

5 高级配置

5.1 滑动模式

模式 说明
SLIDING_CONTENT_ONLY 仅内容区域滑动
SLIDING_WINDOW 整个窗口滑动
SLIDING_CONTENT_FLIPPED 内容区域翻转

5.2 动态切换模式

1
2
3
4
5
// 切换到内容滑动模式
slidingMenu.setMode(SlidingMenu.SLIDING_CONTENT_ONLY);

// 切换到窗口滑动模式
slidingMenu.setMode(SlidingMenu.SLIDING_WINDOW);

5.3 事件监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
slidingMenu.setOnOpenListener(new SlidingMenu.OnOpenListener() {
@Override
public void onOpen() {
// 菜单打开时触发
}
});

slidingMenu.setOnCloseListener(new SlidingMenu.OnCloseListener() {
@Override
public void onClose() {
// 菜单关闭时触发
}
});

slidingMenu.setOnClosedListener(new SlidingMenu.OnClosedListener() {
@Override
public void onClosed() {
// 菜单完全关闭后触发
}
});

6 触摸事件处理

6.1 自定义触摸监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 触摸按下
break;
case MotionEvent.ACTION_MOVE:
// 触摸移动
break;
case MotionEvent.ACTION_UP:
// 触摸抬起
break;
}
return true;
}
});

6.2 事件分发机制

方法 说明
dispatchTouchEvent 事件分发
onInterceptTouchEvent 事件拦截
onTouchEvent 事件处理

7 动画效果

7.1 内置动画

1
2
3
4
// 透明度动画
AlphaAnimation alphaAnim = new AlphaAnimation(0.0f, 1.0f);
alphaAnim.setDuration(500);
view.startAnimation(alphaAnim);

7.2 动画集合

1
2
3
4
AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(alphaAnim);
animationSet.addAnimation(translateAnim);
view.startAnimation(animationSet);

8 实践技巧

8.1 常见问题

问题 解决方案
菜单与内容不同步 确保Fragment生命周期同步
滑动卡顿 优化布局层级,减少复杂视图
阴影不显示 检查阴影资源文件是否存在

8.2 性能优化

  • 减少布局层级
  • 使用硬件加速
  • 合理设置动画帧率(60fps)
  • 避免频繁的Fragment事务

8.3 用户体验原则

  • 一致性:保持应用风格统一
  • 反馈:对用户操作给予即时反馈
  • 简化:简化操作流程,避免复杂交互

9 示例项目

推荐参考 zhy_slidemenu_demo 示例项目,学习不同场景下的SlidingMenu使用方式。


总结

SlidingMenu是Android实现侧滑菜单的经典方案,通过本文可掌握:

  • 组件集成与配置
  • Fragment集成方法
  • 触摸事件处理
  • 动画效果实现
  • 性能优化技巧

适用于需要侧滑导航的Android应用开发场景。

0%