目录

  1. Usenet 是什么?
  2. Usenet 与 Email 的根本区别
  3. Usenet 通信的四个步骤
  4. 新闻组(Newsgroup)层级体系
  5. Usenet 消息格式
  6. NNTP 协议概述
  7. NNTP 服务器间传播:推模式与拉模式
  8. NNTP 客户端读写操作
  9. NNTP 命令与扩展
  10. NNTP 响应码
  11. Gopher 协议
  12. 整体对比总结

1. Usenet 是什么?

一句话理解

Usenet(用户网络,User’s Network)是互联网上最早的公开讨论社区,类似于一个巨大的在线公告栏,任何人都可以发帖,任何人都可以看到并回复。

现实生活中的类比

电子邮件(Email):          Usenet:
像写私信/内部备忘录           像公司公告栏、咖啡机旁的闲聊、
只有指定的人能收到             全球性的话题讨论区
                              任何人都能看到和参与

历史起源

1979年夏天:
  Tom Truscott 在贝尔实验室实习,深爱 UNIX 环境
       ↓
  返校后用 UUCP 协议与贝尔实验室保持连接
       ↓
  与同学 Jim Ellis 合作,联合杜克大学和北卡大学的 UNIX 爱好者
       ↓
  设计出一套能让学生互相分享信息、寻求帮助的系统
最初:只有 2 台电脑,2 个新闻组
后来:发展成全球数万个新闻组,每天数亿条消息

现代 Usenet 的规模

  • 新闻组数量:超过 100,000
  • 话题覆盖:从太空探索到烹饪,从生物化学到电脑故障排除,到养马
  • 传输方式:早期用 UUCP(拨号电话线),现在全部用 NNTP + TCP/IP

2. Usenet 与 Email 的根本区别

Email 的局限性

Email 有两个问题,使其不适合大规模公开讨论:
问题1:必须指定收件人

Email 发送 1000 人:
  发件人 → 指定 1000 个收件人地址 → 服务器复制 1000 份 → 分别投递
                                                       (效率极低!)

问题2:无法"公开张贴"

Email 无法实现"我想让所有感兴趣的人都能看到我的消息"这个需求
就像你想在公告栏贴个告示,但邮件只能寄给你认识的人

Usenet 的解决方案:公开分发模型


对比维度 Email(私信模式) Usenet(公告栏模式)
寻址方式 发给特定收件人地址 发给新闻组名称
存储位置 每人各自的邮箱 服务器上的公共存储区
传递方式 点对点投递 广播式传播到所有服务器
谁能读到 只有指定收件人 任何订阅该新闻组的人

3. Usenet 通信的四个步骤

以用户 Ellen 向 misc.rural 新闻组发帖求助病马为例:

步骤1:撰写文章
Ellen 用新闻阅读器写帖子
包含 Header 和 Body

步骤2:发布与本地存储
NNTP 客户端将文章发送到
Ellen 本地的 NNTP 服务器
立刻可被本服务器用户读到

步骤3:文章传播 Propagation
本地服务器将文章传播到
相邻服务器
逐步扩散到全球所有服务器

步骤4:访问与阅读
Jane 用新闻阅读器连接
自己的 NNTP 服务器
读到 Ellen 的帖子并回复

Jane 的回复再经过
同样的4步传回 Ellen

传播(Propagation)的细节

文章的传播过程像"洪水泛滥"一样向外扩散:

Ellen 发帖到 MediumNews 服务器
         ↓ 传播
    ┌────┴────┐
SmallNews   LargeNews(主干)
              ↓ 继续传播
         其他主干服务器
              ↓ 向下传播
         各地的中小服务器
         (最终全球都能看到)

这种传播模式被称为 Flooding(洪泛):从一个源点向外泛滥,直到所有节点都收到副本。

4. 新闻组(Newsgroup)层级体系

新闻组命名规则

新闻组名称类似 DNS 域名,但从左到右读(从大类到小类):

comp.dcom.cabling
  ↑     ↑      ↑
大类   子类   具体话题
(计算机)(数据通信)(布线)

可视化层级树

Usenet 全体新闻组

大八层级 Big Eight

alt.* 自由层级

地区性层级

comp.*
计算机相关

rec.*
休闲娱乐

sci.*
科学

misc.*
杂项

news.*
Usenet自身

soc.*
社会文化

talk.*
辩论讨论

humanities.*
人文艺术

comp.dcom.*

comp.dcom.cabling
数据布线讨论

ne.*
新英格兰地区

fr.*
法国

de.*
德国

大八层级说明


层级 说明 典型新闻组示例
comp.* 计算机相关(硬件、软件、OS) comp.sys.intel, comp.dcom.cabling
humanities.* 人文艺术(文学、艺术) humanities.classics
misc.* 杂项(不适合其他大类的话题) misc.rural, misc.consumers.house
news.* Usenet 自身的讨论和管理 news.announce.newgroups
rec.* 休闲娱乐(游戏、体育、活动) rec.sport.baseball, rec.food.recipes
sci.* 科学(物理、化学等) sci.physics, sci.bio.ecology
soc.* 社会文化 soc.culture.china
talk.* 时事辩论 talk.politics.mideast, talk.politics.guns

受审核 vs 非受审核新闻组

非受审核(Unmoderated):
  用户发帖 → 直接进入新闻组,立刻可见
  优点:自由,即时
  缺点:容易出现垃圾、无关内容
受审核(Moderated):
  用户发帖 → 先发给审核员 → 审核员批准 → 才进入新闻组
  例子:rec.food.recipes(只允许食谱,不允许闲聊)
        rec.guns(只讨论枪械使用,不讨论政治)
  优点:保持话题聚焦,质量高
  缺点:有延迟,依赖审核员的时间

交叉发布(Cross-Posting)

一篇文章同时发到多个新闻组:

你在西雅图,问关于房屋修缮的问题:
  发到 seattle.general(西雅图本地)
  发到 misc.consumers.house(全国房屋讨论)
效率:
  服务器上只存一份副本(不是两份)
  订阅两个组的用户只看到一次(不会重复)

5. Usenet 消息格式

和 Email 格式的关系

Usenet 文章的格式直接借用 RFC 822 邮件格式,因此:

  • 格式完全相同:头部 + 空行 + 正文
  • 区别只在于头部字段的种类和内容不同
邮件消息(RFC 822):          Usenet 文章(RFC 1036):
From: alice@example.com       From: alice@example.com
To: bob@example.com           Newsgroups: comp.dcom.cabling
Subject: 今天下午开会          Subject: 求助:如何布线?
Date: Mon, 15 May 2026        Date: Mon, 15 May 2026
                              Message-ID: <unique123@server>
[空行]                        Path: serverQ!serverF!serverA
正文内容                       [空行]
                              正文内容

Usenet 强制头部


