Android Socket保持心跳长连接,断线重连

昨天三点钟才睡觉的,现在胸口感觉闷闷的,兄弟们,我是不是要GG了?如果我G了,求大佬们给我烧个女朋友,

ss.gif

1.在使用Socket连接客户端和服务器端的时候,如果服务端断开了连接,我们客户端是收不到任何回调消息的,只是在你发送消息给服务器的时候,会走异常,表示发送失败。
2.所以要判断服务器是否在线,就需要客户端不停的发送心跳消息给服务器,服务器收到心跳消息,就立马回复给你消息,这样就 能知道双方是否都在线。
3.如果在一段时间内,还是没有收到服务器回复的消息,就表示服务器可能已经死了,这时候你可能需要去做一些提示信息给Android前台。
4.在这一段时间内,你可以不停的尝试重新建立Socket连接,即断线重连。

上代码吧:
首先正常创建一个Activity,并创建一个TcpService服务,在服务中去进行Socket的相关操作。在connection中回调的clientBinder 对象,就是Activity和Service通讯的桥梁。上一篇我们是在Activity里去进行Socket测试的,用Service显然要比用Activity好的多。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this,TcpService.class);
        bindService(intent,connection,BIND_AUTO_CREATE);
    }

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            TcpService.ClientBinder clientBinder = (TcpService.ClientBinder) service;
            clientBinder.startConnect();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
}

在TcpService的onBind()方法中,我们首先需要返回ClientBinder这个对象,然后调用clientBinder.startConnect()建立Socket连接。

public void startConnect() {
            //在子线程进行网络操作
            // Service也是运行在主线程,千万不要以为Service意思跟后台运行很像,就以为Service运行在后台子线程
            if (mExecutorService == null) {
                mExecutorService = Executors.newCachedThreadPool();
            }
            mExecutorService.execute(connectRunnable);
        }

        private Runnable connectRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    // 建立Socket连接
                    mSocket = new Socket();
                    mSocket.connect(new InetSocketAddress("192.168.1.186", 8292), 10);
                    bis = new BufferedInputStream(mSocket.getInputStream());
                    bos = new BufferedOutputStream(mSocket.getOutputStream());
                    // 创建读取服务器心跳的线程
                    mReadThread = new ReadThread();
                    mReadThread.start();
                    //开启心跳,每隔3秒钟发送一次心跳
                    mHandler.post(mHeartRunnable);
                    tryCount = 1;
                } catch (Exception e) {
                    tryCount ++ ;
                    e.printStackTrace();
                    Log.d(TAG, "Socket连接建立失败,正在尝试第"+ tryCount + "次重连");
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mExecutorService.execute(connectRunnable);
                        }
                    },mHeart_spacetime);
                }
            }
        };

这里我创建了一个线程池,用线程池去处理Socket的需要联网的Runnable。然后就是正常的创建Socket连接,新建读取线程以及开启mHeartRunnable。在异常处理里面,如果建立Socket失败,就发送一个延时消息,重新去创建连接。下面我们看一下ReadThread。

