端口被谁占了、这台机上开了哪些监听?本机起一个端口占用只读清单,cpolar 给异地同事远程看谁在监听

端口被谁占了、这台机上开了哪些监听?本机起一个端口占用只读清单,cpolar 给异地同事远程看谁在监听

同事在群里问:"你们那台测试机 8080 是不是被占了?我这边拉不起来。"

我回了一句"我看看",然后 SSH 上去 lsof -iTCP -sTCP:LISTEN,输出一大坨,复制到聊天窗口,缩进全没了,同事还是看不出来哪一行是 8080。来回好几轮,问题没解决,人先烦了。

真正的矛盾其实很清晰:同事要远程确认一台机上哪些监听端口被占了、分别归哪个进程,但他不该拿到这台机的 SSH,更不该拥有杀进程的权限。

所以我想要的不是一条命令,而是一个入口:把本机的 TCP 监听端口和占用进程元信息列出来,渲染成一张只读清单页,绑在本机回环地址,用 cpolar 让异地同事打开链接自己看。只读是关键------看得到谁在监听,但点不了任何 kill、重启、改配置。

先把话说在前面:本文是一份"隔离环境中的可执行实验提纲" 。本轮我没有采集任何真实端口、没有启动服务、没有起隧道,文中出现的端口号、进程名、PID 全是示例/模拟值 ,用来演示清单页上长什么样。真跑起来的时候,这些数字来自你自己机器上 lsof/ss 的真实输出。

1 这套东西解决什么问题

先把边界划清楚,免得写着写着变成"进程管理平台选型"。

核心痛点就一句话:要远程确认一台机上哪些端口被监听、归谁占用,但不该把 SSH 交出去,更不该给 kill 权限。

lsof、ss 只在本机终端能看,异地同事用不了。把账号给他,权限太大;让他自己回忆"我这台 8080 是不是nginx",又不可靠。

所以缺的不是一个命令,而是一个只读 + 元信息的查看入口:

  • 只读:只能看,不能 kill、不能重启、不能改配置、不能暂停
  • 元信息:只看端口号、协议、绑定地址、进程名、PID 这些归属信息
  • 不越界:不暴露进程的完整命令行参数、环境变量、凭据

分工是这样的:

  • Python3 标准库负责调起本机的监听端口采集命令,并解析输出
  • 一个极小的 HTTP 服务把解析结果渲染成只读 HTML 清单
  • cpolar 负责把只在本机可达的 127.0.0.1:8111,变成异地同事能打开的地址

有一点必须先强调:这个页面只读。 它只呈现端口和进程元信息,不提供 kill、重启、改配置任何入口,也不吐进程的启动参数和环境变量。它不是进程管理器,就是个临时给同事看端口占用的只读展示页。

2 环境准备

需要的东西很少:

  • 一台 macOS 或 Linux 机器
  • Python 3(macOS 自带 python3,Linux 装完系统一般也有)
  • 采集命令:macOS 用 lsof,Linux 用 ss 或 netstat(下面会讲怎么选)
  • 一个 cpolar 账号(第 6 节讲怎么装和登录)

先确认 Python 版本正常:

bash 复制代码
python3 --version

能看到 Python 3.x.x 就行。这篇只用了 subprocess、html、http.server、urllib.parse、platform 这些标准库模块,不需要 pip install 任何东西。

敲黑板: 有些系统上 python 还是 Python 2,命令一律用 python3。下面所有示例都按 python3 来。

建一个放脚本的工作目录:

bash 复制代码
mkdir -p ~/port-listen-viewer
cd ~/port-listen-viewer

2.1 采集命令怎么选:macOS 用 lsof,Linux 优先 ss

