Java Socket 编程实战:实现双向通信、多线程与心跳机制

一、什么是Socket?

Socket(套接字)是计算机网络中用于进程间通信的一种机制,它充当应用层与传输层之间的接口,使得两个不同主机上的程序能够进行数据交互。

本质与作用

本质上,Socket 封装了 TCP/IP 协议族的复杂细节,提供了统一的编程接口,使开发者可以像操作文件一样,轻松实现网络通信(读取、写入)。

核心功能作用

建立连接: 在客户端和服务器之间建立可靠的网络通信通道。

数据传输: 支持传输任意格式的数据,如文本、音视频、文件等。

双向通信: 支持全双工通信,即可读可写,数据可同时双向传输。

类型

流式 Socket(SOCK_STREAM): 基于 TCP 协议,面向连接,传输可靠,适用于需要稳定数据传输的场景,如文件传输。

数据报 Socket(SOCK_DGRAM): 基于 UDP 协议,无连接,效率高但不保证顺序和可靠性,适合短消息或实时性要求高的场景。

二、TCP Socket 通信流程

客户端服务端
创建Socket创建ServerSocket
连接服务器等待并接受连接
获取输入输出流获取输入输出流
发送/接收数据发送/接收数据
关闭连接关闭连接

三、示例代码

为了实现客户端与服务端间的数据结构化传输(如区分消息、心跳),引入 FastJSON:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.57</version>
</dependency>

FastJSON 是阿里巴巴开源的高性能 JSON 处理框架,支持快速地在 Java 对象与 JSON 字符串之间进行转换。


服务器端代码 Server.java

/**
 * 服务端主类
 * 实现逻辑:
 * 1. 启动 ServerSocket 监听端口
 * 2. 接收客户端连接请求
 * 3. 为每个连接创建监听线程和发送线程
 */
public class Server {
    public static void main(String[] args) {
        System.out.println("服务器启动成功,监听端口8888");

       try(ServerSocket serverSocket = new ServerSocket(8888)){
           while (true) {
               //接收客户端连接请求
               Socket socket = serverSocket.accept();
               System.out.println("客户端已连接:" +socket.getRemoteSocketAddress());

               //创建监听线程和发送线程
               new Thread(new ServerListen(socket)).start();
               new Thread(new ServerSend(socket)).start();

           }
       } catch (Exception e) {
           e.printStackTrace();
       }
    }
}

/**
 * 服务端监听线程
 * 作用:持续读取客户端发送的数据并输出
 */
class ServerListen implements Runnable{
    private Socket socket;

    public ServerListen(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try(ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream())){
            while (true) {
                //接收数据
                Object msg = objectInputStream.readObject();
                System.out.println("接收到客户端信息:" + msg);
            }
        } catch (Exception e) {
            System.out.println("客户端已断开:" + socket.getRemoteSocketAddress());
        }
    }
}

/**
 * 服务端发送线程
 * 作用:持续向客户端发送数据
 */

class ServerSend implements Runnable{
    private Socket socket;
    public ServerSend(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try(
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                Scanner scanner = new Scanner(System.in);
                ) {
            while(true){
                System.out.println("服务器 请输入:");
                String input = scanner.nextLine(); //控制台输入内容

                //构建JSON消息
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("type","message");
                jsonObject.put("content", input);

                objectOutputStream.writeObject(jsonObject); //发送数据
                objectOutputStream.flush();
            }
        } catch (Exception e) {
            System.out.println("发送线程异常:" + socket.getRemoteSocketAddress());
        }
    }
}

客户端代码 Client.java

/**
 * 客户端主类
 * 实现逻辑:
 * 1. 启动时尝试连接服务器
 * 2. 创建监听线程、发送线程、心跳线程
 * 3. 若连接失败则每5秒重试
 */
public class Client {
    //客户端与服务端连接的 Socket
    private static Socket socket;

