跳到主要内容Java 网络编程套接字入门:从数据传输到并发服务器 | 极客日志Javajava
Java 网络编程套接字入门:从数据传输到并发服务器
介绍 Java 网络编程基础,涵盖 Socket 概念、UDP 与 TCP 编程模型及代码实现。通过回显(Echo)示例演示 DatagramSocket 与 ServerSocket 用法,解析阻塞 IO、端口占用、应用层协议及长/短连接区别。最后结合线程池说明并发处理方案,为学习 HTTP、RPC 及 NIO 打下基础。
路由之心1K 浏览 Java 网络编程套接字入门:从数据传输到并发服务器
网络编程最核心的目标:让不同主机(或同一主机的不同进程)通过网络传输数据。你在浏览器看视频、刷图片、读文章,本质都是'客户端进程'向'服务端进程'请求网络资源,然后接收响应数据。
先明确请求/响应、客户端/服务端,再落到 UDP/TCP 两条路线,最后讲到端口占用、并发处理、长短连接这些工程级坑点。下面按一条顺滑的学习路径串起来。
1)网络编程到底在编什么:进程之间的数据传输
来看一个关键定义:网络编程就是网络上的主机,通过不同进程,以编程方式实现网络通信(网络数据传输)。只要是不同进程,哪怕在同一台机器上,通过网络协议栈收发数据,也算网络编程。
于是会出现三个高频'角色名词':
- 发送端 / 接收端:一次数据流向里,发送数据的一方叫发送端,接收数据的一方叫接收端(这俩是相对概念)。
- 请求 / 响应:获取网络资源通常要两次传输:先发请求,再回响应。
客户端 / 服务端:提供服务资源的一方是服务端,获取服务的一方是客户端。常见流程是:客户端请求 → 服务端处理业务 → 服务端响应 → 客户端展示结果。

2)Socket 套接字:网络通信的'基本操作单元'
来看 Socket 的定位:它是系统提供的一种网络通信技术,是基于 TCP/IP 的网络通信基本操作单元。基于 Socket 写出来的程序,就是网络编程。

按传输层协议,Socket 主要分三类:
- 流套接字(TCP)
- 有连接、可靠传输、面向字节流
- 有发送缓冲区也有接收缓冲区
- 数据'没有边界':可以多次发送、分多次接收(只要连接不断)
- 数据报套接字(UDP)
- 无连接、不可靠传输、面向数据报
- 有接收缓冲区、通常不强调发送缓冲区
- 数据'有边界':发 100 字节就要一次发完、一次收完
- 单个数据报大小受限(常见上限 64KB)
- 原始套接字(用于自定义传输层协议/读写内核未处理的 IP 数据,了解即可)
3)UDP 编程模型:一次一包,收发都靠 DatagramPacket
来看 UDP 的'脾气':它不建立连接,发送一块数据就必须整体发送,接收也必须整体接收。Java 里主要靠两个类:
DatagramSocket:UDP Socket,用来 send/receive 数据报
DatagramPacket:数据报本体(携带字节数组 + 目标/来源地址信息)


