Java的Socket通信的断网重连的正确写法

2023-09-18 22:41:23

Socket通信的断网重连介绍

针对于已经建立通信的客户端与服务器,当客户端与服务器因为网络问题导致网络不通而断开连接了或者由于服务器端的服务被突然停掉,而客户端进行的一种尝试重新建立连接的操作;我采用的是Socket自带的往通道内写数据是否成功来判断当前连接是否断开,采用该方式的好处是简洁易懂,高效通用。

一般的采用ping命令,定时尝试重新建立新的连接的方式,都有一些不足之处,前者是没办法针对端口进行判断,效果不好;后者则是每次都建立新的连接,导致服务器端压力会较大,而且相对的代码逻辑也会更加复杂。而采用本文介绍的写数据的方式,即可简单判断连接是否断开;原理是当连接断开后,写数据时会报错,捕获该错误即可;底层原理则是Java的Socket底层代码,采用C语言或者C++编写,在那里肯定是去判断IP和端口是否可以访问到的逻辑;有需要的话,下篇文章可以查看源码,进一步熟悉Java的网络编程知识。

客户端与服务端源码

为了方便演示和理解,仅进行了服务端向客户端发送数据,客户端接收数据的单向通信方式;实际扩展也非常简单。可以由读者进行尝试实现,增加网络编程知识的熟练度。

服务端

package com;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

/**
 * @author BBYH
 */
public class MyServerSocket {
    public static final Integer PORT = 8080;

    private static Socket myServerSocket;
    private static OutputStream outputStream;