头部名 含义 说明
From: 发帖人邮件地址 同邮件
Date: 发帖时间 同邮件
Newsgroups: 发往哪些新闻组 Usenet 特有,邮件没有
Subject: 帖子主题 在 Usenet 是必须的(邮件是可选的)
Message-ID: 文章唯一标识 用于控制传播,避免重复
Path: 传播路径记录 记录经过哪些服务器,如 Q!F!X!G!A

Usenet 可选头部


头部名 含义
Reply-To: 邮件回复地址(和发帖地址不同时使用)
Followup-To: 后续回复应该发到哪个新闻组
References: 被回复的帖子的 Message-ID(用于构建话题树)
Expires: 请求服务器在某时间后删除此文章
Distribution: 限制传播范围(如只在某地区传播)
Organization: 发帖者所属机构
Approved: 受审核组的审核员批准标记
Xref: 交叉发布的服务器内部文章编号

话题线索(Threading)的原理

References: 头部记录了"回复链",用于把相关帖子组织成对话树:

文章 AA207(Ellen 提问:马生病了怎么办?)
  └─ 文章 AA215(Jane 回复:References: AA207)
      ├─ 文章 AA219(Tom 回复 Jane:References: AA207 AA215)
      └─ 文章 AA221(Bob 也回复:References: AA207 AA215)
           └─ 文章 AA230(Ellen 跟进:References: AA207 AA215 AA221)

新闻阅读器读取 References 头部,就能重建这棵对话树,让用户按话题浏览,而不必在时间流中翻找相关帖子。

6. NNTP 协议概述

NNTP 是什么?

NNTP(Network News Transfer Protocol,网络新闻传输协议),是用 TCP/IP 传输 Usenet 文章的专用协议,定义于 RFC 977(1986年),工作在 TCP 端口 119

NNTP 和 SMTP 的对比

SMTP(邮件传输):              NNTP(新闻传输):
  只用于服务器之间传送邮件         既用于服务器之间传播文章
  客户端用 POP3/IMAP 收邮件        也用于客户端发布和阅读文章
  端口 25                          端口 119
  命令4字母(HELO, MAIL...)        命令无长度限制(ARTICLE, IHAVE...)
  回复码 3 位数字(与FTP相似)      回复码 3 位数字(与FTP/SMTP相似)

NNTP 的两大角色

NNTP 协议的用途

服务器间传播
Server-to-Server
把文章从一台服务器
复制到另一台服务器

客户端读写
Client-to-Server
用户发帖、浏览、
阅读文章

7. NNTP 服务器间传播:推模式与拉模式

Usenet 服务器的层次结构

[主干服务器 A]  [主干服务器 B]  [主干服务器 C]
(大型ISP/公司,高速连接,存储全量文章)
      ↓                ↓               ↓
[中型服务器]      [中型服务器]    [中型服务器]
      ↓                ↓
[小型服务器]    [小型服务器]

每台服务器通常只与上游(upstream)和下游(downstream)直接相连,一篇文章从发布点"洪泛"传遍整个网络。

推模式(Push Model)- 最常用

概念:有新文章就主动告诉邻居服务器

NNTP 连接建立
         ↓
客户端(发送方):IHAVE <message-id-1>
服务器(接收方):335 发过来吧     ← 我还没有
客户端:(发送文章内容)
         .                        ← 单独一行的点表示结束
服务器:235 文章收到了
客户端:IHAVE <message-id-2>
服务器:435 不用发了              ← 我已经有了
客户端:QUIT
服务器:205 再见

IHAVE 的优点:避免重复发送(先问有没有,没有才发)
IHAVE 的缺点:慢,必须等服务器回答才能发下一条

流模式(Streaming Mode)- 改进版推模式

为了解决 IHAVE 需要等待回应的问题,引入了流模式扩展命令:

普通 IHAVE(慢):               流模式(快):
  → IHAVE msg1                    → CHECK msg1
  ← 335(等待中...)              → CHECK msg2(不等回应直接发!)
  → (发文章)                    → CHECK msg3
  ← 235                          ← 238(msg1 要)
  → IHAVE msg2                   ← 438(msg2 不要)
  ← 335                          ← 238(msg3 要)
  → (发文章)                   → TAKETHIS msg1(发 msg1)
  ...(串行,慢)                 → TAKETHIS msg3(发 msg3)
                                  ...(并行,快!)

流模式使用两个命令:

  • CHECK <message-id>:问"你要这篇文章吗?"(不等回应直接问下一篇)
  • TAKETHIS <message-id>:发送文章内容(收到服务器确认要的回应后)

拉模式(Pull Model)

概念:由接收方主动来问"有新文章吗?"

客户端:NEWNEWS alt.tech 20260515 120000
(意思:alt.tech 新闻组,从2026年5月15日12:00:00之后的新文章)
服务器:230 新文章列表如下
<msg-id-1>
<msg-id-2>
<msg-id-3>
.            ← 列表结束
客户端:ARTICLE <msg-id-1>
(逐一下载所需文章)

对比项 推模式 拉模式
主动方 发送方服务器主动推送 接收方服务器主动来取
即时性 高(立刻传播) 低(需等下次连接)
常用程度 现代 Usenet 主流 较少使用

8. NNTP 客户端读写操作

新闻阅读器(Newsreader)

类似于邮件客户端(如 Outlook),但用于 Usenet:

邮件客户端(Outlook/Thunderbird)←→ SMTP/IMAP/POP3 ←→ 邮件服务器
新闻阅读器(Thunderbird/Tin...)  ←→ NNTP          ←→ NNTP 服务器

许多邮件客户端同时也是新闻阅读器(如 Mozilla Thunderbird)。

阅读文章的流程

步骤1:连接服务器
  TCP 连接到 NNTP 服务器 端口119
步骤2:选择新闻组
  → GROUP comp.dcom.cabling
  ← 211 234 1 234 comp.dcom.cabling(共234篇,从1到234)
步骤3:获取文章(三种方式选一)

三种文章获取方式对比

方式一:完整下载所有文章

→ ARTICLE 1          ← 下载第1篇完整文章
← 220 1 <msg-id>
← (文章头部+正文)
← .
→ NEXT               ← 移到下一篇
← 223 3 <msg-id>     ← 下一篇是3(2已被删除)
→ ARTICLE            ← 下载当前指针指向的文章(3)
← 220 3 <msg-id>
← ...

适合:小新闻组,或网速很快的用户。
方式二:只下载头部

→ HEAD 1             ← 只下载第1篇的头部(不含正文)
← 221 1 <msg-id>
← From: ...
← Subject: ...
← Date: ...
← .

用户先浏览主题、作者、日期,决定要不要看全文。
方式三:用 XHDR/XOVER 批量获取头部信息(更高效)