采集 TCP 监听端口这件事,不同系统命令不一样,硬套一个命令要么没输出,要么报错:

  • macOS :用 lsof -nP -iTCP -sTCP:LISTEN
  • -n 不做 DNS 反查、-P 不把端口换算成服务名,这样输出快、端口是数字,好解析
  • -iTCP 只看 TCP,-sTCP:LISTEN 只筛出监听态
  • Linux :优先用 ss -lntp
  • -l 监听、-n 数字端口、-t TCP、-p 显示进程
  • 有些精简系统没装 ss,退回 netstat -lntp
  • Windows :这篇不覆盖,netstat -ano 输出格式和上面两套都不一样,留给以后单独写

这里别想着写一个"万能命令" :lsof 和 ss 的输出格式差得很远,宁可判断系统分别走两条解析分支,也别硬凑一个正则同时兼容两边------凑出来的正则该漏的漏、该错配的错配。

3 写一个最小端口占用只读清单服务

这段是本文核心,拆成三块:选命令、解析输出、HTTP 渲染。

3.1 先定只读边界:只看元信息,不碰进程详情

在动手解析之前,先把"要什么、不要什么"定死:

  • 要:端口号、协议(TCP)、绑定地址、进程名、PID
  • 不要 :进程完整命令行参数(lsof 的 COMMAND 加 -c 那些)、环境变量、工作目录、打开的文件

为什么要专门强调"不要命令行参数"?因为一行 lsof 的完整输出里,进程的启动参数里常带着密钥、token、连接串。只读清单只该回答"谁在监听",不该顺手把进程的隐私也抖出去。

python 复制代码
# ---------- 只读字段白名单(骨架示例) ----------
# 只呈现下列字段;不采集、不渲染进程命令行参数与环境变量。
FIELD_PORT = "port"        # 端口号
FIELD_PROTO = "proto"      # 协议,这里只涉及 TCP
FIELD_ADDR = "bind_addr"   # 绑定地址
FIELD_PNAME = "proc_name"  # 进程名
FIELD_PID = "pid"          # 进程 PID

# 渲染上限:最多展示多少条监听记录,防止清单被拉爆
MAX_ROWS = 300

3.2 选命令:按系统分支调起采集

不能用 shell 拼接字符串的方式拼命令,容易出注入和引号问题。标准库 subprocess.run 传列表参数,天然规避:

python 复制代码
import os
import shutil
import platform
import subprocess

def pick_listen_command(system):
    """
    按操作系统选择采集 TCP 监听端口的命令,返回参数列表。
    - macOS:  lsof -nP -iTCP -sTCP:LISTEN
    - Linux:  优先 ss -lntp,缺失时退回 netstat -lntp
    返回 None 表示当前系统不支持。
    """
    if system == "Darwin":  # macOS
        return ["lsof", "-nP", "-iTCP", "-sTCP:LISTEN"]
    if system == "Linux":
        # 优先 ss;ss 不在就退回 netstat
        if shutil.which("ss"):
            return ["ss", "-lntp"]
        if shutil.which("netstat"):
            return ["netstat", "-lntp"]
        return None
    return None


def collect_listen_rows():
    """
    调起采集命令并解析成只读清单行。
    返回 (rows, source),rows 是 dict 列表,source 是所用命令名。
    - 不改动系统状态,只读取监听信息;
    - 采集失败时返回空列表,不抛异常打断页面。
    """
    system = platform.system()
    cmd = pick_listen_command(system)
    if cmd is None:
        return [], "unsupported"

    try:
        proc = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=10,          # 采集超时上限,避免卡死
        )
    except (subprocess.SubprocessError, FileNotFoundError):
        return [], "error"

    rows = parse_output(system, proc.stdout)
    return rows, os.path.basename(cmd[0])

这里有两个点值得说:

第一,subprocess.run 传列表,不传字符串。 传列表时命令和参数是分开的,没有 shell 介入,不会出现把参数当命令执行的注入问题。

第二,采集失败不抛异常。 命令不存在、超时、没权限,都退回空列表,页面照常渲染,只是显示"无数据"。一个只读查看页,不该因为采集命令抽风就整个白屏。

