Internet 进 阶 技 术

万 丈 高 楼 平 地 起
Socket 打 通 网 路 命 脉


上 期 作 者 已 谈 过 Internet 最 重 要 的 理 论 “ TCP” , 本 期 继 续 再 来 看 看 “ Socket” 是 如 何 运 作 , 顺 道 探 讨 它 和 下 层 TCP/IP网 路 之 间 的 关 系 。


马 得 翔 (Derek Ma)


  相 信 大 家 对 Socket这 个 名 词 并 不 陌 生 , 因 为 它 使 得 我 们 的 应 用 程 式 能 轻 松 地 享 用 TCP/IP模 组 , 让 我 们 无 忧 无 虑 的 遨 游 Internet。 简 单 地 说 , 它 就 是 应 用 程 式 与 网 路 作 业 系 统 间 的 桥 梁 , 只 要 指 定 埠 号 、 网 际 位 址 和 传 输 形 态 , Socket就 会 帮 我 们 和 网 路 作 业 系 统 沟 通 , 让 我 们 连 到 所 指 定 的 地 方 。 还 记 得 我 们 以 前 谈 到 的 IP、 ICMP、 TCP吗 ? 它 们 是 要 让 我 们 的 网 路 介 面 能 和 Internet沟 通 、 协 调 ; 同 样 的 道 理 , Socket就 是 提 供 一 个 介 面 来 让 应 用 程 式 使 用 网 路 作 业 系 统 或 动 态 联 结 函 式 库 ( 见图 二 即 是 一 般 我 们 利 用 Socket函 式 来 建 立 主 从 ( Client/Server) 联 结 , 对 Server而 言 , Socket( ) 是 去 向 Socket Interface开 启 一 个 我 们 所 指 定 的 传 输 层 的 服 务 形 态 ( 如 TCP-SOCK_STREAM或 UDP- SOCK_GRAM) , 之 後 再 用 Bind( ) 去 跟 传 输 层 说 , 从 这 个 埠 号 来 的 资 料 都 是 我 的 , 而 我 也 利 用 这 个 埠 号 来 传 送 资 料 , 请 不 要 和 别 人 混 淆 ; 最 後 再 用 listen( ) 去 指 定 最 大 的 联 结 个 数, accept( ) 去 等 待 Client的 connect( ) 上 门 , 一 个 Server就 大 功 告 成 了 。 而 Client端 更 简 单 , 只 要 去 开 启 一 个 Socket( ) ( 如 同 Server般 ) , 再 用 connect( ) 去 指 定 Server的 IP Address与 Port Number, 就 可 以 和 Server端 的 accept( ) 来 产 生 一 条 双 向的 TCP/IP联 结 。

  这 里 , 读 者 应 可 以 和 前 几 期 所 讨 论 的 内 容 来 个 呼 应 。 其 中 accept( ) 、 connect( )、 close( ) 牵 涉 到 TCP模 组 中 的 3-Way Handshaking演 算 法 和 TCP标 头 中 选 择 项 的 设 定 ; 读 写 资 料 send( ) /recv( ) 和 TCP/IP中 的 流 量 控 制 、 滑 动 窗 口 、 Push机 制 等 都 有 关 系 , 而 Socket( ) 、 Bind( ) 、 或listen( ) 是 属 於 TCP/IP内 部 的 管 理 问 题 。 那 麽 资 料 一 次 丢 太 多 呢 ? 您 也 不 必 担 心 , IP 的 fragmentation/reassembly机 制 和 TCP依 据 最 大 分 段 尺 寸 ( MSS) 来 帮 您 把 资 料 分 段 、 编 号 、 封 包 ; 当 然 , 倘 若 您 使 用 的 是 SOCK_STRAEM, TCP也 会 帮 您 建 立 一 条 可 靠 的 联 结 , 您 就 不 必 再 担 心 封 包 遗 失 或 失 序 等 问 题 。 总 之 一 些 543的 问 题 您 都 可 以 丢 在 一 旁 , 您 只 需 要 专 注 於 上 层 应 用 程 式 的 发 展 即 可 。

  在 图 二 我 们 也 指 出 利 用 Socket来 建 立 Concurrent Server的 观 念 , 什 麽 是 Concurrent Server呢 ? 那 就 是 利 用 一 个 或 多 个 Server来 同 时 服 务 一 个 以 上 的 Client。 在 Concurrent Server中 , 虽 然 Server只 有 Bind一 个 埠 号 , 但 由 於 Socket介 面 内 部 的 管 理 , 可 以 使 我 们 分 辨 来 自 不 同 地 方 对 同 一 埠 号 的 联 结 。 这 是 怎 麽 做 到 的 呢 ? 如 图 二 的 两 条 联 结 集 合 {TCP, C1, P1, S, P}与 { TCP, C2, P2, S, P}, C1、 C2、 S 任 两 者 IP Address可 以 不 同 ( 在 不 同 主 机 ) 或 一 样 ( 在 同 一 部 主 机 ) , 但 P1、 P2、 P任 两 者 若 在 同 一 部 主 机 , Port Number一 定 不 能 一 样 ; 若 在 不 同 主 机 , 则 Port Number可 同 可 不 同 。 如 此 一 来 , 上 述 的 两 个 集 合 一 定 不 相 等 。 利 用 这 个 特 性 , Socket介 面 就 可 以 分 辨 来 自 不 同 地 方 对 同 一 个 埠 号 的 联 结 , 资 料 也 因 此 可 以 Pass至 正 确 的 Server或 Client了 。

Socket新 手

  对 於 Socket Programming的 新 手 而 言 , Bind( ) 函 式 似 乎 是 最 令 人 头 疼 的 。 这 是 因 为 所 请 求 的 埠 号 可 能 被 其 他 的 程 式 占 用 , 或 者 埠 号 小 於 1024和 系 统 冲 到 , 或 者 埠 号 没 有 转 成 标 准 的 网 路 格 式 ( htons) , 甚 至 可 能 因 为 之 前 跑 的 程 式 不 正 常 的 结 束 , 导 致 埠 号 暂 时 被 系 统 hold住 , 要 等 一 会 儿 才 能 使 用 ( Maximum Segment Lifetime, MSL, 一 般 大 约 为 2分 钟 ) 。 上 述 的 情 况 都 有 可 能 发 生 , 使 用 者 必 须 要 留 意 的 。

  当 然 , 正 确 的 程 式 设 计 或 利 用 Bind( ) 的 传 回 值 来 做 判 断 可 以 解 决 上 述 的 问 题 , 但 假 使 Client在 连 线 之 前 只 知 Server的 IP Address而 不 知 道 Server的 埠 号 , 那 要 怎 麽 办 呢 ?

  这 个 问 题 有 点 复 杂 , 解 决 的 方 法 是 在 Server端 先 跑 一 个 Daemon( 一 个 在 Background执 行 的 程 式 ) , 它 Bind在 一 个 公 开 ( Well Known) 的 Port( 如 Winsock的 Services档 中 定 义 , 当 然 不 要 和 别 的 定 义 冲 到 ) , Client利 用 Getservbyname取 得 Daemon的 埠 号 ( Client端 的 Services档 也 要 定 义 一 样 ) , 然 後 Connect过 去 , 成 功 之 後 , Client把 要 执 行 的 程 式 名 与 路 径 交 给 Daemon, Daemon就 去 fork这 个 真 正 的 Server( 如 果 早 已 存 在 , 就 不 用 fork) , 然 後 真 正 的 Server去 Bind後 ,利 用 IPC( InterProcess Communication) 把 埠 号 丢 给 Daemon, Daemon在 Pass给 Client, 然 後 Daemon和 Client结 束 联 结 , 最 後 Client再 利 用 刚 才 从 Daemon得 到 的 埠 号 重 新 启 动 一 次 联 结 , 就 大 功 告 成 了 ( 见 图 叁 ) 。

  这 样 有 个 好 处 。 就 是 方 便 Server埠 号 的 管 理 。 试 想 Server怎 会 知 道 要 Bind时 埠 号 已 经 被 别 人 用 了 或 有 问 题 ; 如 果 有 问 题 , Server在 Bind另 外 一 个 埠 号 , 那 麽 Client又 怎 会 知 道 要 去 换 一 个 埠 号 来 Connect, 程 式 是 否 又 要 重 新 编 译 和 联 结 呢 ? 同 样 的 道 理 , 如 果 你 再 出 售 一 套 国 际 化 的 Client/Server软 体 , 你 能 要 求 客 户 不 要 去 使 用 某 某 埠 号 吗 ? 在 换 一 个 角 度 , 如 果 你 要 在 Server维 护 100 个 程 式 , 你 是 否 要 知 道 这 100个 程 式 的 埠 号 , 然 後 又 和 写 编 号 为 第 101的 设 计 师 说 , 请 你 去 用 某 某 埠 号 , 别 的 会 冲 到 。 又 假 设 老 师 出 一 个 习 题 , 在 X主 机 、埠 号 6000写 一 个 目 录 查 询 的 Server, 明 天 交 。 我 敢 保 证 一 定 天 下 大 乱 , 因 为 测 试 时 运 气 好 , 埠 号 没 人 用 , 所 以 不 会 有 问 题 。 可 是 等 到 要 Demo 时 , 谁 都 不 敢 保 证 Bind是 否 可 以 成 功 。

  其 实 最 大 的 问 题 是 UNIX把 这 种 Run Time埠 号 Binding的 问 题 留 给 使 用 者 去 伤 脑 筋 。 想 想 看 , 维 护 程 式 名 ( Client把 要 执 行 的 程 式 名 丢 给 Daemon, 然 後 Daemon启 动 且 传 回 其 埠 号 ) 总 比 维 护 Server的 埠 号 来 得 轻 松 、 高 阶 、 有 意 义 多 了 。

  当 然 从 作 业 系 统 的 观 点 却 不 一 样 , 试 想 若 有 两 家 网 路 公 司 , 各 写 了 一 套 FTP Server , 那 麽 它 们 是 否 要 先 开 个 会 来 决 定 彼 此 间 的 路 径 或 者 是 让 使 用 者 在 FTP时 传 入 FTP Server Daemon所 在 的 路 径 呢 ? 如 果 这 样 做 天 下 一 定 又 大 乱 了 。 而 假 使 各 个 FTP Server 各 Bind一 个 不 相 干 的 系 统 埠 号 , 让 使 用 者 决 定 去 Connect那 一 个 埠 号 , 这 样 应 该 比 较 简 单 吧 !

  上 述 两 种 方 法 如 何 去 取 决 呢 ? 小 弟 认 为 , 若 您 的 系 统 有 很 多 使 用 者 写 Socket有 关 的 程 式 , 最 好 用 图 叁 的 方 法 来 管 理 。 简 言 之 , 就 是 利 用 一 个 Daemon的 系 统 埠 号 , 来 去 管 理 其 他 使 用 者 的 埠 号 。 而 假 使 所 写 的 是 系 统 程 式 , 那 就 直 接 去 Bind一 个 Well-Known系 统 埠 号 比 较 圆 满 。

实 例 研 习 — 探 讨 Internet的 庞 毕 都 中 心 ( Pompidou Centre)

  庞 毕 都 中 心 位 於 巴 黎 市 中 心 的 圣 母 院 附 近 , 它 是 一 栋 非 常 特 别 的 建 物 , 它 把 它 的 骨 架 、 空 调 、 水 管 、 电 梯 等 都 露 在 外 面 , 换 句 话 说 就 是 Turn Inside Out。 同 样 地 , 让 我 们 用 Trumpet Winsock的 Trace来 剥 开 Internet, 以 便 了 解 它 和 Socket、 TCP/IP 模 组 的 关 系 。

  首 先 , 我 们 Telnet到 某 一 主 机 , 成 功 之 後 , 我 们 就 能 Trace其 中 的 Socket、 TCP/IP, 这 时 我 们 每 敲 一 下 键 盘 , 我 们 可 以 收 到 如 下 的 讯 息 ( 见 图 四 ) 。 解 释 如 下 :

( 1) 应 用 程 式 检 查 是 否 有 Blocking动 作 中 。

( 2) Telnet收 到 敲 下 的 键 , 然 後 用 Send送 出 1个 Byte到 Winsock。

( 3) 表 示 TCP送 出 一 个 封 包 , 来 源 埠 号 为 1027, 目 地 埠 号 为 23。 此 封 包 的 序 号 为 67, 2763和 ACK是 Piggy Back, 是 和 对 方 说 2762之 前 的 资 料 都 已 收 到 , 我 希 望 下 一 个 封 包 为 2763。 PSH设 定 表 示 马 上 把 资 料 传 出 , Wind 4096是 和 对 方 说 我 的 Receiving Window还 有 4096, 你 尽 管 丢 , 不 用 怕 。 data1表 示 有 1个 Byte的 资 料 。

( 4) IP层 把 资 料 丢 给 收 方 ( 148.5.3.86 -> 148.5.3.73) 。 len 41是 标 准 的 TCP加 上 IP 的 标 头 长 ( 20+20) , 在 加 上 TCP所 传 来 的 1个 Byte的 资 料 。

( 5) 对 方 利 用 IP回 话 了 ( 148.5.3.73 ->148.5.3.86)。

( 6) 是 对 方 送 给 我 们 的 资 料 ( 23->1027 ) , 序 号 为 2763, 顺 便 和 我 说 67号 之 前 的 资 料 我 都 收 到 了 , 下 一 个 请 丢 68号 给 我 。

( 7) 去 和 对 方 说 你 的 2763资 料 我 已 收 到 , 下 一 个 请 丢 2764。

( 8) IP层 送 出 确 认 封 包 ( 148.5.3.86->148.5.3.73) 。

( 9) 应 用 程 式 检 查 是 否 有 Blocking动 作 中 。

( 10) 程 式 用 recv( ) 从 Winsock收 到 资 料 。

  懂 了 吗 ? Socket与 TCP/IP的 关 系 就 是 如 此 的 简 单 , 从 送 方 键 盘 的 硬 体 中 断 , 到 Windows的 Kernel/User模 组 呼 叫 应 用 程 式 , 然 後 再 进 入 Winsock.dll的 TCP/IP网 路 服 务 , 到 了 收 方 的 Server, 然 後 传 回 结 果 到 送 方 的 IP/TCP与 Socket.dll, 程 式 收 到 後 再 呼 叫 Windows内 的 模 组 , 这 就 是 我 们 敲 一 个 键 的 软 体 动 作 。

  让 我 们 在 举 一 个 较 深 的 例 子 来 看 看 ( 见 图 五 ) 。 读 者 可 由 ( 1) 至 ( 7) 的 顺 序 来 慢 慢 窥 探 一 个 TCP Socket从 Winsock呼 叫 来 开 启 Socket、 请 求 联 结 、 联 结 成 功 、 传 收 资 料 、 到 结 束 联 结 的 软 体 动 作 。 限 於 篇 幅 的 因 素 , 笔 者 不 能 多 做 介 绍 , 仅 把 要 点 按 编 号 列 出 。 此 外 , 有 兴 趣 的 读 者 亦 可 以 Trace一 个 UDPSocket, 看 看 和 TCP Socket有 何 不 同 。

  如 果 Trumpet Winsock的 Trace功 能 还 不 能 满 足 您 对 於 网 路 的 探 索 , 那 笔 者 建 议 您 使 用 ethload 1.04这 个 架 在 Ethernet的 免 费 网 路 分 析 软 体 , ODI、 NDIS、 DEC DLL spec.、 Packet Driver等 驱 动 程 式 它 都 有 支 援 。 它 的 功 能 真 的 没 话 说 , 不 管 Ethernet、 TCP/IP、 DECnet 、 OSI、 Microsoft Net BEUI或 Novel NetWare, 它 都 可 以 透 视 、 分 析 、 统 计 , 只 差 没 有 防 火 墙 ( firewall) 的 功 能 。 对 网 路 有 兴 趣 的 读 者 , 千 万 不 可 错 过 。

  当 然 , 目 前 最 红 的 就 是 Winsock, 如 果 读 者 对 这 方 面 有 兴 趣 的 话 , 可 以 用 Netscape 连 到 http://www.seed.net.tw/~seedw002/ , 或 ftp://tpes1.seed.net.tw/UPLOAD/WINKING都 可 以 拿 到 不 错 的 资 料 。 另 外 交 大 资 工 的ftp Server( ftp://ftp.csie.nctu.edu.tw/pub/winsock) 搜 集 了 许 多 Winsock软 体 ; tw.bbs.comp.network讨 论 群 也 有 许 多 有 关 Winsock的 讨 论 , 有 志 於 网 路 设 计 的 朋 友 要 多 多 留 意 。

  总 之 , 从 4BSD把 档 案 I/O引 入 Socket 到 现 在 Winsock大 行 其 道 , 甚 至 Socket 程 式 产 生 器 ( 如 SUN的 RPC Generator) , 不 用 懂 都 可 写 程 式 。 网 路 介 面 已 经 渐 趋 成 熟 , 随 着 愈 来 愈 多 的 应 用 程 式 和 网 际 服 务 , Internet正 以 等 比 级 数 成 长 着 ; 受 惠 最 大 的 应 是 广 大 的 网 路 爱 用 人 者 。 而 剩 下 来 的 就 是 HiNet与 SeedNet的 降 价 , 与 ISP业 者 的 努 力 吧 !

(作 者 现 任 职 於 台 湾 金 讯 电 子 股 份 有 限 公 司 ) ________________________________________________________________