→ XHDR Subject 1-50  ← 获取1到50号文章的 Subject 头部
← 221
← 1 如何配置路由器?
← 2 回复:如何配置路由器?
← 3 求助:无法上网
...
← .

完整阅读示意图(ASCII)

用户                  新闻阅读器              NNTP 服务器
 |                       |                       |
 |  点击"comp.dcom"       |                       |
 |---------------------->|                       |
 |                       |  GROUP comp.dcom      |
 |                       |---------------------->|
 |                       |  211 50 1 50 comp.dcom|
 |                       |<----------------------|
 |                       |  XHDR Subject 1-50    |
 |                       |---------------------->|
 |                       |  221(50条Subject)    |
 |                       |<----------------------|
 |  显示文章列表          |                       |
 |<----------------------|                       |
 |  点击第3篇文章         |                       |
 |---------------------->|                       |
 |                       |  ARTICLE 3            |
 |                       |---------------------->|
 |                       |  220 3 (完整文章)   |
 |                       |<----------------------|
 |  显示文章内容          |                       |
 |<----------------------|                       |

9. NNTP 命令与扩展

基本命令速查


命令 作用
GROUP 组名 选择要读的新闻组
ARTICLE [编号/ID] 下载完整文章(头部+正文)
HEAD [编号/ID] 只下载文章头部
BODY [编号/ID] 只下载文章正文
STAT [编号] 只获取文章编号和ID(用于设置指针)
NEXT 移到下一篇文章
LAST 移到最后一篇文章
LIST 列出服务器上所有新闻组
NEWGROUPS 日期 时间 列出自指定日期起新创建的新闻组
NEWNEWS 日期 时间 列出自指定日期起新到的文章ID
IHAVE <message-id> 告诉服务器"我有这篇文章,你要吗?"
POST 发布一篇新文章
QUIT 断开连接
HELP 获取帮助信息

传输扩展命令(流模式)


命令 作用
MODE STREAM 启用流模式
CHECK <message-id> 询问服务器是否需要某文章(无需等回应)
TAKETHIS <message-id> 发送服务器要求的文章

新闻阅读器扩展命令


命令 作用
XHDR 头部名 范围 批量获取指定头部(如只取 Subject)
XOVER 文章号范围 获取文章概览信息(用于快速显示列表)
XPAT 头部名 模式 范围 按模式搜索特定文章头部
LISTGROUP 组名 列出新闻组内的所有文章编号
MODE READER 告诉服务器自己是客户端(非服务器)
AUTHINFO USER 用户名 发送用户名进行认证
AUTHINFO PASS 密码 发送密码完成认证

10. NNTP 响应码

响应码格式

NNTP 响应码是三位数 x y z xyz xyz,设计思路与 FTP/SMTP 相同:
x ⏟ 成功/失败类别 y ⏟ 功能分组 z ⏟ 具体编号 \underbrace{x}_{\text{成功/失败类别}} \underbrace{y}_{\text{功能分组}} \underbrace{z}_{\text{具体编号}} 成功/失败类别 x功能分组 y具体编号 z
第一位(x)含义

第一位 含义 通俗理解
1xx 信息性消息 “这是帮助信息/调试输出”
2xx 命令成功 “搞定了!”
3xx 命令进行中,需要更多信息 “好,继续发给我…”
4xx 命令正确但无法执行 “命令没错,但我做不到”
5xx 命令错误或严重错误 “你的命令有问题”

第二位(y)含义

第二位 含义
x0z 连接/初始化/杂项
x1z 新闻组选择相关
x2z 文章选择相关
x3z 文章分发/传输相关
x4z 文章发布相关
x5z 身份认证相关

常见响应码速查


响应码 含义
200 服务器就绪(允许发帖)
201 服务器就绪(不允许发帖)
205 再见(QUIT 的响应)
211 新闻组选择成功(附带文章数量和范围)
215 新闻组列表如下
220 文章检索成功(完整文章)
221 头部检索成功
222 正文检索成功
230 新文章 ID 列表如下(NEWNEWS 的响应)
235 文章传输成功(IHAVE 之后)
240 文章发布成功(POST 之后)
335 请发送文章(IHAVE 的中间响应)
340 请发送文章内容(POST 的中间响应)
411 不存在该新闻组
430 找不到该文章(可能已被删除)
435 不需要该文章(已有副本)
440 不允许发帖
500 无法识别的命令
502 权限不足

11. Gopher 协议

Gopher 是什么?

Gopher 是 1989 年由明尼苏达大学开发的一种分层文档检索协议,工作在 TCP 端口 70
名字的由来:

  • 明尼苏达大学的体育队叫"金地鼠"(Golden Gophers)
  • 地鼠(Gopher)会"挖洞",比喻在互联网中"挖掘"文档
  • “Gopher"也指代"跑腿的人”——“go fer this, go fer that”(去取这个,去取那个)

Gopher 的工作原理

Gopher 把所有信息组织成层级目录树,就像电脑的文件系统:

根目录 /
├── 介绍文档(0类型:文件)
├── 关于我们(0类型:文件)
├── 资源中心(1类型:子目录)
│   ├── 技术文档(0类型:文件)
│   └── 搜索服务(7类型:搜索功能)
└── 外部链接(指向其他Gopher服务器)

通信流程

步骤1:客户端连接 Gopher 服务器端口 70
步骤2:客户端发送"选择字符串"(空字符串=请给我根目录列表)
步骤3:服务器返回目录列表(每行一个条目):
  格式:类型字符+资源名 <Tab> 选择字符串 <Tab> 服务器名 <Tab> 端口
  示例:
  0Gopher介绍<Tab>intro<Tab>gopher.server.org<Tab>70
  1资源中心<Tab>/resources<Tab>gopher.server.org<Tab>70
  7搜索文档<Tab>/search<Tab>gopher.server.org<Tab>70
步骤4:服务器关闭连接(每次请求后立刻断开)
步骤5:客户端显示菜单,用户选择后再次连接请求

Gopher vs Web 的核心区别


对比项 Gopher 万维网(WWW)
界面风格 纯文本菜单 自由排版,支持图文混排
信息组织 强制目录树结构 自由组织,不限结构
链接方式 由服务器目录建立链接 在文档内嵌入 HTML 超链接
多媒体 基本不支持 完整支持图片、音频、视频
创作自由度 低(受目录结构限制) 极高
协议简洁性 非常简单 较复杂(HTTP头部、MIME等)

Gopher 为什么输给了 Web?

1989年:Gopher 诞生(早于 Web)
1990年:Web 诞生(HTTP + HTML + URL)
1993年:Mosaic 浏览器让 Web 变得可视化、易用
1994年:Web 流量开始超过 Gopher
1990年代末:Gopher 基本被遗弃
主要原因:
  1. 灵活性不足:Gopher 只能做文本目录,Web 能做任何东西
  2. 图形化时代来临:1990年代计算机从文本界面转向图形界面,
     Web 支持图片,Gopher 不行
  3. 收费决定:明尼苏达大学对商业用途收取授权费
     (历史证明:对协议收费是扼杀协议最快的方式)
  4. 2002年致命一击:IE 发现 Gopher 安全漏洞,
     微软直接移除对 Gopher 的支持(而不是修复)