    //连接状态标志
    public static boolean socket_status = false;
    public static void main(String[] args) {
        //尝试连接服务器,失败则循环重连
        while (!socket_status) {
            connect();
            try{
                Thread.sleep(5000); //等待5秒后重试
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 建立客户端 Socket 连接,初始化输入输出流和线程
     */
    private static void connect() {
        try{
            //创建socket 并连接服务器
            socket = new Socket("127.0.0.1", 8888);
            socket_status = true;

            //创建输入输出流,用于对象传输
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());

            //创建监听线程,发送线程,心跳线程
            new Thread(new ClientListen(socket, objectInputStream)).start();
            new Thread(new ClientSend(socket, objectOutputStream)).start();
            new Thread(new ClientHeart(socket, objectOutputStream)).start();
        }catch (Exception e){
            System.out.println("连接失败,等待重试...");
            socket_status = false;
        }
    }

    /**
     * 当连接断开时调用,持续尝试重新连接
     */
    public static void reconnect() {
        while(!socket_status){
            System.out.println("正在尝试重新连接...");
            connect();
            try{
                Thread.sleep(5000); //等待5秒后重试
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}


/**
 * 客户端监听线程
 * 作用:持续接收服务端发送过来的数据并输出到控制台
 */
class ClientListen implements Runnable {
    private Socket socket;
    private ObjectInputStream objectInputStream;

    public ClientListen(Socket socket, ObjectInputStream objectInputStream){
        this.socket = socket;
        this.objectInputStream = objectInputStream;
    }

    @Override
    public void run() {
        try{
            while (true) {
                //接收服务端发送过来的数据
                Object response = objectInputStream.readObject(); //读取服务端消息
                System.out.println("服务端: "  + response);
            }
        }catch (Exception e) {
            System.out.println("服务端断开连接,监听停止");
        }
    }
}

/**
 * 客户端发送线程
 * 作用:从控制台读取用户输入,并通过 Socket 发送给服务端
 */
class ClientSend implements Runnable {
    private Socket socket;
    private ObjectOutputStream objectOutputStream;

    public ClientSend(Socket socket, ObjectOutputStream objectOutputStream){
        this.socket = socket;
        this.objectOutputStream = objectOutputStream;
    }

    @Override
    public void run() {
        try(Scanner scanner = new Scanner(System.in)){
            while (true) {
                System.out.println("客户端 请输入:");
                String input = scanner.nextLine(); //控制台输入内容

                // 使用 JSON 封装消息数据
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("type", "message");
                jsonObject.put("content", input);

                objectOutputStream.writeObject(jsonObject); //发送数据
                objectOutputStream.flush();
            }
        }catch (Exception e){
            System.out.println("发送线程异常,尝试重连");
            Client.socket_status = false;
        }
    }
}

/**
 * 客户端心跳线程
 * 作用:定时向服务器发送心跳包,防止连接超时断开
 */
class ClientHeart implements Runnable {
    private Socket socket;
    private ObjectOutputStream objectOutputStream;

    public ClientHeart(Socket socket, ObjectOutputStream objectOutputStream){
        this.socket = socket;
        this.objectOutputStream = objectOutputStream;
    }
    @Override
    public void run() {
        try {
            while (true) {
                Thread.sleep(5000); //每5秒发送一次心跳包
                System.out.println("客户端 发送心跳");

                //使用 JSON 封装心跳包数据
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("type", "heart");
                jsonObject.put("content", "心跳");

                //发送心跳包
                objectOutputStream.writeObject(jsonObject);
                objectOutputStream.flush();
            }
        }catch (Exception e) {
            System.out.println("心跳线程异常,尝试重连...");
            try {
                socket.close(); //关闭socket
            }catch (Exception ex) {
                ex.printStackTrace();
            }
            Client.socket_status = false;
            Client.reconnect(); //重连
        }
    }
}

实现效果:


为什么要加入心跳机制?

在 Socket 长连接通信中,如果客户端与服务器之间长时间没有数据交互,连接可能会因为网络设备(如路由器、防火墙)的空闲超时策略而被关闭。为此,我们在客户端实现了一个心跳线程,每隔 5 秒向服务器发送一个 `type: heart` 的 JSON 消息。

服务端可通过接收心跳包判断客户端是否仍在线。一旦检测不到心跳,或者发送失败,即可断开连接并释放资源。同时,客户端也能在心跳失败时自动重连,确保通信的持续性与可靠性。


注意:

ServerSocket  和 Socket 的区别

在 Java 网络编程中,ServerSocket 和 Socket 是实现 TCP 通信的两个核心类。但是它们的作用不同:

类名作用范围作用描述
ServerSocket服务器端用于监听客户端的连接请求
Socket客户端、服务器端客户端用于发起连接,服务器端用于与某个客户端通信

ServerSocket (服务器端监听器)

专用于服务器,负责监听端口、接收客户端连接。调用 accept()返回一个 Socket

Socket (通信通道)

双方通信的实际通道,客户端用它发起连接,服务端用于和某一客户端通信。

示例代码

// 服务端
ServerSocket serverSocket = new ServerSocket(8888);
Socket clientSocket = serverSocket.accept(); // 接收连接

// 客户端
Socket socket = new Socket("127.0.0.1", 8888); // 发起连接

四、总结

本文介绍了 Socket 的基本原理、通信流程以及基于 Java 的 TCP 编程实践。通过示例代码,我们实现了客户端与服务端的双向通信,支持消息发送、心跳机制与断线重连。

Socket 是构建网络程序的基础工具,理解它的工作方式,有助于我们开发出更加稳定可靠的通信系统。至此,Java Socket 实现双向通信、多线程与心跳机制就完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值