一、前言
在 Java 网络编程领域,Socket 是实现客户端与服务端跨设备通信的核心技术,而在高并发场景下,传统 Socket 通信的性能瓶颈问题突出。本文将从 Socket 基础概念切入,手把手教你实现基础 Socket 通信,再通过线程池技术完成高并发场景下的通信优化,助力开发者夯实网络编程基础。
二、Socket 基础认知
2.1 网络分层与 Socket 的定位
计算机网络遵循七层分层模型,各层职责明确:
- 应用层:由应用程序负责,产生数据并提供 HTTP、DNS 等传输协议
- 表示层:对数据进行加密、压缩等加工处理
- 会话层:负责建立和管理通信连接
- 传输层及以下:由操作系统和物理硬件(如网卡)负责,完成数据的底层传输
Socket(套接字)是处于应用层与传输层之间 的通信抽象,它通过IP 地址 + 端口号的组合唯一标识网络中的通信实体,实现应用程序间的数据交互。例如:
- WX 服务绑定端口 8899
- QQ 服务绑定端口 1122
- Tomcat 服务器默认绑定 8080 端口,对应访问地址
http://localhost:8080/xxx
2.2 Socket 的核心作用
Socket 本质是通信端点的抽象,是应用程序与网络之间的桥梁,能够让不同设备上的应用程序突破硬件和系统限制,实现双向数据传输。
三、Java 实现基础 Socket 通信
3.1 客户端代码
客户端通过创建 Socket 对象连接指定服务端,借助输出流向服务端发送消息:
java
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/**
* Socket通信客户端
*/
public class Client {
public static void main(String[] args) throws IOException {
// 连接本地4477端口的服务端
Socket socket = new Socket("127.0.0.1", 4477);
// 获取输出流,用于发送消息
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
// 发送消息给服务端
printWriter.println("你好服务器,我是客户端");
// 刷新缓冲区,确保消息成功发送
printWriter.flush();
}
}
3.2 服务端代码
服务端通过 ServerSocket 监听指定端口,接收客户端连接后读取客户端发送的消息:
java
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Socket通信服务端
*/
public class Server {
// 服务端监听的端口号
public static final Integer SERVER_PORT = 4477;
public static void main(String[] args) throws Exception {
// 创建ServerSocket,绑定4477端口
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
System.out.println("服务器启动成功,等待客户端连接...");
// 循环监听客户端连接
while (true) {
// 阻塞等待客户端连接
Socket socket = serverSocket.accept();
// 获取输入流,读取客户端消息
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String msg = bufferedReader.readLine();
System.out.println("收到客户端消息:" + msg);
}
}
}
四、基础 Socket 通信的并发瓶颈
上述基础通信模型采用单线程阻塞的处理方式,存在明显的并发缺陷:
- 每一个客户端连接都需要占用一个独立线程处理,线程开销呈 1:1 增长
- 当客户端访问量激增时,会创建大量线程,引发栈溢出 、线程创建失败等问题
- 最终导致服务进程宕机或僵死,无法正常对外提供服务
五、线程池优化 BIO 通信(伪异步 I/O)
为解决高并发瓶颈,可采用伪异步 I/O框架,通过线程池 + 任务队列的组合实现资源可控管理,保障服务稳定性。
5.1 线程池工作原理
Java 线程池由线程集合 (核心线程 + 非核心线程)和阻塞队列构成,核心工作流程如下:
- 提交任务后,先判断核心线程池是否已满,未满则创建核心线程执行任务
- 若核心线程池已满,判断等待队列是否已满,未满则将任务加入队列等待
- 若等待队列已满,判断线程池是否达到最大线程数,未达到则创建非核心线程执行任务
- 若线程池已达最大线程数,则执行拒绝策略处理无法执行的任务
5.2 ThreadPoolExecutor 核心参数
Java 通过ThreadPoolExecutor实现线程池,其构造方法参数如下:
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
各参数含义:
corePoolSize:核心线程数,线程池常驻的线程数量maximumPoolSize:最大线程数,仅队列满时生效,为线程池允许创建的最大线程数keepAliveTime:非核心线程的空闲存活时间,超时则销毁unit:keepAliveTime对应的时间单位workQueue:阻塞队列,用于存放等待执行的任务handler:拒绝策略,处理队列满且线程数达上限时的新任务
5.3 线程池拒绝策略
ThreadPoolExecutor 提供 4 种默认拒绝策略:
- AbortPolicy :丢弃任务并抛出
RejectedExecutionException异常(默认策略) - DiscardPolicy:丢弃任务,不抛出异常
- DiscardOldestPolicy:丢弃队列最前端任务,重新尝试执行新任务
- CallerRunsPolicy:由调用线程直接处理任务,无数据丢失且能减缓任务提交速度
5.4 线程池优化 Socket 服务端(伪异步 I/O 实现)
5.4.1 定义任务类
将客户端 Socket 连接封装为 Runnable 任务:
java
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* 处理客户端Socket连接的任务类
*/
public class SocketTask implements Runnable {
private Socket socket;
public SocketTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
String msg = bufferedReader.readLine();
System.out.println("收到客户端消息:" + msg);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
5.4.2 优化后的服务端代码
java
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
* 线程池优化后的Socket服务端
*/
public class ThreadPoolServer {
public static final Integer SERVER_PORT = 4477;
// 定义线程池
private static final ThreadPoolExecutor THREAD_POOL = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60, // 非核心线程空闲存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 阻塞队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
System.out.println("线程池优化后的服务器启动成功,等待客户端连接...");
while (true) {
Socket socket = serverSocket.accept();
// 将客户端连接封装为任务,提交到线程池处理
THREAD_POOL.execute(new SocketTask(socket));
}
}
}
六、总结
- Socket 是应用层与传输层的通信抽象,依托 IP + 端口实现端到端数据传输
- 传统 Socket BIO 通信为单线程阻塞模式,无法应对高并发场景,易引发服务宕机
- 采用线程池实现伪异步 I/O,可通过核心线程、阻塞队列、最大线程数的协同管控,实现资源合理分配,有效解决高并发瓶颈
- 不同线程池拒绝策略适用于不同业务场景,需结合实际需求选择