12. 整体对比总结

三种消息系统的对比


对比项 Email Usenet/NNTP Web/HTTP
通信模式 私信,点对点 公开,广播式 请求/响应
主要用途 私人通信 公开讨论 资源获取
存储位置 各人邮箱 服务器公共区 Web 服务器
寻址方式 用户@域名 新闻组名 URL
传输协议 SMTP(发)/POP3/IMAP(收) NNTP HTTP
默认端口 SMTP:25, POP3:110, IMAP:143 119 80
消息格式 RFC 822 RFC 822(借用) 类RFC 822(借用)

协议血缘关系

FTP 文件传输协议
1971年

SMTP 邮件传输协议
1981年
借鉴FTP命令/回复码结构

HTTP 超文本传输
1990年
借鉴FTP概念

NNTP 新闻传输协议
1986年
借鉴SMTP命令/回复码

RFC 822 消息格式
1982年

Usenet 文章格式
RFC 1036
借用RFC 822

MIME 扩展
1992年
被HTTP借用

各协议端口号总览


协议 端口 用途
SMTP 25 邮件服务器间传输
POP3 110 客户端收邮件(离线模式)
IMAP 143 客户端收邮件(同步模式)
NNTP 119 Usenet 新闻传输
HTTP 80 网页传输
HTTPS 443 加密网页传输
Gopher 70 文档检索(已过时)
FTP 21 文件传输

TCP/IP 交互与管理工具详解

本文档对原文内容进行详细的中文解读,力求通俗易懂。涵盖 Telnet、Berkeley r 命令、IRC 协议,以及 ping、traceroute、arp、netstat 等管理工具。

目录

  1. 第一部分:交互与远程应用协议
  2. 第二部分:TCP/IP 管理与排错工具

第一部分:交互与远程应用协议

Telnet 协议

什么是 Telnet?

想象一下,你坐在北京,想操作一台放在上海机房里的电脑。Telnet 就是让你能做到这件事的协议——它让你通过网络,像坐在那台远程电脑旁边一样,直接敲键盘、看输出。
Telnet 是 TCP/IP 协议族中最古老、最具历史意义的应用协议之一,首次亮相于 1969 年,比现代互联网的诞生还要早。

历史背景

早期的计算机都很庞大,通常由很多人共用。要操作一台电脑,你必须坐在连接到它的专用终端前。这带来两个大麻烦:

问题一:每台电脑需要一个专用终端,就像家里每台电视只能看一个频道。
问题二:要远程操作另一个城市的电脑,必须专门拉一根昂贵的专线电路。

Telnet 的诞生,就是为了用一种通用方式解决上述问题——让任何终端都能访问任何电脑

Telnet 的三大核心概念

1. 网络虚拟终端(NVT,Network Virtual Terminal)
不同品牌的电脑和终端,内部数据格式各不相同。怎么让它们互相通信?
方案:定义一种大家都懂的"通用语言"——NVT。

类比:
  国际会议上,来自 30 个国家的代表,
  大家各说各的母语 → 无法沟通
  统一改说英语(NVT)→ 人人都能交流

工作流程如下:

用户终端(本地格式) 
    → [Telnet 客户端] 翻译成 NVT 格式 
        → 网络传输 
            → [Telnet 服务器] 翻译成远程主机格式 
                → 远程主机

反向(主机回应用户):

远程主机 
    → [Telnet 服务器] 翻译成 NVT 
        → 网络传输 
            → [Telnet 客户端] 翻译成本地格式 
                → 用户终端显示

2. 选项与选项协商(Options & Option Negotiation)
NVT 是最低公分母——能保证基本通信,但功能很简单。
为了让有能力的终端和主机使用更高级的功能,Telnet 定义了一套选项机制
举例:默认 NVT 是半双工(一方说完才能另一方说),但现代网络支持全双工。客户端和服务器可以协商启用 Suppress Go Ahead 选项,跳过半双工限制。
3. 对称操作(Symmetric Operation)
Telnet 的客户端和服务器地位平等,连接建立后,双方都可以主动发送数据或发起选项协商。这使 Telnet 极其灵活。

Telnet 的连接方式

Telnet 运行在 TCP 之上(因为需要可靠、有序的数据传输),服务器默认监听 TCP 23 端口

[客户端] ←——TCP 三次握手——→ [服务器:23端口]
建立连接后,会话可以持续数小时甚至数天。
NVT 的字符规范

NVT 使用 7 位 US-ASCII 字符集,每个字符用 1 个字节传输。

ASCII值 代码 含义 是否必须支持
0 NUL 空操作(无效果) 必须
7 BEL 响铃(发出提示音) 可选
8 BS 退格(向左移一格) 可选
9 HT 水平制表符 可选
10 LF 换行 必须
12 FF 换页(清屏) 可选
13 CR 回车(回到行首) 必须

特别说明:行尾表示
NVT 用 CR(回车)+ LF(换行)组合 表示一行的结束,即 CRLF
这是因为不同系统定义行尾的方式不同(有的只用 LF,有的只用 CR),NVT 统一用两者组合。

Telnet 协议命令

Telnet 命令和普通数据混在同一个数据流里传输,用特殊字节值 240~254 表示命令。
为了区分"这是命令"还是"这是普通数据值 240~254",每个命令前都加一个 IAC(Interpret As Command,值=255) 转义字符。

发送"中断进程"命令的字节序列:
  [255] [244]
   IAC   IP(Interrupt Process)
如果数据本身就是 255,则发送两个 255:
  [255] [255]

命令值 代码 用途
240 SE 子协商结束
241 NOP 空操作
242 DM 数据标记(用于紧急中断)
243 BRK Break键
244 IP 中断进程
245 AO 中止输出
246 AYT 你还在吗?(心跳检测)
247 EC 删除最后一个字符
248 EL 删除当前行
249 GA 请说(半双工模式下)
250 SB 子协商开始
251 WILL 我愿意执行某选项
252 WONT 我不愿意执行某选项
253 DO 请你执行某选项
254 DONT 请你不要执行某选项
255 IAC 命令转义前缀

Telnet 的中断处理(synch 机制)

问题场景:
假设你用 Telnet 跑一个程序,程序突然卡死了,你的 TCP 接收缓冲区塞满了,你发的"中断进程"命令被堵在队列里出不去怎么办?

[客户端发送队列]
  大量数据 → 大量数据 → 大量数据 → [IP命令] ← 被堵住了!

