在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();
}
}
}