Java Socket 网络编程实战(含线程池优化)
目录
线程池核心参数(Java ThreadPoolExecutor)
1、什么是socket?
1.1 认识
在计算机网络的进程通信中,Socket(套接字)是当之无愧的核心技术 —— 它像一座封装了底层协议复杂性的桥梁,让开发者无需关注 TCP 三次握手、数据分片等细节,仅通过简单 API 就能实现跨设备的数据传输。小到即时通讯工具,大到分布式系统,Socket 都是底层通信的基石。
1.2 定义
Socket(套接字)是计算机网络中进行通信的一种基本技术,它允许不同计算机上的程序通过网络进行数据交换。Socket本质上是一个通信端点,为网络通信提供了编程接口。
1.3 Socket的基本概念
Socket可以理解为网络通信的"插座",它为应用程序提供了与网络协议栈交互的编程接口。在网络编程中有以下特性:
通信端点:每个Socket代表一个通信的端点,包含IP地址和端口号
数据传输通道:在建立连接后,Socket提供了双向的数据传输通道
协议抽象层:Socket抽象了底层网络协议的复杂性,为开发者提供统一接口
具体解释为:
- IP 地址:定位目标设备(如
127.0.0.1是本地设备); - 端口号:定位设备上的目标进程(如 4477 端口对应 Socket 服务器进程);
- 传输协议:定义数据传输规则(TCP 可靠传输,UDP 快速传输)。
形象类比:Socket 就像「快递配送系统」——IP 是收货地址,端口是具体的房间号,协议是配送规则(如顺丰次日达 / TCP 可靠,普通快递 / UDP 快速),三者结合才能让数据精准、有序地送达目标进程。
1.4 Socket的工作原理
Socket通信通常遵循客户端-服务器模型:
-
服务器端:
- 创建Socket并绑定到特定IP地址和端口
- 监听连接请求
- 接受客户端连接
- 进行数据交换
-
客户端:
- 创建Socket
- 连接到服务器指定的IP和端口
- 进行数据交换
Socket 通信需遵循固定流程,缺一不可:
- 服务器端:创建 Socket → 绑定端口(bind)→ 监听连接(listen)→ 接受连接(accept)→ 读写数据(read/write)→ 关闭连接(close);
- 客户端:创建 Socket → 连接服务器(connect)→ 读写数据(read/write)→ 关闭连接(close)。
关键区别:服务器端需「绑定端口 + 监听」,客户端直接「连接目标地址」,这也是「先启动服务器,再启动客户端」的核心原因。
1.5 Socket的类型
主要有两种类型的Socket:
1.流式Socket(SOCK_STREAM):
一种面向连接的可靠通信,使用TCP协议,保证数据按序到达且不丢失,常用于Web浏览、文件传输等场景
2.数据报Socket(SOCK_DGRAM):
一种无连接的不可靠通信,使用UDP协议,不保证数据到达顺序或是否到达,常用于视频流、在线游戏等实时性要求高的场景
1.6 关于TCP和UDP协议:
UDP协议:
-
基本特性:
- 无连接:通信前不需要建立连接,直接发送数据
- 不可靠:不保证数据包顺序和完整性,可能丢失或重复
- 简单高效:头部开销小(仅8字节),传输效率高
-
应用场景:
- 实时音视频传输(如Zoom、Skype)
- DNS查询
- 在线游戏
- SNMP网络管理
- DHCP动态主机配置
-
优缺点:
- 优点:延迟低、开销小、适合实时应用
- 缺点:不保证可靠性、可能丢包、无序到达
-
相关协议:
- 基于UDP的上层协议:RTP、QUIC、TFTP等
- 替代方案:当需要可靠性时可用TCP或QUIC协议
TCP协议:
-
基本特性:
- 面向连接:在数据传输前需要建立连接(三次握手),传输结束后需要释放连接(四次挥手)。
- 可靠性保证:使用确认应答机制(ACK)、超时重传机制、数据包排序、流量控制和拥塞控制。
- 全双工通信:允许双方同时发送和接收数据
- 基于字节流:将数据视为无结构的字节序列
-
应用场景:
- 数据库连接
- SSH远程登录
- SMTP/POP3/IMAP电子邮件
- FTP文件传输