解决方案:synch 函数
Telnet 借助 TCP 的**紧急数据(Urgent Data)**功能,让"中断进程"命令绕过普通队列,优先送达。
具体做法:

  1. 客户端发送 DM(Data Mark)命令,并标记为 TCP 紧急数据
  2. 服务器收到紧急标志后,扫描缓冲区,专找控制命令(IP、AO、AYT 等)立即执行
  3. 扫描到 DM 为止,中间的普通数据全部丢弃(需重传)
Telnet 选项协商详解

协商使用四个命令:WILLWONTDODONT
情形一:我想启用某功能

客户端 → 服务器:WILL [选项X]    (我想启用X)
  服务器同意:DO [选项X]          (好的,你用吧)
  服务器拒绝:DONT [选项X]        (不行,别用)

情形二:我希望对方启用某功能

客户端 → 服务器:DO [选项X]      (请你启用X)
  服务器同意:WILL [选项X]        (好,我来用)
  服务器拒绝:WONT [选项X]        (我不用)

关闭选项:

任何一方 → 另一方:WONT [选项X]  (我要停用X)
  另一方必须回:DONT [选项X]      (好,确认停用)
任何一方 → 另一方:DONT [选项X]  (请你停用X)
  另一方必须回:WONT [选项X]      (好,我停用)
选项子协商(Subnegotiation)

有些选项不只是"开/关",还需要传递参数。比如 TERMINAL-TYPE 选项需要告知终端类型名称。
完整交互流程举例:

服务器 → 客户端:IAC DO TERMINAL-TYPE
  ("请你告诉我你的终端类型")
客户端 → 服务器:IAC WILL TERMINAL-TYPE
  ("好的,我来告诉你")
服务器 → 客户端:IAC SB TERMINAL-TYPE SEND IAC SE
  ("发给我吧")
客户端 → 服务器:IAC SB TERMINAL-TYPE IS vt100 IAC SE
  ("我的终端类型是 vt100")
常见 Telnet 选项速览

选项号 代码 含义
0 TRANSMIT-BINARY 允许发送8位二进制数据(非7位ASCII)
1 ECHO 本地回显/远端回显协商
3 SUPPRESS-GO-AHEAD 禁用半双工的GA命令,改为全双工
24 TERMINAL-TYPE 协商终端类型
31 NAWS 协商窗口大小
34 LINEMODE 按行发送(而非逐字符),减少小包数量
37 AUTHENTICATION 协商认证方式

Berkeley 远程命令(r 命令)

早期互联网主要跑的是 UNIX 系统,加州大学伯克利分校(UCB)开发了一套以字母 r(remote,远程)开头的命令,统称 r 命令

rlogin(远程登录)

rlogin 就是为远程登录量身定做的命令,比 Telnet 更简单直接。

用法示例:
  rlogin 目标主机名
服务端进程:rlogind(监听 TCP 513 端口)

登录时,客户端会发送:

  • 本地用户名
  • 远程用户名(通常相同)
  • 终端类型和速率
    自动登录: 系统管理员可以配置信任文件,让用户无需输密码就能登录,适合内部多机环境。
    安全隐患: 原始 rlogin 认证机制薄弱,不适合连接到互联网的系统。现代系统用 slogin(安全登录)替代。
rsh(远程 Shell)

rlogin 适合需要多次交互的场景。但如果你只想在远程主机上执行一条命令呢?用 rsh

用法示例:
  rsh 目标主机 ls -l > 本地文件
效果:在远程主机上执行 ls -l,结果保存到本地文件

rsh 支持 UNIX 标准的输入/输出重定向,可以配合脚本自动化远程操作。现代替代品是 ssh(Secure Shell)。

其他 r 命令

命令 全称 功能
rcp Remote Copy 在本地与远程之间复制文件,类似简化版 FTP
ruptime Remote Uptime 显示网络中所有机器的运行时间和负载
rwho Remote Who 显示所有机器上当前登录的用户

ruptimerwho 依赖后台运行的 rwhod 守护进程,各机器之间定期交换状态信息。

IRC 协议(互联网中继聊天)

为什么需要 IRC?

电子邮件虽然快,但是异步的——你发了,对方不一定马上看到。多轮来回可能要几个小时甚至几天。对于需要实时对话的场景,需要更好的工具。
IRC 就是互联网上的"文字电话",由芬兰人 Jarkko Oikarinen 于 1988 年创建,1993 年正式定义为 RFC 1459。

IRC 的工作模型
IRC 服务器网络(骨干):
  服务器A ←——TCP——→ 服务器B ←——TCP——→ 服务器C
        \                              /
         ↓                            ↓
      用户1连入                     用户2连入
       (通过服务器A访问整个IRC网络)
  • IRC 服务器:互相连接,形成一个应用层网络
  • IRC 客户端:连接到任意一台服务器,即可与全网用户通信
  • IRCop(IRC 运营者):管理服务器的特权用户
IRC 频道(Channel)

频道是 IRC 的核心概念,类似"聊天室":

#weather 频道 → 气象爱好者聚集
#linux 频道   → Linux 用户交流
#music 频道   → 音乐讨论
  • 所有人发消息 → IRC 网络实时转发给所有频道成员
  • 每个用户有一个昵称(nick)
  • 也支持私聊(一对一,但不加密)
IRC 的特点
  • 开放性:任何人都能立刻创建新频道,无需审批
  • 去中心化:有几十个不同的 IRC 网络,任何人都能搭建自己的
  • 历史地位:IRC 是现代即时通讯(QQ、微信、Slack 等)的鼻祖

第二部分:TCP/IP 管理与排错工具

hostname(主机名查询)

最基础的工具,查询当前主机的名字。

# 查看完整主机名
hostname
# 输出:fearn.pair.com
# 只看短名(不含域名)
hostname -s
# 输出:fearn

ping(连通性测试)

原理

ping 利用 ICMP 回显请求(Echo Request)回显应答(Echo Reply) 消息。
RTT = T 收到Echo Reply − T 发出Echo Request \text{RTT} = T_{\text{收到Echo Reply}} - T_{\text{发出Echo Request}} RTT=T收到Echo ReplyT发出Echo Request
其中 RTT(Round-Trip Time)是往返时延,单位毫秒。
工作示意:

主机A ——[ICMP Echo Request]——→ 主机B
主机A ←——[ICMP Echo Reply]——— 主机B
能诊断什么?

测试方式 诊断内容
ping 127.0.0.1 本机 TCP/IP 协议栈是否正常
ping 本机IP 本地网卡是否正常
ping 同局域网设备IP 局域网连通性
ping 本地网关IP 路由器是否可达
ping 域名 DNS 解析是否正常
ping 远程服务器IP 广域网连通性

