SSH端口转发远程访问 Stable Diffusion

为了随时随地都能愉快的使用 SD,打算把自己的 SD 部署到公网上。不要问为什么不直接上云部署平台,问就是没钱,不管按次或者按时收费在我自己有部署好的设备的情况下都不划算。(并且受限制画不了涩图)
尝试了以下方法进行远程画图。

方案一:贝锐花生壳

花生壳是一款用于实现远程访问和管理设备的工具,无需依赖公网 IP,简单易用、高安全性、支持多平台使用。为用户提供了简单而安全的远程访问和管理设备的方式。无论是个人用户还是企业用户,都可以通过该客户端轻松地实现设备的远程访问和管理。

  • 优势:部署简单、快速,小白也能快速实现远程访问。
  • 劣势:贵。体验版限制流量、带宽,付费版最少的也要398一年。

方案二:云服务器端口转发

本机没有公网 IP 也没有 IPV6,可以通过云服务器的弹性公网 IP 进行端口转发实现内网穿透,并且有了云服务器后面也可以尝试接入物联网玩玩。

  • 优势:便宜,华为2核2G3M云服务器36一年,月流量 400G,并且可拓展性强,适合爱折腾的人玩。
  • 劣势:操作会稍微复杂一点点。

教程

SD 启动器设置

为了能在远程访问 SD,需要在启动器中进行一下设置。在启动器的高级选项中找到网络设置,然后在监听设置中打开开放远程连接通过Gradio共享。监听端口可以自行修改,我这里保持默认设置的7860不变。为了安全可以继续往下翻到安全性设置,在登录凭据管理中添加 Gradio 账号密码,这样的话登录 SD 就需要密码了。

云服务器设置

  1. 购买一台云服务器。我这里以华为云服务器为例。(不是广告,你自己想买哪个就买哪个)

  2. 我的服务器安装的是 Ubuntu 系统,到手后先重置密码,然后远程登录。

  3. ssh开启配置使其生效
    出于安全考虑,如果 SSH 服务器 没有开启允许 GatewayPorts 选项,则即使指定了 远程 IP,也仍然只会绑定到 127.0.0.1
    在 /etc/ssh/sshd_config 配置文件中找到 GatewayPorts,设置为 yes,并重启 ssh 服务。
    使用以下命令重启 SSH 服务,这将应用你所做的任何更改到 /etc/ssh/sshd_config 文件中:

    • sudo systemctl restart sshd
      或者,如果你的系统使用的是 service 命令:
    • sudo service ssh restart

    重启服务后,你可以再次检查 SSH 服务的状态,确保它正在运行:

    • sudo systemctl status sshd
      或者
    • sudo service ssh status
  4. 修改安全组
    在安全组中配置入规则方向,添加规则,协议端口选择 80,源地址选择 0.0.0.0/0

本机设置

  1. 使用ping命令验证网络连通性
    ICMP协议用于网络消息的控制和传递,因此在进行一些基本测试操作之前,需要开通ICMP协议访问端口。比如,您需要在某个个人PC上使用ping命令来验证云服务器的网络连通性,则您需要在云服务器所在安全组的入方向添加以下规则,放通ICMP端口。
方向 优先级 策略 类型 协议端口 源地址
入方向 1 允许 IPv4 ICMP: 全部 IP地址:0.0.0.0/0
  1. 端口转发
    win+r 打开 cmd,在控制台里输入下面的指令,输入服务器的密码并回车
    1
    2
    3
    4
    5
    6
    7
    ssh -N -R 46000:localhost:7860 root@ip

    # 46000 绑定的云服务器的端口,随便绑一个
    # localhost 本机的IP
    # 7860 实际想访问的本地端口
    # root 云服务器的用户名,默认root
    # ip 云服务器的公网IP

注意:国内80、443端口需要备案,谨慎使用或不要使用

  1. 网页输入云服务器的公网IP,此时不出意外就应该可以打开 SD 了。

远程访问文件夹