public class ReadThread extends Thread {
            @Override
            public void run() {
                int size;
                byte[] buffer = new byte[1024];
                try {
                    while ((size = bis.read(buffer)) != -1) {
                        String str = new String(buffer, 0, size);
                        Log.d(TAG,"我收到来自服务器的消息: " +str);
                        //收到心跳消息以后,首先移除断连消息,然后创建一个新的60秒后执行断连的消息。
                        //这样每次收到心跳后都会重新创建一个60秒的延时消息,在60秒后还没收到心跳消息,表明服务器已死,就会执行断开Socket连接
                        //在60秒钟内如果收到过一次心跳消息,就表明服务器还活着,可以继续与之通讯。
                        mHandler.removeCallbacks(disConnectRunnable);
                        mHandler.postDelayed(disConnectRunnable, mHeart_spacetime * 40);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

ReadThread主要处理的是disConnectRunnable,接收到服务器的心跳消息就移除断连任务,然后重新创建一个新的断连任务。在指定的时间内没有收到服务端的心跳消息,断连任务就会执行。反之,则会又进入一个 "移除旧的—创建新的" 的循环。当然,这个发送心跳消息的时间间隔(mHeart_spacetime )肯定是要小于这个断连任务延时时间的(mHeart_spacetime * 40)。接下来,看一下mHeartRunnable,心跳发送失败以后立马执行重连操作。

 private Runnable mHeartRunnable = new Runnable() {
            @Override
            public void run() {
                sendData();
            }
        };

        private void sendData() {
            mExecutorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        bos.write("给你一张过去的CD,听听那时我们的爱情!".getBytes());
                        //一定不能忘记这步操作
                        bos.flush();
                        //发送成功以后,重新建立一个心跳消息
                        mHandler.postDelayed(mHeartRunnable, mHeart_spacetime);
                        Log.d(TAG, "我发送给服务器的消息: 给你一张过去的CD,听听那时我们的爱情!");
                    } catch (Exception e) {
                        e.printStackTrace();
                        Log.d(TAG, "心跳任务发送失败,正在尝试第"+ tryCount + "次重连");
                        //mExecutorService.schedule(connectRunnable,mHeart_spacetime, TimeUnit.SECONDS);
                        mExecutorService.execute(connectRunnable);
                    }
                }
            });
        }

下面来看一下效果图:


客户端和服务器正常收发心跳。客户端每隔3秒钟发送一次心跳,服务器收到心跳后立马回复心跳。此时双方都正常在线。

当我停止服务端连接的时候,程序开始自动重连

然后我又重新开启了服务,可以看到在重连到第10次的时候,Socket连接重新建立,并正常收发心跳消息

最后,附上TcpService类和AppServer类的代码:

客户端代码

/**
 * Create by Fiora on 2018/10/24 0024
 */
