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();
        }
    }
}
相关推荐
2301_322414280417 小时前
基于SSM框架的在线考试管理系统的设计与实现14750-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·redis·mysql·tomcat·maven·mybatis·ssm
毕业设计7033 天前
(免费领源码)SSM董家湾线上点餐系统04682- java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
java·redis·mysql·tomcat·maven·mybatis·ssm
张张123y3 天前
后端第一课:输入 URL 后发生了什么?我用手写 Socket 把全链路跑通了
python·websocket·ai·socket·url·后端开发
paopaokaka_luck4 天前
校园新闻推荐系统(DeepSeek新闻助手、个性化推荐算法、 Redis热词排行榜、ECharts运营分析、互动与实时通知、新闻运营闭环)
java·spring boot·mysql·spring·tomcat·echarts·intellij idea
雲烟4 天前
服务器迁移导致404错误
tomcat
土豆.exe4 天前
chunked 编码的行尾:Go、h11 与 Tomcat 各错在哪一处
开发语言·网络安全·golang·tomcat
duxingzhe03114 天前
我的一些书
java·数据库·tomcat·数据库开发·etl工程师
Mikko75 天前
Spring Boot 3.5.16 自带的 Tomcat 10.1.55 中了哪些 CVE?其中一条是修上一个漏洞时带进来的
java·spring boot·后端·安全·tomcat
似是燕归来5 天前
Rtthread OS 功能开发之 socket udp协议程序开发
网络·stm32·网络协议·udp·socket·rtthread
captain3766 天前
Spring Web MVC入门(1)
ide·spring·java-ee·tomcat