导入那几行里,os 和 shutil 下面解析和渲染部分也会用到,这里一起写上,省得你后面拼代码时到处找不到 NameError。

3.3 解析输出:把 lsof / ss / netstat 三套格式拆开

这是最容易写错的地方。三套输出格式不同,得分开解析。下面锚定各自的典型行格式:

python 复制代码
# ---------- 三套输出的典型行格式(示例/模拟值) ----------
# lsof (macOS):
#   nginx     1234 admin  6u  IPv4 0xabc  0t0  TCP 127.0.0.1:80 (LISTEN)
#   ^进程名   ^PID  ^用户            ...       ^协议 ^绑定地址     ^状态
#   nginx     1234 admin  7u  IPv6 0xdef  0t0  TCP *:443 (LISTEN)
#
# ss (Linux):
#   LISTEN 0  128  127.0.0.1:8111  0.0.0.0:*  users:(("python3",pid=4321,fd=3))
#   ^状态    ...   ^本地地址      ^对端       ^进程名与PID
#
# netstat (Linux):
#   tcp  0  0  127.0.0.1:8111  0.0.0.0:*  LISTEN  4321/python3
#   ^协议     ^本地地址        ^对端      ^状态   ^PID/进程名

对应三套解析函数:

python 复制代码
import os
import re
import shutil

# 匹配 "地址:端口",兼容 IPv4(127.0.0.1:80)、IPv6([::1]:80)、通配(*:443)
ADDR_PORT_RE = re.compile(r"^(?P<addr>.+):(?P<port>\d+)$")


def _split_addr_port(text):
    """把 '127.0.0.1:8111' / '[::1]:80' / '*:443' 拆成 (addr, port)。"""
    text = text.strip()
    m = ADDR_PORT_RE.match(text)
    if not m:
        return None
    try:
        port = int(m.group("port"))
    except ValueError:
        return None
    return m.group("addr"), port


def parse_lsof(text):
    """解析 macOS 的 lsof -nP -iTCP -sTCP:LISTEN 输出。"""
    rows = []
    for line in text.splitlines():
        if not line.strip() or line.startswith("COMMAND"):
            continue
        # 用空白切分;lsof 的 TCP 行里协议列就是 "TCP"
        parts = line.split()
        if "TCP" not in parts:
            continue
        idx = parts.index("TCP")
        # 协议列之后是 "绑定地址:端口",再往后是 "(LISTEN)"
        if idx + 1 >= len(parts):
            continue
        ap = _split_addr_port(parts[idx + 1])
        if ap is None:
            continue
        addr, port = ap
        rows.append({
            "port": port,
            "proto": "TCP",
            "bind_addr": addr,
            "proc_name": parts[0],
            "pid": parts[1] if len(parts) > 1 else "-",
        })
    return rows


def parse_ss(text):
    """解析 Linux 的 ss -lntp 输出。"""
    rows = []
    for line in text.splitlines():
        if not line.strip() or line.startswith("State"):
            continue
        parts = line.split()
        # ss 行: State Recv-Q Send-Q Local Peer Process
        if len(parts) < 5 or parts[0] != "LISTEN":
            continue
        ap = _split_addr_port(parts[3])  # 本地地址列
        if ap is None:
            continue
        addr, port = ap
        pname, pid = "-", "-"
        # 进程信息在行尾 users:(("python3",pid=4321,fd=3)),一条监听可对应多个进程
        m = re.search(r'\("(?P<name>[^"]+)",pid=(?P<pid>\d+)', line)
        if m:
            pname, pid = m.group("name"), m.group("pid")
        rows.append({
            "port": port,
            "proto": "TCP",
            "bind_addr": addr,
            "proc_name": pname,
            "pid": pid,
        })
    return rows


