Java Socket案例:从零实现基础通信(附完整代码)
目录导读
- Socket通信原理:一文讲透TCP/IP与Socket的关系
- 环境准备:JDK版本与开发工具推荐
- 服务端实现:创建ServerSocket监听客户端连接
- 客户端实现:通过Socket连接服务端并发送消息
- 双向通信案例:实现服务端与客户端的实时对话
- 常见问题与解答:连接失败、端口占用、中文乱码等
- 总结与扩展:Socket在物联网、即时通讯中的应用
Socket通信原理:一文讲透TCP/IP与Socket的关系
问:Socket到底是个什么东西?它与TCP/IP协议栈有什么关系?
答:Socket是应用层与传输层之间的抽象接口,你可以把它理解为“网络通信的管道端点”,当两个程序要通过网络通信时,每一端都需要创建一个Socket,然后通过这个Socket发送或接收数据,在Java中,java.net.Socket代表客户端Socket,java.net.ServerSocket代表服务端监听Socket。

TCP/IP协议栈分为应用层、传输层、网络层、数据链路层,Socket位于应用层与传输层之间,它封装了TCP的三次握手、数据分段、流量控制等复杂细节,开发者只需调用InputStream和OutputStream即可像操作本地文件一样进行网络读写。
环境准备:JDK版本与开发工具推荐
问:运行Socket程序需要什么环境?用IDE还是命令行?
答:
- JDK版本:Java 8及以上即可,推荐Java 11(LTS长期支持版)。
- 开发工具:IntelliJ IDEA或Eclipse,初学者可使用命令行编译运行:
javac Server.java java Server
- 注意事项:确保防火墙允许程序访问本地网络(通常本地回环地址127.0.0.1无限制)。
服务端实现:创建ServerSocket监听客户端连接
核心步骤:
- 绑定端口(例如8888)
- 调用
accept()方法阻塞等待客户端连接 - 获取输入流读取客户端消息
- 获取输出流回复客户端
完整代码(Server.java):
import java.io.*;
import java.net.*;
public class Server {
public static void main(String[] args) throws IOException {
// 1. 创建ServerSocket,监听端口8888
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端启动,等待客户端连接...");
// 2. accept()是阻塞方法,直到有客户端连接
Socket socket = serverSocket.accept();
System.out.println("客户端已连接:" + socket.getInetAddress());
// 3. 获取输入流,读取客户端数据
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String message = reader.readLine();
System.out.println("收到客户端消息:" + message);
// 4. 获取输出流,向客户端发送响应
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
writer.println("服务端已收到消息:" + message);
// 5. 关闭资源
reader.close();
writer.close();
socket.close();
serverSocket.close();
}
}
关键点说明:
accept()会一直阻塞,直到有客户端接入。PrintWriter的第二个参数true表示自动刷新缓冲区(每调用一次println就立即发送)。
客户端实现:通过Socket连接服务端并发送消息
问:客户端需要知道服务端的哪些信息?
答:IP地址(本地测试用127.0.0.1或localhost)和端口号(必须与服务端绑定的一致)。
完整代码(Client.java):
import java.io.*;
import java.net.*;
public class Client {
public static void main(String[] args) throws IOException {
// 1. 连接服务端(IP+端口)
Socket socket = new Socket("127.0.0.1", 8888);
System.out.println("已连接到服务端");
// 2. 获取输出流,向服务端发送数据
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
writer.println("Hello, Socket!");
// 3. 获取输入流,读取服务端响应
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String response = reader.readLine();
System.out.println("服务端回复:" + response);
// 4. 关闭资源
reader.close();
writer.close();
socket.close();
}
}
运行顺序:先运行Server.java,再运行Client.java。
预期输出:
服务端打印:收到客户端消息:Hello, Socket!
客户端打印:服务端回复:服务端已收到消息:Hello, Socket!
双向通信案例:实现服务端与客户端的实时对话
问:如何让服务端持续接收消息,而不是处理一次就退出?
答:使用while(true)循环,并为每个客户端连接创建独立线程。
优化版服务端(支持多客户端):
public class MultiThreadServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("多线程服务端启动...");
while (true) {
Socket socket = serverSocket.accept();
// 每个客户端分配一个线程处理
new Thread(new ClientHandler(socket)).start();
}
}
}
class ClientHandler implements Runnable {
private Socket socket;
public ClientHandler(Socket socket) { this.socket = socket; }
@Override
public void run() {
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
String msg;
while ((msg = reader.readLine()) != null) {
System.out.println("收到:" + msg);
writer.println("服务端回复:" + msg);
if ("bye".equalsIgnoreCase(msg)) break;
}
System.out.println("客户端断开");
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端增加循环发送:
// 在客户端main方法中
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
String input;
while ((input = console.readLine()) != null) {
writer.println(input);
String response = reader.readLine();
System.out.println(response);
if ("bye".equalsIgnoreCase(input)) break;
}
常见问题与解答
Q1:启动服务端时报错“Address already in use: bind”怎么办?
A:端口被占用,换一个端口(如9999),或使用netstat -ano | findstr 8888查看占用进程,同时确保服务端先启动。
Q2:客户端发送中文后服务端收到乱码?
A:统一编码,在创建InputStreamReader时指定字符集:
new InputStreamReader(socket.getInputStream(), "UTF-8");
客户端和服务端使用相同编码。
Q3:为什么客户端发送消息后服务端收不到?
A:最可能的原因是忘记调用flush()或关闭输出流,使用PrintWriter的自动刷新模式(第二个参数true)可解决,另一种可能是服务端readLine()要求消息必须换行结尾(\n)。
Q4:如何检测客户端是否断开?
A:调用socket.isConnected()不可靠,正确做法:从readLine()返回null时表示对方关闭了输出流,也可以发送心跳包(如每5秒发送一个特定字符串)。
总结与扩展:Socket在物联网、即时通讯中的应用
核心要点回顾:
- Socket是网络通信的基础,Java通过
Socket和ServerSocket类简化了TCP编程。 - 服务端需先绑定端口并调用
accept()等待连接;客户端通过IP和端口发起连接。 - 通信通过
InputStream/OutputStream实现,读写需注意字符编码和流关闭。 - 多线程处理可支持多个客户端同时连接。
进阶方向:
- NIO (Non-blocking IO):使用
ServerSocketChannel和Selector处理高并发。 - WebSocket:基于HTTP升级协议,适合网页实时聊天。
- 自定义协议:在Socket上层定义包头+包体的格式,解决粘包问题。
典型应用场景:
- 物联网设备数据采集(传感器定期发送数据到服务器)
- 局域网即时通讯软件(飞鸽传书、QQ早期版本)
- 分布式系统节点间心跳检测
如果你正在学习网络编程,建议先将本文基础案例亲手敲一遍并运行成功,再尝试增加文件传输、线程池、SSL加密等特性,掌握Socket原理后,后续学习Netty、MINA等高性能框架会事半功倍。