public class TcpService extends Service {
    public static final String TAG = TcpService.class.getSimpleName();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new ClientBinder();
    }

    public class ClientBinder extends Binder {
        private int mHeart_spacetime = 3 * 1000; //心跳间隔时间
        private BufferedInputStream bis;
        private BufferedOutputStream bos;
        private ReadThread mReadThread;
        private Handler mHandler = new Handler();
        private Socket mSocket;
        private ExecutorService mExecutorService;
        private int tryCount = 0;//重试次数

        public void startConnect() {
            //在子线程进行网络操作
            // Service也是运行在主线程,千万不要以为Service意思跟后台运行很像,就以为Service运行在后台子线程
            if (mExecutorService == null) {
                mExecutorService = Executors.newCachedThreadPool();
            }
            mExecutorService.execute(connectRunnable);
        }

        private Runnable connectRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    // 建立Socket连接
                    mSocket = new Socket();
                    mSocket.connect(new InetSocketAddress("192.168.1.186", 8292), 10);
                    bis = new BufferedInputStream(mSocket.getInputStream());
                    bos = new BufferedOutputStream(mSocket.getOutputStream());
                    // 创建读取服务器心跳的线程
                    mReadThread = new ReadThread();
                    mReadThread.start();
                    //开启心跳,每隔15秒钟发送一次心跳
                    mHandler.post(mHeartRunnable);
                    tryCount = 1;
                } catch (Exception e) {
                    tryCount ++ ;
                    e.printStackTrace();
                    Log.d(TAG, "Socket连接建立失败,正在尝试第"+ tryCount + "次重连");
                    mHandler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mExecutorService.execute(connectRunnable);
                        }
                    },mHeart_spacetime);
                }
            }
        };

        public class ReadThread extends Thread {
            @Override
            public void run() {
                int size;
                byte[] buffer = new byte[1024];
                try {
                    while ((size = bis.read(buffer)) != -1) {
                        String str = new String(buffer, 0, size);
                        Log.d(TAG,"我收到来自服务器的消息: " +str);
                        //收到心跳消息以后,首先移除断连消息,然后创建一个新的60秒后执行断连的消息。
                        //这样每次收到心跳后都会重新创建一个60秒的延时消息,在60秒后还没收到心跳消息,表明服务器已死,就会执行断开Socket连接
                        //在60秒钟内如果收到过一次心跳消息,就表明服务器还活着,可以继续与之通讯。
                        mHandler.removeCallbacks(disConnectRunnable);
                        mHandler.postDelayed(disConnectRunnable, mHeart_spacetime * 40);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        private Runnable mHeartRunnable = new Runnable() {
            @Override
            public void run() {
                sendData();
            }
        };

        private void sendData() {
            mExecutorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        bos.write("给你一张过去的CD,听听那时我们的爱情!".getBytes());
                        //一定不能忘记这步操作
                        bos.flush();
                        //发送成功以后,重新建立一个心跳消息
                        mHandler.postDelayed(mHeartRunnable, mHeart_spacetime);
                        Log.d(TAG, "我发送给服务器的消息: 给你一张过去的CD,听听那时我们的爱情!");
                    } catch (Exception e) {
                        e.printStackTrace();
                        Log.d(TAG, "心跳任务发送失败,正在尝试第"+ tryCount + "次重连");
                        //mExecutorService.schedule(connectRunnable,mHeart_spacetime, TimeUnit.SECONDS);
                        mExecutorService.execute(connectRunnable);
                    }
                }
            });
        }

        private Runnable disConnectRunnable = new Runnable() {
            @Override
            public void run() {
                disConnect();
            }
        };

        private void disConnect() {
            mExecutorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Log.d(TAG, "正在执行断连: disConnect");
                        //执行Socket断连
                        mHandler.removeCallbacks(mHeartRunnable);
                        if (mReadThread != null) {
                            mReadThread.interrupt();
                        }

                        if (bos != null) {
                            bos.close();
                        }

                        if (bis != null) {
                            bis.close();
                        }

                        if (mSocket != null) {
                            mSocket.shutdownInput();
                            mSocket.shutdownOutput();
                            mSocket.close();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

服务端代码:

/**
 * Create by Fiora on 2018/10/24 0024
 */
public class AppServer {
    public static final String TAG = AppServer.class.getSimpleName();
    private static BufferedOutputStream bos;
    private static BufferedInputStream bis;
    private static Socket acceptSocket;

    public static void main (String args[]){
        try{
            ServerSocket serverSocket  = new ServerSocket(8292);
            while(true) {
                acceptSocket = serverSocket.accept();
                bos = new BufferedOutputStream(acceptSocket.getOutputStream());
                bis = new BufferedInputStream(acceptSocket.getInputStream());

                ReadThread readThread = new ReadThread();
                readThread.start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private static class ReadThread extends Thread {
        @Override
        public void run() {
            while (true) {
                byte[] data = new byte[1024];
                int size = 0;
                try {
                    while ((size = bis.read(data)) != -1) {
                        String str = new String(data,0,size);
                        System.out.println(TAG+"----"+str);
                        //收到客户端发送的请求后,立马回一条心跳给客户端
                        bos.write("有时会突然忘了,我依然爱着你!".getBytes());
                        bos.flush();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 钧钰反思日志0610-2017 必读:12条人生原则 早睡早起——晚不超过22:30,早不晚于6:00,醒后不睡回...
    钧钰阅读 162评论 0 0
  • 中心图是一个穿裙子的小女孩,因为自己比较喜欢穿裙子,感觉这个卡通人物很符合我。 四个分叉点主要概括了我的爱好、专业...
    美院王之娇阅读 485评论 1 0
  • 判断用到的例子 var a = "iamstring.";var b = 222;var c= [1,2,3];v...
    疯人愿的疯言疯语阅读 242评论 0 0
  • 老人们常说“人越闲,做的事情越是无用功” 以前不懂 总觉得“回味”一件事是会让人反省的 现在却慢慢明白了 有很多事...
    不停杂货铺阅读 97评论 0 0