1. 课程导入与开发环境搭建
Java 网络编程并不是一门孤立的技术,它处在“Java 语言基础”与“服务端工程能力”之间的关键位置。往下看,它依赖 JVM、线程模型、IO 流与异常处理;往上看,它支撑 HTTP 服务器、RPC 框架、消息中间件、网关与分布式系统的通信底座。理解这一层定位,才能明白为什么我们既要写得出一个能收发数据的 Socket 程序,也要理解阻塞、连接、协议边界这些工程概念。
一、Java 网络编程的定位与知识地图
从工程视角看,Java 网络编程解决的核心问题是:让运行在不同主机上的进程,通过约定的协议交换字节流,并把字节流还原为有意义的数据。围绕这个问题,知识可以分成四层:
- 传输基础层:TCP/UDP、IP 与端口、三次握手、连接与断开、字节序。
- Java API 层:
InetAddress、Socket、ServerSocket、DatagramSocket、InputStream/OutputStream。 - 并发与 IO 模型层:阻塞 IO、线程池、NIO 的
Channel、Buffer、Selector,以及 Reactor 思想。 - 协议与应用层:HTTP、自定义应用层协议、序列化、粘包拆包、心跳与超时。
本课程后续章节会沿着这条链路逐步展开。本章先完成两件事:把开发环境搭好,并亲手写出第一个可运行的 TCP 回声服务端,让“网络编程”从概念变成可观察的程序行为。
二、JDK 与 IDE 配置
本课程示例统一使用 JDK 17(LTS 版本),IDE 推荐 IntelliJ IDEA Community 或 Ultimate。JDK 17 在 Socket、ServerSocket 等 API 上保持稳定,同时支持 var、文本块等语法,便于书写清晰的示例代码。
安装完成后,先在终端确认环境变量是否生效:
java -version
javac -version
预期输出类似:
java version "17.0.10" 2024-01-16 LTS
Java(TM) SE Runtime Environment (build 17.0.10+11-LTS-240)
javac 17.0.10
如果 java -version 能输出但 javac 提示“不是内部或外部命令”,说明只装了 JRE 或 JAVA_HOME 未指向 JDK 根目录。此时应把 JAVA_HOME 设置为 JDK 安装目录,并把 %JAVA_HOME%\bin(Windows)或 $JAVA_HOME/bin(macOS/Linux)加入 PATH。
在 IDEA 中新建一个 Maven 或普通 Java 项目后,需要检查三处配置:Project SDK 选择 JDK 17;Project language level 设为 17;Run/Debug Configurations 中确认使用项目 SDK。这三处一致,才能避免“代码能写但编译报错”的环境问题。
三、第一个 TCP 回声服务端
TCP 回声(Echo)服务端的语义很简单:客户端发来什么,服务端原样返回什么。它虽然简单,却完整覆盖了服务端网络编程的基本动作:绑定端口、监听连接、接受连接、读取字节、写回字节、关闭连接。
下面是一个单连接版本,便于先看清主流程:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class EchoServer {
public static void main(String[] args) throws IOException {
int port = 9000;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("Echo server listening on " + port);
try (Socket socket = serverSocket.accept()) {
System.out.println("Client connected: " + socket.getRemoteSocketAddress());
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
out.flush();
}
}
}
}
}
关键点有三个。第一,new ServerSocket(9000) 会占用本机 9000 端口,若端口已被占用会抛出 BindException。第二,accept() 是阻塞方法,没有客户端连接时线程会停在这里。第三,in.read(buffer) 返回 -1 表示对端关闭了输出流,循环随之结束。
启动服务端后,可以用 telnet 或 nc 测试:
# macOS / Linux
nc 127.0.0.1 9000
# Windows(需先启用 Telnet 客户端)
telnet 127.0.0.1 9000
连接成功后输入 hello 并回车,终端应立即回显 hello。若连接被拒绝,检查服务端是否已启动、端口是否一致;若一直无响应,检查是否被防火墙拦截。
accept()。这是理解阻塞模型的最佳起点,但绝不能直接用于生产。后续章节会引入线程池与 NIO 来支撑并发连接。四、从回声服务端看工程要点
这个最小示例已经暴露了服务端开发中反复出现的几个问题:端口与地址绑定、阻塞与并发、流的读写边界、资源释放、异常处理。例如,TCP 是字节流协议,不保留消息边界,客户端连续发送两次数据,服务端可能一次读到,也可能分两次读到——这就是“粘包/拆包”问题的根源,后续会专门讨论。
再比如资源释放:ServerSocket、Socket、输入输出流都必须关闭。示例中使用 try-with-resources 保证异常时也能释放。若手动关闭,顺序应是先关流、再关 Socket、最后关 ServerSocket。
下表给出本章示例中几个关键参数的参考值与含义,便于后续调试时对照:
| 参数/对象 | 示例值 | 含义与影响 |
|---|---|---|
| 监听端口 | 9000 | 服务端对外提供服务的 TCP 端口,需未被占用 |
| 缓冲区大小 | 1024 字节 | 单次 read 最多读取的字节数,影响吞吐与内存 |
| 绑定地址 | 0.0.0.0(默认) | 监听所有网卡;指定 127.0.0.1 则仅本机可访问 |
| read 返回值 | -1 | 对端关闭连接,循环应结束 |
完成本章后,你应当能够独立完成:安装并验证 JDK 17、在 IDEA 中正确配置 SDK、编写并运行一个 TCP 回声服务端、用 nc/telnet 验证收发行为,并能解释 accept、read、write 三个调用在阻塞模型下的行为。这些是后续所有网络编程内容的起点。