笔记 socket长连接工具类

在Android(以及更广泛的网络编程领域)中,Socket长连接是指客户端(通常是Android应用程序)与服务器端之间建立的一种持续开放的网络通信通道。这种连接不同于短连接(即每次请求-响应后关闭连接),其特点是连接建立后会保持一段时间的活跃状态,允许双方在无需频繁重新建立连接的情况下进行多次双向数据传输。Socket长连接主要应用于需要实时数据交换、低延迟响应或持续推送服务的场景,如即时通讯、实时数据流、消息推送、在线游戏等。

连接持久性: 在长连接模式下,客户端与服务器在首次成功建立连接后,不会立即关闭连接,而是保持其有效直至应用程序明确关闭它,或者由于网络故障、超时或其他异常情况导致连接中断。这种持久连接减少了建立新连接所需的握手过程,有助于降低延迟,提高数据传输效率。

双向通信: 长连接支持双向数据传输。客户端不仅可以向服务器发送请求,还可以接收服务器主动推送的数据。这对于实时更新、事件通知、聊天消息等需要服务器主动推送信息的应用至关重要。

心跳机制与保活: 为了维持长连接的活性,尤其是在移动网络环境下应对可能的网络波动和中间设备(如路由器、防火墙)的连接超时清理,通常会引入心跳机制。客户端和服务器定期互发“心跳包”(通常包含少量数据或简单状态指示),确认对方仍然在线且连接有效。如果一方在设定时间内未收到心跳响应,可以认为连接已断开,并尝试重新连接。

资源管理: 长连接占用网络资源(如端口、带宽)和系统资源(如内存、CPU)。在Android应用中,通常通过Service组件来管理长连接的生命周期,确保即使在应用退到后台或设备屏幕关闭时,连接仍能保持。服务可以设置为前台服务(带有通知)或绑定服务(依赖于其他组件的绑定),以适应不同的保活需求和权限限制。

异步处理与UI更新: 从长连接接收到的数据通常需要在主线程中更新用户界面。为了不影响UI的流畅性,数据处理和UI更新通常在单独的线程(如通过Handler、BroadcastReceiver、LiveData、EventBus等机制)或回调接口中进行。这样可以确保网络操作与UI操作分离,避免阻塞主线程。

框架与库支持: 虽然可以直接使用Java或Kotlin的原生Socket类来实现长连接,但为了简化开发和处理复杂性,往往会选择使用成熟的网络库或框架,如Apache MINA、Netty、OkHttp(配合WebSocket或HTTP/2的长轮询)等。这些库提供了更高层次的抽象、更好的性能优化、错误处理机制以及与Android平台更好的集成。

socket长连接工具类:

package com.github.jasonhancn.tvcursor;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class ServiceSocket implements Runnable {

    private final int port;
    private final List<ClientSocket> clientSockets = new CopyOnWriteArrayList<>();

    public ServiceSocket(int port) {
        this.port = port;
        new Thread(this).start();
    }

    public void send(String msg) {
        List<ClientSocket> removeList = new ArrayList<>();
        for (ClientSocket item : clientSockets) {
            boolean send = item.send(msg);
            if (!send) {
                removeList.add(item);
            }
        }
        clientSockets.removeAll(removeList);
    }

    @Override
    public void run() {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(port);
            Log.i("jason_log_main", "run: enter receive client");
            while (true) {
                Socket accept = serverSocket.accept();
                Log.i("jason_log_main", "run: get socket client:" + accept.getInetAddress().getHostAddress());
                ClientSocket clientSocket = new ClientSocket(accept);
                clientSockets.add(clientSocket);
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.i("jason_log_main", "run: error receive client:" + e.getMessage());
        } finally {
            if (serverSocket != null && !serverSocket.isClosed()) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static class ClientSocket implements Runnable {

        private Socket socket;
        private PrintWriter writer;

        public ClientSocket(Socket socket) {
            this.socket = socket;
            new Thread(this).start();
        }

        public boolean send(String msg) {
            if (writer != null) {
                writer.println(msg);
                return true;
            } else {
                Log.i("jason_log_main", "send: error for server");
                return false;
            }
        }

        @Override
        public void run() {
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                writer = new PrintWriter(socket.getOutputStream(), true);

                while (true) {
                    // 读取客户端消息
                    String inputLine = reader.readLine();
                    Log.i("jason_log_main", "run: server receiver:" + inputLine);
                    if (inputLine == null) {
                        break;
                    }
                    if ("heart".equals(inputLine)) {
                        // 回复客户端消息
                        writer.println("server_heart_receive");
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                Log.i("jason_log_main", "run: server client error:" + e.getMessage());
            } finally {
                if (socket != null && !socket.isClosed()) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                socket = null;
                writer = null;
            }
        }
    }

    public static class OtherClient implements Runnable {

        private String ip;
        private int port;
        private PrintWriter writer;

        public OtherClient(String ip, int port) {
            this.ip = ip;
            this.port = port;
            new Thread(this).start();
        }

        public boolean send(String msg) {
            if (writer != null) {
                writer.println(msg);
                return true;
            } else {
                Log.i("jason_log_main", "send: error");
                return false;
            }
        }

        @Override
        public void run() {
            Socket socket = null;
            try {
                socket = new Socket();
                socket.connect(new InetSocketAddress(ip, port));
                writer = new PrintWriter(socket.getOutputStream(), true);
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                Log.i("jason_log_main", "run: enter client looper");
                heartBreak();
                while (true) {
                    String responseLine = reader.readLine();
                    Log.i("jason_log_main", "run: client receive:" + responseLine);
                    if (responseLine == null) {
                        break;
                    }
                    if ("toast".equals(responseLine)) {
                        new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(SocketText.context, "接受勒", Toast.LENGTH_LONG).show());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                Log.i("jason_log_main", "run: client error:" + e.getMessage());
            } finally {
                if (socket != null && !socket.isClosed()) {
                    try {
                        socket.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                writer = null;
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                new Thread(this).start();
                Log.i("jason_log_main", "run: start new looper for client");
            }
        }

        private void heartBreak() {
            new Thread(() -> {
                while (true) {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (!send("heart")) {
                        break;
                    }
                }
            }).start();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值