3.1 DatagramSocket 关键用法
new DatagramSocket():绑定本机随机端口(更常见于客户端)
new DatagramSocket(port):绑定本机指定端口(更常见于服务端)
receive(packet):阻塞等待接收
send(packet):发送(通常不阻塞等待)
close():关闭套接字
3.2 DatagramPacket 关键用法
- 接收包:
new DatagramPacket(byte[] buf, int length)
- 发送包:
new DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) 或指定 InetAddress + port
getAddress()/getPort()/getData():获取对端地址、端口和数据
4)来看 UDP 回显:最短路径理解'请求→处理→响应'
回显(Echo)是网络编程里的'Hello World':客户端发什么,服务端回什么。下面这两段就是完整可运行版本(带注释)。
4.1 UDP Echo Server(服务端)
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while (true) {
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
String response = process(request);
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());
socket.send(responsePacket);
System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(), responsePacket.getPort(), request, response);
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws SocketException, IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
4.2 UDP Echo Client(客户端)
package network;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
socket = new DatagramSocket();
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入要发送的内容!");
if (!scanner.hasNext()) break;
String request = scanner.next();
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp), serverPort);
socket.send(requestPacket);
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
client.start();
}
}
- UDP 服务端
receive() 会阻塞等待;
- UDP 必须在发送包里指定对端地址;
- '一次一包'的边界来自
DatagramPacket。
5)把 Echo 改造成'英译汉':核心就是改 process
来看一个非常实用的抽象:服务器主循环基本都一样,差异常常只在'如何处理 request 得到 response'。因此做英译汉字典服务时,核心就是把 process(request) 改成'查表并返回'。
(工程上常见做法是让 process 具备可扩展性:例如改成 protected,再用继承/组合注入不同处理逻辑。)
6)TCP 编程模型:先建立连接,再用字节流持续收发
来看 TCP 的关键区别:它是面向连接的。通信前要建立连接;建立后双方通过 InputStream/OutputStream 像读写文件一样收发数据。
ServerSocket:用来创建 TCP 服务端监听套接字
new ServerSocket(port):绑定端口
accept():阻塞等待客户端连接,返回 Socket
Socket:客户端 socket;或服务端 accept 后得到的连接 socket
new Socket(host, port):客户端发起连接
getInputStream()/getOutputStream():获取读写流
7)来看 TCP 回显:阻塞点、换行协议、flush 都在这里
7.1 TCP Echo Server(服务端:线程池并发版)
package network;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpEchoServer {
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("启动服务器");
ExecutorService executorService = Executors.newCachedThreadPool();
while (true) {
Socket clientSocket = serverSocket.accept();
executorService.submit(() -> {
processConnection(clientSocket);
});
}
}
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) {
Scanner scanner = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);
while (true) {
if (!scanner.hasNext()) {
break;
}
String request = scanner.nextLine();
String response = process(request);
writer.println(response);
writer.flush();
System.out.printf("[%s:%d] req:%s,resp:%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String process(String request) {
return "this server accept your request = " + request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
7.2 TCP Echo Client(客户端)
package network;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient(String serverIp, int serverPort) throws IOException {
socket = new Socket(serverIp, serverPort);
}
public void start() {
Scanner scanner = new Scanner(System.in);
try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {
Scanner scannerNet = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);
while (true) {
String request = scanner.next();
writer.println(request);
writer.flush();
String response = scannerNet.nextLine();
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
client.start();
}
}
accept() 是阻塞点:没有客户端连上来就会一直等。
- 用
println + flush 相当于定义了一个很简单的'应用层协议':一行一个消息,否则对端读取边界会很痛苦。
- 服务端要并发就必须'一个连接交给一个线程/任务',否则一个客户端卡住会拖死所有人。
8)Socket 编程注意事项:端口占用、目的地址、以及'别忘了协议'
8.1 目的 IP + 目的端口决定'你把数据发给谁'
一次数据传输里,目的 IP + 目的端口唯一标识了对端主机和对端进程。写错了就不是'收不到',而是'发给了别的地方'。
8.2 端口被占用:同一端口只能被一个进程绑定
如果进程 A 已经绑定端口,再让进程 B 绑定同一端口会报错(典型错误:Address already in use)。排查方式:
netstat -ano | findstr 端口号 查到 PID
- 在任务管理器按 PID 找到并结束进程,或换一个未被占用的端口
8.3 应用层协议不能忽略
就算底层用 TCP/UDP,应用层仍需要约定'数据怎么分隔、怎么解析、字段怎么定义'。否则双方读写会互相折磨。
9)长连接 vs 短连接:什么时候关连接决定系统形态
来看一个经典分叉:TCP 发数据前要先建连接,什么时候关闭连接决定你是短连接还是长连接。
- 短连接:一次请求 - 响应就关闭,只能收发一次
- 长连接:不关闭连接,双方可多次收发
- 短连接每次都要建连/断连,耗时更高;长连接首次建连后复用,效率更高
- 短连接通常是客户端主动发起;长连接场景里服务端也可能主动推送
- 短连接适合低频请求(比如普通网页浏览);长连接适合高频通信(聊天室、实时游戏等)
还有一个'扩展但很重要'的工程提醒:基于 BIO(同步阻塞 IO)的长连接会长期占用线程资源,并发高时成本非常昂贵;实际高并发长连接更常用 NIO(同步非阻塞 IO)来实现,性能能上一个量级。
10)怎么把代码跑起来:最短运行方式
- 先启动服务端:
UdpEchoServer 或 TcpEchoServer
- 再启动客户端:
UdpEchoClient 或 TcpEchoClient
- 在客户端控制台输入字符串,观察是否收到回显/响应
只要端口一致(示例里都是 9090)且未被占用,就能跑通。
把这些概念和代码吃透之后,你就拥有了写网络程序的'底盘能力':知道什么时候会阻塞、如何定义消息边界、怎么做并发、为什么端口会炸,以及长连接为什么不能傻用 BIO。剩下的就是在这个底盘上继续往上盖:HTTP、RPC、自定义协议、NIO/Netty——都只是更复杂、更工程化的版本而已。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online