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 内核级有状态防火墙。支持连接跟踪,包含四种有效状态:ESTABLISHED、INVALID、NEW、RELATED。


一、安装与检测

  • 检测安装: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 个 status 为 ready 的节点。
  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代理服务器的搭建方法,包括SS5、Dante、Squid和Microsocks,旨在为不同需求和场景提供选择。

一、 方案概览与选择

方案 特点 适用场景 复杂度
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 pass 和 socks 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_LOGIN 和 MICROSOCKS_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服务:选择 SS5 或 Dante。
  • 主要需求是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%