Socket学习

在网络编程中,Socket 是实现进程间通信的基石,而线程池则是解决高并发场景的关键技术。本文将从 Socket 的基础概念入手,逐步讲解其工作原理、通信流程,并延伸到多线程与线程池的实践应用,帮你构建完整的网络编程知识体系。

1. 什么是 Socket

1.1 定义

Socket(套接字)是操作系统提供的网络通信接口,本质是一组编程 API,用于实现不同设备(或同一设备)上进程之间的网络数据传输。

1.2 Socket 的基本概念

Socket 通过IP 地址 + 端口号唯一标识网络中的一个进程:

  • IP 地址:定位网络中的设备;
  • 端口号:定位设备中的具体进程(取值范围 0~65535,其中 1024 以下为知名端口);
  • 通信双方需建立 "Socket 连接",才能进行数据收发。

1.3 Socket 的工作原理

Socket 基于TCP/IP 协议栈实现通信,核心流程是 "三次握手建立连接→数据传输→四次挥手断开连接":

  1. 服务端创建 Socket 并绑定端口,进入 "监听" 状态;
  2. 客户端创建 Socket,向服务端发起 "连接请求";
  3. 服务端接收请求,与客户端建立双向通信通道;
  4. 双方通过该通道收发数据;
  5. 通信结束后,双方断开连接并释放资源。

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为例,客户端与服务端的通信步骤如下:

服务端流程:
  1. 创建 Socket:调用socket()函数,指定协议类型(如 TCP);
  2. 绑定端口:调用bind()函数,将 Socket 与 "IP + 端口" 绑定;
  3. 监听连接:调用listen()函数,进入监听状态;
  4. 接收连接:调用accept()函数,阻塞等待客户端连接,返回新的 Socket(用于与该客户端通信);
  5. 收发数据:通过新 Socket 的recv()/send()函数与客户端交互;
  6. 关闭连接:通信结束后,调用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);
        }
    }
}
客户端流程:
  1. 创建 Socket:调用socket()函数;
  2. 发起连接:调用connect()函数,向服务端 "IP + 端口" 发起连接请求;
  3. 收发数据:通过send()/recv()与服务端交互;
  4. 关闭连接:调用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为例,核心流程是:

  1. 提交任务到线程池;
  2. 若当前线程数 < 核心线程数,创建核心线程执行任务;
  3. 若核心线程已满,将任务加入任务队列
  4. 若队列已满,且当前线程数 < 最大线程数,创建非核心线程执行任务;
  5. 若线程数已达最大值,触发拒绝策略(如抛出异常、丢弃任务);
  6. 任务执行完成后,线程回到线程池;若线程空闲时间超过 "存活时间",且当前线程数 > 核心线程数,则销毁该线程。

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();
        }
    }
}
相关推荐
我真会写代码6 小时前
从入门到精通:Java Socket 网络编程实战(含线程池优化)
java·linux·服务器·socket·tcp/ip协议
w1wi7 小时前
【环境部署】MacOS安装Tomcat
java·macos·tomcat
7澄18 小时前
Java Socket 网络编程实战:从基础通信到线程池优化
java·服务器·网络·网络编程·socket·多线程·客户端
杀死那个蝈坦1 天前
Caffeine
java·jvm·spring cloud·tomcat
q***16081 天前
解决 IntelliJ IDEA 中 Tomcat 日志乱码问题的详细指南
java·tomcat·intellij-idea
世界尽头与你1 天前
CVE-2020-1938_ Apache Tomcat AJP 文件读取与包含漏洞
java·网络安全·渗透测试·tomcat·apache
古城小栈1 天前
SpringBoot Web容器选型指南:Tomcat与Undertow技术对比及迁移实践
spring boot·后端·tomcat
Super小白&2 天前
C++ 高可用线程池实现:核心 / 非核心线程动态扩缩容 + 任务超时监控
c++·线程池