典型输出分析
D:\> ping www.pcguide.com
Pinging pcguide.com [209.68.14.80] with 32 bytes of data:
Reply from 209.68.14.80: bytes=32 time=582ms TTL=56   ← 成功,耗时582毫秒
Reply from 209.68.14.80: bytes=32 time=601ms TTL=56   ← 成功
Request timed out.                                    ← 超时,此包丢失
Reply from 209.68.14.80: bytes=32 time=583ms TTL=56   ← 成功
Packets: Sent=4, Received=3, Lost=1 (25% loss)       ← 丢包率25%
Minimum=582ms, Maximum=601ms, Average=588ms           ← 时延统计

TTL(Time to Live):数据包最多能经过多少个路由器,每经过一个就减1,到0就丢弃。

UNIX 常用选项

选项 说明
-c <次数> 发送指定次数的包
-f 洪泛模式(高速发包,压力测试,慎用!)
-i <秒> 发包间隔
-s <字节> 指定包大小
-n 只显示IP地址,不解析域名

traceroute(路由追踪)

解决的问题

ping 只能告诉你"能不能通",但不知道数据包经过哪些路由器、哪一跳出了问题。
traceroute(Windows 下叫 tracert)就是来解决这个问题的。

核心原理:利用 TTL 字段

IP 数据包有一个 TTL 字段,每经过一个路由器就减1,减到0时路由器会丢包并回送 ICMP Time Exceeded 消息
traceroute 就是故意把 TTL 设得很小,让每个路由器都"报到":

假设路径:设备A → 路由器R1 → 路由器R2 → 设备B(共3跳)
第1次探测(TTL=1):
  A ——[TTL=1]——→ R1(TTL变0,丢包,回送 Time Exceeded)
  A ←——[ICMP Time Exceeded,来自R1]———
第2次探测(TTL=2):
  A ——[TTL=2]——→ R1(TTL减为1)→ R2(TTL变0,丢包,回送 Time Exceeded)
  A ←——[ICMP Time Exceeded,来自R2]———
第3次探测(TTL=3):
  A ——[TTL=3]——→ R1 → R2 → B(到达!但端口号非法)
  A ←——[ICMP Destination Unreachable,来自B]———

通过收到 ICMP 消息的来源,就能知道每一跳是谁,并能计算每一跳的耗时。

实际输出示例
traceroute www.pcguide.com
 1  cisco0fe0 (209.198.87.10)          1.2 ms    ← 第1跳,路由器1
 2  cisco1fe0 (209.198.87.12)          1.3 ms    ← 第2跳
 3  cisco0a5 (216.114.153.170)         8.0 ms
 4  207.136.212.234                    7.2 ms
 5  sl-gw18-nyc (144.232.228.145)     15.9 ms    ← 进入骨干网
...
 7  sl-bb27-pen (144.232.20.97)       18.3 ms  * ← *表示这次探测超时
...
13  * *                                          ← 整跳无响应(该路由器不回ICMP)
14  pcguide.com (209.68.14.80)        41.3 ms    ← 到达目的地

注意:第13跳没有响应,不一定是故障,可能是那台设备配置了不回 ICMP 消息(出于安全考虑)。

mermaid 流程图:traceroute 工作原理
设备B 路由器R2 路由器R1 设备A 设备B 路由器R2 路由器R1 设备A UDP探测包 TTL=1 ICMP Time Exceeded (TTL耗尽) UDP探测包 TTL=2 转发 TTL=1 ICMP Time Exceeded (TTL耗尽) UDP探测包 TTL=3 转发 TTL=2 转发 TTL=1 ICMP Destination Unreachable (端口非法)

arp(地址解析缓存管理)

背景知识

网络通信分两层:

  • IP 层(第3层):用 IP 地址寻址(逻辑地址)
  • 数据链路层(第2层):用 MAC 地址传输(物理地址)
    当主机 A(IP: 192.168.1.1)要向主机 B(IP: 192.168.1.2)发数据时,A 必须先知道 B 的 MAC 地址。这个"IP→MAC"的转换,就是 ARP(Address Resolution Protocol) 做的。
    为了避免每次都发 ARP 请求(浪费时间和带宽),系统维护一张 ARP 缓存表
IP地址           MAC地址              类型
192.168.1.1    00:1A:2B:3C:4D:5E    动态(自动学到的)
192.168.1.100  AA:BB:CC:DD:EE:FF    静态(手动配置的)
arp 命令的三大功能
# 1. 查看当前 ARP 缓存表(UNIX 和 Windows 相同)
arp -a
# 2. 手动添加一条静态映射
arp -s 192.168.1.100 AA-BB-CC-DD-EE-FF
# 3. 删除一条缓存条目
arp -d 192.168.1.100

DNS 查询工具 nslookup / host / dig

这三个工具都用于手动查询 DNS,功能从简到繁依次递进。

nslookup

两种模式:

# 非交互模式:直接查一个域名
nslookup www.example.com
# 输出示例:
# Server:  ns1-mar.starband.com     ← 使用的 DNS 服务器
# Address: 148.78.249.200
# Name:    example.com
# Address: 93.184.216.34            ← 解析到的 IP
# 反向解析(IP → 域名)
nslookup 93.184.216.34
# 交互模式:连续查询
nslookup
> www.example.com          ← 查正向解析
> set type=MX              ← 改查邮件服务器记录
> example.com
> exit
host(更简洁的查询)
host www.example.com
# 输出:
# www.example.com is an alias for example.com.
# example.com has address 93.184.216.34
dig(最详细的查询)
dig www.example.com
# 输出包含:
# - 查询的问题(QUESTION SECTION)
# - 答案(ANSWER SECTION)
# - 权威服务器信息(AUTHORITY SECTION)
# - 查询耗时、使用的DNS服务器等

三者对比:

工具 适合场景 信息量
nslookup 快速查询,支持交互模式
host 一行命令,快速看结果
dig 深度调试,查看完整 DNS 记录 最多

whois(域名注册信息查询)

whois 不是查 IP,而是查域名的注册信息:谁拥有这个域名、何时到期、联系人是谁。

whois example.com
# 输出示例:
# Registrant: Charles Kozierok
# Domain Name: EXAMPLE.COM
# Created: August 25, 1997
# Expires: August 24, 2028
# Domain Name Servers: NS1.EXAMPLE.COM

netstat(网络状态查看)

netstat 是"网络状态的仪表盘",能显示:

  • 当前哪些 TCP/UDP 连接是活跃的
  • 各协议的统计数据
  • 路由表
  • 网络接口信息
常用用法对比

命令 平台 作用
netstat UNIX/Win 显示活跃TCP连接
netstat -a UNIX/Win 显示所有连接和监听端口
netstat -s UNIX/Win 显示各协议统计数据
netstat -r UNIX/Win 显示路由表
netstat -i UNIX 显示网络接口信息
netstat -s -p ip UNIX 只看IP层统计
netstat -s -p icmp Win 只看ICMP统计

