import java.util.*; 3.1.3 在 Java 中 的 简 单 的 TCP 服 务 器 在 Java 中, 一 个 服 务 器 端 的 被 动 的 套 接 字 由 java.net.serversocket 表 示. 一 个 TCP 服 务 器 构 造 一 个 java.net.s



Similar documents
Java Access 5-1 Server Client Client Server Server Client 5-2 DataInputStream Class java.io.datainptstream (extends) FilterInputStream InputStream Obj

Socket Socket TcpClient Socket.Connect TcpClient.Connect Socket.Send / Receive NetworkStream 6-5

Chap6.ppt

新・解きながら学ぶJava

穨control.PDF

(Methods) Client Server Microsoft Winsock Control VB 1 VB Microsoft Winsock Control 6.0 Microsoft Winsock Control 6.0 1(a). 2

Chapter 9: Objects and Classes

untitled

JavaIO.PDF

3.1 num = 3 ch = 'C' 2

UDP 8.2 TCP/IP OSI OSI 3 OSI TCP/IP IP TCP/IP TCP/IP Transport Control Protocol TCP User Datagram Protocol UDP TCP TCP/IP IP TCP TCP/IP TC

计 算 机 系 统 应 用 年 第 25 卷 第 4 期 线 程 的 复 用 [2,3]. 通 常 情 况 下, 服 务 器 端 程 序 在 启 动 时 创 建 若 干 数 量 的 线 程 对 象 并 缓 存 起 来, 此 时 它 们 处 于

Java

Microsoft Word - 01.DOC

TCP/IP TCP/IP OSI IP TCP IP IP TCP/IP TCP/IP

W. Richard Stevens UNIX Sockets API echo Sockets TCP OOB IO C struct C/C++ UNIX fork() select(2)/poll(2)/epoll(4) IO IO CPU 100% libevent UNIX CPU IO

Go构建日请求千亿微服务最佳实践的副本

chp6.ppt

/ / (FC 3)...

R3105+ ADSL

財金資訊-80期.indd

untitled

PIC_SERVER (11) SMTP ( ) ( ) PIC_SERVER (10) SMTP PIC_SERVER (event driven) PIC_SERVER SMTP 1. E-

1.JasperReport ireport JasperReport ireport JDK JDK JDK JDK ant ant...6

C6_ppt.PDF

ebook140-9

09 (File Processes) (mkdir) 9-3 (createnewfile) 9-4 (write) 9-5 (read) 9-6 (deletefile) 9-7 (deletedir) (Exercises)

ebook140-11

ebook67-D


Fun Time (1) What happens in memory? 1 i n t i ; 2 s h o r t j ; 3 double k ; 4 char c = a ; 5 i = 3; j = 2; 6 k = i j ; H.-T. Lin (NTU CSIE) Referenc

KillTest 质量更高 服务更好 学习资料 半年免费更新服务

untitled