上图为TCP的三次握手和四次挥手的图解。其中在四次挥手中,第三阶段的服务器处理完数据后回向客户端发送确定取消信息!
TCP与UDP的区别:
| 特性 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接 | 无连接 |
| 可靠性 | 可靠 | 不可靠 |
| 传输顺序 | 保证顺序 | 不保证顺序 |
| 流量控制 | 有 | 无 |
| 传输效率 | 相对较低 | 相对较高 |
| 头部大小 | 20-60字节 | 8字节 |
| 适用场景 | 可靠传输需求 | 实时性要求高 |
1.7 socket在OSI 七层模型的体现
这个图展示的是OSI 七层模型,它是网络通信的分层框架,各层功能对应图里的分类(应用程序、操作系统、物理硬件),具体解释如下:
| 层级 | 所属范畴 | 核心功能 | 与 Socket 的关系 |
|---|---|---|---|
| 应用层 | 应用程序当中 | 产生数据,提供 HTTP/DNS 等协议(如浏览器请求网页) | 调用 Socket 接口传递数据 |
| 表示层 | 应用程序当中 | 数据加密、压缩、格式转换(如 SSL 加密、ZIP 压缩) | 对 Socket 传输的数据预处理 |
| 会话层 | 应用程序当中 | 建立、管理、断开通信会话(如视频通话连接) | Socket 依赖会话层维护连接状态 |
| 传输层 | 操作系统 | 端到端可靠传输(TCP 有序重传、UDP 无连接快速传输) | Socket 直接封装传输层协议(TCP/UDP) |
| 网络层 | 操作系统 | 通过 IP 地址路由数据(如 IPv4 寻址) | Socket 无需关注,由操作系统处理 |
| 数据链路层 | 操作系统 | 局域网内设备通信(如 MAC 地址识别) | 底层依赖,Socket 无感知 |
| 物理层 | 物理硬件 | 电 / 光信号传输(如网线、光纤) | 硬件支撑,与 Socket 无关 |
核心结论:Socket 介于「应用层」与「传输层」之间,向上提供标准化 API(创建、绑定、监听等),向下封装 TCP/UDP 协议细节,是应用程序与网络传输的「衔接枢纽」。
1.8 底层解读:
从操作上来说,无非就是两端之间互相收发数据,对应的就是读数据和写数据,所以从编程的角度来看,可以考虑一个数据结构,而这个数据结构就是sock,由于接收端和发送端可能不止一个,因此引入ip和端口进行区分,ip用来指定是哪台电脑,端口用来指定是哪个进程。另外传输方法可以是可靠的TCP协议,也可以是不可靠的UDP协议,以及还需要支持ICMP协议的ping命令!虽然这些协议各不相同,但是有公共的部分可以提取出来,通过继承的方式复用功能,进而引出sock这个最基础的数据结构,用来维护这些协议都可能会用到的收发数据缓冲区!inet_sock是网络传输功能的sock,在sock的基础上加上了TTL,ip地址,端口这些跟网络传输相关的字段信息。unix domain socket是用于本机进程之间的通信,直接读写文件,不需要经过网络协议栈,而inet_connection_sock是指面向连接的sock,在inet_sock上加上面向连接的协议里相关字段,比如accept队列,数据包分片大小,握手失败,重试次数等,虽然目前所说的面向连接协议就是指TCP,但是从设计上来说Linux需要支持扩展其他面向连接的新协议---比如SCTP协议。tcp_sock就是正儿八经的TCP协议的专用的sock结构,在inet_connection_sock的基础上还加入了TCP特有的滚动窗口、拥塞避免等功能。现在了解了这套数据结构,每个数据结构实现自己职责范围之内的功能,然后再将它们跟硬件网卡对接,就实现了网络传输的功能!考虑到性能和安全,所以将它们放在操作系统内核里面。有句话叫做Linux里一切皆是文件,且读文件和写文件的思想与读数据和写数据一致,就将sock结构封装成文件就好了,在创建sock的同时也创建一个文件,文件有个句柄fd(可以理解为文件系统里面的身份证号码),通过它可以唯一确定是哪个sock。将fd句柄暴露给用户之后,用户操作这个句柄文件,系统就会将操作指向内核sock结构,有了sock fd句柄之后,我们将这部分功能抽象成一个个简单的API接口,让用户更方便的实现 特定的网络编程功能,以后其他人只需要调用这些API接口,就可以驱动我们写好的这一大堆复杂的数据结构去发送数据,这些API我们发现需要send、recv、bind、listen、connect...方法,到这里我们的内核就算设计完成了。所以说socket就是一个代码库或者接口层,它介于内核和应用程序之间,提供了一些高度封装过的接口,让我们使用内核网络的传输功能,所以我们平时写的应用程序的代码里,虽然用了socket实现了收发数据包的功能,但其实真正执行网络通讯功能的不是应用程序,而是Linux内核,相当于应用程序通过socket提供的接口将网络传输的这部分工作外包给了Linux内核。这个socket_fd是int类型的数字,现在回看中文翻译Socket(套接字)可以理解为一套用于连接的数字。
总结一下:在操作系统内核空间里面,实现网络传输功能的结构是sock,基于不同的协议和应用场景有socket会被泛化为各种类型的sock,它们结合硬件共同实现了网络传输功能,为了将这部分功能暴露给用户空间的应用程序使用,于是引入了socket层,同时将sock嵌入文件系统的框架里,sock就变成了一个特殊的文件,用户就可以在用户空间使用句柄,也就是socket fd来操作内核sock的网络传输能力。
那么是怎么具体实现网络传输功能的呢?
以TCP为例,将其分为两个阶段建立连接和数据传输,在客户端执行socket提供的connect方法,传入sock_fd和ip端口
connect(句柄fd,IP:port);
内核会通过sock_fd句柄找到对应的文件,再根据文件里面的信息,找到内核的sock结构,通过这个sock结构主动发起3次握手,至此就算连接好了。
接着就可以传输数据了,为了实现发送和接收数据的功能,sock结构体里面是有一个发送缓冲区和一个接收缓冲区,说清楚点就是一个链表,上面挂着一个个准备要发送/接收的数据。
当应用执行send方法发送数据时,同样也会通过sock_fd句柄找到对应的文件,根据文件里面的信息,找到内核的sock结构,再找到这个sock结构里面的发送缓冲区,将数据放到发送缓冲区,
send(句柄fd,消息);
然后结束流程,内核看心情选择时间将这份数据发送出去。接收数据流程也类似,当数据送到Linux内核后,数据不是立马给到应用程序的,而是先放到接收缓冲区中,数据静静躺着,等待应用程序什么时候执行recv方法来带走它。那么问题又来了,服务端listen的时候,那么多数据包发送到一个listen socket上,服务端是如何区分多个客户端的?因为客户端发来的会有源ip和端口以及目的ip和端口,这四个元素构成一个四元组,可以唯一标记一个客户端,服务端会创建一个新的sock,并用四元组生成一个hash key将它放进一个hash表里面。
下次再有消息进来的时候,通过消息自带的四元组生成hash_key,再到这个hash表里面重新取出对应的sock就好了,所以说服务端是通过四元组来区分多个客户端的。那么Linux是基于c语言实现的,c是怎么实现继承关系的呢?可以利用套娃结构体、魔法指针转换(container_of 宏)、函数指针当虚函数。

