在网络编程中,Socket 是实现进程间通信的基石,而线程池则是解决高并发场景的关键技术。本文将从 Socket 的基础概念入手,逐步讲解其工作原理、通信流程,并延伸到多线程与线程池的实践应用,帮你构建完整的网络编程知识体系。
1. 什么是 Socket
1.1 定义
Socket(套接字)是操作系统提供的网络通信接口,本质是一组编程 API,用于实现不同设备(或同一设备)上进程之间的网络数据传输。
1.2 Socket 的基本概念
Socket 通过IP 地址 + 端口号唯一标识网络中的一个进程:
- IP 地址:定位网络中的设备;
- 端口号:定位设备中的具体进程(取值范围 0~65535,其中 1024 以下为知名端口);
- 通信双方需建立 "Socket 连接",才能进行数据收发。
1.3 Socket 的工作原理
Socket 基于TCP/IP 协议栈实现通信,核心流程是 "三次握手建立连接→数据传输→四次挥手断开连接":
- 服务端创建 Socket 并绑定端口,进入 "监听" 状态;
- 客户端创建 Socket,向服务端发起 "连接请求";
- 服务端接收请求,与客户端建立双向通信通道;
- 双方通过该通道收发数据;
- 通信结束后,双方断开连接并释放资源。
1.4 Socket 的类型
常见的 Socket 类型有两种:
- TCP Socket(面向连接):基于 TCP 协议,提供可靠、有序、面向字节流的传输(如 HTTP、FTP);特点:需要建立连接,数据无丢失 / 重复,适合传输重要数据。
- UDP Socket(无连接):基于 UDP 协议,提供不可靠、无序列、面向报文的传输(如 DNS、直播);特点:无需建立连接,传输速度快,适合对实时性要求高的场景。
1.5 在 OSI 七层网络模型的数据传输
1.5.1 层级与功能

OSI 七层模型从下到上为:物理层→数据链路层→网络层→传输层→会话层→表示层→应用层。Socket 主要工作在传输层(对应 TCP/UDP),同时关联应用层(Socket API 由应用层调用)。
1.5.2 核心作用
Socket 是传输层与应用层的 "桥梁":
- 对应用层:隐藏了 TCP/UDP 的底层细节,提供简单的 "收发数据" 接口;
- 对传输层:将应用层数据封装为 TCP/UDP 报文(或解析报文为应用层数据)。
2. 如何创建 Socket 实现客户端和服务器通信
Socket 通信流程
以TCP Socket为例,客户端与服务端的通信步骤如下:
服务端流程:
- 创建 Socket:调用socket()函数,指定协议类型(如 TCP);
- 绑定端口:调用bind()函数,将 Socket 与 "IP + 端口" 绑定;
- 监听连接:调用listen()函数,进入监听状态;
- 接收连接:调用accept()函数,阻塞等待客户端连接,返回新的 Socket(用于与该客户端通信);
- 收发数据:通过新 Socket 的recv()/send()函数与客户端交互;
- 关闭连接:通信结束后,调用close()关闭 Socket。
java
package com.socket;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
*服务器
*/
public class Server {
// 服务器端键监听的端口
public static final Integer SERVER_PORT = 4477;
public static void main(String[] args) throws Exception{
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
System.out.println("服务器启动成功,等待客户端连接...");
while (true){
// 监听客户端连接
Socket socket = serverSocket.accept();// 阻塞监听,直到有客户端连接
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String msg = br.readLine();
System.out.println("收到客户端信息:"+msg);
}
}
}
客户端流程:
- 创建 Socket:调用socket()函数;
- 发起连接:调用connect()函数,向服务端 "IP + 端口" 发起连接请求;
- 收发数据:通过send()/recv()与服务端交互;
- 关闭连接:调用close()关闭 Socket。
java
package com.socket;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/*
*客户端
*/
public class Click {
public static void main(String[] args) throws IOException {
//http://localhost:4477/
Socket socket = new Socket("127.0.0.1", 4477);
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("你好服务器,我是客户端");
printWriter.flush();
}
}
3. 引入子线程
在基础 Socket 通信中,服务端的accept()和recv()都是阻塞操作------ 同一时间只能处理一个客户端请求,无法应对高并发场景。
java
public class Server {
// 服务器端键监听的端口
public static final Integer SERVER_PORT = 4477;
public static void main(String[] args) throws Exception{
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
System.out.println("服务器启动成功,等待客户端连接...");
//创建线程池
HandleSocketServerPool handleSocketServerPool =
new HandleSocketServerPool(5,10,10,120, TimeUnit.SECONDS);
while (true){
// 监听客户端连接
Socket socket = serverSocket.accept();// 阻塞监听,直到有客户端连接
Runnable task = new ServerThreadReader(socket);//将socket对象封装成任务
handleSocketServerPool.execute(task);//提交任务,线程池会自动分配线程处理任务
}
}
3.1 解决方法:
为每个客户端连接单独开启一个子线程,让主线程继续监听新连接,子线程负责与对应客户端的通信。
3.2 开启子线程的方式有没有不好的地方?
有明显缺点:
- 资源消耗大:每个线程会占用内存(如 Java 中线程栈默认 1MB),高并发下会耗尽系统资源;
- 线程创建 / 销毁开销高:频繁创建、销毁线程会浪费 CPU 时间;
- 难以管理:大量线程会增加调度、同步的复杂度,易出现线程安全问题。
4. 线程池
为解决 "线程频繁创建 / 销毁" 的问题,线程池应运而生 ------ 预先创建一批线程,复用线程处理多个任务,降低资源开销。
4.1 概括
线程池是管理线程的容器:
- 预先初始化固定数量的线程;
- 接收 "任务" 并分配给空闲线程执行;
- 任务执行完成后,线程不会销毁,而是回到线程池等待下一个任务。
4.2 线程池里面的类容流程
以 Java 的ThreadPoolExecutor为例,核心流程是:
- 提交任务到线程池;
- 若当前线程数 < 核心线程数,创建核心线程执行任务;
- 若核心线程已满,将任务加入任务队列;
- 若队列已满,且当前线程数 < 最大线程数,创建非核心线程执行任务;
- 若线程数已达最大值,触发拒绝策略(如抛出异常、丢弃任务);
- 任务执行完成后,线程回到线程池;若线程空闲时间超过 "存活时间",且当前线程数 > 核心线程数,则销毁该线程。
4.3 工作原理图


4.5 定义线程池
java
public class HandleSocketServerPool {
//创建一个线程池成员变量,用于存储线程对象
private ExecutorService executorService;//线程池对象,jdk提供的线程池对象
/*
*创建线程池
* @param corePoolSize 核心线程数
* @param maxThreadNum 最大线程数
* @param queueSize 队列大小
* @param keepAliveTime 线程空闲时间
* @param unit 时间单位
*/
public HandleSocketServerPool(Integer corePoolSize, int maxThreadNum, int queueSize, Integer keepAliveTime, TimeUnit unit){
executorService = new ThreadPoolExecutor(corePoolSize, maxThreadNum, keepAliveTime, unit, new ArrayBlockingQueue<Runnable>(queueSize));
}
/**
* 提交任务
*/
public void execute(Runnable task){
executorService.execute(task);
}
}
4.6 使用
将 "处理客户端" 的逻辑封装为任务,提交给线程池执行:
java
public class ServerThreadReader extends Thread{
private Socket socket;
public ServerThreadReader(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//处理数据
try {
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String msg = br.readLine();
System.out.println("收到客户端信息:"+msg);
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("你好客户端,我是服务器");
printWriter.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}