虽然现在可以远程访问SD了,但是画的图仍然是保存在本机上的,如果远程访问的人要文件还得到主机上操作。刚好 SD 用的 gradio 能够实现快速部署 webUI,我也干脆直接使用 gradio 来配置远程访问文件夹。

下载 gradio

直接pip install gradio就行,但是如果用 anaconda 配置环境的记得切换一个新的环境,不知道怎么的我下载的版本和原来的环境有冲突直接给干废了,直接重新装了 anaconda。

源代码

现学了一晚上 gradio,写的大概能用就行了。代码运行后和上面的操作一样就可以直接远程访问了。记得安全组放行。

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
import gradio as gr
import os
from PIL import Image


num = 0 # 当前图片位置
images = []
folder_path = 'D:\SD\sd-webui-aki(2)\sd-webui-aki-v4.4\log\images'
for filename in os.listdir(folder_path):
if filename.endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tif', '.tiff')):
img_path = os.path.join(folder_path, filename)
image = Image.open(img_path)
images.append(image)


def next_img():
global num
num = num + 1
if num >= len(images):
num = len(images) - 1
return images[num]


def last_img():
global num
num = num - 1
if num < 0:
num = 0
return images[num]


def next_10_img():
global num
num = num + 10
if num >= len(images):
num = len(images) - 1
return images[num]


def last_10_img():
global num
num = num - 10
if num < 0:
num = 0
return images[num]

def top_img():
global num
num = 0
return images[num]


def bottom_img():
global num
num = len(images)-1
return images[num]


def refresh():
global images, folder_path
images = []
for temp_filename in os.listdir(folder_path):
if temp_filename.endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tif', '.tiff')):
temp_img_path = os.path.join(folder_path, temp_filename)
temp_image = Image.open(temp_img_path)
images.append(temp_image)
return images


with gr.Blocks() as demo:
with gr.Column():
with gr.Row():
top_btn = gr.Button("首页")
bottom_btn = gr.Button("尾页")
with gr.Row():
last_btn = gr.Button("上一张")
next_btn = gr.Button("下一张")
with gr.Row():
last_10_btn = gr.Button("上十张")
next_10_btn = gr.Button("下十张")
refresh_btn = gr.Button("刷新")
with gr.Row(): # 水平显示
image1 = gr.Image(images[num], format='png', label="下载") # 初始显示图片
gallery = gr.Gallery(images, columns=3)

refresh_btn.click(fn=refresh, outputs=gallery)
last_btn.click(fn=last_img, outputs=image1) # 按键回调
next_btn.click(fn=next_img, outputs=image1) # 按键回调
top_btn.click(fn=top_img, outputs=image1) # 按键回调
bottom_btn.click(fn=bottom_img, outputs=image1) # 按键回调
last_10_btn.click(fn=last_10_img, outputs=image1) # 按键回调
next_10_btn.click(fn=next_10_img, outputs=image1) # 按键回调

if __name__ == "__main__":
demo.launch()

出现问题与解决方案

  1. Warning: remote port forwarding failed for listen port 80
    原因是这个端口被占用了, 或者是之前的连接还没断开, 这个时候就去远程服务器上kill掉这个程序就可以了。

    1
    2
    3
    4
    5
    netstat -anp | grep 80

    # 找到处于 LISTENING 状态的进程直接kill

    kill xxxx
  2. ssh 远程连接一段时间无操作后会自动断开连接
    解决方案:

  • 本机
    在用户目录下,例如C:\Users\你的用户名\.ssh\ 新建 config 配置文件(没有后缀名),在里面添加 ServerAliveInterval 60 并保存,意思是ssh客户端每60秒与ssh服务端自动通信一次,以防止长时间不操作导致的连接断开。
  • 云服务器
    在服务器中的/etc/ssh/sshd_config中去掉原有注释并改成这样:
1
2
ClientAliveInterval 60
ClientAliveCountMax 3

ClientAliveInterval 60表示每分钟发送一次, 然后客户端响应, 从而保持长连接. ClientAliveCountMax表示服务器发出请求后客户端没有响应的次数达到3次, 就自动断开。

参考资料