2、基础实现:客户端与服务器端单向通信
要实现 Socket 通信,需遵循 “先启动服务器,再连接客户端” 的原则,核心是通过输入流(InputStream)和输出流(OutputStream)传递数据。
2.1 服务器端代码实现
import java.io.*;
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 IOException {
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);
}
}
}
2.2 客户端代码实现
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://ip:端口号
Socket socket = new Socket("127.0.0.1",4477);
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("你好服务器,我是客户端");
printWriter.flush();//刷新
}
}
2.3 核心注意事项:要先启动服务器端
2.4 运行说明:
- 先启动
Server类,控制台输出 “服务器启动成功,等待客户端连接...” - 再启动
Click类,客户端发送消息后,服务器端会打印 “收到客户端消息:你好服务器,我是客户端” - 注意:基础版仅支持单向通信(客户端→服务器),且只能处理一个客户端连接,处理完后程序终止。
3、引入子线程
3.1 子线程优化背景
在上面的代码里面我们发现一个问题,在main线程里面是主线程,既要监听,又要处理。
假如有很多很多的客户端,有可能发送信息很快很快,这个时候main线程就没办法监听了,因为有些还没处理完,新的就来了,会导致数据的丢失,进而子线程用于处理。
基础版的核心缺陷是 “单线程瓶颈”—— 主线程既要监听客户端连接,又要处理数据读写,当多个客户端同时接入时,后续客户端会被阻塞,甚至导致数据丢失。
3.2 问题分析
- 主线程执行
accept()阻塞监听,接入一个客户端后,会进入数据读取流程 - 此时若有第二个客户端发起连接,需等待第一个客户端处理完成,才能被
accept()接收 - 高并发场景下,会出现 “连接超时”“消息丢失” 等问题
3.3 解决方案:子线程异步处理
让主线程仅负责 “监听连接”,每接入一个客户端,就创建一个子线程专门处理该客户端的数据读写,实现 “并发处理多客户端”。
3.4 服务器端子线程改造代码
import java.io.*;
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 IOException {
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
System.out.println("服务器启动成功,等待客户端连接。。。");
while(true){
Socket socket = serverSocket.accept();//阻塞监听 --- 等待客户端信息发送
//创建子线程
new Thread(new Runnable() {
@Override
public void run() {
try {
handler(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();//.start();进入就绪队列,等待cpu调度,人为不能控制
}
}
public static void handler(Socket socket) throws IOException {
InputStream inputStream = socket.getInputStream();//获取输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String msg = bufferedReader.readLine();//转化为字符串
System.out.println("收到客户端消息:"+msg);
}
}
3.5 基础版客户端代码:
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://ip:端口号
Socket socket = new Socket("127.0.0.1",4477);
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("你好服务器,我是客户端");
printWriter.flush();//刷新
}
}
3.6 支持自定义消息 + 双向交互的改造
实际应用中需要 “客户端→服务器”“服务器→客户端” 的双向通信,同时支持客户端自定义发送消息,优化客户端代码并增强服务器端响应逻辑:
服务器端:
import java.io.*;
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 IOException {
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
System.out.println("服务器启动成功,等待客户端连接。。。");
while(true){
Socket socket = serverSocket.accept();//阻塞监听 --- 等待客户端信息发送
//创建子线程
new Thread(new Runnable() {
@Override
public void run() {
try {
handler(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();//.start();进入就绪队列,等待cpu调度,人为不能控制
}
}
public static void handler(Socket socket) throws IOException {
InputStream inputStream = socket.getInputStream();//获取输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String msg = bufferedReader.readLine();//转化为字符串
System.out.println("收到客户端消息:"+msg);
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("你好客户端,我是服务器端,是你找我吧!");
printWriter.flush();//刷新
}
}
客户端:
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class Click {
public static void main(String[] args) throws IOException {
//http://ip:8080
Socket socket = new Socket("127.0.0.1",4477);
//通过键盘输入
Scanner scanner = new Scanner(System.in);
String sendMessage = scanner.nextLine();
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(sendMessage);
printWriter.flush();//刷新
InputStream inputStream = socket.getInputStream();//获取输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String msg = bufferedReader.readLine();//转化为字符串
System.out.println("收到服务器端消息:"+msg);
}
}
3.7 运行效果
- 启动服务器,控制台输出 “服务器启动成功,等待客户端连接...”
- 启动多个客户端,每个客户端输入自定义消息(如 “客户端 1 请求连接”)
- 服务器端会打印所有客户端的消息,并分别响应每个客户端
- 客户端能接收服务器的针对性响应,实现多客户端并发双向通信
4、线程池
4.1 子线程方案的局限性
但是开启子线程的问题比较大,会出现以下问题:
1.每次接收到客户端发来的数据,就会创建一个新的线程,线程的创建和销毁都会消耗计算机资源
2.客户端的访问量增多时,服务器和客户端的线程比是1:1,访问量多,则线程增加,线程多了可能会导致线程创建失败......,最终导致服务器宕机!!!

子线程处理输入和输出流。
4.2 解决方案:线程池技术
线程池是 “线程的容器”,核心思想是线程复用—— 提前创建一定数量的核心线程,客户端请求到来时直接复用线程,请求结束后线程不销毁,而是放回池中等待下一个任务,从而减少线程创建 / 销毁的开销。
线程池核心参数(Java ThreadPoolExecutor)
Java 提供 ThreadPoolExecutor 类实现线程池,核心参数决定了线程池的运行机制:
4.2.1 线程池:


对于java线程池ThreadPoolExecutor:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler
}
4.2.2 参数的含义:
corePoolSize:核心线程数。规定线程池有几个线程(worker)在运行。
maximumPoolSize:最大线程数。当workQueue满了,不能添加任务的时候,这个参数才会生效。规定线程池最多只能有多少个线程(work)在执行。
keepAliveTime:超出corePoolSize大小的那些线程的生存时间,这些线程如果长时间没有执行任务并且超过了keepAliveTime设定的时间,就会消亡。
unit:生产时间的单位。
workQueue:阻塞队列。存放任务的队列。
handler:当workQueue已经满了,并且线程池数已经达到maximumPoolSize,将执行拒绝策略。
4.2.3 拒绝策略选型(根据业务场景选择)
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(适用于核心业务,需感知任务丢失)DiscardPolicy:丢弃任务但不抛出异常(适用于非核心业务,允许任务丢失)DiscardOldestPolicy:丢弃队列最前面的任务,重新尝试提交当前任务(适用于任务时效性要求高的场景)CallerRunsPolicy:由提交任务的线程(如主线程)自己执行任务(适用于不想丢失任务,且并发量可控的场景)
调整后利用线程池的代码:
(1)创建线程池:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 线程池
*/
public class HandleSocketServletPool {
private ExecutorService executorService;//线程池对象 jdk提供的线程池对象
/**
* 创建线程池
* @param corePoolSize
* @param maxThreadNum
* @param queueSize
* @param keepAliveTime
* @param unit
*/
public HandleSocketServletPool(Integer corePoolSize, int maxThreadNum,int queueSize, Integer keepAliveTime, TimeUnit unit) {
executorService = new ThreadPoolExecutor(corePoolSize,maxThreadNum,keepAliveTime,unit,
new ArrayBlockingQueue<Runnable>(queueSize));
}
/**
* 任务提交的方法
* @param task
*/
public void execte(Runnable task){
executorService.execute(task);
}
}
(2)将socket对象封装成任务
import java.io.*;
import java.net.Socket;
/**
* 将socket对象封装成任务
*/
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 bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String msg = bufferedReader.readLine();//转化为字符串
System.out.println("收到客户端消息:"+msg);
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("你好客户端,我是服务器端,是你找我吧!");
printWriter.flush();//刷新
} catch (IOException e) {
e.printStackTrace();
}
}
}
(3)服务器
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
public class Server {
//服务器监听的端口
public static final Integer SERVER_PORT = 4477;
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
System.out.println("服务器启动成功,等待客户端连接。。。");
//定义线程池
HandleSocketServletPool socketServletPool = new HandleSocketServletPool(5, 10, 10, 120, TimeUnit.SECONDS);
while (true) {
Socket socket = serverSocket.accept();//阻塞监听
ServerThreadReader task = new ServerThreadReader(socket);//将socket对象封装成任务
socketServletPool.execte(task);//提交任务,让线程池执行
}
}
}
(4)客户端
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class Click {
public static void main(String[] args) throws IOException {
//http://ip:8080
Socket socket = new Socket("127.0.0.1",4477);
//通过键盘输入
Scanner scanner = new Scanner(System.in);
String sendMessage = scanner.nextLine();
OutputStream outputStream = socket.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(sendMessage);
printWriter.flush();//刷新
InputStream inputStream = socket.getInputStream();//获取输入流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String msg = bufferedReader.readLine();//转化为字符串
System.out.println("收到服务器端消息:"+msg);
}
}
5、工业级优化补充
要让代码达到工业级标准,还需补充以下细节优化,解决实际部署中的潜在问题:
5.1 资源释放优化
- 使用
try-with-resources语法自动关闭流和 Socket,避免资源泄漏(已在上述代码中实现) - 服务器端需处理客户端异常断开场景(如客户端强制关闭时,
readLine()会返回 null,需及时关闭 Socket)
5.2 超时设置
- 为
socket.setSoTimeout(int timeout)设置读取超时时间(如 30 秒),避免线程因客户端长时间不发送数据而阻塞 - 示例:在
SocketHandlerTask的run()方法中添加socket.setSoTimeout(30000);
5.3 日志替代打印
- 用
SLF4J + Logback替代System.out.println,支持日志分级(DEBUG/INFO/ERROR)、日志持久化,便于问题排查 - 示例:
logger.info("收到客户端[{}]消息:{}", socket.getInetAddress(), clientMsg);
5.4 端口占用处理
- 服务器端启动时若端口被占用,会抛出
BindException,需添加异常处理,提示用户更换端口或释放占用
try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT)) {
// 启动逻辑
} catch (BindException e) {
System.err.println("端口 " + SERVER_PORT + " 已被占用,请释放后重新启动");
System.exit(1); // 退出程序
}
5.5 配置化管理
- 将端口号、线程池参数(核心线程数、队列大小等)抽取到配置文件(如
application.properties),避免硬编码,便于动态调整 - 示例:通过
Properties类读取配置文件中的server.port=4477
6、总结与扩展
本文从 Socket 基础概念出发,逐步实现了 “单向通信→多客户端并发双向通信→线程池优化” 的完整链路,代码可直接用于实际开发,核心亮点:
- 层层递进:从基础到优化,每个阶段都解决明确的问题,符合学习和开发逻辑
- 代码完整:所有代码可运行,包含异常处理、资源释放等细节
- 工业级标准:线程池优化 + 细节补充,满足高并发、高可用需求
扩展方向
- 实现 TCP 粘包 / 拆包处理(适用于大数据传输场景,可通过 “消息长度 + 消息内容” 格式解决)
- 基于 Socket 实现文件传输功能(通过流读取文件字节,分段发送)
- 结合 NIO 实现非阻塞 Socket 通信,进一步提升并发性能(适用于十万级并发场景)
通过本文的学习,不仅能掌握 Socket 编程的核心技能,还能理解 “问题→解决方案→优化” 的开发思路,为后续分布式系统、即时通讯等高级应用打下基础。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)