    public static void listen() {
        System.out.println("服务端监听端口" + PORT);

        new Thread(() -> {
            try (ServerSocket serverSocket = new ServerSocket(PORT)) {
                while (true) {
                    try {
                        myServerSocket = serverSocket.accept();
                        System.out.println("服务端接收到来自客户端的连接");
                        outputStream = myServerSocket.getOutputStream();

                        new Thread(() -> {
                            Scanner scanner = new Scanner(System.in);
                            while (true) {
                                try {
                                    System.out.println("向客户端发送消息:");
                                    String sendContent = scanner.next();
                                    outputStream.write(sendContent.getBytes(StandardCharsets.UTF_8));
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        }).start();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

    public static void main(String[] args) {
        listen();
    }
}

客户端

package com;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * @author BBYH
 * @description 通过socket的写数据,来判断当前的socket是否连通
 */
public class ClientSocket {
    public static final String IP = "127.0.0.1";
    public static final Integer PORT = 8080;

    private static Socket socket;
    private static InputStreamReader reader;
    private static OutputStream outputStream;

    public static void main(String[] args) {
        // 首次建立连接
        connect();
    }

    public static void connect() {
        System.out.println("向服务端端口" + PORT + "申请建立连接");
        try {
            socket = new Socket(IP, PORT);
            System.out.println("客户端与服务器端连接建立成功");
            reader = new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8);
            outputStream = socket.getOutputStream();

            // 开启读取线程
            openReadThread();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void openReadThread() {
        new Thread(() -> {
            // 开启断网重连线程
            new Thread(() -> {
                while (true) {
                    if (isDisContentWithServer()) {
                        while (true) {
                            try {
                                socket = new Socket(IP, PORT);
                                reader = new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8);
                                outputStream = socket.getOutputStream();
                                System.out.println(new Date() + ":重新建立连接成功");
                                break;
                            } catch (IOException e) {
                                System.out.println(new Date() + ":尝试重新建立连接失败");
                                try {
                                    Thread.sleep(3000);
                                } catch (InterruptedException ex) {
                                    throw new RuntimeException(ex);
                                }
                            }
                        }
                    }
                }
            }).start();

            char[] readBuf = new char[1024];
            while (true) {
                try {
                    if (reader.ready()) {
                        int read = reader.read(readBuf);
                        System.out.println("接收到来自服务端的消息:" + new String(readBuf, 0, read));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public static boolean isDisContentWithServer() {
        try {
            outputStream.write(("发送数据").getBytes(StandardCharsets.UTF_8));
            Thread.sleep(3000);
            return false;
        } catch (Exception e) {
            return true;
        }
    }
}

尽管在客户端开启断网重连线程中进行了两次while循环,不是很优雅,但仍然可以较为清晰的理解到代码的执行逻辑;即,每隔固定时间(3秒)进行是否连通的判断,当发现不连通的,则进行尝试重连(重新建立连接),该处的逻辑没有办法省略,由于客户端一般不长时间运行,消耗的资源虽然较多,但在程序运行结束后也自然就释放了。

演示截图

演示流程为:开启服务器 – 客户端建立连接 – 服务器发送一些数据 – 服务器断开连接 – 客户端尝试重连(自动进行) – 重新开启服务器 – 服务器再次发送数据

在该过程中,服务器在首次开启后的关闭,即代表了客户端的断网,实际情况中可以采用服务器,然后采用断开Wifi的方式代替这个关闭服务器,只要保证了客户端与服务器无法连通,即代表断网了,后面的重连则可以采用再次打开Wifi,重新连接上网络,连接则自动建立好

本地演示

开启服务器 – 客户端建立连接 – 服务器发送一些数据
在这里插入图片描述

服务器断开连接 – 客户端尝试重连(自动进行)
在这里插入图片描述

重新开启服务器 – 服务器再次发送数据
在这里插入图片描述

服务器演示

采用的是我申请的阿里云服务器,流程与上述文字说明一致,采用断开Wifi的形式来代替服务器关闭的效果;服务器关闭同样被我测试过,与本地一样,可以进行重连。由于我采用Xshell进行连接,当断开连接后日志会失效,所以采用后台任务记录运行打印的日志;命令为 nohup java com.MyServerSocket > MyServerSocket.out &

关于不采用防火墙来演示断网效果,则是因为防火墙无法拦截已经建立好的Socket通道,经测试发现该现象,读者可手动进行验证,如有意外情况,可向我进行反馈。

由于nohup命令的输入重定向,我暂时还没研究明白,所以目前服务端也不进行发送数据,两边都只是简单的建立连接和断开,代码进行了简单的调整,如下:

服务器端

package com;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author BBYH
 */
public class MyServerSocket {
    public static final Integer PORT = 8080;
    private static Socket myServerSocket;

    public static void listen() {
        System.out.println("服务端监听端口" + PORT);

        new Thread(() -> {
            try (ServerSocket serverSocket = new ServerSocket(PORT)) {
                while (true) {
                    try {
                        myServerSocket = serverSocket.accept();
                        System.out.println("服务端接收到来自客户端的连接");
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

    public static void main(String[] args) {
        listen();
    }
}

客户端

package com;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * @author BBYH
 * @description 通过socket的写数据,来判断当前的socket是否连通
 */
public class ClientSocket {
    public static final String IP = "127.0.0.1";
    public static final Integer PORT = 8080;

    private static Socket socket;
    private static OutputStream outputStream;

    public static void main(String[] args) {
        // 首次建立连接
        connect();
    }

    public static void connect() {
        System.out.println("向服务端端口" + PORT + "申请建立连接");
        try {
            socket = new Socket(IP, PORT);
            System.out.println("客户端与服务器端连接建立成功");
            outputStream = socket.getOutputStream();

            // 开启断网重连线程
            openReConnectThread();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void openReConnectThread() {
        new Thread(() -> {
            while (true) {
                try {
                    if (isDisContentWithServer()) {
                        while (true) {
                            try {
                                socket = new Socket(IP, PORT);
                                outputStream = socket.getOutputStream();
                                System.out.println(new Date() + ":重新建立连接成功");
                                break;
                            } catch (IOException e) {
                                System.out.println(new Date() + ":尝试重新建立连接失败");
                                try {
                                    Thread.sleep(2000);
                                } catch (InterruptedException ex) {
                                    throw new RuntimeException(ex);
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public static boolean isDisContentWithServer() {
        try {
            outputStream.write(("发送数据").getBytes(StandardCharsets.UTF_8));
            Thread.sleep(2000);
            return false;
        } catch (Exception e) {
            return true;
        }
    }
}
演示截图

开启服务器 – 客户端建立连接
在这里插入图片描述

断开Wifi – 客户端尝试重新建立连接 – 重新打开Wifi – 连接成功
在这里插入图片描述

总结

本次断网重连演示完美结束,如有疑问,可在评论区进行提问

更多推荐

备战2024秋招面试题-查看Linux的进程

前言:\textcolor{Green}{前言:}前言:💞快秋招了,那么这个专栏就专门来记录一下,同时呢整理一下常见面试题💞部分题目来自自己的面试题,部分题目来自网络整理给我冲学习目标:面试题:算法题:完成?学习目标:Linux有哪些命令查看Linux的进程算法题:排序链表面试题:Linux有那些命令?文件和目录管

四川百幕晟科技:抖店精选联盟怎么使用?

近年来,电商平台的兴起让很多人纷纷加入进来,希望通过在网上销售产品来赚取更多的利润。在这个竞争激烈的市场中,如何找到稳定的渠道来推广自己的产品成为了每个卖家的追求。抖店精选联盟是一个不错的选择,可以帮助卖家快速提升销量。1.如何使用抖店精选联盟?1.注册成为联盟会员首先,您需要在抖店精选联盟官网注册。注册过程比较简单,

Azure Kubernetes Service中重写规则踩坑小记录

前言最近在做标准产品在不同云平台中的部署验证,有幸体验了一下微软的Azure。负责采购的运维部门这次采用了ApplicationGateway来搭配AKS(AzureKubernetesService)对外暴露服务,正好借着这个机会来体验一下ApplicationGateway。应用场景域名api.demo.com指向

探索科技地图:通向未来的科技之路

科技地图是一张连接现实与未来的路线图,它标示着创新的方向和科技的潜力。在这个信息爆炸的时代,我们深陷于新技术和新理念的海洋中,科技地图为我们提供了一颗指南针,帮助我们更好地了解和探索科技的前沿。科技地图的起源:科技的演化之路科技地图并非一夜之间出现,它承载着几十年科技发展的积淀。从最早的计算机革命,到移动互联网的崛起,

深入理解Linux网络笔记(一):内核是如何接收网络包的

本文为《深入理解Linux网络》学习笔记,使用的Linux源码版本是3.10,网卡驱动是Intel的igb网卡驱动Linux源码在线阅读:https://elixir.bootlin.com/linux/v3.10/source1、内核是如何接收网络包的1)、Linux网络收包总览在TCP/IP网络分层模型里,整个协议

vue3学习源码笔记(小白入门系列)------ 重点!响应式原理 代码逐行分析

目录备注响应式数据创建ref和reactive核心作用第一轮的依赖收集发生时机setup阶段去更改了响应式数据会发生依赖收集吗派发更新派发更新是什么时候触发的?扩展:setup阶段响应式数据被修改会触发组件更新吗vue是如何根据派发更新来触发组件的更新渲染的?组件副作用函数执行时有多个响应式数据更新是如何保证组件只会触

一个手机ip从这个城市去到另一个城市多久会变

随着现代社会的互联网和移动通信的普及,手机IP地址的变化成为了一个备受关注的话题。当我们从一个城市移动到另一个城市时,我们可能会好奇手机IP地址会在多长时间内发生变化。下面虎观代理小二二将详细介绍一下。手机IP地址会随着其所连接的网络的变化而发生改变。当您从一个城市移动到另一个城市,手机可能会重新连接到新的基站或使用不

基于YOLOv5开发构建农林作物害虫检测识别分析系统

智慧农业现在在很多试点区域已经推广开来了,这个借助各种助力政策的利好对于农业的发展是不错的机会,比如:激光自动除草、自动灭虫等等,结合AI的检测识别技术整合相关的硬件设备,比如:无人机、机械、喷淋等等可以实现大农田块的自动化工作,还是有蛮不错的前景的。这里本文的主要目的就是想要收集构建农林作物中常见的害虫数据来开发构建

淘天集团联合爱橙科技开源大模型训练框架Megatron-LLaMA

9月12日,淘天集团联合爱橙科技正式对外开源大模型训练框架——Megatron-LLaMA,旨在让技术开发者们能够更方便地提升大语言模型训练性能,降低训练成本,并保持和LLaMA社区的兼容性。测试显示,在32卡训练上,相比HuggingFace上直接获得的代码版本,Megatron-LLaMA能够取得176%的加速;在

QUIC协议报文解析(三)

在前面的两篇文字里我们简单介绍了QUIC的发展历史,优点以及QUIC协议的连接原理。本篇文章将会以具体的QUIC报文为例,详细介绍QUIC报文的结构以及各个字段的含义。早期QUIC版本众多,主要有谷歌家的gQUIC,以及IETF致力于将QUIC标准化,即IETFQUIC(iQUIC),还有Facebook家的mvfst

【C刷题训练营】第四讲(打好基础很重要)

前言:大家好,这是c语言刷题训练营的第四讲,打好基础便于对c语言语法与算法思维的提高,感谢你的来访与支持!💥🎈个人主页:​​​​​​Dream_Chaser~🎈💥✨✨刷题专栏:http://t.csdn.cn/baIPx⛳⛳本篇内容:c语言刷题训练营第四讲(牛客网)目录BC23-时间转换解题思路:BC24-总成

热文推荐