def parse_netstat(text):
    """解析 Linux 的 netstat -lntp 输出。"""
    rows = []
    for line in text.splitlines():
        parts = line.split()
        # netstat 行: Proto Recv-Q Send-Q Local Foreign State PID/Program
        if len(parts) < 4 or parts[0] not in ("tcp", "tcp6"):
            continue
        ap = _split_addr_port(parts[3])  # 本地地址列
        if ap is None:
            continue
        addr, port = ap
        pname, pid = "-", "-"
        if len(parts) >= 7 and "/" in parts[6]:
            pid, pname = parts[6].split("/", 1)
        rows.append({
            "port": port,
            "proto": "TCP",
            "bind_addr": addr,
            "proc_name": pname,
            "pid": pid,
        })
    return rows


def parse_output(system, text):
    """按系统选择解析器。"""
    if system == "Darwin":
        return parse_lsof(text)
    # Linux:先按 ss 解析,行数太少再尝试 netstat 格式
    rows = parse_ss(text)
    if not rows:
        rows = parse_netstat(text)
    return rows

解析这里最容易踩的坑,是绑定地址里的冒号。 IPv6 的地址长这样 [::1]:80,如果拿 line.split(":") 硬切,会被切成好几段,端口就取错了。所以这里用正则从右边 匹配最后一段数字当端口(^(?P<addr>.+):(?P<port>\d+)$),. 是贪婪的,会把最后那个冒号留给端口。

第二个坑是 *:443 这种通配地址。 它是监听在所有网卡上的意思,不是"绑在一个叫 * 的地址上"。清单里原样呈现就行,别自作主张替换成 0.0.0.0,免得读者以为是绑定的意思变了。

3.4 HTTP 服务:只允许 GET,越界路径 404,写请求 405

接下来是服务部分。核心是三条只读规则:

  1. 只监听 127.0.0.1(绑回环地址,本地才可达,再靠 cpolar 转发)
  2. 只允许 GET/HEAD,其他方法一律 405
  3. 非白名单路径一律 404
python 复制代码
import html
import urllib.parse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

PAGE_CSS = """
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
margin:24px;color:#222}
h1{font-size:20px}
table{border-collapse:collapse;margin-top:16px;font-size:14px}
th,td{border:1px solid #ddd;padding:6px 10px;text-align:left}
th{background:#f5f5f5}
.note{color:#888;font-size:13px;margin-top:12px}
"""

# 只读路径白名单:只有这些路径可达,其余一律 404
ALLOWED_PATHS = {"/", "/index.html", "/listen"}


def render_page(rows, source):
    body = "".join(
        "<tr>"
        f"<td>{r['port']}</td>"
        f"<td>{html.escape(r['proto'])}</td>"
        f"<td>{html.escape(r['bind_addr'])}</td>"
        f"<td>{html.escape(r['proc_name'])}</td>"
        f"<td>{html.escape(str(r['pid']))}</td>"
        "</tr>"
        for r in rows
    )
    if not body:
        body = '<tr><td colspan="5">(未采集到监听端口,或采集命令不可用)</td></tr>'
    return f"""<!doctype html>
<html lang="zh-CN"><head><meta charset="utf-8">
<title>端口占用只读清单</title><style>{PAGE_CSS}</style></head>
<body>
<h1>端口占用只读清单</h1>
<p class="note">采集来源:{html.escape(source)} | 仅展示端口与进程元信息</p>
<table>
<tr><th>端口</th><th>协议</th><th>绑定地址</th><th>进程名</th><th>PID</th></tr>
{body}
</table>
<p class="note">本页只读:只展示监听端口与占用进程元信息,不提供 kill / 重启 / 改配置入口,不暴露进程启动参数与环境变量。</p>
</body></html>"""


