
写这篇东西之前我先说句题外话。很多刚接触JavaEE的同学一上来就扎进Servlet、Spring这些框架里配置好了环境、代码也照着敲了结果一到部署、联调、看日志的时候就卡壳——报错看不懂连不上数据库请求发不出去接口返回超时。一问才知道根本不是代码问题而是网络基础没打牢。所以这篇“JavaEE初阶:网络初识”不是让你去背协议栈而是把JavaEE开发最常用到的网络知识串一遍让你知道浏览器请求到服务器响应这中间到底发生了什么为什么TCP连接会断为什么端口会被占用Servlet里拿到的request和response到底是靠什么传过来的。学完这一篇你对后面学Tomcat、HTTP协议、SpringMVC、微服务这些都会顺畅很多报错时至少知道该往哪查。1. 为什么学JavaEE前先要把网络基础补上1.1 JavaEE开发的本质就是和网络打交道JavaEE说白了就是一套基于Java语言的企业级开发规范但不管它封装得多高级底层最终做的事情仍然是接收请求、处理业务、返回响应。这套流程跑在什么上面跑在TCP/IP网络协议栈上。你在浏览器输入一个网址敲下回车浏览器会先做DNS解析把域名换成IP然后通过TCP三次握手和服务器建立连接再按照HTTP协议格式把请求发出去。服务器这边Tomcat也好、Jetty也好监听某个端口把收到的字节解析成结构化数据再交给Servlet去处理。整个过程全是网络的事。这就带来一个很现实的问题如果你不懂网络排查问题的效率会非常低。举个例子你启动一个SpringBoot项目控制台报端口被占用这算比较友好的报错。但还有更隐蔽的场景接口偶尔超时服务间调用不稳定数据库连接池报Connection refused或者部署到Linux服务器之后本地能访问、外部访问不了——这些问题如果只盯着Java代码看永远找不到根因因为它们发生在网络层面。所以我的观点很明确网络不是JavaEE的副科而是地基。地基不牢上面盖多少层框架都白搭。这篇只做“初识”不追求研究多深但要保证你明确掌握那些在JavaEE开发中被反复用到的核心网络概念。1.2 对接“JavaEE初阶”的知识断层再说一个我在带新人时经常看到的现象。很多初学者会用IDE启动项目能在浏览器看到页面就觉得自己会了。但是当我问“浏览器是怎么找到你Tomcat的”、“POST和GET在传输层有什么本质区别”、“为什么有时候代码没改服务就莫名连不上了”这些问题时回答的人就少了。正是这些“断层”让我决定写一篇以网络为主线的JavaEE入门文章。热词里出现的“vscode配置javaee语言环境”、“JavaEE工具链”这些问题背后都绕不开同一个话题你写的代码最终要通过网络协议跑起来。换句话说把网络初识搞懂了配置环境、跑通项目、排查异常这些痛苦至少能减少一半。这一篇我会沿着一套完整的链路来讲网络分层模型、HTTP协议、TCP/UDP核心机制、Java网络编程最小闭环实操、常见网络异常的排查思路。每部分都尽量贴近实际开发场景不扯纯理论。2. 网络分层模型从物理信号到HTTP报文2.1 用快递分拣理解TCP/IP四层模型一说到网络分层很多教材喜欢一上来就甩OSI七层模型把人看得头大。但实际开发中你接触最多的是TCP/IP四层模型它长这样链路层也叫网络接口层、网络层、传输层、应用层。我倾向于用快递来类比。假设你在淘宝下单商家发了一个包裹给你。包裹本身是你的商品应用层的数据比如HTTP请求体里带的表单数据。为了让包裹能运输快递公司会套一个快递袋传输层封装比如TCP头上面写上发货人和收货人源端口和目的端口。接着快递公司要把包裹从一个城市运到另一个城市这需要物流网络规划路线相当于网络层的IP地址在做路由。最后快递小哥通过某个小区的门牌号找到你这就是链路层的MAC地址和物理传输。对应关系是这样的HTTP协议在应用层TCP和UDP在传输层IP协议在网络层以太网、WiFi这些在链路层。每次数据从上层往下传都会在原有数据前面加上一层的头部信息。到了接收端再一层层剥掉头部。这个“封装-传递-解封装”的过程就是网络通信最底层的循环。2.2 IP地址、端口以及它们的配合关系要通信就得有地址。IP地址是网络层的标识它负责把数据从一个主机送到另一个主机。比如192.168.1.100。但一台机器上往往同时跑着很多服务MySQL占3306、Tomcat占8080、Redis占6379。数据进来了操作系统怎么知道该交给哪个进程靠端口号区分。这里我建议你把IP地址理解成小区地址端口号理解成具体的门牌号。光有小区地址能找到楼但找不到户必须有门牌号才能精准送达。在JavaEE开发中InetSocketAddress这个类就是用“IP端口”的组合来定义一个网络端点。日常排查问题时你看到Connection refused: /127.0.0.1:3306意思就是通往本机3306端口的连接被拒绝了。这里有两个关键点第一端口范围是0到65535其中0到1023是知名端口比如HTTP默认80、HTTPS默认443、MySQL默认3306第二一个端口同一时间只能被一个进程监听如果被占用后面启动的进程会报找不到可用端口或地址已被使用。2.3 初识HTTPJavaEE里最常打交道的应用层协议应用层协议里JavaEE开发者最常碰到的就是HTTP协议。你可以用curl或者浏览器开发者工具直接观察它。一个HTTP请求报文由三部分组成请求行、请求头、请求体。请求行包含方法、URL和HTTP版本请求头是一堆key-value比如Host、User-Agent、Content-Type请求体则承载POST提交的表单数据或JSON。一个典型的GET请求长这样GET /index.html?page1 HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 Accept: text/html Connection: keep-alive对应的响应报文长这样HTTP/1.1 200 OK Content-Type: text/html; charsetutf-8 Content-Length: 1234 Set-Cookie: sessionidabc123; Path/ !DOCTYPE html html.../html在JavaEE里Servlet容器比如Tomcat的作用就是解析这些报文把请求行和请求头封装成HttpServletRequest对象把响应状态和响应头封装成HttpServletResponse对象。你写Servlet代码时操作的是Java对象但最终落到网络上的就是上面这些明文文本。这个认知特别重要——“封装”只是让你用起来方便协议层面的格式从来没变过。3. 传输层核心TCP和UDP你绕不开的通信规则3.1 TCP三次握手与四次挥手拆解你打开浏览器访问一个网站或者写Java代码用Socket去连接另一台服务器本质上都在做一件事建立TCP连接。TCP是面向连接的可靠传输协议它通过三次握手建立连接通过四次挥手断开连接。三次握手的过程我用文字描述一遍客户端发送一个SYN报文并随机初始化一个序列号seqx表示“我想和你建立连接”。服务端收到后回复一个SYNACK报文确认号ackx1并带上自己的序列号seqy表示“我收到了你的请求我也准备好建立连接”。客户端收到后再发送一个ACK报文确认号acky1。这个包发完连接正式建立双方可以开始传数据。你可能想知道为什么非要三次两次不行吗这个问题面试也常问。两次握手有一个致命缺陷如果客户端第一次发的SYN在网络中滞留了客户端超时后重新发起连接但老的那个SYN突然又到达了服务端。如果没有第三次握手服务端会误以为这是一个新的合法连接造成资源浪费。三次握手让服务端等客户端再确认一次能有效规避这种“失效的连接请求”。四次挥手的过程是这样的假设客户端先发起关闭它发送FIN报文服务端收到后回复ACK确认然后服务端把剩余数据传完再发送自己的FIN报文客户端最后回复ACK。主动关闭方在发送最后一个ACK后会进入TIME_WAIT状态等待2MSL最大报文生存时间后才真正关闭。在排查问题时TIME_WAIT和CLOSE_WAIT这两个状态是经常出现的高频词。TIME_WAIT过多通常意味着高并发的短连接太多系统层面可以通过调整内核参数解决。而CLOSE_WAIT过多绝大多数原因是程序代码里没有正确关闭连接——你的资源没释放。3.2 TCP的可靠性机制为何HTTP选择TCP做底座HTTP协议选择TCP作为传输层协议一个重要原因是TCP提供了可靠性保障。它通过序列号与确认应答机制、超时重传、滑动窗口、流量控制和拥塞控制确保数据不丢、不乱序、不重复。序列号和确认应答是基础每发送一个报文段接收方收到后会回复确认号发送方如果超时未收到确认就重传。这个机制保证数据不会因为网络丢包而消失。滑动窗口则解决效率问题允许发送方在未收到确认前连续发送多个报文段通过窗口大小动态调节发送速率。流量控制是接收方告诉发送方“我这边处理不过来了你慢点发”拥塞控制则为了防止整个网络被堵死TCP会动态调整发送速率。对JavaEE初阶的学习者来说你不需要把拥塞控制算法背下来但至少要知道TCP是有状态的连接传输稳定可靠代价是连接建立和释放有开销。这也解释了为什么HTTP/1.1引入了Connection: keep-alive来复用连接避免频繁握手导致效率低下。3.3 UDP无连接、快但不可靠的通信方式UDP和TCP是完全不同的路子。UDP是无连接的发送数据之前不需要握手直接把数据包扔出去。所以它的头部只有8个字节延迟低、效率高但是没有确认机制丢包了也不会重传。JavaEE日常开发中UDP没有TCP出场率高但你仍然会碰到。比如某些日志采集系统用UDP上报日志视频直播、语音通话这些对实时性要求高但可以容忍少量丢包的场景也基本走UDP。在Java里DatagramSocket和DatagramPacket就是用来实现UDP通信的类。做个简单的选择判断如果你的业务对数据完整性要求高比如转账、下单、数据库操作必须用TCP。如果追求实时性、数据量又大丢几个包无所谓比如实时监控画面可以考虑UDP。这个选择直接决定了你Java代码里应该用Socket还是DatagramSocket。4. 实操闭环用Java搭建一个最小网络通信Demo4.1 准备环境与命令行验证工具纸上谈兵没意思这一节我们直接在代码层面跑一遍网络通信的过程。环境方面你只需要一个JDK8以上的开发环境随便哪个IDE都行文本编辑器加命令行也可以。建议同时打开一个命令行窗口Windows用户用cmd或PowerShellmacOS/Linux用户用Terminal因为我们一会儿要用命令观察端口和连接状态。用到的基础命令先列一下netstat -anoWindows或netstat -tunlpLinux查看端口监听和连接状态。telnet 127.0.0.1 9090测试某个TCP端口是否可连通。tcpdump -i lo port 9090Linux/macOS需要sudo抓包观察数据包交互。这些命令在排查问题时是刚需早年我带项目时全靠netstat和tcpdump定位过快连不上、端口被占的一系列问题。4.2 用InetAddress解析域名和IP我们先用一个最基础的类InetAddress来体验一下Java的网络能力。它的作用类似命令行里的ping或nslookup可以根据域名获取IP地址也可以反向获取主机名。import java.net.InetAddress; import java.net.UnknownHostException; public class InetAddressDemo { public static void main(String[] args) { try { InetAddress address InetAddress.getByName(www.baidu.com); System.out.println(主机名 address.getHostName()); System.out.println(IP地址 address.getHostAddress()); System.out.println(本机地址 InetAddress.getLocalHost()); } catch (UnknownHostException e) { e.printStackTrace(); } } }运行后你大概率会看到类似IP地址110.242.68.3的输出。这个过程中发生了什么InetAddress.getByName()会发起一次DNS查询把域名解析成IP。如果本机的DNS配置有问题或者目标域名不存在就会抛UnknownHostException。实际开发中一些部署在机房的应用连不上外部数据库排查第一步就是先ping域名看解析是否正常经常能发现是DNS没配好。4.3 基于Socket的TCP服务端与客户端接下来写一个最经典的TCP通信例子。服务端监听9090端口客户端主动连接并发送消息。注意为了演示方便我把异常直接抛出去了实际项目中不要这么写需要做完善的异常处理。先看服务端代码import java.io.*; import java.net.*; public class TcpServer { public static void main(String[] args) throws IOException { // 1. 创建ServerSocket并监听9090端口 ServerSocket serverSocket new ServerSocket(9090); System.out.println(服务端已启动监听端口9090); // 2. 阻塞等待客户端连接连接建立后返回该客户端的Socket Socket socket serverSocket.accept(); System.out.println(接收到客户端连接 socket.getRemoteSocketAddress()); // 3. 通过socket的输入输出流与客户端通信 BufferedReader reader new BufferedReader( new InputStreamReader(socket.getInputStream())); PrintWriter writer new PrintWriter(socket.getOutputStream(), true); String line; while ((line reader.readLine()) ! null) { System.out.println(收到客户端消息 line); writer.println(服务端已收到 line); } // 4. 关闭连接 socket.close(); serverSocket.close(); System.out.println(服务端已退出); } }这段代码里有几个值得注意的点。ServerSocket构造器里传了端口9090这意味着服务端会在9090端口上等待连接。accept()方法是阻塞的在没有客户端接入时会一直停在这里。getRemoteSocketAddress()返回的是客户端的IP和端口信息——每次客户端建立连接操作系统都会为它分配一个随机的高位端口比如52343服务端的9090端口仍然是固定监听端口。再看客户端代码import java.io.*; import java.net.*; public class TcpClient { public static void main(String[] args) throws IOException { // 1. 连接指定IP和端口的服务端 Socket socket new Socket(127.0.0.1, 9090); System.out.println(已连接服务端 socket.getRemoteSocketAddress()); // 2. 获取输入输出流 BufferedReader reader new BufferedReader( new InputStreamReader(socket.getInputStream())); PrintWriter writer new PrintWriter(socket.getOutputStream(), true); // 3. 发送消息并接收回执 writer.println(你好服务端); System.out.println(服务端回复 reader.readLine()); // 4. 关闭连接 socket.close(); System.out.println(客户端已退出); } }运行顺序是先启动服务端再启动客户端。你能看到服务端打印“接收到客户端连接”客户端打印“服务端回复服务端已收到你好服务端”。这背后就是一次完整的TCP三次握手、数据传输、四次挥手的缩影。实操时建议你做一个动作在客户端连接建立后立刻打开另一个命令行窗口输入netstat -ano | findstr 9090你能看到一条ESTABLISHED状态的TCP连接记录以及客户端源端口号。这一步能帮你把代码和真实网络状态对上印象会深得多。4.4 基于DatagramSocket的UDP通信接着看UDP版本。UDP的代码模型和TCP完全不同因为没有连接的概念发送方只管把数据封装成DatagramPacket丢出去。服务端import java.net.*; public class UdpServer { public static void main(String[] args) throws Exception { // 1. 创建DatagramSocket并绑定端口 DatagramSocket socket new DatagramSocket(9091); System.out.println(UDP服务端已启动监听端口9091); // 2. 准备接收数据的字节数组和DatagramPacket byte[] buffer new byte[1024]; DatagramPacket packet new DatagramPacket(buffer, buffer.length); // 3. 阻塞等待接收数据 socket.receive(packet); String msg new String(packet.getData(), 0, packet.getLength()); System.out.println(收到客户端消息 msg 来源 packet.getAddress() : packet.getPort()); // 4. 回复数据 byte[] response (已收到 msg).getBytes(); DatagramPacket reply new DatagramPacket(response, response.length, packet.getAddress(), packet.getPort()); socket.send(reply); socket.close(); } }客户端import java.net.*; public class UdpClient { public static void main(String[] args) throws Exception { DatagramSocket socket new DatagramSocket(); // 1. 把要发送的数据封装成包目标地址是127.0.0.1:9091 byte[] data 你好UDP服务端.getBytes(); DatagramPacket packet new DatagramPacket(data, data.length, InetAddress.getByName(127.0.0.1), 9091); socket.send(packet); // 2. 接收服务端回执 byte[] buffer new byte[1024]; DatagramPacket response new DatagramPacket(buffer, buffer.length); socket.receive(response); System.out.println(服务端回复 new String(response.getData(), 0, response.getLength())); socket.close(); } }有个细节要留意DatagramSocket构造器不传参数时操作系统会自动分配一个可用端口给客户端这是UDP和TCP客户端的共同特点。而UdpServer必须指定一个固定端口否则客户端不知道往哪发。在receive()之前如果客户端先发来数据数据会先进入操作系统为该端口分配的接收缓冲区receive()只是把缓冲区里的内容取出来。4.5 观察网络状态的小技巧代码跑通之后建议养成查看网络连接状态的习惯。Windows下用netstat -ano看全部连接-a显示所有连接和监听端口-n直接用IP和端口号显示不做域名反解速度更快-o显示占用进程的PID。拿到PID之后还能在任务管理器里反查是哪个程序。Linux下我常用netstat -tunlp和ss -tnlp。ss是netstat的升级替代品在连接数很多时性能更好。下面是我在一个TCP通信过程中实际抓到的连接状态示例Proto Local Address Foreign Address State PID TCP 127.0.0.1:9090 0.0.0.0:0 LISTENING 12345 TCP 127.0.0.1:9090 127.0.0.1:52343 ESTABLISHED 12345 TCP 127.0.0.1:52343 127.0.0.1:9090 ESTABLISHED 23456第一行是服务端在9090端口监听第二行和第三行分别是服务端和客户端视角下的同一对连接。看到这两行ESTABLISHED说明TCP连接状态正常。如果客户端退出但服务端仍显示ESTABLISHED多半就是四次挥手没走完或者代码没释放连接。5. 常见网络异常与排查思路实录5.1 Connection refused端口都没在听Java代码里最常见的异常之一就是Connection refused (Connection refused)。这个报错的核心含义是你发起的连接请求被目标机器拒了。排查顺序我通常是这样第一确认目标IP和端口对不对。在命令行里用telnet 目标IP 端口试一下如果提示连接失败基本可以确定是网络层面到不了。第二确认目标端口是否有进程在监听。Linux下执行ss -tlnp | grep 端口号Windows下netstat -ano | findstr 端口号。如果没有输出说明服务没启动或者崩了。我遇到过一种情况很气人用systemctl start启动了服务结果进程秒退日志又没输出这时ss一查端口根本没监听。第三考虑防火墙和安全组。本机能连、外部不能连优先查防火墙。Linux的iptables -L、firewall-cmd --list-all云服务器还要看安全组规则有没有放行对应端口。刚部署Java应用时踩过不少次这个坑。这个异常还有一个特殊情况连接有时通有时不通。如果出现在高并发场景下要考虑是不是连接数超过系统限制或者TIME_WAIT太多导致端口耗尽。5.2 端口被占用改了端口还是不行端口占用是启动Java服务时的经典报错Tomcat也好SpringBoot也好都容易出这个问题。报错长这样Web server failed to start. Port 8080 was already in use.解决办法第一条是找出谁占了端口。Windows下用netstat -ano | findstr 8080最后一列是PID然后在任务管理器里按PID找到进程。Linux下用lsof -i:8080或者ss -tlnp | grep 8080。确认无误后要么停掉那个进程要么改你Java应用的端口。改SpringBoot端口最简单的方式是在application.properties里加一行server.port8081或者启动时用参数指定java -jar app.jar --server.port8081这里再多说两句如果端口明明被占用了但你netstat找不到它那大概率是权限问题。Linux下1024以下端口只有root能监听普通用户启动时会报权限不足。另外server.socket在某些框架里还有独立的配置项别只改了一个地方结果另一个组件还在监听旧端口。5.3 TIME_WAIT与CLOSE_WAIT过多生产环境排查网络问题时TIME_WAIT和CLOSE_WAIT是两座绕不开的大山。TIME_WAIT出现在主动关闭连接的这一侧。HTTP短连接场景下服务端主动关闭连接后每个连接都会进入TIME_WAIT持续2MSLLinux上通常约60秒。如果并发量很大TIME_WAIT连接可能堆积很多占用大量本地端口导致新连接因为端口不够而失败。可以在Linux上调整内核参数# 允许TIME_WAIT状态下的socket被快速回收和重用 sysctl -w net.ipv4.tcp_tw_reuse1 sysctl -w net.ipv4.tcp_timestamps1注意tcp_tw_reuse发挥作用的前提是客户端主动关闭连接并且双方都开启了时间戳选项。生产环境调整内核参数前一定要先看清楚当前的会话状态由哪一方发起关闭否则改了无效。CLOSE_WAIT过多则是另一种味道它多半意味着代码里有连接没关。CLOSE_WAIT是四次挥手的中间状态被动关闭方收到FIN并回复ACK后进入CLOSE_WAIT。正常情况下程序处理完剩余数据后应该调用close()或shutdown()发送自己的FIN然后进入LAST_ACK。但如果代码里忘了关闭连接就会一直卡在CLOSE_WAIT。早期做过一个服务间的HTTP调用工具用的是HttpURLConnection响应读完之后没有disconnect()线上CLOSE_WAIT堆积到几千把服务拖垮了。后来在finally块里确保连接关闭问题立刻消失。排查这类问题lsof -p 进程ID | grep TCP可以看到进程所有TCP连接状态按CLOSE_WAIT数量排序就能定位。5.4 网络超时与DNS解析问题超时是比Connection refused更让人头疼的问题因为它的原因太多了。常见的有几类目标服务负载过高导致响应慢网络链路拥塞丢包严重导致发送方一直等确认防火墙丢弃数据包注意丢弃不回复所以表现为超时而不是拒绝DNS解析慢导致应用迟迟无法发起真实请求。排查时我习惯分三步走。第一步先ping目标IP观察延迟和丢包率。延迟高丢包那链路大概率有问题。第二步用curl -v或者Postman直接调目标HTTP接口看耗时卡在哪个阶段——建立连接慢还是首字节返回慢这能区分是网络问题还是业务处理慢。第三步用nslookup或dig查看DNS解析耗时Java应用里InetAddress.getByName()在DNS异常时会表现成首包延迟特别大。有一次部署环境里某台机器访问外部服务经常超时最后发现是DNS服务器配置成了错误的地址解析每次都要等超时才回退到下一个DNS服务器白白多了好几秒。这种问题在代码层面基本无解只能从系统和网络配置入手。5.5 抓包用Wireshark或tcpdump看清网络真相前面几种排查方式都是在“外围侦察”如果还定位不了问题就该上抓包工具了。命令行下用tcpdump图形界面用Wireshark。在Linux服务器上抓一个TCP端口的包# 抓取发往本机9090端口的所有TCP数据包 sudo tcpdump -i any tcp port 9090 -n -vv抓完能直接看到SYN、SYN-ACK、ACK、FIN这些报文的交互序列。比如你怀疑三次握手没完成那就看客户端发SYN之后服务端有没有回SYN-ACK如果只有SYN没有后续多半是服务端根本没收到或者被防火墙丢了。Wireshark更适合在本地Windows/macOS上做抓包分析。启动抓包后在过滤器里输入tcp.port 9090就能只看到和9090端口相关的数据包。每一行的[SYN]、[SYN, ACK]、[ACK]标记非常直观新手也能很快上手。我至今记得第一次用Wireshark看到自己的Java程序的TCP握手包时之前对三次握手的那些抽象理解一下子全通了。6. 学完网络初识后JavaEE下一步该往哪走6.1 Servlet与HTTP从Socket到Web的桥梁网络初识并不是终点而是你深入学习JavaEE其他模块的跳板。下一步建议接触Servlet。当你理解了HTTP报文格式之后再去看Servlet会轻松很多。HttpServletRequest不过是Tomcat把HTTP请求报文解析成的一个Java对象HttpServletResponse则是你在Java代码里构造HTTP响应报文的工具。Servlet的生命周期、过滤器、监听器本质上都是建立在“请求-处理-响应”这条网络链路之上的。实践方式是先用Tomcat写一个最简单的Servlet设置doGet和doPost方法用浏览器或Postman发起请求观察控制台日志和返回数据。借助浏览器开发者工具里的“Network”面板你能看到每个请求完整的状态码、响应头、请求头再把它们和之前学的HTTP报文知识对照理解会比单纯背概念扎实得多。6.2 框架中的网络配置与调优等你开始用SpringBoot、SpringCloud这些框架时网络配置又会出现。比如HttpClient连接池的配置最大连接数、每个路由的最大连接数、空闲连接存活时间。再比如Feign客户端的超时时间连接超时connectTimeout和读取超时readTimeout如果设置不合理服务间调用就会频繁超时。这些配置看似是框架用法追根溯源都是在调整TCP连接、HTTP协议的行为。我在项目里被问过很多次的一个经典问题Read timed out到底是什么意思它就是客户端向服务端发送请求后服务端在设置的readTimeout内没有返回任何数据。而Connection timed out则是TCP连接根本没建立起来。这两个报错分别对应链路的不同阶段排查思路完全不同区分清楚能省去大量无效操作。6.3 接下来的学习建议以网络为线索串起整个技术栈如果你现在刚学完“网络初识”想继续往深走我建议按照这条线索来先熟悉TCP/IP协议栈的核心机制然后亲手跑一遍Socket和HTTP通信接着用Tomcat写Servlet体会从HTTP报文到Java对象的转化再上SpringBoot框架做接口开发最后在服务拆分和微服务架构里体会网络通信带来的新挑战。这个过程里网络知识会反复出现。做微服务服务发现依赖注册中心本质是各节点通过网络交换元数据做消息队列消息的生产与消费走的是TCP长连接做分布式事务反而需要谨慎设计网络异常场景因为分布式环境下网络分区、丢失、延迟都是常态。所以“JavaEE初阶:网络初识”这一篇先帮大家把地基补上后面再学其他东西就会顺手很多。最后再分享一个小经验与其去背各种网络参数不如自己动手把一个Java TCP客户端、服务端跑起来然后用netstat、tcpdump去观察连接状态和数据包变化。踩过几次坑、亲眼见过SYN和FIN的流转之后你会发现网络的那些“理论”突然变得特别具体以后排错也有了直觉。