典型连接状态字段
Prot  Rcv  Snd  Local Address          Foreign Address        State
tcp4  0    0    pcguide.com.http       192.168.1.5.3384       ESTABLISHED  ← 已建立
tcp4  0    0    pcguide.com.http       192.168.1.6.1337       FIN_WAIT_1   ← 正在关闭
tcp4  0    0    pcguide.com.http       192.168.1.7.9954       TIME_WAIT    ← 等待超时

TCP 连接状态机简示:

CLOSED → SYN_SENT → SYN_RECEIVED → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED

ifconfig / ipconfig(网络配置工具)

UNIX:ifconfig

功能强大,既能查看也能修改网络接口配置。

# 查看所有接口
ifconfig -a
# 输出示例:
# fxp0: flags=8843<UP,BROADCAST,RUNNING> mtu 1500
#   inet 166.84.1.3 netmask 0xffffffe0
#   media: Ethernet 100baseTX full-duplex
# 禁用一个接口
ifconfig eth0 down
# 启用一个接口
ifconfig eth0 up
# 设置IP地址和子网掩码
ifconfig eth0 192.168.1.10 netmask 255.255.255.0
Windows:ipconfig

功能较简单,主要用于查看配置和操作 DHCP 租约。

# 查看基本信息(IP、子网掩码、网关)
ipconfig
# 查看详细信息(还包括MAC地址、DNS服务器、DHCP租约时间)
ipconfig /all
# 释放 DHCP 租约(断开IP)
ipconfig /release
# 重新申请 DHCP 租约(重新获取IP)
ipconfig /renew
# 清除 DNS 解析缓存
ipconfig /flushdns

典型输出:

Windows IP Configuration
Ethernet adapter Local Area Connection 2:
  IP Address:       148.64.133.73
  Subnet Mask:      255.255.192.0
  Default Gateway:  148.64.128.1
  DHCP Server:      148.64.128.1
  DNS Servers:      148.78.249.200
  Lease Obtained:   April 19, 2003 11:51:37 AM
  Lease Expires:    April 19, 2003 12:21:37 PM

杂项排错协议

这些是早期 TCP/IP 内置的简单测试协议,每个监听固定端口,可用 TCP 或 UDP 访问:

协议 端口 功能
Echo(回显) 7 原样返回收到的数据(验证基本数据传输)
Discard(丢弃) 9 把收到的数据全部丢弃(测试发送方)
Character Generator(字符生成) 19 持续生成随机字符发给客户端(压力测试)
Quote of the Day(每日引言) 17 返回一条管理员设置的短消息
Active Users(活跃用户) 11 返回当前登录用户列表
Daytime(日期时间) 13 返回人类可读的当前时间字符串
Time(时间戳) 37 返回从1900年1月1日00:00 GMT起的秒数(机器可读)

Time 协议说明:
返回值是一个整数,表示从固定时间点起经过的秒数:
T = seconds since  1900 - 01 - 01    00 : 00 : 00    GMT T = \text{seconds since } 1900\text{-}01\text{-}01 \; 00:00:00 \; \text{GMT} T=seconds since 1900-01-0100:00:00GMT
注意:该协议不能用于服务器时间同步,因为它不补偿网络传输时延的变化。

工具总览

                    TCP/IP 管理工具一览
                           |
         ┌─────────────────┼─────────────────┐
         |                 |                 |
      主机信息           连通性测试          配置查看
         |                 |                 |
      hostname           ping             ifconfig
                           |             ipconfig
                       traceroute
                           |
                        地址管理
                           |
                          arp
                           |
                        DNS工具
                           |
             ┌─────────────┼─────────────┐
             |             |             |
          nslookup        host          dig
                           |
                     注册信息查询
                           |
                         whois
                           |
                       网络状态
                           |
                        netstat

完整 C++ 演示代码