class Handler(BaseHTTPRequestHandler):
    def _send(self, code, body, ctype="text/html; charset=utf-8"):
        data = body.encode("utf-8")
        self.send_response(code)
        self.send_header("Content-Type", ctype)
        self.send_header("Content-Length", str(len(data)))
        self.end_headers()
        if self.command != "HEAD":
            self.wfile.write(data)

    def _handle_read(self):
        parsed = urllib.parse.urlparse(self.path)
        # 非白名单路径一律 404
        if parsed.path not in ALLOWED_PATHS:
            self._send(404, "<h1>404 未找到</h1>")
            return
        rows, source = collect_listen_rows()
        rows = rows[:MAX_ROWS]
        self._send(200, render_page(rows, source))

    def do_GET(self):
        self._handle_read()

    def do_HEAD(self):
        self._handle_read()

    # 写操作一律 405:服务是只读的,不是"没实现"
    def do_POST(self):
        self._send(405, "<h1>405 只读服务,不支持写操作</h1>")

    def do_PUT(self):
        self._send(405, "<h1>405 只读服务,不支持写操作</h1>")

    def do_DELETE(self):
        self._send(405, "<h1>405 只读服务,不支持写操作</h1>")


def main():
    host, port = "127.0.0.1", 8111
    httpd = ThreadingHTTPServer((host, port), Handler)
    print(f"端口占用只读清单服务已启动:http://{host}:{port}")
    print("按 Ctrl+C 停止")
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        print("\n已停止")
        httpd.server_close()


if __name__ == "__main__":
    main()

两个容易被忽略的点:

第一,路径白名单是"集合判断",不是"拼路径"。 请求路径只拿去做 in ALLOWED_PATHS 判断,命中了才渲染。这样请求 /../etc/passwd 之类的根本不在集合里,直接 404,路径拼接类的越界从根上就不存在。

第二,写方法全部显式实现并返回 405。 不实现的话,BaseHTTPRequestHandler 对未知方法默认返回 501。显式写 405,语义才对------这是只读服务,不是"功能没做"。

4 先造一份示例监听记录,把链路跑通

这套东西不能拿真实机器直接开测,先用示例数据把链路跑通。示例/模拟值如下:

端口 协议 绑定地址 进程名 PID
8111 TCP 127.0.0.1 python3 4321
22 TCP * sshd 700
443 TCP * nginx 1234

上面这张表里的端口、进程名、PID 全是示例/模拟值,用来演示清单页渲染成什么样,不是任何真实机器的实测结果。

想在本地看渲染效果,可以先把 collect_listen_rows() 临时换成一个返回固定示例列表的桩函数,确认页面排版正常:

python 复制代码
def collect_listen_rows():
    # 本地演示用桩数据(示例/模拟值);真实部署时改回真实采集
    demo = [
        {"port": 8111, "proto": "TCP", "bind_addr": "127.0.0.1", "proc_name": "python3", "pid": "4321"},
        {"port": 22,   "proto": "TCP", "bind_addr": "*",         "proc_name": "sshd",    "pid": "700"},
        {"port": 443,  "proto": "TCP", "bind_addr": "*",         "proc_name": "nginx",   "pid": "1234"},
    ]
    return demo, "demo"

MAX_ROWS = 300

然后启动服务:

bash 复制代码
python3 port_listen_server.py

终端会打印:

复制代码
端口占用只读清单服务已启动:http://127.0.0.1:8111
按 Ctrl+C 停止

浏览器打开 http://127.0.0.1:8111,就能看到上面那张三行的清单。这一步做完先在本地核对一遍: 切回真实采集模式后,页面里 8111 这一行应该指向你自己刚起的这个 Python 进程。对不上,先检查解析器有没有把 IPv6 地址的端口切错。

这里提醒一句: 换回真实采集后,lsof/ss 在部分系统上要看别的用户进程得用 sudo。别为了看全就无脑加 sudo 跑成 root------只读清单能看自己进程的监听就够了,真要看全局,也先确认这台机上没有敏感进程参数会被顺带读出来。

5 安装 cpolar 并登录

现在清单只在本机 127.0.0.1:8111 可达,异地同事打不开。接下来用 cpolar 把只读清单暴露出去。