1: public class MyOutputStream implements AutoCloseable { 3: public void close() throws IOException { 4: throw new IOException(); 5: } 6:

概述

ARP ICMP

SL2511 SR Plus 操作手冊_單面.doc

untitled

(TestFailure) JUnit Framework AssertionFailedError JUnit Composite TestSuite Test TestSuite run() run() JUnit

其中有些限制比现实更常出现. 数据报模型非常适合这样的应用程序 : (a) 请求 - 回复的交易 (FIXME:Transactions are request-reply). (b) 负载比较小. (c) 服务器是无连接的. (d) 事务是幂等的 (FIXME:Transactions are i

KillTest 质量更高 服务更好 学习资料 半年免费更新服务

提纲 1 2 OS Examples for 3

IP505SM_manual_cn.doc

雲端 Cloud Computing 技術指南 運算 應用 平台與架構 10/04/15 11:55:46 INFO 10/04/15 11:55:53 INFO 10/04/15 11:55:56 INFO 10/04/15 11:56:05 INFO 10/04/15 11:56:07 INFO

C/C++ - 文件IO

Simulator By SunLingxi 2003

KillTest 质量更高 服务更好 学习资料 半年免费更新服务

Chapter 9: Objects and Classes

<4D F736F F F696E74202D20A1B6CFEEC4BFD2BB20B3F5CAB6BCC6CBE3BBFACDF8C2E7A1B7C8CECEF1C8FD20CAECCFA A1A24950D0ADD2E9BACD4950B5D8D6B72E707074>

untitled

Web

Microsoft PowerPoint - ch6 [相容模式]

ebook12-1

OSI OSI 15% 20% OSI OSI ISO International Standard Organization 1984 OSI Open-data System Interface Reference Model OSI OSI OSI OSI ISO Prototype Prot

Java java.lang.math Java Java.util.Random : ArithmeticException int zero = 0; try { int i= 72 / zero ; }catch (ArithmeticException e ) { // } 0,

IP Access Lists IP Access Lists IP Access Lists

1 Project New Project 1 2 Windows 1 3 N C test Windows uv2 KEIL uvision2 1 2 New Project Ateml AT89C AT89C51 3 KEIL Demo C C File

2009年9月全国计算机等级考试二级Java真题及答案

序 屈 指 数 来, 柔 情 滑 落 指 尖 : 是 父 母 给 了 我 们 生 命, 是 家 人 给 了 我 们 亲 情, 是 朋 友 给 了 我 们 友 谊 ; 是 不 幸 给 了 我 们 成 熟, 是 挫 折 给 了 我 们 坚 定 ; 是 苦 难 给 了 我 们 刚 毅, 是 逆 境 给 了

中 文 摘 要 智 慧 型 手 機 由 於 有 強 大 的 功 能, 以 及 優 渥 的 便 利 性, 還 能 與 網 路 保 持 隨 時 的 鏈 結 與 同 步 更 新, 因 此 深 受 廣 大 消 費 者 喜 愛, 當 然, 手 機 遊 戲 也 成 為 現 代 人 不 可 或 缺 的 娛 樂 之

untitled

RUN_PC連載_12_.doc

weblogic

Microsoft PowerPoint - 数据通信-ch1.ppt

VoIP Make a Rtp Call VoIP Abstract... 2 VoIP RTP...3 Socket IP...9 Config Two Voice-hub

Windows RTEMS 1 Danilliu MMI TCP/IP QEMU i386 QEMU ARM POWERPC i386 IPC PC104 uc/os-ii uc/os MMI TCP/IP i386 PORT Linux ecos Linux ecos ecos eco

C/C++ - 字符输入输出和字符确认

KillTest 质量更高 服务更好 学习资料 半年免费更新服务

27 :OPC 45 [4] (Automation Interface Standard), (Costom Interface Standard), OPC 2,,, VB Delphi OPC, OPC C++, OPC OPC OPC, [1] 1 OPC 1.1 OPC OPC(OLE f

EJB-Programming-4-cn.doc

小 说 天 地 欲 望 摩 托 尚 成 河 血 溅 维 纳 斯 刘 步 明 长 调 短 歌 海 上 天 湖 李 转 生 目 海 尖 高 处 的 三 种 陈 述 谢 应 华 乡 村 笔 记 阿 曼 桃 花 渡 林 小 耳 种 诗 歌 江 良 热 雨 花 石 张 彩 霞 刊 名 书 法 陈 奋 武 屏

!

** 状 态 二 亚 健 康 亚 健 康 是 指 处 于 健 康 和 疾 病 两 者 之 间 的 一 种 状 态 即 机 体 内 出 现 某 些 功 能 紊 乱 但 未 影 响 到 行 使 社 会 功 能 主 观 上 有 不 适 感 觉 它 是 人 体 处 于 健 康 和 疾 病 之 间 的 过 渡

./ /

!

!

Microsoft PowerPoint - talk8.ppt

2009年3月全国计算机等级考试二级Java语言程序设计笔试试题

使 用 Java 语 言 模 拟 保 险 箱 容 量 门 板 厚 度 箱 体 厚 度 属 性 锁 具 类 型 开 保 险 箱 关 保 险 箱 动 作 存 取 款

《大话设计模式》第一章

TX-NR3030_BAS_Cs_ indd

ebook140-8

提问袁小兵:

(UTM???U_935_938_955_958_959 V )

Symantec™ Sygate Enterprise Protection 防护代理安装使用指南

2 Java 语 言 程 序 设 计 教 程 简 单 性 Java 语 言 的 语 法 与 C 语 言 和 C++ 语 言 很 接 近, 使 得 大 多 数 程 序 员 很 容 易 学 习 和 使 用 Java 另 一 方 面,Java 丢 弃 了 C++ 中 很 少 使 用 的 很 难

Untitled

第 1 章 概 述 1.1 计 算 机 网 络 在 信 息 时 代 中 的 作 用 1.2 计 算 机 网 络 的 发 展 过 程 *1.2.1 分 组 交 换 的 产 生 *1.2.2 因 特 网 时 代 *1.2.3 关 于 因 特 网 的 标 准 化 工 作 计 算 机 网 络 在

ch08.PDF

mppp-ddr.pdf

untitled

<4D F736F F D20CDF8B9A42DC9CFCEE7CCE22D3038CFC2>

AL-M200 Series

interfaces which it implements): int read(bytebuffer) throws IOException; int read(bytebuffer[] buffers) throws IOException; int read(bytebuffer[] buf

前 言

Microsoft Word - Java Socket 連線的建立課程_柯志亨老師_電子書.doc

前言 C# C# C# C C# C# C# C# C# microservices C# More Effective C# More Effective C# C# C# C# Effective C# 50 C# C# 7 Effective vii

Chapter #

Microsoft Word - 第3章.doc

51 C 51 isp 10 C PCB C C C C KEIL

Transcription:

这 一 章 讨 论 了 TCP/IP 协 议 的 基 础 和 它 在 阻 塞 模 式 中 Java Socket 和 ServerSocket 对 象 的 实 现. 这 一 章 假 设 对 TCP/IP 的 基 本 概 念 和 Java 套 接 字 有 一 个 认 识, 虽 然 提 供 了 一 个 简 要 的 回 顾. TCP Channel I/O 和 非 阻 塞 模 式 在 第 5 章 讨 论. 3.1 基 本 的 TCP Sockets 在 这 节 我 们 简 要 回 顾 基 本 的 TCP/IP 的 Sockets 和 如 何 在 Java 中 编 程. 3.1.1 TCP 总 结 TCP/ 提 供 了 在 一 个 客 户 端 - 服 务 器 端 架 构 的 端 点 之 间 可 靠 的 双 向 的 流 的 连 接. 一 个 TCP 端 点 定 义 为 {IP address, port 对, 代 表 了 TCP 编 程 中 的 接 口, 作 为 一 个 TCP 套 接 字, 如 2.2.5 节 中 的 定 义. 通 过 流, 意 味 着 数 据 传 输 和 接 收 被 当 做 一 个 持 续 的 字 节 流, 没 有 消 息 边 界. 有 两 种 类 型 的 TCP socket:" 主 动 (active)" 和 " 被 动 (passive)"( 通 常 被 称 为 " 监 听 "). 一 个 TCP 服 务 器 创 建 一 个 TCP 套 接 字 ; 绑 定 到 一 个 端 口 ; 使 它 进 入 " 监 听 " 状 态 ; 然 后 循 环 " 接 收 " 客 户 端 的 连 接. 客 户 端 创 建 一 个 主 动 的 TCP 套 接 字, 然 后 " 连 接 " 到 服 务 器 的 端 口. 服 务 器 " 接 收 " 这 个 连 接 请 求, 在 这 个 过 程 中 接 收 一 个 新 的 活 动 的 socket 代 表 它 的 连 接 端. 现 在 服 务 器 端 和 客 户 端 连 接 上 了, 可 以 可 靠 地 发 送 数 据 给 另 一 方 任 意 数 量 的 数 据, 如 果 有 必 要 两 边 同 时. 数 据 通 过 这 个 连 接 被 完 整 的 传 递 和 以 正 确 的 顺 序 被 发 送, 作 为 一 个 数 据 流 而 不 是 不 同 的 消 息. TCP 连 接 过 程 如 图 3.1. 产 生 的 连 接 由 { 本 地 端 口, 本 地 地 址, 远 程 端 口, 远 程 地 址 对 定 义. 每 个 TCP 段 包 含 了 这 个 对, 保 证 了 它 传 递 到 正 确 的 端 点. 在 这 一 节 提 供 的 材 料 在 随 后 的 章 节 阐 明 了 TCP 选 项.TCP 服 务 器 和 客 户 端 的 更 多 高 级 的 架 构 在 第 12 章 讨 论. 3.1.2 导 入 语 句 下 面 的 Java 导 入 语 句 假 设 在 这 章 的 例 子 中 至 始 至 终 存 在. import java.io.*; import java.net.*;

import java.util.*; 3.1.3 在 Java 中 的 简 单 的 TCP 服 务 器 在 Java 中, 一 个 服 务 器 端 的 被 动 的 套 接 字 由 java.net.serversocket 表 示. 一 个 TCP 服 务 器 构 造 一 个 java.net.serversocket, 然 后 循 环 调 用 ServerSocket.accept. 循 环 的 每 次 迭 代 返 回 一 个 java.net.socket 代 表 一 个 已 经 接 收 的 连 接. 一 个 最 简 单 的 可 行 TCP 服 务 器 处 理 每 个 连 接 在 接 受 一 个 新 的 之 前, 如 Example3.1 描 述 : public class TCPServer implements Runnable { private ServerSocket serversocket; // constructor public TCPServer(int port) throws IOException { this.serversocket = new ServerSocket(port); public void run() { for (;;) { try { Socket socket = serversocket.accept(); // 注 意 : 这 里 不 是 以 线 程 的 方 式 启 动 的 new ConnectionHandler(socket).run(); catch (IOException e) { /* */ // end finally // end run() // end class Example 3.1 单 线 程 的 TCP 服 务 器 这 个 类 的 连 接 处 理 类 和 随 后 的 服 务 在 Example3.2 中 展 示. public class ConnectionHandler implements Runnable { private Socket socket; public ConnectionHandler(Socket socket) { this.socket = socket; public void run() { handleconversation(socket); /** * @param socket * Socket: must be closed on exit */ public void handleconversation(socket socket) { try { InputStream in = socket.getinputstream();

// read request from the input:( 读 取 请 求 ) // conversation not shown ( 交 互 没 有 展 示 ) OutputStream out = socket.getoutputstream(); // write reply to the output( 回 复 ) out.flush(); catch (IOException e) { /* */ finally { try { socket.close(); catch (IOException e) { // end finally // end run() Example 3.2 TCP 服 务 器 连 接 处 理 器 Example 3.1 的 单 线 程 设 计 不 太 适 用, 因 为 它 顺 序 地 处 理 客 户 端, 而 不 是 并 发 - 一 个 新 的 客 户 端 将 阻 塞 当 先 前 的 客 户 端 正 在 服 务. 为 了 解 决 客 户 端 并 发, 服 务 器 必 为 每 个 接 收 的 连 接 使 用 一 个 不 同 的 线 程. 这 样 的 TCP 服 务 器 的 最 简 单 的 形 式, 使 用 同 样 的 连 接 - 处 理 类, 在 Example 3.3 描 述. public class TCPServer implements Runnable { private ServerSocket serversocket; // constructor public TCPServer(int port) throws IOException { this.serversocket = new ServerSocket(port); public void run() { for (;;) { try { Socket socket = serversocket.accept(); // 以 线 程 的 方 式 启 动 new Thread(new ConnectionHandler(socket)).start(); catch (IOException e) { /* */ // end finally // end run() Example 3.3 简 单 的 TCP 服 务 器 - 多 线 程 一 个 连 接 - 处 理 类 简 单 地 回 复 它 的 输 入 到 它 的 输 出, 对 于 测 试 非 常 有 用, 在 Example 3.4 中 展 示. public class EchoConnectionHandler extends ConnectionHandler { public EchoConnectionHandler(Socket socket) { super(socket); /**

* @param socket * Socket: must be closed on exit */ public void handleconversation(socket socket) { try { InputStream in = socket.getinputstream(); OutputStream out = socket.getoutputstream(); // read requests from the input until EOF( 读 取 请 求 直 到 EOF) byte[] buffer = new byte[8192]; int count; while ((count = in.read(buffer)) >= 0) { // echo input to the output( 回 复 ) out.write(buffer, 0, count); out.flush(); // loop terminates at EOF catch (IOException e) { /* */ finally { try { socket.close(); catch (IOException e) { // end finally // end run() Example 3.4 TCP 服 务 器 连 接 处 理 器 -echo 3.1.4 Java 中 的 简 单 的 TCP 客 户 端 在 Java 中, 连 接 的 客 户 端 的 端 点 由 java.net.socket 表 示, 通 常 构 造 已 经 连 接 到 服 务 器 的 端 口. 一 个 典 型 的 TCP 客 户 端 在 Example 3.5 中 描 述. public class TCPClient implements Runnable { Socket socket; @Override public void run() { try { socket = new Socket(host, port); OutputStream out = socket.getoutputstream(); // write request, not shown ( 写 入 请 求 ) out.flush(); InputStream in = socket.getinputstream(); // get reply ( 取 得 回 复 ) catch (IOException e) { /* */ finally // ensure socket is closed {

try { if (socket!= null) socket.close(); catch (IOException e) { // end finally // end run()o Example 3.5 TCP 客 户 端 3.2 TCP 的 功 能 和 成 本 的 功 能 和 成 本 (cost) 正 如 我 们 上 面 看 到 的,TCP 实 现 了 一 个 双 向 的 可 靠 的 数 据 流, 在 任 意 一 方 任 意 的 大 量 的 数 据 可 被 传 输, 或 者 两 边 同 时. 3.2.1 功 能 在 TCP 中, 数 据 接 收 是 自 动 确 认 的, 有 序 的, 必 要 的 话 会 重 发. 应 用 程 序 不 能 接 收 损 坏 (corrupt) 的 或 者 无 序 的 数 据, 或 者 是 数 据 " 空 洞 ". 传 输 是 自 动 地 一 步 一 步 增 长 网 络 间 的 容 量 (FIXME:Transmissions are automatically paced to the capacity of the intervening network). 如 果 没 有 确 认, 有 必 要 的 话 会 重 新 传 输. 所 有 可 用 的 带 宽 被 使 用 而 不 会 使 网 络 饱 和 (saturating) 或 者 对 其 它 网 络 用 户 不 公 平. TCP 迅 速 和 可 靠 地 调 整 去 改 变 网 络 条 件 - 根 据 不 同 的 负 载 和 路 由. TCP 实 现 了 " 协 商 (negotiation) 连 接 " 来 保 证 一 个 服 务 器 启 动 和 运 行, 服 务 器 主 机 已 经 接 收 到 了 一 个 客 户 端 请 求, 在 客 户 端 连 接 请 求 完 成 之 前. TCP 实 现 了 " 协 商 关 闭 " 来 保 证 所 有 在 传 输 中 的 数 据 已 经 被 传 输 了, 在 连 接 最 终 丢 弃 之 前 接 收 到 了. 3.2.2 成 本 所 有 这 些 功 能 都 有 相 应 的 成 本. 有 计 算 开 销, 协 议 开 销 和 时 间 开 销 : (a) 连 接 协 商 由 三 方 的 数 据 包 交 换 组 成 客 户 端 发 送 一 个 SYN; 服 务 器 回 复 一 个 SYN/ACK; 然 后 客 户 端 回 复 一 个 ACK; 如 果 第 一 个 SYN 没 有 产 生 回 复, 它 将 以 增 长 的 时 间 间 隔 重 试 ( 重 新 发 送 ). 第 一 次 重 试 间 隔 是 实 现 依 赖 的, 通 常 是 3 到 6 秒, 每 次 失 败 的 话 至 少 双 倍 时 间 后 重 试. 尝 试 去 连 接 的 总 共 花 费 的 时 间 也 是 实 现 依 赖 的, 通 常 限 制 在 75 秒 或 者 3 次 重 试. 因 此, 总 的 来 说, 一 个 连 接 完 全 失 败 的 连 接 尝 试 时 间 通 常 可 能 是 :6 + 12 + 24 = 42 秒. (b) 关 闭 协 商 由 四 方 的 数 据 包 交 换 组 成 每 一 端 发 送 一 个 FIN, 然 后 使 用 一 个 ACK 回 复 给 传 入 的 FIN. (c) 数 据 序 列, 确 认 和 (FIXME:pacing) 确 实 需 要 相 当 多 的 计 算, 包 含 了 维 护 一 个 在 两 个 端 点 之 间 当 前 数 据 包 的 移 动 的 来 回 时 间 的 平 滑 估 量 的 统 计 (FIXME:which includes maintaining a statistically smoothed estimator of the current round-trip time for a packet travelling between the two endpoints). (d) (FIXME:The provisions for congestion avoidance require an exponentially increasing retry timer on retransmissions ( exponential backoff ) and a slow start to the transmission: this implies that the first few packets are generally

exchanged at a sub-optimal speed, although the speed increases exponentially to the maximum feasible) 3.2.3 TCP 和 请 求 和 请 求 - 应 答 交 易 TCP 是 为 批 量 数 据 传 输 设 计 的. 一 个 简 单 的 请 求 - 应 答 交 易, 理 论 上 只 需 要 在 每 个 方 向 上 只 发 送 一 个 IP 数 据 包, 系 统 的 总 效 率 不 是 非 常 高, 因 为 实 际 上 至 少 有 9 个 TCP 片 段 交 换, 如 Figure 3.2 所 展 示 的 序 列 图. 在 每 个 方 向 上 的 包 是 一 步 一 步 的, 受 限 于 (subject to)' 缓 慢 启 动 ' 的 需 求. 除 了 确 认 数 据 包.(FIXME:The packets in each direction are paced and are subject to the requirement for slow start, with the exception of acknowledgement packets.) 3.3 Socket 初 始 化 - 服 务 器 端 在 这 节, 我 们 将 查 看 所 有 可 以 采 取 的 步 骤 和 当 初 始 化 一 个 ServerSocket 时 可 以 设 置 的 所 有 可 能 的 参 数. 3.3.1 构 造 ServerSocket 对 象 使 用 以 下 4 个 构 造 中 的 一 个 创 建. class ServerSocket { ServerSocket(int port) throws IOException;

ServerSocket(int port, int backlog)throws IOException; ServerSocket(int port, int backlog, InetAddress localaddress) throws IOException; ServerSocket() throws IOException; 前 三 个 构 造 函 数 创 建 一 个 已 经 " 绑 定 " 的 服 务 器 端 套 接 字. 一 个 绑 定 的 套 接 字 准 备 好 使 用 - 准 备 好 ServerSocket.accept() 被 调 用. 默 认 的 构 造 函 数 在 JDK 1.4 被 引 入, 创 建 一 个 " 未 绑 定 " 状 态 的 服 务 器 端 套 接 字. 一 个 未 绑 定 的 套 接 字 必 须 使 用 ServerSocket.bind() 去 绑 定 在 ServerSocket.accept() 可 以 被 调 用 之 前. 这 个 方 法 将 在 3.3.7 描 述. 首 先 我 们 看 下 构 造 已 经 - 绑 定 的 套 接 字 的 参 数 ; 然 后 我 们 看 下 绑 定 未 绑 定 的 套 接 字 的 方 法. 3.3.2 端 口 TCP 服 务 器 通 常 指 定 本 地 端 口 来 监 听 连 接, 提 供 一 个 非 零 的 端 口 数. 如 果 端 口 数 为 0, 系 统 分 配 一 个 端 口 来 使 用, 这 个 值 可 以 调 用 以 下 的 方 法 来 获 取. class ServerSocket { int getlocalport(); 如 果 使 用 这 种 技 术, 需 要 一 些 外 部 的 方 法 让 实 际 的 端 口 与 客 户 端 通 信 ; 另 外, 客 户 端 不 知 道 如 何 连 接 到 服 务 器. 通 常 这 个 功 能 更 假 设 通 过 一 个 命 名 服 务, 比 如 LDAP(Lightweight Directory Access Protocol, 轻 量 级 目 录 访 问 协 议 ). 在 Java RMI 中, 这 个 功 能 假 定 使 用 RMI 注 册. 在 Sun RPC(Remote Procedure Call), 假 定 使 用 端 口 映 射 服 务. 本 地 端 口 通 被 一 个 接 收 的 连 接 使 用, 也 就 是 说, 通 过 ServerSocket.accept() 产 生 一 个 Socket, 通 过 以 下 的 方 法 返 回. int getlocalport(); 这 个 总 是 和 服 务 器 套 接 字 监 听 的 端 口 相 同. 这 个 是 客 户 端 需 要 连 接 的 端 口, 所 以 没 有 其 它 的 可 能 性 (*). 使 用 一 个 ' 众 所 周 知 ' 的 端 口, 也 就 是 说, 端 口 范 围 在 1-1023, 在 ServerSocket 中 可 能 需 要 特 殊 的 权 限, 比 如, 像 Unix -like 中 的 超 级 用 户 权 限. * Sun 的 在 线 Java 教 程 (Customer Networking/All About Sockets/What is a Socket?) 在 这 点 上 已 经 错 误 很 多 年 了. 3.3.3 Backlog TCP 本 身 在 接 收 连 接 中 可 以 在 一 个 TCP 服 务 器 应 用 程 序 之 前 获 得 连 接. 它 维 护 一 个 连 接 到 一 个 监 听 的 套 接 字 的 "backlog 队 列 ",TCP 自 身 已 经 完 成, 但 是 还 没 被 应 用 程 序 接 受 (*). 这 个 队 列 存 在 于 底 层 的 TCP 实 现 和 服 务 器 创 建 的 监 听 套 接 字 进 程 之 间. 预 完 成 连 接 的 目 的 是 可 以 加 速 连 接 阶 段, 但 是 队 列 是 有 长 度 限 制 的, 以 免 预 申 请 太 多 的 连 接 到 服 务 器, 可 能 会 不 接 受 它 们 在 它 们 以 相 同 的 速 率 而 没 有 任 何 理 由 (FIXNE: so as not to preform too many connections to servers which are not accepting them at the same rate for any reason). 当 一 个 进 来 的 连 接 请 求 已 经 接 收,backlog 队 列 还 没 满,TCP 完 成 连 接 协 议, 然 后 把 连 接 加 入 到 backlog 队 列. 同 时, 客 户 端 应 用 程 序 已 经 完 全 连 接, 但 是 服 务 器 应 用 还 没 有 完 全 接 收 到 ServerSocket.accept() 产 生 的 结 果 的 连 接. 当 它 这 么 做 的 时 候, 实 体 从 队 列 中 移 除.

backlog 参 数 指 定 了 backlog 队 列 的 最 大 长 度. 如 果 backlog 省 略, 负 数 或 者 为 0, 系 统 选 择 的 默 认 值 将 被 使 用. 比 如 50. 指 定 的 backlog 可 以 被 底 层 的 平 台 调 整. 如 果 backlog 值 对 平 台 来 说 过 多, 平 台 将 默 默 的 调 整 到 一 个 合 法 的 值. 在 Java 或 者 Berkely 套 接 字 API 中 没 有 方 法 去 知 晓 有 效 的 backlog 值. 一 个 非 常 小 的 backlog 值, 比 如 1, 可 能 是 故 意 (deliberately) 用 来 " 削 弱 (cripple)" 一 个 服 务 器 应 用, 比 如, 为 了 产 品 演 示 (demonstration) 目 的, 如 果 底 层 实 现 不 调 整 向 上, 服 务 器 仍 然 能 正 常 工 作, 但 是 它 处 理 并 发 客 户 端 的 能 力 被 严 重 (severely) 限 制 了. * 这 个 定 义 一 直 随 时 间 变 化. 它 用 于 包 括 连 接 仍 然 形 成, 也 就 是 说, 这 些 SYN 已 经 接 收 到, 但 是 发 送 完 成 ACK 还 没 有 被 接 收 到. ** P29 3.3.4 本 地 地 址 一 个 服 务 器 套 接 字 的 本 地 地 址 是 监 听 传 入 的 连 接 的 IP 地 址. 默 认 情 况 下,TCP 服 务 器 监 听 所 有 的 本 地 的 IP 地 址. 它 们 可 以 去 监 听 单 个 的 本 地 IP 地 址, 通 过 提 供 一 个 非 null 的 loacladdress 到 构 造 中. 如 果 地 址 省 略 或 者 为 null, 套 接 字 绑 定 到 所 有 的 本 地 地 址. 指 定 一 个 本 地 的 IP 地 址 只 是 更 有 有 意 义, 如 果 本 机 是 多 地 址 的, 也 就 是 说, 有 1 个 以 上 的 IP 地 址, 通 常 因 为 它 有 多 于 1 个 的 物 理 网 络 接 口. 在 这 种 情 况 下, 一 个 服 务 器 可 能 只 想 去 通 过 一 个 IP 地 址 而 不 是 所 有 使 它 自 身 可 用. 看 3.14 多 地 址 的 讨 论 获 取 更 多 的 细 节. 本 地 IP 地 址 由 一 个 监 听 的 服 务 器 套 接 字 的 以 下 方 法 返 回. class ServerSocket { InetAddress getinetaddress(); SocketAddress getlocalsocketaddress(); 这 些 方 法 返 回 null, 如 果 socket 未 绑 定, 如 3.3.1 和 3.3.7 节 描 述 的 (*). 这 种 情 况 在 JDK 1.4 之 前 是 不 可 能 的, 因 为 未 绑 定 的 ServerSocket 不 能 构 造, 默 认 构 造 在 JDK 1.4 中 才 被 加 入. * ServerSocket.getInetAddress 在 所 有 1.4.1 之 前 的 JDK 版 本 中 的 文 档 都 是 不 正 确 的, 返 回 'null 如 果 套 接 字 还 没 有 连 接 '. 但 是 ServerSocket 对 象 从 不 连 接. 3.3.5 重 用 本 地 地 址 在 3.3.7 节 描 述 的 在 绑 定 服 务 器 socket 之 前, 你 可 能 希 望 去 设 置 " 重 用 本 地 地 址 " 选 项, 这 个 实 际 上 意 味 着 重 用 本 地 端 口. 重 用 地 址 的 方 法 在 JDK 1.4 中 加 入 : class ServerSocket { void setreuseaddress(boolean reuse) throws SocketException; boolean getreuseaddress() throws SocketException; 这 个 设 置 在 开 发 中 是 有 用 的, 当 服 务 器 频 繁 停 止 和 启 动. 默 认 情 况 下,TCP 阻 止 一 个 监 听 的 端 口 重 用 当 有 一 个

活 动 的, 更 通 常 来 说, 一 个 正 在 关 闭 端 口 的 连 接. 关 闭 连 接 持 续 大 约 2 分 钟, 因 为 一 些 协 议 完 整 性 原 因. 在 开 发 环 境 中, 两 分 钟 的 等 待 可 能 是 浪 费 的 和 令 人 烦 恼 (annoyance) 的. 设 置 这 个 选 项, 停 止 了 浪 费 和 减 轻 烦 恼. 在 服 务 器 socket 绑 定 之 后 改 变 这 个 设 置, 或 者 使 用 一 个 不 是 默 认 的 构 造 函 数 构 造, 这 个 行 为 是 无 效 的 ( 必 须 在 未 绑 定 到 一 个 端 口 前 设 置 ). 注 意 : 这 些 方 法 设 置 和 获 取 一 个 boolean 状 态, 而 不 是 某 些 "reuse-address" 方 式 如 它 们 名 字 所 建 议 的. Java 没 有 定 义 这 个 设 置 的 默 认 值, 但 在 MacOS/X, 根 据 http://lists.apple.com/archives/javadev/2004/dec/msgoo570.html, 它 是 true. 所 有 的 其 它 系 统, 我 遇 过 的 都 是 false. 3.3.6 设 置 接 收 的 缓 冲 区 大 小 如 3.3.7 节 描 述 的 绑 定 服 务 器 socket 之 前, 你 可 能 希 望 设 置 接 收 的 缓 冲 区 大 小. 你 必 须 在 绑 定 之 前 做 这 个, 如 果 你 想 达 到 最 大 的 吞 吐 量, 使 用 一 个 大 的 接 收 缓 冲 区 ( 大 于 64KB), 因 为 一 个 大 的 缓 冲 区 是 有 用 的, 如 果 发 送 端 知 道 它, 接 收 端 只 能 通 知 缓 冲 区 大 于 64KB, 如 果 它 允 许 window 在 连 接 顺 序 之 间 测 量 (FIXME: if it enables window scaling during the connection sequence). 第 一 次 可 以 出 现 在 套 接 字 绑 定, 也 就 是 说, 在 ServerSocket.accept() 返 回 之 前. 因 此, 你 必 须 在 绑 定 一 个 服 务 器 套 接 字 之 前 设 置 接 收 缓 存 大 小 ( 有 点 歧 义, 应 该 是 在 设 置 大 于 64KB 的 缓 冲 区 的 时 候 才 需 要 这 样 做 ). 通 过 ServerSocket.accept() 返 回 的 Sockets 继 承 这 个 设 置 ( 实 际 上 所 有 的 套 接 字 选 项 设 置 都 这 样 ). 你 可 以 设 置 一 个 大 的 接 收 缓 冲 区 大 小 在 服 务 器 sokcet 上 在 它 绑 定 之 后 或 者 使 用 非 有 参 的 构 造 函 数 构 造, 但 它 在 已 经 接 收 的 连 接 上 不 会 有 期 望 的 效 果. 你 也 可 以 在 一 个 已 经 接 收 的 socket 上 设 置, 但 是 这 是 无 效 的. 设 置 一 个 大 的 发 送 缓 冲 区 大 小 在 一 个 已 经 接 收 的 Socket 上 不 会 有 预 期 的 效 果. 因 为 大 的 发 送 缓 冲 区 大 小 不 会 通 知 给 其 它 的 终 端. 因 此,ServerSocket.setSendBufferSize() 方 法 是 不 需 要 的 或 者 提 供 的. 接 收 的 缓 冲 区 大 小 设 置 和 获 取 通 过 以 下 方 法 : class ServerSocket { void setreceivebuffersize(int size) throws SocketException; int getreceivebuffersize() throws SocketException; 查 看 3.13 节 获 取 socket 缓 冲 区 大 小 的 更 多 讨 论. 3.3.7 绑 定 操 作 在 JDK 1.4 引 入 的 默 认 构 造 函 数 产 生 一 个 ServerSocket 必 须 在 连 接 可 以 被 接 收 之 前 绑 定. 这 个 使 用 以 下 JDK 1.4 的 方 法 : class ServerSocket { void bind(socketaddress address) throws IOException; void bind(socketaddress address, int backlog) throws IOException; boolean isbound();

address 通 常 是 一 个 使 用 一 个 端 口 构 造 的 InetSocketAddress, 如 3.3.2 节 描 述, 一 个 localaddress, 如 3.3.4 节 描 述,backlog 如 3.3.3 节 描 述. 在 一 个 ServerSocket 关 闭 之 后, 它 不 能 被 重 用, 所 有 它 不 能 再 次 被 绑 定. 在 Berkeley Sockets API 中,ServerSocket.bind() 方 法 合 并 了 bind() 和 listen() 两 个 功 能. 3.4 Socket 初 始 化 - 客 户 端 3.4.1 构 造 客 户 端 的 Socket 使 用 以 下 的 构 造 中 的 一 个 创 建. Socket(InetAddress host, int port) throws IOException; Socket(String host, int port) throws IOException; Socket(InetAddress host, int port, InetAddress localaddress, int localport) throws IOException; Socket(String host, int port, InetAddress localaddress, int localport) throws IOException; Socket() throws IOException; Socket(Proxy proxy) throws IOException; 前 四 个 创 建 已 经 连 接 到 指 定 目 标 的 Socket. 一 个 连 接 的 Socket 可 以 准 备 使 用 -I/O 操 作. 默 认 构 造 在 JDK 1.4 中 引 入 创 建 一 个 未 连 接 状 态 的 socket. 一 个 未 连 接 的 socket 必 须 使 用 在 3.4 节 描 述 的 Socket.connect() 连 接 到 一 个 目 标 在 它 用 来 进 行 任 何 I/O 操 作 之 前. 列 表 上 的 最 后 一 个 构 造, 在 JDK 1.5 中 引 入, 连 接 一 个 到 本 地 代 理 服 务 器 的 套 接 字 ; 在 构 造 这 样 的 一 个 socket 后 你 必 须 调 用 Socket.connect() 连 接 到 通 过 代 理 服 务 器 到 真 实 的 目 标. 3.4.2 远 程 主 机 host 参 数 指 定 了 要 连 接 的 远 程 主 机. 它 可 以 指 定 为 一 个 InetAddress 或 者 一 个 String. 一 个 InetAddrss 可 以 调 用 InetAddress.getByName 或 者 InetAddress.getByAddress 构 造. 一 个 String 的 host 可 能 包 含 了 一 个 主 机 名 比 如 "java.sun.com", 使 用 命 名 服 务 比 如 DNS 解 析, 或 者 一 个 代 表 它 的 IP 地 址 的 文 本. 对 于 文 本 表 示, 只 有 地 址 格 式 的 校 验 会 被 检 查.IPv4 是 一 个 众 所 周 知 的 " 点 分 四 组 " 格 式, 比 如 "192.168.1.24". 对 于 IPv6, 在 RFC 2372 中 的 文 本 IPv6 地 址 格 式 的 任 意 地 址 可 以 被 接 收, 比 如 "1080::8:800:200C:417A" 或 者 "::192.168.1.24". 远 程 主 机 可 以 使 用 以 下 方 法 获 得 : InetAddress getinetaddress(); 如 果 socket 没 有 连 接 返 回 null. 远 程 地 址 也 可 以 通 过 下 面 的 JDK 1.4 的 代 码 顺 序 获 得. SocketAddress sa = socket.getremotesocketaddress(); if (sa!= null) return ((InetSocketAddress)sa).getAddress();

return null; 3.4.3 远 程 端 口 port 参 数 指 定 了 要 连 接 的 远 程 端 口, 也 就 是 说, 这 就 是 服 务 器 监 听 的 端 口, 在 3.3.2 节 中 描 述. 远 程 端 口 可 以 通 过 下 面 的 方 法 获 得 : int getport(): 如 果 套 接 字 没 有 连 接, 返 回 0. 远 程 端 口 也 可 以 通 过 下 面 的 JDK 1.4 代 码 顺 序 获 得, 如 果 套 接 字 没 有 连 接, 也 返 回 0. SocketAddress sa = socket.getremotesocketaddress(); if (sa!= null) return ((InetSocketAddress)sa).getPort(); return 0; 3.4.4 本 地 地 址 localaddress 参 数 指 定 了 本 地 的 IP 地 址. 如 果 缺 省 或 者 为 null, 将 由 系 统 选 择. 在 为 一 个 TCP 客 户 端 指 定 本 地 IP 地 址 有 一 点 : 它 不 完 全 这 样 做, 只 有 在 多 宿 主 的 主 机 中.(FIXME: It might be done to force the connection to go via a network interface known to be faster than the others), 或 者 因 为 某 些 原 因 预 决 定 IP 路 由 器. 一 个 套 接 字 绑 定 的 本 地 IP 地 址 可 以 通 过 下 面 的 方 法 获 得 : InetAddress getlocaladdress(); 如 果 套 接 字 没 有 连 接, 返 回 null(xxx: 从 这 里 的 上 下 文 来 看, 这 里 说 的 返 回 null 应 该 的 对 于 上 面 getlocaladdress() 而 言 的, 而 返 回 null 这 个 描 述 是 有 误 的 ). 无 论 怎 样, 它 对 TCP 客 户 端 没 有 什 么 实 用 性. 本 地 地 址 也 可 以 通 过 下 面 的 JDK 1.4 代 码 顺 序 获 得 : SocketAddress sa = socket.getlocalsocketaddress(); if (sa!= null) return ((InetSocketAddress)sa).getAddress(); return null; 这 些 方 法 也 工 作 于 在 一 个 服 务 器 中 的 已 经 接 收 的 套 接 字. 结 果 可 用 于 在 多 宿 主 主 机 中 的 TCP 服 务 器. 查 看 3.14 节 的 多 宿 主 的 讨 论. 3.4.5 本 地 端 口 localport 参 数 指 定 了 本 地 端 口 到 绑 定 的 套 接 字. 如 果 缺 省 或 者 为 null, 将 由 系 统 分 配. 在 为 一 个 TCP 客 户 端 指 定 本 地 端 口 有 一 点, 这 个 操 作 很 少 使 用. 本 地 端 口 数 到 一 个 绑 定 的 套 接 字 可 通 过 下 面 的 方 法 获 得 :

int getlocalport(); 如 果 套 接 字 没 有 连 接 返 回 0(XXX: 描 述 有 误, 和 上 面 本 地 地 址 一 样 ). 端 口 数 也 可 以 通 过 下 面 的 JDK 1.4 的 代 码 顺 序 获 得, 如 果 套 接 字 没 有 连 接 也 返 回 0: SocketAddress sa = socket.getlocalsocketaddress(); if (sa!= null) return ((InetSocketAddress)sa).getPort(); return 0; 这 个 信 息 对 于 TCP 客 户 端 没 有 什 么 实 际 的 使 用 性. 这 个 方 法 也 工 作 于 在 一 个 服 务 器 中 接 收 的 套 接 字, 尽 管 结 果 总 是 和 监 听 的 服 务 器 的 端 口 一 样, 如 3.3.2 节 讨 论 的. 3.4.6 代 理 对 象 代 理 对 象 指 定 了 代 理 的 类 型 (Direct,Sock,HTTP) 和 它 的 SocketAddress. 3.4.7 设 置 接 收 缓 冲 区 大 小 如 3.4.10 节 所 描 述 的 在 接 收 套 接 字 连 接 之 前, 你 可 能 希 望 去 设 置 接 收 的 缓 冲 区 大 小. 接 收 的 缓 冲 区 大 小 通 过 以 下 的 方 法 设 置 和 获 取 : void setreceivebuffersize(int size) throws SocketException; int getreceivebuffersize() throws SocketException; 你 必 须 在 连 接 之 前 设 置 接 收 缓 冲 区 大 小 如 果 你 想 去 使 用 一 个 大 ( 64KB) 的 缓 冲 区 和 你 想 获 得 最 大 的 吞 吐 量. 你 也 仍 然 可 以 在 套 接 字 连 接 之 后 设 置 接 收 缓 冲 区 大 小, 但 是 它 不 会 有 期 望 的 效 果, 如 3.3.6 节 所 讨 论 的. 在 3.3.6 节 也 讨 论 了, 在 一 个 连 接 的 套 接 字 上 设 置 一 个 大 的 发 送 缓 冲 区 大 小 会 有 预 期 的 效 果, 因 为 大 的 发 送 缓 冲 区 不 需 要 去 通 知 另 一 端. 因 此, 你 可 以 在 套 接 字 关 闭 之 前 的 任 意 时 刻 设 置 发 送 缓 冲 区 大 小. 查 看 3.13 节 获 得 套 接 字 缓 冲 区 大 小 的 进 一 步 讨 论. 3.4.8 绑 定 操 作 一 个 从 默 认 构 造 中 产 生 的 套 接 字 在 JDK 1.4 中 被 引 入, 可 以 在 连 接 之 前 ' 绑 定 '. 这 个 等 同 于 在 构 造 中 指 定 一 个 或 者 localaddress 和 localport 两 者, 在 3.4.1 节 描 述. 这 个 可 以 通 过 JDK 1.4 方 法 达 成 : void bind(socketaddress address) throws IOException; boolean isbound(); address 使 用 一 个 localaddress 构 造 如 3.4.4 节 描 述 的. 端 口 数 如 3.4.5 节 描 述 的. 如 在 3.3.4 和 3.4.5 节 描 述 的, 这 个 操 作 很 少 使 用. 3.4.9 重 用 本 地 地 址

如 3.4.8 节 描 述 的 在 绑 定 套 接 字 之 前, 你 可 能 希 望 去 设 置 ' 重 用 本 地 地 址 ' 选 项. 这 个 实 际 上 意 味 着 重 用 本 地 端 口. 重 用 地 址 的 方 法 在 JDK 1.4 中 被 加 入 : void setreuseaddress(boolean reuse) throws SocketException; boolean getreuseaddress() throws SocketException; 像 客 户 端 套 接 字 绑 定 操 作 自 身 一 样, 这 个 操 作 基 本 上 完 全 没 有 意 义 (pointless) 和 几 乎 从 不 使 用 的. 3.4.10 连 接 操 作 一 个 从 默 认 构 造 函 数 产 生 的 Socket 在 JDK 1.4 中 引 入, 或 者 从 JDK 1.5 中 引 入 的 Proxy 构 造 产 生. 在 它 能 为 I/O 使 用 之 前 必 须 连 接. 这 个 可 以 通 过 JDK 1.4 方 法 中 的 一 种 达 成 : void connect(socketaddress address) throws IOException; void connect(socketaddress address, int timeout) throws IOException; boolean isconnected(); address 通 常 是 一 个 使 用 一 个 如 3.4.2 节 描 述 的 remotehost 和 一 个 如 3.4.3 节 描 述 的 remoteport 构 造 的 InetSocketAddress,timeout 指 定 了 连 接 超 时 的 毫 秒 数 : 如 果 为 0 或 者 缺 省, 则 使 用 无 限 的 超 时 时 间 : 这 个 操 作 阻 塞 直 到 连 接 建 立 或 者 有 错 误 发 生. connect 方 法 可 以 在 失 败 前 等 待 timeout 毫 秒, 但 是 它 可 以 更 快 的 失 败 ( 如 果 有 主 机, 但 端 口 没 有 监 听, 主 机 可 以 立 即 生 成 一 个 TCP RST). 通 常, 超 时 周 期 将 被 耗 尽 如 果 服 务 器 端 的 backlog 队 列 ( 在 3.3.3 节 描 述 ) 满 的 时 候. isconnect 方 法 判 断 本 地 的 套 接 字 是 否 已 经 连 接 上. 这 个 方 法 不 会 告 诉 你 另 一 端 是 否 关 闭 了 连 接. 一 个 套 接 字 不 能 被 关 闭 然 后 重 连 接. 3.5 接 收 客 户 端 连 接 一 旦 一 个 服 务 器 端 套 接 字 被 构 造 和 绑 定, 客 户 端 连 接 通 过 下 面 的 方 法 接 收 : class ServerSocket { Socket accept() throws IOException; 这 个 方 法 返 回 一 个 准 备 好 I/O 的 连 接 的 套 接 字. 连 接 的 套 接 字 继 承 了 服 务 器 端 套 接 字 的 许 多 设 置. 特 别 是 包 括 了 它 的 本 地 端 口, 发 送 和 接 收 的 缓 冲 区 大 小 (XXX: 描 述 有 误 ), 阻 塞 / 非 阻 塞 状 态, 但 是 不 包 括 读 的 超 时 时 间 (XXX: 描 述 不 准 确 ). 另 一 个 没 有 继 承 的 设 置 是 本 地 地 址. 通 过 Socket.getLocalAddres 或 者 Socket.getLocalSocketAddress 返 回 的 值 是 客 户 端 用 来 连 接 到 服 务 器 的 地 址. 这 个 在 多 地 址 主 机 中 是 重 要 的 : 见 3.14 节. 服 务 器 需 要 去 构 造 为 了 尽 可 能 的 频 繁 循 环 调 用 ServerSocket.accept, 为 了 不 拖 延 连 接 中 的 客 户 端. 各 种

体 系 架 构 都 是 可 能 的. 接 收 的 套 接 字 通 常 传 递 到 另 一 个 线 程 去 处 理 当 接 收 的 线 程 再 次 循 环, 如 示 例 3.3 中 展 示 的 最 简 单 的 可 用 的 架 构. 更 多 高 级 的 服 务 器 架 构 在 第 12 章 讨 论. 这 个 循 环 应 该 被 编 码, 这 样 它 不 会 拖 延 任 何 地 方, 只 在 ServerSocket.accept. 在 accept 和 派 发 到 另 一 个 线 程 之 间 这 个 通 常 排 除 做 任 何 的 I/O 操 作, 然 而 后 者 是 被 管 理 的. 这 个 影 响 了 应 用 协 议 的 设 计 : 它 应 该 不 需 要 从 客 户 端 读 取 任 何 东 西 在 派 发 连 接 到 它 自 己 的 线 程 之 前. 3.7 套 接 字 I/O 3.6.1 输 出 在 Java 中, 输 出 到 一 个 套 接 字 是 通 过 从 套 接 字 中 Socket.getOutputStream 获 得 一 个 OutputStream 来 完 成, 如 下 面 所 示, 或 通 过 在 第 5 章 讨 论 的 高 性 能 的 套 接 字 管 道. 这 节 讨 论 输 出 流. Socket socket; // initialization not shown OutputStream out = socket.getoutputstream(); byte [] buffer = new byte [ 8192 ]; int offset = 0 ; int count = buffer.length; out.write(buffer,offset,count); 对 于 本 地 发 送 缓 冲 区 而 言, 在 一 个 TCP 套 接 字 上 的 所 有 输 出 操 作 都 是 同 步 的, 对 于 网 络 和 远 程 应 用 程 序 而 言 是 异 步 的.(FIXME:All that a output operation does is buffer the data locally to be sent according to the timing and pacing rules of TCP). 如 果 本 地 套 接 字 发 送 缓 冲 区 是 满 的, 套 接 字 的 一 次 写 操 作 通 常 延 迟 直 到 发 送 缓 冲 区 空 间 被 释 放, 作 为 之 前 传 输 的 确 认 接 收 的 结 果. 只 要 有 足 够 的 本 地 缓 冲 区 空 间 可 用, 控 制 回 到 应 用 程 序. 如 果 缓 冲 空 间 可 用 于 数 据 的 一 部 分, 这 部 分 数 据 被 缓 冲, 应 用 程 序 延 迟 直 到 进 一 步 的 空 间 出 现 ; 这 个 持 续 直 到 所 有 的 数 据 被 写 入 到 缓 冲 中. 很 明 显 这 个 意 味 着 如 果 数 据 的 数 量 超 过 了 发 送 缓 冲 区 大 小, 开 始 超 过 的 (initial excess) 将 被 写 入 到 网 络 中, 只 有 最 后 未 超 过 的 部 分 数 据 被 本 地 缓 冲, 当 write 方 法 返 回. 这 意 味 着, 在 上 面 的 输 出 示 例 中, 当 out.write 返 回, 所 有 的 count 字 节 被 写 入 到 本 地 发 送 缓 冲 区. 这 是 Java 和 其 它 的 套 接 字 实 现 比 如 Berkeley Sockets 或 winsock 之 间 的 一 个 差 异 点. 在 Java 流 I/O 中,write 方 法 阻 塞 直 到 所 有 的 数 据 被 处 理. 其 它 的 阻 塞 模 式 套 接 字 -write 实 现 返 回 一 个 count, 至 少 为 1, 但 是 可 能 小 于 发 送 的 数 目 ; 唯 一 的 保 证 是 某 些 数 据 已 经 被 缓 冲. 在 写 入 到 一 个 套 接 字 之 后, 不 能 保 证 所 有 的 数 据 已 经 被 应 用 程 序 ( 或 TCP) 的 另 一 端 接 收. 一 个 应 用 程 序 可 以 确 定 数 据 传 输 已 经 达 到 远 程 应 用 程 序 的 唯 一 方 式 是 : 通 过 远 程 应 用 发 送 (send) 明 确 地 接 收 到 一 个 确 认. 通 常, 这 样 的 一 个 确 定 内 置 在 了 应 用 程 序 间 的 协 议, 并 通 过 TCP 传 递. 换 句 话 说, 大 部 分 的 TCP 会 话 遵 循 一 个 请 求 - 回 复 (request-reply) 模 型. 没 有 更 多 的 保 证 数 据 被 写 入 到 了 一 个 套 接 字, 被 发 送 到 了 网 络 ; 同 样 也 没 有 任 何 保 证 之 前 的 write 操 作 已 经 接 收 或 者 发 送. 你 可 以 通 过 写 入 的 字 节 的 总 量 减 去 发 送 缓 冲 区 大 小 明 确 地 计 算 有 多 少 数 据 被 发 送 到 了 网 络, 但 是 这 个 仍 然 不 能 告 诉 你 数 据 是 否 已 经 被 接 收 到, 所 以 这 个 没 有 什 么 意 义. 最 好 附 着 ( 包 装 ) 一 个 BufferedOutputStream 到 从 套 接 字 中 获 取 的 输 出 流. 理 论 上,BufferedOutputStream 的 缓 冲 应 该 是 传 输 的 最 大 请 求 或 者 回 复, 如 果 这 个 预 先 知 道 并 且 不 会 没 有 原 因 的

变 大 ; 否 则 它 应 该 至 少 和 套 接 字 的 发 送 缓 冲 区 一 样 大 ; 这 个 最 小 化 了 内 核 的 上 下 文 切 换, 它 使 得 TCP 一 次 写 入 更 多 的 数 据, 允 许 它 形 成 更 大 的 分 段 和 更 有 效 地 使 用 网 络. 同 样 它 最 小 化 了 JVM 和 JNI 之 间 的 来 回 切 换. 你 必 须 在 合 适 的 时 候 刷 新 (flush) 缓 冲 区, 也 就 是 说, 在 完 成 了 一 次 请 求 信 息 的 写 操 作, 和 在 读 取 回 复 之 前, 保 证 在 BufferedOutputStream 缓 冲 中 的 所 有 的 数 据 是 从 套 接 字 获 取 的. 为 了 发 送 Java 数 据 类 型, 直 接 在 套 接 字 输 出 流 中 附 着 一 个 DataOutputStream, 或 者 最 后, 如 上 所 以 附 着 在 BufferedOutputStream. DataOutput dos = new DataOutputStream(out); // examples dos.writeboolean( ); dos.writebyte( ); dos.writechar( ); dos.writedouble( ); dos.writefloat( ); dos.writelong( ); dos.writeshort( ); dos.writeutf( );// write a String 为 了 发 送 序 列 化 Java 对 象, 在 你 的 输 出 流 中 包 装 一 个 ObjectOutputStream. ObjectOutput oos = new ObjectOutputStream(out); // example Object object;// initialization not shown oos.writeobject(object); 由 于 ObjectOutputStream 继 承 了 DataOutputStream, 你 可 以 使 用 上 面 的 数 据 类 型 方 法. 然 后 要 注 意 ObjectOutputStream 加 入 了 自 己 的 协 议 到 数 据 流 中, 所 以 其 它 终 端 你 只 能 使 用 ObjectInputStream. 你 可 以 使 用 ObjectOutputStream 写 入 数 据 类 型, 然 后 用 DataInputStream 读 取 它 们. 如 上 面 对 DataOutputStream 的 建 议, 你 应 该 使 用 BufferedOutputStream 协 同 一 个 ObjectOutputStream. 3.6.2 对 象 流 死 锁 (Object stream deadlock) 注 意 使 用 对 象 输 出 和 输 入 流 的 一 个 死 锁 问 题. 下 面 的 代 码 片 段 总 是 死 锁 如 果 同 时 出 现 在 客 户 端 和 服 务 器 端 : ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); 3.6.3 输 入 类 似 地, 一 个 套 接 字 的 输 入 流 通 过 Socket.getInputStream 获 取 一 个 输 入 流, 如 下 所 示, 或 者 通 过 高 性 能 套 接 字 通 道, 在 第 5 章 讨 论. 这 节 讨 论 输 入 流. Socket socket;// initialization not shown InputStream in = socket.getinputstream(); byte[] buffer = new byte[8192];

int offset = 0; int size = buffer.length; int count = in.read(buffer,offset,size); 在 一 个 TCP 套 接 字 上 的 输 入 操 作 阻 塞 直 到 至 少 有 一 些 数 据 被 接 收 到 (*). 然 而, 接 收 到 的 数 据 长 度 可 能 小 于 请 求 的 数 据 长 度. 如 果 一 些 数 据 已 经 被 接 收 到 了 套 接 字 接 收 缓 冲 区 中, 输 入 操 作 可 能 只 返 回 那 些 数 据. 如 果 接 收 缓 冲 区 空 了, 输 入 阻 塞 直 到 一 些 数 据 被 接 收 到, 可 能 是 一 个 单 独 的 TCP 片 段, 可 能 只 返 回 那 些 数 据. 换 句 话 说,count 可 能 小 于 输 入 中 的 size, 如 上 面 的 示 例. 这 个 行 为 体 现 在 套 接 字 输 入 流 自 身 从 InputStream 继 承 的 read 方 法, 和 任 何 插 入 式 (interposed) 的 I/O 流 的 read 方 法, 比 如,BufferedInputStream, DataInputStream, ObjectInputStream, 或 者 PushbackInputStream. 然 而,DataInput.readFully 方 法 内 部 循 环 直 到 请 求 的 数 据 完 全 读 取 或 者 直 到 EOP 或 者 一 个 异 常 出 现, 无 论 哪 一 个 先 出 现.readFully 方 法 在 一 些 从 Reader 继 承 的 read 方 法 内 部 调 用,DataInputStream(readBoolean, readchar, readdouble, readfloat, readint, readlong, readshort, 和 readutf) 方 法 的 数 据 类 型, 和 通 过 ObjectInputStream.readObject, 所 以 这 些 方 法 也 读 取 请 求 的 全 部 数 据 或 者 抛 出 一 个 异 常. 当 且 仅 当 另 一 端 已 经 关 闭 了 套 接 字 或 者 关 闭 了 输 出 - 见 3.7, 接 收 的 count 为 -1. 一 个 套 接 字 的 输 入 流 的 InputStream.available 方 法 返 回 当 前 套 接 字 缓 冲 区 的 数 据 的 数 量. 这 个 可 能 为 0. 这 是 它 做 的 一 切. 不 要 去 预 测 未 来 : 不 要 在 一 些 网 络 协 议 操 作 中 去 询 问 另 一 端 当 前 有 多 少 数 据 正 在 发 送 中 ( 也 就 是 说, 它 已 经 发 送 了 多 少 数 据 ), 或 者 在 最 后 的 write 方 法 中 发 送 了 多 少 数 据, 或 者 下 一 条 信 息 多 达, 或 者 它 将 要 一 共 发 送 多 少 ( 比 如, 一 共 文 件 中 有 多 少 数 据 正 在 被 发 送 ). 在 TCP 中 没 有 任 何 这 样 的 协 议, 所 以 不 能. 最 好 附 着 一 个 BufferedInputStream 到 从 套 接 字 中 获 取 的 输 入 流. 这 个 最 小 化 了 内 核 的 上 下 文 切 换, 并 且 能 更 快 的 汲 取 (drain) 套 接 字 接 收 缓 冲 区, 反 过 来 会 减 少 发 送 端 的 拖 延. 它 也 最 小 化 了 JVM 和 JNI 之 间 的 来 回 切 换. 理 论 上 BufferedInputStream 缓 冲 区 应 该 至 少 和 套 接 字 的 缓 冲 区 一 样 大, 这 样 接 收 缓 冲 区 可 能 尽 可 能 快 的 汲 取. Socket socket;// initialization not shown InputStream in = socket.getinputstream(); in = new BufferedInputStream (in, socket.getreceivebuffersize()); 为 了 接 收 Java 数 据 类 型, 使 用 DataInputStream 直 接 附 着 在 套 接 字 输 入 流 或 者 最 好, 附 着 一 个 BufferedInputStream( 如 上 所 示 ): DataInputdis = new DataInputStream(in); // examples booleanbl = dis.readboolean(); byte b = dis.readbyte(); char c = dis.readchar(); double d = dis.readint(); float f = dis.readint(); long l = dis.readint(); short s = dis.readint(); String str = dis.readutf(); 为 了 接 收 序 列 化 Java 对 象, 在 你 的 输 入 流 中 包 装 一 个 ObjectInputStream:

ObjectInput ois = new ObjectInputStream(in); // example Object object = ois.readobject(); 因 为 ObjectInputStream 继 承 了 DataInputStream, 你 也 可 以 使 用 上 面 所 示 的 数 据 类 型 方 法. 然 后 要 注 意 ObjectInputStream 假 设 了 在 数 据 流 中 使 用 的 是 ObjectOoutputStream 协 议, 这 样 你 只 能 用 来 输 入, 如 果 你 在 另 一 端 的 输 入 使 用 ObjectOutputStream. 你 不 可 以 使 用 DataOutputStream 写 入 数 据 类 型 然 后 使 用 ObjectInputStream 读 取 它 们. 同 样 见 3.6.2 讨 论 的 对 象 流 死 锁. * 除 非 你 使 用 第 5 章 讨 论 的 非 阻 塞 I/O. 3.6.4 通 道 I/O 通 道 I/O 在 JDK 1.4 引 入, 通 过 文 件 和 套 接 字 提 供 了 高 性 能 和 可 扩 展 的 I/O. 将 在 第 5 章 详 细 讨 论. 3.7 终 止 终 止 一 个 连 接 的 最 简 单 的 方 式 就 是 关 闭 套 接 字, 终 止 两 个 方 向 的 连 接, 并 释 放 平 台 的 套 接 字 资 源. 在 关 闭 一 个 套 接 字 之 前,TCP 'shutdown' 功 能 为 套 接 字 的 输 入 和 输 入 独 立 地 提 供 了 一 种 终 止 的 方 式, 如 3.7.1 和 3.7.2 节 讨 论 的. 套 接 字 必 须 由 会 话 的 双 方 关 闭, 当 会 话 完 成, 如 3.7.4 讨 论 的. 当 服 务 器 提 供 的 服 务 被 终 止, 监 听 中 的 套 接 字 必 须 被 关 闭, 如 3.7.4 讨 论 的. 这 个 可 以 在 和 接 收 的 套 接 字 会 话 处 理 中 的 时 候 完 成, 而 不 是 干 扰 这 些 会 话. 3.7.1 输 出 关 闭 ( 半 关 闭 ) 输 出 关 闭 也 称 为 ' 半 关 闭 '. 它 使 用 以 下 方 法 完 成 : void shutdownoutput() throws IOException; boolean isoutputshutdown(); 输 出 关 闭 有 以 下 的 影 响 : (a) 本 地 的, 本 地 套 接 字 和 它 的 输 入 流 行 为 通 常 是 为 了 读 取, 但 是 写, 套 接 字 和 它 的 输 出 行 为 好 像 已 经 被 这 个 终 端 关 闭 了 : 随 后 套 接 字 的 写 将 抛 出 IOException. (b) TCP 的 正 常 的 连 接 - 关 闭 顺 序 ( 一 个 FIN 通 过 一 个 ACK 确 认 ) 是 排 队 去 发 送 在 所 有 挂 起 的 数 据 已 经 被 发 送 并 确 认. (c) 远 程 端, 远 程 套 接 字 行 为 通 常 是 为 了 写 目 的, 而 不 是 读 取 目 的, 套 接 字 的 行 为 好 像 已 经 被 关 闭 了 : 从 套 接 字 中 的 进 一 步 的 读 将 返 回 一 个 EOF. 也 就 是,-1 的 count 或 者 EOFException, 决 定 于 被 调 用 的 方 法. (d) 当 本 地 套 接 字 最 终 被 关 闭, 连 接 - 终 止 顺 序 已 经 被 发 送, 不 会 重 复 ; 如 果 另 一 端 已 经 同 样 做 了 一 个 半 关 闭 操 作, 套 接 字 上 的 所 有 的 协 议 交 互 现 在 是 完 成 的.

3.7.3 关 闭 一 个 连 接 的 套 接 字 一 旦 会 话 完 成, 套 接 字 必 须 被 关 闭. 在 Java 中, 这 个 通 常 通 过 下 面 的 方 法 完 成 : void close() throws IOException; boolean isclosed(); 实 际 上 完 成 这 个 有 以 下 几 种 方 式 : (a) 使 用 socket.close() 关 闭 套 接 字 自 身 ; (b) 通 过 调 用 socket.getoutputstream().close() 关 闭 从 套 接 字 获 得 的 输 出 流 ; (c) 通 过 调 用 socket.getinputstream().close() 关 闭 从 套 接 字 获 得 的 输 入 流. 为 了 关 闭 套 接 字 和 释 放 所 有 的 资 源, 其 中 任 何 一 个 就 足 够 了, 并 且 其 中 的 某 一 个 是 必 要 的. 你 不 能 使 用 在 任 意 给 定 的 套 接 字 上 的 除 此 之 外 的 其 它 技 术. 作 为 一 个 通 用 的 规 则, 你 应 该 关 闭 输 出 流 而 不 是 输 入 流 或 者 是 套 接 字, 因 为 输 出 流 可 能 需 要 刷 新. 关 闭 一 个 套 接 字 是 一 个 输 出 操 作, 并 且, 就 像 上 面 讨 论 的 输 出 操 作, 它 通 常 是 异 步 发 生 的 ( 但 是 见 3.13): 不 能 保 证 另 一 端 已 经 接 收 到 了 关 闭, 或 者 再 一 次, 它 已 经 接 收 到 了 之 前 输 出 操 作 的 数 据. 服 务 器 端 和 客 户 端 都 必 须 关 闭 套 接 字. 如 果 Socket.close() 抛 出 一 个 IOException, 这 意 味 着 可 能 你 已 经 关 闭 了 套 接 字, 比 如, 上 面 列 出 的 方 式 的 另 一 种. 也 可 能 意 味 着 TCP 已 经 探 测 到 它 不 能 发 送 之 前 缓 冲 的 数 据. 如 上 面 讨 论 的, 你 的 应 该 程 序 协 议 是 唯 一 可 同 步 探 测 这 个 问 题 的 方 式. Socket.close() 出 现 IOException 并 不 意 味 着 另 一 端 已 经 关 闭 了 它 的 连 接. 另 一 端 可 能 已 经 关 闭 了 它 的 连 接, 但 是 这 是 一 个 正 常 的 状 态,TCP 协 议 设 计 明 确 地 (FIXME:caters for it). 两 边 都 必 须 关 闭, 总 要 有 先 关 闭 的. 关 闭 一 个 套 接 字 如 果 另 一 端 已 经 关 闭 了, 不 会 抛 出 IOException. isclose 方 法 判 断 本 地 套 接 字 是 否 已 经 关 闭. 它 不 会 判 断 关 于 另 一 端 连 接 的 任 何 信 息. 3.7.4 关 闭 一 个 TCP 服 务 器 服 务 器 通 常 应 该 有 一 些 机 制 去 关 闭. 通 常 这 个 通 过 在 一 个 接 收 的 连 接 上 发 送 一 个 协 议 命 令 完 成 ; 也 可 以 通 过 一 个 命 令 行 或 者 图 形 用 户 界 面 完 成. 关 闭 一 个 服 务 器 需 要 关 闭 监 听 中 的 套 接 字. 在 Java 中, 这 个 意 味 着 调 用 以 下 的 方 法 : class ServerSocket { void close() throws IOException; boolean isclosed(); 套 接 字 上 的 任 意 的 并 发 或 者 随 后 的 ServerSocket.accept 操 作 将 抛 出 SocketException. 然 而 任 何 存 在 的 已 经 接 收 的 套 接 字 不 会 会 关 闭 中 的 ServerSocket 影 响. ServerSocket.accept 抛 出 的 异 常 的 消 息 文 本 在 JDK1.4.1 中 是 'Socket Closed', 但 这 个 可 能 在 Java 不 同 的 版 本 和 实 现 中 变 化.

isclose 方 法 判 断 本 地 套 接 字 是 否 已 经 关 闭. 它 不 会 判 断 关 于 另 一 端 连 接 的 任 何 信 息. 3.8 套 接 字 工 厂 在 面 向 对 象 设 计 中, 工 厂 是 创 建 对 象 的 对 象 ( 或 者 类 ). 一 个 套 接 字 工 厂 是 一 个 创 建 套 接 字 或 者 服 务 器 套 接 字, 或 者 两 者 的 工 厂. 和 所 有 的 工 厂 对 象 一 样, 套 接 字 工 厂 关 注 于 对 象 创 建 处 理 ; 隐 藏 了 系 统 其 它 部 分 的 实 现 细 节 ; 为 能 提 供 的 不 同 的 实 现 提 供 了 一 致 的 对 象 创 建 接 口. Java 在 三 个 层 次 上 支 持 套 接 字 工 厂 :java.net.socket 工 厂,RMI 套 接 字 工 厂, 和 SSL 套 接 字 工 厂. 这 些 就 爱 那 个 在 下 面 分 别 描 述. java.net.socket 工 厂 由 Java 使 用 去 提 供 套 接 字 自 身 的 实 现. java.net.socket 和 java.net.serversockt 类 是 实 际 的 门 面. 这 些 门 面 类 定 义 了 Java 套 接 字 API, 但 是 代 理 了 做 实 际 工 作 的 套 接 字 实 现 对 象 的 所 有 动 作, 套 接 字 实 现 继 承 了 抽 象 的 java.net.socketimpl 类 : class SocketImpl { // 工 厂 提 供 了 java.net.socketimplfactory 接 口 的 实 现 : interface SocketImplFactory { SocketImpl createsocketimpl(); 一 个 默 认 的 套 接 字 工 厂 总 是 被 安 装, 产 生 一 个 SocketImpl 对 象, 类 型 为 package-protected 的 类 java.net.plainsocketimpl. 这 个 类 有 本 地 C 语 言 套 接 字 API 接 口 的 native 方 法, 比 如,Berkeley Sockets API 或 者 winsock. 套 接 字 工 厂 可 以 被 设 置 ; static void setsocketfactory(socketimplfactory factory); setsocketfactory 方 法 在 JVM 的 生 命 周 期 只 能 被 调 用 一 次. 它 需 要 被 分 配 一 个 RuntimePermission 'setfactory', 否 则 抛 出 SecurityException. 应 用 程 序 很 少 或 者 不 使 用 这 个 功 能. 3.8.2 RMI 套 接 字 工 厂 ( 未 完 成 ) 3.8.3 SSL 套 接 字 工 厂 javax.net 工 厂 类 SocketFactory 和 ServerSocketFactory 在 第 7 章 讨 论.

3.9 TCP 中 的 权 限 如 果 安 装 了 Java security 管 理 器, 每 个 套 接 字 操 作 需 要 java.net.socketpermission. 权 限 在 一 个 安 全 策 略 文 件 中 被 管 理 - 一 个 名 为 'java.policy' 的 策 略 文 件, 通 过 JDK 和 JRE 提 供 的 policytool 程 序 管 理.Java 2 权 限 框 架 在 JDK 'Guide to Features/Security' 文 档 中 描 述, 在 这 里 就 不 讨 论 了. 在 策 略 文 件 中 的 一 个 SocketPermission 条 目 有 两 个 字 段 :'action', 也 就 是, 要 尝 试 的 网 络 操 作. 和 'target', 也 就 是 动 作 引 用 的 本 地 或 者 远 程 TCP 端 点, 如 下 格 式 : host[:port] host 是 一 个 明 确 或 者 通 配 符 的 主 机 名 或 者 一 个 IP 地 址,port 是 一 个 端 口 数 或 范 围.action 字 段 和 每 个 TCP 网 络 操 作 的 相 应 的 target 字 段 在 表 格 3.1 显 示. 动 作 描 述 表 格 3-1 TCP 中 的 权 限 accept ServerSocket.accept 方 法 需 要. 目 标 host 指 明 了 正 在 被 接 收 的 远 程 TCP 端 点. connect Socket 的 非 默 认 构 造 函 数 和 它 的 connect 方 法, 和 获 取 InetAddress 对 象 的 时 候 需 要. 目 标 host 指 明 了 要 正 在 连 接 的 远 程 TCP 端 点. ServerSocket 的 非 默 认 构 造 函 数 和 它 的 bind 方 法 需 要. 目 标 host 指 明 了 本 地 TCP 套 接 字, listen 也 就 是,ServerSocket 要 绑 定 的 本 地 端 点.host 的 唯 一 合 理 的 值 为 'localhost'. 默 认 的 策 略 文 件 给 'localhost:1024-' 分 配 了 'listen' 权 限. resolve'accept','connect', 或 者 'listen' 权 限 隐 含 的, 所 以 没 有 必 要 明 确 地 指 定 它. 3.10 TCP 中 的 异 常 在 阻 塞 模 式 的 TCP 套 接 字 操 作 中 有 意 义 的 Java 异 常, 和 它 们 的 来 源 和 起 因, 在 表 格 3-2 展 示. 在 这 个 表 格 中,'C' 或 'U' 表 明 了 异 常 为 检 查 的 (C) 是 未 检 查 的 (U). 表 格 3-2 TCP 中 的 异 常 Exception Thrown by & cause C/U 由 ServerSocket 和 Socket 的 构 造, 和 它 们 的 bind 方 法 抛 出, 如 java.net.bindexception 果 请 求 的 本 地 地 址 或 者 端 口 不 能 被 C 分 配 ( 补 充, 还 有 一 种 情 况 : 地 址 java.net.connectexception java.rmi.connectexception java.lang.illegalargumentexception 已 经 被 使 用 ). 由 Socket 构 造 和 它 的 connect 方 法 抛 出, 当 错 误 地 连 接 到 一 个 远 程 地 址 和 端 口, 通 常 因 为 连 接 被 拒 C 绝 ( 在 指 定 的 (address, port) 上 没 有 监 听 )( 补 充, 还 有 一 种 情 况 : 连 接 超 时 ) C 由 InetSocketAddress,Socket 和 ServerSocket 的 一 些 方 法 抛 出, 如 果 一 个 参 数 为 null 或 者 超 出 异 常. 由 Socket.connect,Socket U

的 输 入 和 输 出 流 操 作, 和 java.nio.channels.illegalblockingmodeexceptionserversocket.accept 抛 出, java.io.interruptedioexception java.io.ioexception java.net.noroutetohostexception java.net.protocolexception java.lang.securityexception java.net.socketexception java.net.sockettimeoutexception java.net.unknownhostexception 如 果 套 接 字 有 一 个 关 联 的 非 阻 塞 模 式 的 通 道 ; 从 JDK 1.4 开 始. 由 Socket 输 入 流 操 作 抛 出, 如 果 C 发 生 超 时. 在 JDK 1.4 之 前. 基 本 的 I/O 异 常 类. 与 TCP 关 联 的 衍 生 异 常 类 包 括 BindExceptin, ConnectException, EOFException, C InterruptedIOException, NoRouteToHostException, ProtocolException, SocketException 和 UnknownHostException 由 Socket 的 非 默 认 构 造 函 数 和 它 的 connect 方 法 抛 出, 表 明 一 个 错 误 发 生 当 连 接 到 一 个 远 程 地 址 和 端 口, C 大 部 分 通 常 是 如 果 远 程 的 主 机 不 能 达 到, 因 为 一 个 间 接 的 防 火 墙, 或 者 中 间 的 路 由 器 down 了. 由 Socket 和 ServerSocket 的 构 造 方 法, 和 Socket 的 输 入 和 输 C 出 流 操 作 抛 出, 表 明 一 个 错 误 发 生 在 底 层 的 协 议, 比 如 TCP 错 误. 由 Socket 和 ServerSocket 的 某 些 方 法 抛 出, 如 果 一 个 需 要 的 权 U 限 没 有 分 配, 如 表 格 3.1 所 示. 由 Socket 的 许 多 方 法 和 Socket 输 入 流 操 作 抛 出, 表 明 一 个 底 层 的 TCP 错 误 发 生, 或 者 套 接 字 被 另 一 个 线 程 通 过 InterruptibleChannel.close 关 闭 了. 如 果 消 息 包 含 文 本 'Connection C reset', 连 接 的 另 一 端 已 经 发 出 了 一 个 reset(rst): 从 此 时 起 套 接 字 是 无 用 的, 它 应 该 被 关 闭 和 废 弃. 许 多 异 常 从 它 衍 生 出 来 : 包 括 BindException 和 ConnectException. 由 Socket 输 入 流 操 作 抛 出, 表 明 发 生 了 超 时 ; 从 JDK 1.4 开 始 ; 为 了 向 后 兼 容 JDK 1.4 之 前 的 程 序 C 继 承 了 InterruptedIOException 由 InetAddress 的 工 厂 方 法 抛 出, 当 使 用 String 主 机 名, 由 这 些 C 方 法 隐 式 解 析. 表 明 了 命 名 的 主 机 C

的 IP 地 址 不 能 从 命 名 服 务 中 确 认. 3.11 套 接 字 选 项 套 接 字 选 项 控 制 了 TCP 的 高 级 特 性. 在 Java 中, 套 接 字 选 项 通 过 java.net.socket 和 java.net.serversocket 的 方 法 控 制. 下 面 出 现 的 套 接 字 选 项 或 多 或 少 是 按 照 他 们 的 相 对 重 要 性 的 顺 序. 3.12 套 接 字 超 时 ( 需 要 重 译 ) 不 能 假 设 一 个 应 用 程 序 永 远 等 待 一 个 远 程 的 服 务, 或 是 服 务 总 是 及 时 响 应, 或 是 服 务 或 中 间 的 网 络 基 础 设 施 在 检 测 方 面 只 能 失 败. 事 实 上, 一 个 TCP 连 接 可 以 在 不 会 被 服 务 器 或 者 客 户 端 检 测 到 的 情 况 下 失 败. 任 何 无 限 超 时 读 取 的 网 络 程 序 迟 早 会 无 限 延 迟. 在 3.17 节 描 述 的 'keep-alive' 特 性 提 供 了 这 个 问 题 的 部 分 解 决 方 案, 如 果 平 台 支 持 的 话.Java 程 序 可 以 运 行 于 任 何 的 平 台, 无 权 承 担 这 个. 即 使 平 台 知 道, 不 支 持 keep-alive, 默 认 的 延 迟 是 两 个 小 时, 在 死 亡 的 (dead) 的 连 接 被 检 测 到, 这 只 能 由 管 理 员 系 统 范 围 的 改 变 (this can only be altered systemwide by an administrator), 如 果 真 的 发 生 的 话.(Usually this twohour detection period is only palatable as a final fall-back). 因 为 这 些 原 因, 谨 慎 (prudent) 的 网 络 程 序 总 是 使 用 一 个 有 限 的 读 超 时. 这 个 通 过 以 下 方 法 管 理 : void setsotimeout(int timeout) throws SocketException: int getsotimeout() throws SocketException; timeout 指 定 为 毫 秒, 并 且 必 须 为 正 数, 表 明 一 个 有 限 的 超 时, 或 者 为 0, 表 明 不 超 时. 默 认 情 况 下,read 超 时 是 无 限 的. 如 果 在 套 接 字 的 阻 塞 读 操 作 之 前 设 置 为 一 个 正 数 ( 有 限 ) 的 超 时 值,read 将 阻 塞 直 到 timeout 周 期, 如 果 数 据 不 可 用, 然 后 将 抛 出 一 个 InterruptedIOException( 补 充, 注 意 :JDK 1.4 之 后 抛 出 的 是 SocketTimeoutException, 在 JDK 1.4 之 前 抛 出 的 是 此 异 常 ). 如 果 超 时 是 无 限 的,read 将 永 远 阻 塞, 或 者 直 到 一 个 错 误 发 生. 客 户 端 只 是 传 输 一 个 请 求 和 等 待 一 个 回 复,timeout 的 持 续 时 间 应 该 考 虑 两 边 的 预 期 的 传 输 时 间 加 上 请 求 的 延 迟 时 间 - 在 另 一 端 的 延 迟 当 回 复 正 在 被 取 回 或 者 计 算 的 时 候. 总 的 预 期 时 间 你 应 该 等 待 多 久 是 一 个 策 略 问 题 : 一 开 始,time-out 应 该 设 置 为 预 期 时 间 总 和 的 两 倍. 通 常,timeouts 应 该 稍 微 设 置 的 长 一 些 而 不 是 短 一 些. 服 务 器 等 待 一 个 客 户 端 的 请 求, 超 时 值 完 全 是 一 个 策 略 问 题 : 在 获 取 连 接 之 前 服 务 器 准 备 等 待 一 个 请 求 多 久? 选 择 的 周 期 应 该 足 够 长 去 支 持 沉 重 的 网 络 负 载 和 合 理 数 量 的 客 户 端, 但 是 不 应 该 太 长 而 占 用 宝 贵 的 服 务 器 资 源 ( 给 一 个 服 务 器 连 接 分 配 的 资 源 由 连 接 的 套 接 字 自 身 组 成, 和 一 个 线 程 和 客 户 端 上 下 文 的 其 它 方 面 ). 超 时 也 可 以 在 ServerSocket 上 设 置 ; class ServerSocket { void setsotimeout(int timeout) throws SocketException; int getsotimeout() throws SocketException; timeout 和 之 前 一 样 设 置 为 毫 秒. 这 个 设 置 决 定 了 ServerSocket.accpet() 在 获 得 一 个 InterruptedIOException 之 前 阻 塞 多 久. 这 个 设 置 不 会 被 接 收 的 连 接 的 连 接 继 承, 也 就 是 说, 不 会 被

ServerSocket.accept() 返 回 的 套 接 字 继 承. 一 个 ServerSocket 超 时 可 以 在 一 个 单 独 的 线 程 中 用 来 去 轮 询 若 干 的 ServerSockets, 虽 然 在 5.3.1 节 描 述 的 Selector 提 供 了 一 种 更 好 的 方 式 这 样 做. 设 置 一 个 套 接 字 超 时 不 会 对 一 个 已 经 在 处 理 中 的 阻 塞 的 套 接 字 操 作 有 影 响. 3.13 套 接 字 缓 冲 区 TCP 为 每 个 套 接 字 分 配 一 个 发 送 缓 冲 区 和 接 收 缓 冲 区. 这 些 缓 冲 区 存 在 于 内 核 的 地 址 空 间 或 者 TCP 协 议 栈 ( 如 有 不 同 的 话 ), 而 不 是 在 "jvm 或 者 进 程 地 址 空 间 ". 这 些 缓 冲 区 的 默 认 大 小 是 由 底 层 平 台 的 TCP 实 现 决 定 的, 而 不 是 Java. 在 最 初 的 TCP 实 现 中, 发 送 和 接 收 缓 冲 区 默 认 都 是 2KB. 在 一 些 实 现 中, 它 们 现 在 通 常 的 默 认 大 小 差 不 多 为 28KB, 32KB, 或 者 64KB, 但 是 你 必 须 检 查 你 的 目 标 系 统 的 特 征. 3.13.1 方 法 一 个 套 接 字 的 发 送 和 接 收 缓 冲 区 的 大 小 通 过 以 下 方 法 管 理 : void setreceivebuffersize(int size) throws SocketException; int getreceivebuffersize() throws SocketException; void setsendbuffersize(int size) throws SocketException; int getsendbuffersize() throws SocketException; class ServerSocket { void setreceivebuffersize(int size) throws SocketException; int getreceivebuffersize() throws SocketException; size 指 定 为 字 节. 给 这 些 方 法 提 供 的 值 只 是 作 为 底 层 平 台 的 一 个 建 议, 在 两 端 可 能 被 调 整 来 适 应 允 许 的 范 围, 或 者 向 上 或 者 向 下 调 整 为 合 适 的 边 界. 你 可 以 在 关 闭 套 接 字 之 前 的 任 意 时 刻 设 置 套 接 字 的 发 送 缓 冲 区 大 小. 对 于 接 收 缓 冲 区, 见 3.3.6( 服 务 器 ) 和 3.4.7( 客 户 端 ) 的 讨 论. 通 过 'get' 方 法 返 回 的 值 可 能 和 你 设 置 的 值 不 匹 配. 它 们 也 可 能 与 正 在 被 底 层 平 台 使 用 的 实 际 值 也 不 匹 配. 3.13.2 一 个 套 接 字 缓 冲 区 应 该 多 大? 8KB, 或 者 32KB, 或 者 64KB, 在 如 今 的 网 络 速 度 中 足 够 吗? 较 大 的 缓 冲 区,TCP 可 能 操 作 更 高 效. 较 大 的 缓 冲 区 大 小 利 用 网 络 的 能 力 从 而 更 有 效 : 它 们 减 少 了 向 网 络 写 入 的 物 理 次 数 ; 通 过 一 个 较 大 的 包 大 小 分 担 了 40 字 节 的 TCP 开 销 和 IP 数 据 包 头 ; 允 许 更 多 的 字 节 被 传 输,' 填 充 管 道 '; 允 许 更 多 的 数 据 被 传 输 在 停 止 前. 下 面 的 原 则 应 该 被 遵 守. (a) 在 以 太 网 上,4KB 肯 定 是 不 够 的 : 将 缓 冲 区 从 4KB 提 升 到 16KB 将 带 来 40% 的 吞 吐 量 提 升. (b) 套 接 字 缓 冲 区 大 小 应 该 总 是 至 少 是 连 接 的 最 大 的 分 段 大 小 (maxumiun segment size - MSS) 的 三 倍, 通 常 由 网 络 接 口 的 最 大 传 输 单 元 (maximum transmission unit - MTU) 决 定,(FIXME:less 40 to account for the size of the TCP and IP headers). 以 太 网,MSS 小 于 1500, 使 用 8KB 或 者 以 上 的 缓 冲 区 大 小 这 是 不 存 在 问 题 的, 但 是 其 它 物 理 层 行 为 有 所 不 同. (c) 发 送 缓 冲 区 大 小 应 该 至 少 和 另 一 端 的 接 收 缓 冲 区 一 样 大. (d) 对 于 每 次 发 送 大 量 数 据 的 应 用 程 序, 将 缓 冲 区 大 小 从 48KB 增 加 到 64KB 可 能 只 会 让 你 的 应 用 程 序 单 次 的 更 高 效 的 性 能 提 升. 对 于 这 样 的 应 用 程 序 的 最 大 性 能, 发 送 缓 冲 区 应 该 和 中 间 的 网 络 产 生 的 宽 带 - 延 迟 一 样 大.

(e) 对 于 每 次 接 收 大 量 数 据 的 应 用 程 序 的 最 大 性 能 ( 比 如, 应 用 程 序 的 另 一 端 发 送 大 量 的 数 据 ), 接 收 缓 冲 区 需 要 尽 可 能 和 上 面 的 约 束 一 样 大, 因 为 TCP 根 据 在 接 收 者 的 缓 冲 区 空 间 限 制 了 发 送 者 - 一 个 发 送 者 可 以 不 发 送 数 据 除 非 它 知 道 在 接 收 者 中 有 空 间. 如 果 接 收 的 应 用 程 序 在 从 缓 冲 区 中 读 取 数 据 方 面 比 较 慢, 它 的 接 收 缓 冲 区 大 小 需 要 更 大, 这 样 不 会 拖 延 发 送 者. (f) 对 于 发 送 和 接 收 大 量 数 据 的 应 用 程 序, 同 时 增 加 缓 冲 区 大 小. (g) 在 如 今 的 大 多 数 实 现 中 缓 冲 区 大 小 至 少 是 8KB(WINSOCK),28KB(OS/2),52KB(Solaris). 早 期 的 TCP 实 现 允 许 大 于 52,000 字 节 的 最 大 缓 冲 区 大 小. 一 些 当 前 的 实 现 支 持 最 大 256,000,000 字 节 的 大 小 或 者 更 多. (h) 为 了 在 一 个 服 务 器 中 接 收 超 过 64KB 的 缓 冲 区 大 小, 你 必 须 在 监 听 套 接 字 之 前 设 置 缓 冲 区, 接 收 的 套 接 字 将 继 承 这 个 属 性, 如 3.3.6 节 描 述 的. (i) 无 论 缓 冲 区 大 小 是 什 么, 你 应 该 通 过 以 那 个 大 小 的 块 的 方 式 写 入 来 帮 助 TCP, 也 就 是 说, 通 过 使 用 至 少 那 个 大 小 的 BufferedOutputStream 或 者 ByteBuffer. 3.14 多 宿 主 如 我 们 在 2.2.5 节 看 到 的, 一 个 多 宿 主 是 一 个 有 多 于 1 个 IP 地 址 的 主 机. 多 宿 主 对 于 TCP 服 务 器 有 重 要 的 影 响, 对 客 户 端 没 有 什 么 影 响. 3.14.1 多 宿 主 - 服 务 器 一 个 TCP 服 务 器 通 常 在 所 有 的 本 地 IP 地 址 上 监 听, 这 样 的 一 种 服 务 器 通 常 不 必 关 心 它 可 能 运 行 于 一 个 多 宿 主 的 主 机. 下 面 是 一 个 TCP 服 务 器 可 能 需 要 注 意 多 宿 主 的 情 况. 如 果 一 个 服 务 器 服 务 于 一 个 子 网, 它 应 该 将 它 自 身 绑 定 到 合 适 的 本 地 IP 地 址. 这 个 反 过 来 可 能 需 要 在 3.3.5 节 讨 论 的 Socket.setReuseAddress 方 法 的 使 用. 如 果 服 务 器 提 供 它 自 己 的 IP 地 址 给 客 户 端, 它 必 须 返 回 一 个 客 户 端 可 以 访 问 的 IP 地 址. 通 常 客 户 端 不 比 访 问 服 务 器 的 所 有 IP 地 址, 但 是 可 能 只 需 要 访 问 其 中 一 个. 如 果 客 户 端 已 经 连 接 到 了 服 务 器, 保 证 一 个 返 回 的 IP 地 址 可 用 的 最 简 单 的 方 式 就 是 强 制 它 的 地 址 为 客 户 端 用 来 连 接 的 地 址, 由 被 接 收 的 套 接 字 的 Socket.getLocalAddress 方 法 给 出, 如 3.4.4 节 描 述 的. 在 目 录 服 务 中 (directory services) 当 注 册 服 务 描 述 符 的 时 候, 服 务 器 必 须 通 过 一 个 所 有 客 户 端 都 能 访 问 的 IP 地 址 通 知 它 自 己. 保 证 被 通 知 服 务 地 址 是 最 可 用 的 最 好 的 方 式 是 在 每 种 情 况 中 通 知 'most public( 大 多 数 公 共 的 )'IP 地 址 或 者 主 机 名. 3.14.2 多 宿 主 - 客 户 端 一 个 TCP 客 户 端 通 常 不 关 注 它 的 本 地 地 址, 如 我 们 之 前 在 3.4.8 看 到 的. 如 果 有 某 些 原 因 需 要 关 注 它 用 于 连 接 的 网 络 接 口, 它 应 该 指 定 IP 地 址 当 这 当 这 么 做 的 时 候, 如 3.4.4 节 讨 论 的. 3.15 Nagle's 算 法 ( 未 完 成 ) 3.16 Linger on close Socket.setSoLinger 方 法 控 制 当 一 个 套 接 字 关 闭 时 TCP 的 行 为 :

void setsolinger(boolean linger, int timeout) throws SocketException; int getsolinger() throws SocketException; timeout 指 定 为 秒.getLinger 方 法 的 返 回 值 为 -1 表 明 了 默 认 设 置 (linger = false). TCP 为 "linger on close" 定 义 了 三 种 不 同 的 行 为, 如 表 3.3 所 示. 表 3.3 TCP 'linger' 设 置 lingertimeout 描 述 默 认 情 况. 当 套 接 字 被 关 闭 (a), 关 闭 中 的 线 程 不 会 阻 塞, 但 是 套 接 字 不 会 被 立 即 销 毁 : 它 进 入 CLOSING 状 态, 当 任 意 剩 余 的 数 据 被 传 输,FIN-ACK 关 闭 协 议 与 另 一 端 交 互 ; 套 接 字 然 后 进 入 TIME-WAIT 状 态, 持 续 一 个 TCP 分 段 的 最 大 生 命 周 期 的 两 次, 为 false ignored了 保 证 后 面 传 输 到 套 接 字 的 数 据 被 一 个 TCP RST 拒 绝, 被 选 的 时 间 间 隔,TCP 可 以 转 播 关 闭 协 议 的 最 后 一 部 分 如 果 有 必 要 的 话, 这 样 本 地 和 远 程 端 口 对 在 TIME-WAIT 期 间 不 会 被 重 用, 这 样 已 经 关 闭 的 连 接 的 延 迟 的 数 据 分 段 不 会 传 递 到 新 的 连 接. 当 TIME- WAIT 到 期, 套 接 字 被 销 毁 (b). true 0 true 0 'Linger'. 当 套 接 字 被 关 闭 (a), 关 闭 的 线 程 阻 塞 ('lingers'), 同 时 任 意 挂 起 的 数 据 被 发 送, 关 闭 协 议 交 互, 或 者 超 时 过 期, 看 哪 一 个 先 发 生 ; 线 程 然 后 继 续 运 行 ; 如 果 超 时 过 期,(i) 连 接 'hard-closed' 如 下 面 描 述 的 (c), 或 者 (ii) 剩 余 的 数 据 仍 然 排 队 等 待 传 递, 在 连 接 通 过 FIN-ACK 关 闭 之 后, 如 上 面 描 述 的 (d). 这 些 语 义 是 平 台 依 赖 的 (Java 不 能 克 服 它 们 ). 在 Java 中,timeout 是 一 个 int 值, 指 定 为 秒, 限 于 秒 ; 一 些 平 台 进 一 步 限 制 为 = 32.767 秒, 通 过 使 用 一 个 内 部 的 16-bit 有 符 号 的 数 量 代 表 了 百 分 之 一 秒 (e). 'Hard-Close'. 当 套 接 字 被 关 闭 (a), 任 何 挂 起 的 数 据 被 废 弃, 关 闭 协 议 交 互 (FIN-ACK) 不 会 发 生, 取 而 代 之 的 是, 发 出 一 个 RST, 引 起 另 一 端 抛 出 一 个 SocketException 'connection reset by peer'. (a) 也 就 是 通 过 Socket.close,Socket.getXXXStream.close, 或 者 Socket.shutdownOutput. (b) 许 多 实 现 不 必 阻 止 本 地 端 口 的 重 用 在 TIME-WAIT 期 间, 即 使 当 连 接 到 一 个 不 同 的 远 程 端 点. (c) 这 个 行 为 是 必 须, 通 过 winsock 2 规 范 3.4. (d) 这 个 行 为 是 必 须 的, 通 过 a Posix.1g draft (quoted in the comp.unix.bsd newsgroup by W.R. Stevens, 22 May 1996, 但 是 IEEE Std 1003.1-201 并 没 有 这 样, 它 没 有 定 义. (e) 如 果 timeout 过 去,Berkeley Sockets 和 winsock API 都 设 置 EWOULDBLOCK, 并 返 回 -1, 尽 管 这 个 在 IEEE Std 1003.1-201 中 并 没 有 指 定. 在 JDK 1.4.2 中,Java 忽 略 了 这 些, 所 有 你 不 能 在 Java 中 判 断 在 关 闭 端 timeout 是 否 过 期 了. 作 者 已 经 为 Socket.close 请 了 一 个 改 进, 在 这 种 情 况 下 抛 出 InterruptedIOException. 3.17 Keep-Alive 3.21 将 它 们 综 合 在 一 起 一 个 修 正 的 TCP 服 务 器 实 现 了 上 面 建 议 的 改 进, 在 示 例 3.6 中 展 示.

public class ConcurrentTCPServer implements Runnable { public static final int BUFFER_SIZE = 128 * 1024;// 128k public static final int TIMEOUT = 30 * 1000; // 30s ServerSocket serversocket; ConcurrentTCPServer(int port) throws IOException { // (Don t specify localaddress for ServerSocket) serversocket = new ServerSocket(port); // Set receive buffer size for accepted sockets // before accepting any, i.e. before they are connected serversocket.setreceivebuffersize(buffer_size); // Don t set server socket timeout public void run() { for (;;) { try { Socket socket = serversocket.accept(); // set send buffer size socket.setsendbuffersize(buffer_size); // Don t wait forever for client requests socket.setsotimeout(timeout); // despatch connection handler on new thread new Thread(new ConnectionHandler(socket)).start(); catch (IOException e) { /* Exception handling, not shown */ // end for (;;) // end run() 示 例 3.6 改 进 的 TCP 服 务 器 进 一 步 的 服 务 器 架 构 在 第 5 章 和 第 12 章 讨 论. 一 个 修 正 的 TCP 客 户 端 实 现 了 上 面 的 建 议 的 改 进, 在 示 例 3.7 展 示. public class TCPClient implements Runnable { public static final int BUFFER_SIZE = 128 * 1024;// 128k public static final int TIMEOUT = 30 * 1000; // 30s Socket socket; public TCPClient(String host, int port) throws IOException { this.socket = new Socket(); // Set receive buffer size before connecting socket.setreceivebuffersize(buffer_size); // connect to target socket.connect(new Inet4Address(host, port)); // Set send buffer size and read timeout

socket.setsendbuffersize(buffer_size); socket.setsotimeout(timeout); public void run() { try { // prepare to send request OutputStream out = new BufferedOutputStream(socket.getOutputStream(), BUFFER_SIZE); // send request data, not shown // flush request out.flush(); // prepare to read reply InputStream in = new BufferedInputStream(socket.getInputStream(), BUFFER_SIZE); // receive reply & process it, not shown catch (IOException e) { /* */ finally { try { if (socket!= null) socket.close(); catch (IOException e) { // ignored // end finally // end run() 示 例 3.7 改 进 的 TCP 客 户 端