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 回声服务端,让“网络编程”从概念变成可观察的程序行为。

Java 网络编程知识地图 协议与应用层 HTTP · 自定义协议 · 序列化 · 粘包拆包 · 心跳与超时 并发与 IO 模型层 阻塞 IO · 线程池 · NIO Channel/Buffer/Selector · Reactor Java API 层 InetAddress · Socket · ServerSocket · DatagramSocket · 流 传输基础层 TCP/UDP · IP 与端口 · 三次握手 · 连接与断开 · 字节序

二、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。这三处一致,才能避免“代码能写但编译报错”的环境问题。

网络编程调试时,建议关闭 IDE 的“自动折叠控制台输出”,并保留完整异常堆栈。连接被拒绝、端口占用、读超时等问题,往往就藏在堆栈的第一行。

三、第一个 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 三个调用在阻塞模型下的行为。这些是后续所有网络编程内容的起点。