5.1 安装

macOS 上可以用 Homebrew:

bash 复制代码
brew install cpolar

Linux 用官方一键脚本:

bash 复制代码
curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash

装完之后 cpolar 会起一个本地管理服务。确认一下它在跑:

bash 复制代码
# macOS
brew services list | grep cpolar
# Linux
sudo systemctl status cpolar

装完可以打开本机管理面板看一眼:http://127.0.0.1:9200。能打开就说明 cpolar 本地服务正常。

别跳过这一步。 很多人后面隧道起不来,其实是本地 cpolar 服务根本没起来,结果一路去怀疑网络。

5.2 登录绑定账号

在 cpolar 后台"验证"页面能看到自己的 authtoken。拿到之后命令行执行:

bash 复制代码
cpolar authtoken 你的authtoken

或者直接在 http://127.0.0.1:9200 的 Web 界面里登录账号,也会自动写入 token。

6 用 cpolar http 8111 暴露只读清单

这次要暴露的是一个 HTTP 页面,所以用 HTTP 隧道:

bash 复制代码
cpolar http 8111

执行后终端会打印一块 Forwarding 信息,里面有一条 https://xxxx.cpolar.cn 这样的公网地址,指向本地的 8111 端口。把这条地址发给异地同事,他打开就能看到那张只读端口清单。

划重点:免费版的这个地址是随机地址,会话结束就失效,重启隧道地址就变了,24 小时内都不保证稳定。 临时让同事看一次,免费用就够了。

但端口占用这件事,经常不是"看一次"就完事------今天问一次、明天还要再核对。所以我更建议这次直接上固定二级子域名,让地址稳定下来,同事存成书签,下次直接打开还是同一个链接。

按官方规则:

  • 固定二级子域名:需基础套餐及以上
  • 固定 TCP 端口:需专业套餐及以上
  • 自定义域名:需专业套餐及以上

配好固定二级子域名之后,地址就不再随会话变化。同事把链接存下来,隔几天回来核对,还是那个地址。

安全提醒: 隧道暴露出去后,任何拿到地址的人都能访问这张只读清单。它虽然不是写入口,但会把你这台机的监听端口和进程名露出来------这份信息对排查有用,对扫描的人也有用。真要长期给同事用,建议配合 cpolar 的访问控制,或者至少别把地址发到公开场合。

7 验证只读边界:越界路径 404、写请求 405

只读这个承诺,得验证过才算数。下面三条自检,建议每个人跑完都过一遍。

7.1 白名单以外的路径必须 404

请求一个不在白名单里的路径:

bash 复制代码
curl -i "http://127.0.0.1:8111/../etc/passwd"
curl -i "http://127.0.0.1:8111/admin/kill?pid=1234"

预期都返回 404,而不是把清单以外的信息吐出来。注意这里和路径拼接类漏洞的区别: 路径只做集合判断,请求里带 ../ 或者别的路径都不会命中,服务端直接 404。

7.2 写请求必须 405

模拟一次 kill 请求:

bash 复制代码
curl -i -X POST "http://127.0.0.1:8111/listen" -d "action=kill&pid=1234"

预期返回 405 Method Not Allowed。同理试试 PUT、DELETE:

bash 复制代码
curl -i -X PUT    "http://127.0.0.1:8111/listen"
curl -i -X DELETE "http://127.0.0.1:8111/listen"

都应该是 405。

7.3 经隧道访问结果一致

把上面两条里的 127.0.0.1:8111 换成 cpolar 给你的公网地址,再各跑一次,确认异地访问的返回码和本机一致------404 还是 404,405 还是 405。

如果经隧道访问时越界路径没有返回 404,先关掉隧道再排查。这说明暴露出来的入口比预期的大,不能放着不管。

8 异地同事核对清单