以下代码演示如何用 C++ 在 Linux/Unix 下:

  1. 调用系统命令 pingtraceroutearp -a 并捕获输出
  2. 解析简单 DNS 查询(使用 getaddrinfo
  3. 显示本机网络接口信息(使用 getifaddrs
#include <iostream>
#include <string>
#include <cstdio>       // popen, pclose, fgets
#include <cstring>      // memset
#include <vector>
#include <sstream>
// POSIX 网络接口相关头文件
#include <ifaddrs.h>
#include <netinet/in.h>
#include <arpa/inet.h>  // inet_ntop
#include <netdb.h>      // getaddrinfo, gethostbyname
// ================================================================
// 函数:执行一个 shell 命令,并把输出作为字符串返回
// 参数:cmd - 要执行的命令字符串
// 返回:命令的标准输出内容
// ================================================================
std::string runCommand(const std::string& cmd) {
    std::string result;
    // popen 打开一个管道,执行命令,返回文件指针
    FILE* pipe = popen(cmd.c_str(), "r");
    if (!pipe) {
        return "[错误:无法执行命令]";
    }
    char buffer[256];
    // 逐行读取命令输出
    while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
        result += buffer;
    }
    // 关闭管道
    pclose(pipe);
    return result;
}
// ================================================================
// 函数:演示 hostname 命令
// ================================================================
void demoHostname() {
    std::cout << "\n=== hostname 演示 ===" << std::endl;
    // 方法一:调用系统命令
    std::string output = runCommand("hostname");
    std::cout << "主机名: " << output;
    // 方法二:使用 C 标准库函数 gethostname
    char hostbuf[256];
    if (gethostname(hostbuf, sizeof(hostbuf)) == 0) {
        std::cout << "gethostname() 返回: " << hostbuf << std::endl;
    }
}
// ================================================================
// 函数:演示 DNS 解析(类似 nslookup/host)
// 使用 POSIX 标准函数 getaddrinfo
// 参数:hostname - 要解析的域名
// ================================================================
void demoDnsLookup(const std::string& hostname) {
    std::cout << "\n=== DNS 解析演示 (类似 nslookup) ===" << std::endl;
    std::cout << "查询域名: " << hostname << std::endl;
    // addrinfo 是返回结果的链表节点结构
    struct addrinfo hints;
    struct addrinfo* result = nullptr;
    // 清零 hints,设置查询参数
    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_UNSPEC;   // 同时接受 IPv4 和 IPv6
    hints.ai_socktype = SOCK_STREAM; // TCP 类型
    // 执行 DNS 查询
    int ret = getaddrinfo(hostname.c_str(), nullptr, &hints, &result);
    if (ret != 0) {
        // gai_strerror 将错误码转为可读字符串
        std::cout << "查询失败: " << gai_strerror(ret) << std::endl;
        return;
    }
    // 遍历所有返回的地址
    for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
        char ipstr[INET6_ADDRSTRLEN]; // 足够存放 IPv4 或 IPv6 地址的缓冲区
        if (rp->ai_family == AF_INET) {
            // IPv4 地址
            // sockaddr_in 是 IPv4 地址结构
            struct sockaddr_in* addr4 =
                reinterpret_cast<struct sockaddr_in*>(rp->ai_addr);
            // inet_ntop 把二进制地址转换成点分十进制字符串
            inet_ntop(AF_INET, &addr4->sin_addr, ipstr, sizeof(ipstr));
            std::cout << "IPv4 地址: " << ipstr << std::endl;
        } else if (rp->ai_family == AF_INET6) {
            // IPv6 地址
            struct sockaddr_in6* addr6 =
                reinterpret_cast<struct sockaddr_in6*>(rp->ai_addr);
            inet_ntop(AF_INET6, &addr6->sin6_addr, ipstr, sizeof(ipstr));
            std::cout << "IPv6 地址: " << ipstr << std::endl;
        }
    }
    // 释放 getaddrinfo 分配的内存(必须调用!)
    freeaddrinfo(result);
}
// ================================================================
// 函数:显示本机所有网络接口信息(类似 ifconfig -a)
// 使用 POSIX 函数 getifaddrs
// ================================================================
void demoIfconfig() {
    std::cout << "\n=== 网络接口信息 (类似 ifconfig -a) ===" << std::endl;
    struct ifaddrs* ifaddr = nullptr;
    // getifaddrs 获取所有网络接口的链表
    if (getifaddrs(&ifaddr) == -1) {
        std::cerr << "getifaddrs 调用失败" << std::endl;
        return;
    }
    // 遍历所有接口
    for (struct ifaddrs* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr == nullptr) continue; // 跳过没有地址的接口
        int family = ifa->ifa_addr->sa_family;
        char ipstr[INET6_ADDRSTRLEN];
        if (family == AF_INET) {
            // IPv4 接口
            struct sockaddr_in* addr4 =
                reinterpret_cast<struct sockaddr_in*>(ifa->ifa_addr);
            inet_ntop(AF_INET, &addr4->sin_addr, ipstr, sizeof(ipstr));
            std::cout << "接口: " << ifa->ifa_name
                      << "  IPv4: " << ipstr << std::endl;
        } else if (family == AF_INET6) {
            // IPv6 接口
            struct sockaddr_in6* addr6 =
                reinterpret_cast<struct sockaddr_in6*>(ifa->ifa_addr);
            inet_ntop(AF_INET6, &addr6->sin6_addr, ipstr, sizeof(ipstr));
            std::cout << "接口: " << ifa->ifa_name
                      << "  IPv6: " << ipstr << std::endl;
        }
    }
    // 释放 getifaddrs 分配的内存
    freeifaddrs(ifaddr);
}
// ================================================================
// 函数:演示 ping(通过调用系统命令,仅发3个包)
// 参数:target - 目标 IP 或域名
// ================================================================
void demoPing(const std::string& target) {
    std::cout << "\n=== ping 演示 ===" << std::endl;
    std::cout << "目标: " << target << std::endl;
    // -c 3 表示只发3个包,-W 2 表示超时2秒
    std::string cmd = "ping -c 3 -W 2 " + target + " 2>&1";
    std::string output = runCommand(cmd);
    std::cout << output;
}
// ================================================================
// 函数:演示 traceroute(通过调用系统命令)
// 参数:target - 目标 IP 或域名
// ================================================================
void demoTraceroute(const std::string& target) {
    std::cout << "\n=== traceroute 演示 ===" << std::endl;
    std::cout << "目标: " << target << std::endl;
    // -m 10 限制最多追踪10跳,避免输出太长
    // 先尝试 traceroute,如果不存在则用 tracepath
    std::string cmd = "traceroute -m 10 " + target + " 2>&1 || "
                      "tracepath -m 10 " + target + " 2>&1";
    std::string output = runCommand(cmd);
    std::cout << output;
}
// ================================================================
// 函数:演示 ARP 缓存查看
// ================================================================
void demoArp() {
    std::cout << "\n=== ARP 缓存表 (arp -a) ===" << std::endl;
    std::string output = runCommand("arp -a 2>&1");
    if (output.empty()) {
        std::cout << "[ARP 缓存为空或无权限查看]" << std::endl;
    } else {
        std::cout << output;
    }
}
// ================================================================
// 函数:演示 netstat(显示活跃连接)
// ================================================================
void demoNetstat() {
    std::cout << "\n=== netstat 活跃连接 ===" << std::endl;
    // -t 只显示TCP,-n 不解析主机名,只显示前20行
    std::string output = runCommand("netstat -tn 2>&1 | head -20");
    std::cout << output;
}
// ================================================================
// 主函数:依次演示各工具
// ================================================================
int main() {
    std::cout << "TCP/IP 管理工具演示程序" << std::endl;
    std::cout << "======================" << std::endl;
    // 1. hostname:查看主机名
    demoHostname();
    // 2. DNS 解析:类似 nslookup(用本机 DNS 解析 google.com)
    demoDnsLookup("www.google.com");
    // 3. 显示网络接口(类似 ifconfig -a)
    demoIfconfig();
    // 4. ping:测试与 8.8.8.8 (Google DNS) 的连通性
    demoPing("8.8.8.8");
    // 5. traceroute:追踪到 8.8.8.8 的路由
    demoTraceroute("8.8.8.8");
    // 6. ARP 缓存
    demoArp();
    // 7. netstat:当前活跃连接
    demoNetstat();
    std::cout << "\n演示完成。" << std::endl;
    return 0;
}

编译和运行方式(Linux):

# 编译(需要 g++,C++11 及以上)
g++ -std=c++11 -o tcpip_demo tcpip_demo.cpp
# 运行
./tcpip_demo

知识点总结

Telnet 协议
├── 历史:1969年诞生,第一个ARPAnet应用
├── 核心:NVT(网络虚拟终端)解决兼容性
├── 传输:TCP 23端口,全双工
├── 命令:IAC转义 + 240-254命令码
└── 选项:WILL/WONT/DO/DONT 四命令协商
Berkeley r命令
├── rlogin:远程登录(TCP 513)
├── rsh:执行单条远程命令
├── rcp:远程文件复制
├── ruptime:查看所有主机运行时长
└── rwho:查看所有主机登录用户
IRC 协议
├── 服务器互联形成骨干网
├── 客户端连接到任意服务器
├── 频道(Channel)= 群聊
├── 私聊 = 点对点(不加密)
└── 是QQ/微信/Slack的历史前身
管理工具
├── hostname → 查主机名
├── ping → 测通断 + 测时延(ICMP Echo)
├── traceroute → 追踪路由路径(TTL递增探测)
├── arp → 管理 IP↔MAC 映射缓存
├── nslookup/host/dig → DNS查询
├── whois → 域名注册信息
├── netstat → 查连接状态/路由表/统计
└── ifconfig/ipconfig → 查改网络接口配置
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