把链接发给同事后,他那边应该能看到:

  • 一张端口清单表,列出端口、协议、绑定地址、进程名、PID
  • 只看得到监听态的 TCP 端口,看不到进程的启动参数和环境变量
  • 任何 kill、重启、改配置都做不了(本来就没有入口)

同事要做的事很简单:打开链接、找到他在意的那个端口、看它归哪个进程。这一步不是为了测试而测试,而是确认"本机采集到的监听清单 = 异地看到的清单"这条链路已经打通。 两边对不上,先检查同事打开的是不是同一个隧道地址、页面是不是拉到最新一次采集。

9 收尾与清理

验证完把服务停掉,别让它一直跑着:

bash 复制代码
# 停掉 cpolar 隧道:回到 cpolar 那个终端按 Ctrl+C
# 停掉清单服务:回到 port_listen_server.py 那个终端按 Ctrl+C

养成这个习惯:临时暴露的服务,用完就关。端口清单页也别忘了它暴露的是这台机真实的监听面。

10 总结

走到这一步,你手上已经有一条完整链路:本机 Python3 调起 lsof -nP -iTCP -sTCP:LISTEN(macOS)或 ss -lntp / netstat -lntp(Linux)采集 TCP 监听端口,解析出端口、协议、绑定地址、进程名、PID,渲染成只读端口占用清单,绑在 127.0.0.1:8111,再用 cpolar http 8111 把只读入口给到异地同事看谁在监听。整条链路不装任何第三方库,macOS 和 Linux 直接能跑。

几个关键点拎出来:

  • 三套命令的输出格式不同,解析分成 parse_lsof / parse_ss / parse_netstat 三条分支,别硬凑一个正则
  • 绑定地址带冒号(尤其 IPv6 的 [::1]:80),端口要从右边正则匹配,不能用 split(":") 硬切
  • 只读边界写死在代码里:写方法一律 405,非白名单路径一律 404,采集字段只留元信息、不碰命令行参数与环境变量
  • cpolar 负责把本机只读清单变成异地可访问入口,地址经常要复用就配固定二级子域名(需基础套餐及以上),只是临时看一次用免费随机地址就够

想继续扩展的话,自然的下一步是给清单加个"只看某个端口号"的过滤、把协议从 TCP 扩到 UDP、或者按进程名分组。但不管怎么扩,只读这条线不要破------给同事看端口占用的页面,永远不该变成能 kill、能重启、能改配置的入口。


说明:本文是隔离环境中的可执行实验提纲 。本轮未实际采集真实端口、未启动服务、未起隧道,文中所有端口号、进程名、PID 均为示例/模拟值 ,不代表任何真实机器的实测结果。实际部署时,请以你自己机器上 lsof / ss / netstat 的真实输出和 cpolar 返回的真实地址为准。

相关推荐
JavaPub-rodert1 小时前
Docker 容器为什么 stop 半天停不下来?讲透 PID 1、SIGTERM、SIGKILL、僵尸进程与 tini
运维·docker·容器
WeiXin_DZbishe1 小时前
基于springboot装修风格选择系统的设计与开发-计算机毕设【课程设计】77964
java·vue.js·spring boot·后端·python·php·课程设计
XZ-0700011 小时前
week9-三元表达式
java·前端·python
峰哥AI手记1 小时前
金融合规问答助手-RAG查询路由落地
python·aigc
benchmark_cc1 小时前
量化项目启动时,应该先设计数据接口还是先编写策略?从系统架构角度做出正确选择
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
CaptainT1 小时前
第 4 卷:面向对象、数据建模与类型系统
python
2601_962885721 小时前
如何用 Python 判断一只股票“追涨”还是“抄底”更有效?
android·开发语言·python
用户7783366132112 小时前
选 SERP API 别靠感觉:用 SerpBase 实测的评估脚本与判定标准
python·api
学IT的周星星2 小时前
记忆系统:让 Agent 记住你是谁、上次聊到哪
运维·服务器·人工智能·windows·大模型·llm·agent