HTTP、HTTPS、Socket、WebSocket 全解析第三部

4 阅读1小时+

第十四章:HTTPS 在 Android 中的应用(6 题)

14.1 Android如何支持HTTPS?

答案:

Android支持HTTPS的方式:

  1. 系统级支持

    • Android内置SSL/TLS支持
    • 自动处理HTTPS连接
    • 自动验证证书
  2. API支持

    • HttpsURLConnection
    • OkHttp
    • Retrofit
  3. 证书存储

    • 系统信任存储
    • 用户安装的证书
    • 应用级证书

Android HTTPS支持:

1. HttpsURLConnection(系统API)

// Android系统API
URL httpsUrl = new URL("https://www.example.com");
HttpsURLConnection conn = (HttpsURLConnection) httpsUrl.openConnection();
// 自动处理HTTPS和证书验证
conn.connect();

2. OkHttp(第三方库,推荐)

// OkHttp
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url("https://www.example.com")
    .build();
Response response = client.newCall(request).execute();

3. Retrofit(网络框架)

// Retrofit
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.example.com")
    .client(okHttpClient)
    .build();

证书存储:

  • 系统信任存储:/system/etc/security/cacerts
  • 用户证书:设置 → 安全 → 安装证书
  • 应用级证书:应用内存储

总结:

  • Android内置SSL/TLS支持
  • 使用HttpsURLConnection或OkHttp
  • 自动处理HTTPS和证书验证
  • 支持系统级和用户级证书

14.2 Android的证书存储在哪里?

答案:

Android证书存储位置:

  1. 系统信任存储

    • 路径:/system/etc/security/cacerts
    • 系统预装的CA证书
    • 只读,需要root权限修改
  2. 用户证书存储

    • 用户安装的证书
    • 设置 → 安全 → 安装证书
    • 存储在用户数据分区
  3. 应用级证书

    • 应用内存储
    • 应用私有目录
    • 应用可以管理

证书存储访问:

1. 系统信任存储

// 访问系统信任的CA
KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
keyStore.load(null, null);
Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
    String alias = aliases.nextElement();
    X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
}

2. 用户证书

// 用户安装的证书也在AndroidCAStore中
// 系统自动验证时会使用

3. 应用级证书

// 应用内证书存储
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(inputStream, password.toCharArray());
X509Certificate cert = (X509Certificate) keyStore.getCertificate("alias");

证书文件格式:

  • 系统证书:.crt文件,存储在/system/etc/security/cacerts
  • 用户证书:通过设置安装
  • 应用证书:.p12.bks等格式

总结:

  • 系统信任存储:/system/etc/security/cacerts
  • 用户证书:用户数据分区
  • 应用级证书:应用私有目录
  • 通过KeyStore API访问

14.3 如何配置HTTPS请求?

答案:

配置HTTPS请求的方法:

1. 使用HttpsURLConnection(系统API)

// 基本HTTPS请求
URL httpsUrl = new URL("https://www.example.com");
HttpsURLConnection conn = (HttpsURLConnection) httpsUrl.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", "MyApp/1.0");
conn.connect();

InputStream inputStream = conn.getInputStream();
// 读取响应

2. 使用OkHttp(推荐)

// OkHttp配置
OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(30, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .build();

Request request = new Request.Builder()
    .url("https://www.example.com")
    .header("User-Agent", "MyApp/1.0")
    .build();

Response response = client.newCall(request).execute();

3. 自定义SSL配置

// 自定义SSL配置
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);

HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

配置选项:

  • 超时设置
  • 请求头设置
  • SSL/TLS配置
  • 证书验证配置

总结:

  • 使用HttpsURLConnection或OkHttp
  • 配置超时、请求头等
  • 可以自定义SSL配置
  • OkHttp提供更多配置选项

14.4 如何处理HTTPS证书验证?

答案:

处理HTTPS证书验证的方法:

1. 默认验证(推荐)

// Android默认验证证书
URL httpsUrl = new URL("https://www.example.com");
HttpsURLConnection conn = (HttpsURLConnection) httpsUrl.openConnection();
// 自动验证证书
conn.connect();

2. 自定义验证

// 自定义证书验证
TrustManager[] trustManagers = new TrustManager[]{
    new X509TrustManager() {
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
            // 自定义验证逻辑
            for (X509Certificate cert : chain) {
                cert.checkValidity();
                // 其他验证
            }
        }
        
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
        
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }
    }
};

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

3. 证书锁定

// 使用OkHttp证书锁定
CertificatePinner pinner = new CertificatePinner.Builder()
    .add("www.example.com", "sha256/...")
    .build();

OkHttpClient client = new OkHttpClient.Builder()
    .certificatePinner(pinner)
    .build();

最佳实践:

  • 使用默认验证(推荐)
  • 只在特殊情况下自定义验证
  • 不要跳过证书验证(不安全)
  • 高安全要求使用证书锁定

总结:

  • 默认验证:自动处理
  • 自定义验证:实现X509TrustManager
  • 证书锁定:固定证书
  • 不要跳过验证

14.5 如何信任自签名证书?

答案:

信任自签名证书的方法(仅用于开发/测试):

1. 安装证书到系统

  • 设置 → 安全 → 安装证书
  • 选择证书文件
  • 安装到系统信任存储

2. 应用内信任(不推荐,仅开发)

// 仅用于开发/测试,生产环境不要使用
TrustManager[] trustManagers = new TrustManager[]{
    new X509TrustManager() {
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
            // 信任所有证书(不安全!)
        }
        
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
        
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }
    }
};

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagers, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

3. 信任特定自签名证书(相对安全)

// 信任特定的自签名证书
X509Certificate trustedCert = loadCertificateFromFile("self-signed.crt");

TrustManager[] trustManagers = new TrustManager[]{
    new X509TrustManager() {
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
            // 只信任特定的证书
            if (!chain[0].equals(trustedCert)) {
                throw new CertificateException("Untrusted certificate");
            }
        }
        
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{trustedCert};
        }
        
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }
    }
};

最佳实践:

  • 生产环境使用CA签名的证书
  • 开发/测试可以安装自签名证书到系统
  • 不要在生产环境跳过证书验证
  • 使用Let's Encrypt等免费CA

总结:

  • 安装证书到系统(推荐)
  • 应用内信任(仅开发)
  • 信任特定证书(相对安全)
  • 生产环境使用CA证书

答案:

实现证书锁定的方法(详见13.12题):

使用OkHttp(推荐)

CertificatePinner pinner = new CertificatePinner.Builder()
    .add("www.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .build();

OkHttpClient client = new OkHttpClient.Builder()
    .certificatePinner(pinner)
    .build();

自定义X509TrustManager

// 详见13.12题答案

总结:

  • 使用OkHttp CertificatePinner(推荐)
  • 或自定义X509TrustManager
  • 计算和比较证书哈希值

答案:

处理SSL错误的方法:

1. 捕获SSLException

try {
    HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
    conn.connect();
} catch (SSLException e) {
    // 处理SSL错误
    if (e.getCause() instanceof CertificateExpiredException) {
        // 证书过期
    } else if (e.getCause() instanceof CertificateNotYetValidException) {
        // 证书未生效
    } else {
        // 其他SSL错误
    }
}

2. 错误类型

  • CertificateExpiredException:证书过期
  • CertificateNotYetValidException:证书未生效
  • SSLHandshakeException:握手失败
  • SSLPeerUnverifiedException:证书验证失败

3. 错误处理

try {
    // HTTPS请求
} catch (SSLException e) {
    String errorMessage = getErrorMessage(e);
    showError(errorMessage);
    // 记录错误日志
    Log.e("HTTPS", "SSL error", e);
}

最佳实践:

  • 捕获并处理SSL错误
  • 显示友好错误提示
  • 记录错误日志
  • 不要忽略SSL错误

总结:

  • 捕获SSLException
  • 识别错误类型
  • 显示友好提示
  • 记录错误日志

14.6 如何调试HTTPS请求?

答案:

调试HTTPS请求的方法:

1. 使用日志

// 启用HTTP日志
if (BuildConfig.DEBUG) {
    HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);
    
    OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(logging)
        .build();
}

2. 使用Charles/Fiddler

  • 安装Charles/Fiddler证书
  • 配置代理
  • 查看HTTPS请求和响应

3. 使用Stetho(Facebook)

// Stetho调试
Stetho.initializeWithDefaults(this);

OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new StethoInterceptor())
    .build();

4. 检查证书信息

// 打印证书信息
Certificate[] certificates = conn.getServerCertificates();
X509Certificate cert = (X509Certificate) certificates[0];
Log.d("HTTPS", "Subject: " + cert.getSubjectDN());
Log.d("HTTPS", "Issuer: " + cert.getIssuerDN());
Log.d("HTTPS", "Valid from: " + cert.getNotBefore());
Log.d("HTTPS", "Valid until: " + cert.getNotAfter());

最佳实践:

  • 开发环境使用日志和调试工具
  • 生产环境禁用详细日志
  • 使用Charles/Fiddler查看请求
  • 检查证书信息

总结:

  • 使用日志拦截器
  • 使用Charles/Fiddler
  • 使用Stetho
  • 检查证书信息

答案:

HTTPS性能优化的方法:

  1. 会话复用

    • TLS会话复用
    • 减少握手开销
    • 提高性能
  2. 连接池

    • 复用HTTPS连接
    • 减少连接建立开销
    • 提高性能
  3. HTTP/2.0

    • 多路复用
    • 头部压缩
    • 提高性能
  4. 硬件加速

    • 使用硬件加密
    • 提高加密性能
    • 现代设备支持

Android代码示例:

// 使用连接池
ConnectionPool connectionPool = new ConnectionPool(5, 5, TimeUnit.MINUTES);
OkHttpClient client = new OkHttpClient.Builder()
    .connectionPool(connectionPool)
    .build();

// HTTP/2.0(自动支持)
// OkHttp自动使用HTTP/2.0如果服务器支持

优化建议:

  • 启用会话复用
  • 使用连接池
  • 使用HTTP/2.0
  • 现代HTTPS性能已足够好

总结:

  • 会话复用:减少握手开销
  • 连接池:复用连接
  • HTTP/2.0:多路复用
  • 硬件加速:提高加密性能

第三部分:Socket 编程答案

第十五章:Socket 基础(22 题)

15.1 Socket是什么?它的作用是什么?

答案:

Socket(套接字)是网络通信的端点,用于实现不同主机之间的数据通信

Socket定义:

  • 网络通信的抽象接口
  • IP地址 + 端口号的组合
  • 实现进程间通信

Socket作用:

  1. 建立连接

    • 客户端和服务器建立连接
    • 实现网络通信
  2. 数据传输

    • 发送数据
    • 接收数据
    • 双向通信
  3. 协议支持

    • 支持TCP(可靠)
    • 支持UDP(快速)

Socket通信模型:

客户端Socket ←→ 网络 ←→ 服务器Socket

Android代码示例:

// TCP Socket
Socket socket = new Socket("www.example.com", 80);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();

// UDP Socket
DatagramSocket socket = new DatagramSocket();
DatagramPacket packet = new DatagramPacket(data, data.length, 
    InetAddress.getByName("www.example.com"), 80);
socket.send(packet);

总结:

  • Socket是网络通信的端点
  • 用于建立连接和传输数据
  • 支持TCP和UDP协议

15.2 Socket属于OSI模型的哪一层?

答案:

Socket属于OSI模型的第4层(传输层)和第5层(会话层)之间

详细说明:

  • Socket是传输层的接口
  • 提供会话层的功能
  • 通常归类为传输层

OSI模型分层:

7. 应用层:HTTP、FTP
6. 表示层:数据格式转换
5. 会话层:Socket(部分功能)
4. 传输层:TCP、UDP(Socket主要功能)
3. 网络层:IP
2. 数据链路层:以太网
1. 物理层:物理介质

总结:

  • Socket主要属于传输层
  • 提供会话层功能
  • 是传输层的编程接口

15.3 Socket属于TCP/IP模型的哪一层?

答案:

Socket属于TCP/IP模型的第3层(传输层)

TCP/IP模型分层:

4. 应用层:HTTP、FTP
3. 传输层:TCP、UDP(Socket)
2. 网络层:IP
1. 网络接口层:以太网

总结:

  • Socket属于传输层
  • 是TCP/UDP的编程接口
  • 提供网络通信功能

15.4 Socket的通信模型是什么?

答案:

Socket的通信模型:

1. 客户端-服务器模型(C/S)

客户端Socket → 连接 → 服务器Socket
客户端发送请求 → 服务器处理 → 服务器返回响应

2. 点对点模型(P2P)

客户端1 ←→ 客户端2
双向通信

通信流程:

1. 服务器创建Socket,绑定端口,监听
2. 客户端创建Socket,连接服务器
3. 服务器接受连接
4. 双方通过Socket发送和接收数据
5. 关闭连接

Android代码示例:

// 服务器端
ServerSocket serverSocket = new ServerSocket(8080);
Socket clientSocket = serverSocket.accept();  // 等待客户端连接

// 客户端
Socket socket = new Socket("server.com", 8080);  // 连接服务器

总结:

  • 客户端-服务器模型(常用)
  • 点对点模型
  • 通过Socket建立连接和传输数据

15.5 Socket和HTTP的区别是什么?

答案:

Socket和HTTP的主要区别:

  1. 协议层次

    • Socket:传输层接口
    • HTTP:应用层协议
  2. 连接方式

    • Socket:长连接或短连接
    • HTTP:请求-响应模式
  3. 数据格式

    • Socket:原始数据(字节流)
    • HTTP:结构化数据(请求头、响应头)
  4. 使用场景

    • Socket:实时通信、游戏、聊天
    • HTTP:Web应用、RESTful API

对比表:

特性SocketHTTP
协议层次传输层应用层
连接方式灵活请求-响应
数据格式原始数据结构化数据
使用场景实时通信Web应用

总结:

  • Socket是传输层接口,HTTP是应用层协议
  • Socket更底层,HTTP更高级
  • 不同层次,不同用途

15.6 Socket的端口号是什么?

答案:

Socket的端口号是用于区分不同应用程序的网络端口

端口号作用:

  • 标识应用程序
  • 范围:0-65535
  • 0-1023:知名端口(HTTP:80, HTTPS:443)
  • 1024-49151:注册端口
  • 49152-65535:动态端口

Android代码示例:

// 指定端口号
Socket socket = new Socket("www.example.com", 8080);  // 端口8080

// 服务器绑定端口
ServerSocket serverSocket = new ServerSocket(8080);  // 监听8080端口

总结:

  • 端口号用于区分应用程序
  • 范围0-65535
  • 客户端连接时指定端口,服务器监听端口

15.7 Socket的地址是什么?

答案:

Socket的地址是IP地址 + 端口号的组合

Socket地址组成:

  • IP地址:标识主机
  • 端口号:标识应用程序
  • 格式:IP地址:端口号

Android代码示例:

// Socket地址
InetAddress address = InetAddress.getByName("www.example.com");
int port = 8080;
SocketAddress socketAddress = new InetSocketAddress(address, port);

// 创建Socket
Socket socket = new Socket();
socket.connect(socketAddress);

总结:

  • Socket地址 = IP地址 + 端口号
  • 唯一标识网络通信端点
  • 客户端和服务器都需要地址

15.8 Socket的协议有哪些?

答案:

Socket支持的协议:

  1. TCP(Transmission Control Protocol)

    • 可靠传输
    • 面向连接
    • 流式传输
  2. UDP(User Datagram Protocol)

    • 快速传输
    • 无连接
    • 数据报传输

Android代码示例:

// TCP Socket
Socket tcpSocket = new Socket("www.example.com", 80);

// UDP Socket
DatagramSocket udpSocket = new DatagramSocket();

总结:

  • TCP:可靠,面向连接
  • UDP:快速,无连接
  • 根据需求选择协议

15.9 TCP Socket和UDP Socket的区别是什么?

答案:

TCP Socket和UDP Socket的主要区别:

  1. 可靠性

    • TCP:可靠(保证数据到达)
    • UDP:不可靠(可能丢失)
  2. 连接方式

    • TCP:面向连接(需要建立连接)
    • UDP:无连接(直接发送)
  3. 传输方式

    • TCP:流式传输(字节流)
    • UDP:数据报传输(数据包)
  4. 性能

    • TCP:较慢(有连接开销)
    • UDP:较快(无连接开销)

对比表:

特性TCP SocketUDP Socket
可靠性可靠不可靠
连接面向连接无连接
传输流式数据报
性能较慢较快

总结:

  • TCP:可靠,面向连接,较慢
  • UDP:不可靠,无连接,较快
  • 根据需求选择

15.10 TCP Socket的特点是什么?

答案:

TCP Socket的特点:

  1. 可靠性

    • 保证数据到达
    • 保证数据顺序
    • 自动重传
  2. 面向连接

    • 需要建立连接(三次握手)
    • 需要关闭连接(四次挥手)
    • 连接状态管理
  3. 流式传输

    • 字节流传输
    • 无边界
    • 需要处理粘包
  4. 全双工

    • 双向通信
    • 可以同时发送和接收

Android代码示例:

// TCP Socket
Socket socket = new Socket("www.example.com", 80);
// 自动处理连接建立、数据重传等
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();

总结:

  • 可靠性高
  • 面向连接
  • 流式传输
  • 全双工通信

15.11 UDP Socket的特点是什么?

答案:

UDP Socket的特点:

  1. 不可靠

    • 不保证数据到达
    • 不保证数据顺序
    • 可能丢失数据
  2. 无连接

    • 不需要建立连接
    • 直接发送数据
    • 无连接状态
  3. 数据报传输

    • 数据包传输
    • 有边界
    • 每个数据包独立
  4. 快速

    • 无连接开销
    • 传输速度快
    • 适合实时应用

Android代码示例:

// UDP Socket
DatagramSocket socket = new DatagramSocket();
DatagramPacket packet = new DatagramPacket(data, data.length, 
    address, port);
socket.send(packet);

总结:

  • 不可靠但快速
  • 无连接
  • 数据报传输
  • 适合实时应用

15.12 什么时候使用TCP Socket?

答案:

使用TCP Socket的场景:

  1. 需要可靠性

    • 文件传输
    • 数据同步
    • 重要数据传输
  2. 需要顺序

    • 文本传输
    • 数据库操作
    • 需要保证顺序的场景
  3. 长连接

    • 持久连接
    • 实时通信(WebSocket基于TCP)
    • 需要保持连接

使用场景:

  • HTTP/HTTPS
  • FTP
  • 数据库连接
  • 实时通信(WebSocket)
  • 文件传输

总结:

  • 需要可靠性时使用TCP
  • 需要顺序时使用TCP
  • 长连接场景使用TCP

15.13 什么时候使用UDP Socket?

答案:

使用UDP Socket的场景:

  1. 实时性要求高

    • 视频直播
    • 语音通话
    • 游戏
  2. 可以容忍丢失

    • 实时音视频(丢失一帧不影响)
    • DNS查询
    • 广播/组播
  3. 低延迟要求

    • 游戏
    • 实时通信
    • 需要快速响应

使用场景:

  • DNS查询
  • 视频直播
  • 语音通话
  • 游戏
  • 广播/组播

总结:

  • 实时性要求高时使用UDP
  • 可以容忍丢失时使用UDP
  • 低延迟要求时使用UDP

15.14 Socket编程的基本流程是什么?

答案:

Socket编程的基本流程:

TCP Socket流程:

服务器端:

1. 创建ServerSocket
2. 绑定端口(bind)
3. 监听连接(listen)
4. 接受连接(accept)
5. 发送/接收数据
6. 关闭连接

客户端:

1. 创建Socket
2. 连接服务器(connect)
3. 发送/接收数据
4. 关闭连接

UDP Socket流程:

1. 创建DatagramSocket
2. 创建DatagramPacket
3. 发送/接收数据
4. 关闭Socket

Android代码示例:

// TCP服务器
ServerSocket serverSocket = new ServerSocket(8080);
Socket clientSocket = serverSocket.accept();
// 处理数据
clientSocket.close();

// TCP客户端
Socket socket = new Socket("server.com", 8080);
// 发送/接收数据
socket.close();

// UDP
DatagramSocket socket = new DatagramSocket();
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
socket.send(packet);
socket.close();

总结:

  • TCP:创建Socket → 连接 → 通信 → 关闭
  • UDP:创建Socket → 发送/接收 → 关闭
  • 遵循基本流程

15.15 如何创建Socket连接?

答案:

创建Socket连接的方法:

TCP Socket:

// 方式1:直接创建并连接
Socket socket = new Socket("www.example.com", 80);

// 方式2:先创建后连接
Socket socket = new Socket();
socket.connect(new InetSocketAddress("www.example.com", 80));

// 方式3:指定本地地址和端口
Socket socket = new Socket("www.example.com", 80, 
    InetAddress.getLocalHost(), 0);

UDP Socket:

// 创建UDP Socket
DatagramSocket socket = new DatagramSocket();

// 绑定端口
DatagramSocket socket = new DatagramSocket(8080);

Android代码示例:

// TCP客户端
Socket socket = new Socket("www.example.com", 80);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();

// UDP
DatagramSocket socket = new DatagramSocket();

总结:

  • TCP:使用Socket类
  • UDP:使用DatagramSocket类
  • 可以指定地址和端口

15.16 如何绑定Socket地址?

答案:

绑定Socket地址的方法:

服务器端绑定:

// ServerSocket绑定端口
ServerSocket serverSocket = new ServerSocket(8080);

// 绑定特定地址和端口
ServerSocket serverSocket = new ServerSocket(8080, 10, 
    InetAddress.getByName("192.168.1.100"));

客户端绑定(可选):

// 客户端通常不需要绑定,系统自动分配
Socket socket = new Socket();
socket.bind(new InetSocketAddress("192.168.1.100", 0));  // 0表示系统分配
socket.connect(new InetSocketAddress("server.com", 8080));

UDP绑定:

// UDP绑定端口
DatagramSocket socket = new DatagramSocket(8080);

// UDP绑定地址和端口
DatagramSocket socket = new DatagramSocket(
    new InetSocketAddress("192.168.1.100", 8080));

总结:

  • 服务器必须绑定端口
  • 客户端通常不需要绑定
  • 可以绑定特定地址和端口

15.17 如何监听Socket连接?

答案:

监听Socket连接的方法(TCP服务器):

// 创建ServerSocket并监听
ServerSocket serverSocket = new ServerSocket(8080);

// 设置监听队列大小
ServerSocket serverSocket = new ServerSocket(8080, 10);  // 队列大小10

// 监听连接(阻塞)
Socket clientSocket = serverSocket.accept();  // 等待客户端连接

监听流程:

1. 创建ServerSocket
2. 绑定端口
3. 调用accept()监听
4. 等待客户端连接
5. 接受连接,返回Socket

Android代码示例:

// 服务器监听
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
    Socket clientSocket = serverSocket.accept();  // 阻塞等待
    // 处理客户端连接
    new Thread(() -> {
        // 处理客户端请求
    }).start();
}

总结:

  • 使用ServerSocket.accept()监听
  • 阻塞等待客户端连接
  • 可以设置监听队列大小
  • 通常使用多线程处理多个客户端

15.18 如何接受Socket连接?

答案:

接受Socket连接的方法:

// ServerSocket接受连接
ServerSocket serverSocket = new ServerSocket(8080);
Socket clientSocket = serverSocket.accept();  // 接受连接,返回客户端Socket

接受连接流程:

1. ServerSocket监听(accept)
2. 客户端连接
3. accept()返回客户端Socket
4. 使用客户端Socket通信

Android代码示例:

// 接受连接
ServerSocket serverSocket = new ServerSocket(8080);
Socket clientSocket = serverSocket.accept();

// 获取客户端信息
InetAddress clientAddress = clientSocket.getInetAddress();
int clientPort = clientSocket.getPort();

// 使用客户端Socket通信
OutputStream out = clientSocket.getOutputStream();
InputStream in = clientSocket.getInputStream();

总结:

  • 使用accept()接受连接
  • 返回客户端Socket
  • 使用客户端Socket通信
  • 可以获取客户端地址信息

15.19 如何发送数据?

答案:

发送数据的方法:

TCP Socket发送:

// 获取输出流
Socket socket = new Socket("server.com", 8080);
OutputStream out = socket.getOutputStream();

// 发送数据
String data = "Hello Server";
out.write(data.getBytes());
out.flush();  // 确保数据发送

// 或使用PrintWriter
PrintWriter writer = new PrintWriter(out, true);
writer.println("Hello Server");

UDP Socket发送:

// 创建数据包
DatagramSocket socket = new DatagramSocket();
byte[] data = "Hello Server".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length,
    InetAddress.getByName("server.com"), 8080);

// 发送数据包
socket.send(packet);

Android代码示例:

// TCP发送
Socket socket = new Socket("server.com", 8080);
OutputStream out = socket.getOutputStream();
out.write("Hello".getBytes());
out.flush();

// UDP发送
DatagramSocket socket = new DatagramSocket();
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
socket.send(packet);

总结:

  • TCP:使用OutputStream发送
  • UDP:使用DatagramPacket发送
  • 记得flush确保数据发送

15.20 如何接收数据?

答案:

接收数据的方法:

TCP Socket接收:

// 获取输入流
Socket socket = new Socket("server.com", 8080);
InputStream in = socket.getInputStream();

// 接收数据
byte[] buffer = new byte[1024];
int length = in.read(buffer);  // 阻塞读取
String data = new String(buffer, 0, length);

// 或使用BufferedReader
BufferedReader reader = new BufferedReader(
    new InputStreamReader(in));
String line = reader.readLine();

UDP Socket接收:

// 创建接收缓冲区
DatagramSocket socket = new DatagramSocket(8080);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

// 接收数据包
socket.receive(packet);  // 阻塞接收
String data = new String(packet.getData(), 0, packet.getLength());
InetAddress senderAddress = packet.getAddress();
int senderPort = packet.getPort();

Android代码示例:

// TCP接收
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = in.read(buffer);

// UDP接收
DatagramSocket socket = new DatagramSocket(8080);
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);

总结:

  • TCP:使用InputStream接收
  • UDP:使用DatagramPacket接收
  • 都是阻塞操作
  • 需要处理缓冲区

15.21 如何关闭Socket连接?

答案:

关闭Socket连接的方法:

TCP Socket关闭:

// 关闭Socket
Socket socket = new Socket("server.com", 8080);
socket.close();  // 关闭连接

// 关闭输入/输出流(会自动关闭Socket)
socket.getInputStream().close();
socket.getOutputStream().close();

// 优雅关闭(先关闭输出流)
socket.shutdownOutput();  // 关闭输出流,通知对方
socket.close();

UDP Socket关闭:

// 关闭UDP Socket
DatagramSocket socket = new DatagramSocket();
socket.close();

ServerSocket关闭:

// 关闭ServerSocket
ServerSocket serverSocket = new ServerSocket(8080);
serverSocket.close();  // 停止监听

Android代码示例:

// 关闭TCP Socket
socket.close();

// 优雅关闭
socket.shutdownOutput();  // 先关闭输出
socket.close();

// 关闭UDP Socket
socket.close();

最佳实践:

  • 及时关闭Socket释放资源
  • 使用try-finally确保关闭
  • TCP可以优雅关闭(shutdownOutput)

总结:

  • 使用close()关闭Socket
  • TCP可以优雅关闭
  • 及时关闭释放资源

15.22 Socket编程的异常如何处理?

答案:

Socket编程的异常处理:

常见异常:

  • IOException:网络IO异常
  • SocketException:Socket异常
  • ConnectException:连接异常
  • UnknownHostException:主机未知异常
  • SocketTimeoutException:超时异常

异常处理示例:

try {
    Socket socket = new Socket("server.com", 8080);
    // Socket操作
    socket.close();
} catch (UnknownHostException e) {
    // 主机不存在
    Log.e("Socket", "Host not found", e);
} catch (ConnectException e) {
    // 连接被拒绝
    Log.e("Socket", "Connection refused", e);
} catch (SocketTimeoutException e) {
    // 连接超时
    Log.e("Socket", "Connection timeout", e);
} catch (IOException e) {
    // 其他IO异常
    Log.e("Socket", "IO error", e);
} finally {
    // 确保关闭Socket
    if (socket != null) {
        try {
            socket.close();
        } catch (IOException e) {
            // 关闭异常
        }
    }
}

最佳实践:

  • 捕获具体异常类型
  • 使用try-finally确保关闭
  • 记录异常日志
  • 显示友好错误提示

总结:

  • 捕获和处理各种异常
  • 使用try-finally确保资源释放
  • 记录日志和显示错误提示

第十六章:TCP Socket(23 题)

16.1 TCP是什么?它的全称是什么?

答案:

TCP(Transmission Control Protocol):传输控制协议,是面向连接的、可靠的传输层协议。

TCP定义:

  • 传输层协议
  • 面向连接
  • 可靠传输
  • 流式传输

TCP特点:

  • 可靠性:保证数据到达和顺序
  • 面向连接:需要建立连接
  • 全双工:双向通信
  • 流式传输:字节流

Android代码示例:

// TCP Socket
Socket socket = new Socket("www.example.com", 80);
// 使用TCP协议

总结:

  • TCP是传输控制协议
  • 面向连接、可靠传输
  • 广泛使用

16.2 TCP属于OSI模型的哪一层?

答案:

TCP属于OSI模型的第4层(传输层)

OSI模型分层:

7. 应用层
6. 表示层
5. 会话层
4. 传输层:TCP、UDP
3. 网络层:IP
2. 数据链路层
1. 物理层

总结:

  • TCP属于传输层
  • 提供端到端的可靠传输

16.3 TCP属于TCP/IP模型的哪一层?

答案:

TCP属于TCP/IP模型的第3层(传输层)

TCP/IP模型分层:

4. 应用层
3. 传输层:TCP、UDP
2. 网络层:IP
1. 网络接口层

总结:

  • TCP属于传输层
  • 在IP层之上

16.4 TCP的特点是什么?

答案:

TCP的特点(详见15.10题答案):

  1. 可靠性:保证数据到达和顺序
  2. 面向连接:需要建立连接
  3. 流式传输:字节流传输
  4. 全双工:双向通信

总结:

  • 可靠性高
  • 面向连接
  • 流式传输
  • 全双工通信

16.5 TCP的可靠性如何保证?

答案:

TCP的可靠性保证机制:

  1. 确认机制(ACK)

    • 接收方确认收到数据
    • 发送方等待确认
    • 未收到确认则重传
  2. 重传机制

    • 超时重传
    • 快速重传
    • 保证数据到达
  3. 序号机制

    • 每个字节都有序号
    • 保证数据顺序
    • 检测重复数据
  4. 校验和

    • 数据校验
    • 检测数据错误
    • 保证数据完整性
  5. 流量控制

    • 滑动窗口
    • 控制发送速度
    • 防止接收方溢出
  6. 拥塞控制

    • 检测网络拥塞
    • 调整发送速度
    • 保证网络稳定

总结:

  • 确认和重传机制
  • 序号和校验和
  • 流量和拥塞控制
  • 多重机制保证可靠性

16.6 TCP的三次握手是什么?

答案:

TCP的三次握手是建立TCP连接的过程

三次握手过程:

1. 客户端 → 服务器:SYN(同步请求)
   客户端发送SYN包,序列号为x

2. 服务器 → 客户端:SYN-ACK(同步确认)
   服务器发送SYN-ACK包,序列号为y,确认号为x+1

3. 客户端 → 服务器:ACK(确认)
   客户端发送ACK包,确认号为y+1

连接建立完成

详细说明:

  • 第一次:客户端发起连接请求
  • 第二次:服务器确认并发送自己的请求
  • 第三次:客户端确认
  • 三次握手后连接建立

Android代码示例:

// TCP三次握手(自动处理)
Socket socket = new Socket("server.com", 8080);
// 系统自动进行三次握手

总结:

  • 三次握手建立TCP连接
  • 客户端和服务器互相确认
  • 系统自动处理

16.7 为什么需要三次握手?

答案:

需要三次握手的原因:

  1. 确认双方通信能力

    • 客户端确认服务器能接收
    • 服务器确认客户端能接收
    • 双向确认
  2. 防止过期连接

    • 如果只有两次握手,可能建立过期连接
    • 三次握手确保连接有效
  3. 同步序列号

    • 双方交换初始序列号
    • 建立可靠传输基础

为什么不是两次或四次?

  • 两次:无法确认服务器接收能力,可能建立过期连接
  • 四次:冗余,三次已足够

总结:

  • 确认双方通信能力
  • 防止过期连接
  • 同步序列号
  • 三次是最少且足够的次数

16.8 TCP的四次挥手是什么?

答案:

TCP的四次挥手是关闭TCP连接的过程

四次挥手过程:

1. 客户端 → 服务器:FIN(结束请求)
   客户端发送FIN包,请求关闭连接

2. 服务器 → 客户端:ACK(确认)
   服务器确认收到FIN

3. 服务器 → 客户端:FIN(结束请求)
   服务器发送FIN包,请求关闭连接

4. 客户端 → 服务器:ACK(确认)
   客户端确认收到FIN

连接关闭完成

详细说明:

  • 第一次:客户端请求关闭
  • 第二次:服务器确认
  • 第三次:服务器请求关闭
  • 第四次:客户端确认
  • 四次挥手后连接关闭

Android代码示例:

// TCP四次挥手(自动处理)
socket.close();
// 系统自动进行四次挥手

总结:

  • 四次挥手关闭TCP连接
  • 双方都需要关闭
  • 系统自动处理

16.9 为什么需要四次挥手?

答案:

需要四次挥手的原因:

  1. 全双工通信

    • TCP是全双工的
    • 客户端和服务器都可以发送数据
    • 需要分别关闭两个方向
  2. 数据可能未发送完

    • 一方关闭时,另一方可能还有数据要发送
    • 需要等待数据发送完再关闭
  3. 为什么不是三次?

    • 如果服务器收到FIN后立即关闭,可能还有数据未发送
    • 需要先确认FIN,发送完数据后再发送FIN

总结:

  • 全双工通信需要分别关闭
  • 可能还有数据要发送
  • 四次挥手保证数据完整传输

16.10 TCP的连接状态有哪些?

答案:

TCP的连接状态:

  1. CLOSED:关闭状态
  2. LISTEN:监听状态(服务器)
  3. SYN_SENT:已发送SYN(客户端)
  4. SYN_RCVD:已接收SYN(服务器)
  5. ESTABLISHED:已建立连接
  6. FIN_WAIT_1:等待FIN确认
  7. FIN_WAIT_2:等待对方FIN
  8. CLOSE_WAIT:等待关闭
  9. CLOSING:正在关闭
  10. LAST_ACK:最后确认
  11. TIME_WAIT:时间等待

状态转换:

CLOSED → LISTEN(服务器监听)
CLOSED → SYN_SENT(客户端连接)
SYN_SENT → ESTABLISHED(连接建立)
ESTABLISHED → FIN_WAIT_1(开始关闭)
FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED

总结:

  • TCP有多种连接状态
  • 状态转换反映连接生命周期
  • 系统自动管理状态

16.11 TCP的状态转换图是什么?

答案:

TCP状态转换图(详见16.10题答案):

主要状态转换:

  • 建立连接:CLOSED → SYN_SENT → ESTABLISHED
  • 关闭连接:ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED

总结:

  • 状态转换图描述TCP连接生命周期
  • 包括建立和关闭过程
  • 系统自动管理

16.12 Java中如何创建TCP Socket?

答案:

Java中创建TCP Socket的方法(详见15.15题答案):

// 方式1:直接创建并连接
Socket socket = new Socket("www.example.com", 80);

// 方式2:先创建后连接
Socket socket = new Socket();
socket.connect(new InetSocketAddress("www.example.com", 80));

总结:

  • 使用Socket类创建TCP Socket
  • 可以指定地址和端口
  • 系统自动处理连接建立

16.13 如何实现TCP客户端?

答案:

实现TCP客户端的方法:

// TCP客户端
public class TCPClient {
    public static void main(String[] args) {
        try {
            // 1. 创建Socket并连接服务器
            Socket socket = new Socket("server.com", 8080);
            
            // 2. 获取输入输出流
            OutputStream out = socket.getOutputStream();
            InputStream in = socket.getInputStream();
            
            // 3. 发送数据
            String message = "Hello Server";
            out.write(message.getBytes());
            out.flush();
            
            // 4. 接收数据
            byte[] buffer = new byte[1024];
            int length = in.read(buffer);
            String response = new String(buffer, 0, length);
            System.out.println("Response: " + response);
            
            // 5. 关闭连接
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Android代码示例:

// Android TCP客户端
new Thread(() -> {
    try {
        Socket socket = new Socket("server.com", 8080);
        OutputStream out = socket.getOutputStream();
        InputStream in = socket.getInputStream();
        
        // 发送和接收数据
        out.write("Hello".getBytes());
        byte[] buffer = new byte[1024];
        int length = in.read(buffer);
        
        socket.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}).start();

总结:

  • 创建Socket并连接
  • 获取输入输出流
  • 发送和接收数据
  • 关闭连接

16.14 如何实现TCP服务端?

答案:

实现TCP服务端的方法:

// TCP服务端
public class TCPServer {
    public static void main(String[] args) {
        try {
            // 1. 创建ServerSocket并绑定端口
            ServerSocket serverSocket = new ServerSocket(8080);
            
            while (true) {
                // 2. 监听并接受连接
                Socket clientSocket = serverSocket.accept();
                
                // 3. 处理客户端连接(多线程)
                new Thread(() -> {
                    try {
                        // 获取输入输出流
                        InputStream in = clientSocket.getInputStream();
                        OutputStream out = clientSocket.getOutputStream();
                        
                        // 接收数据
                        byte[] buffer = new byte[1024];
                        int length = in.read(buffer);
                        String request = new String(buffer, 0, length);
                        
                        // 发送响应
                        String response = "Hello Client";
                        out.write(response.getBytes());
                        out.flush();
                        
                        // 关闭客户端连接
                        clientSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Android代码示例:

// Android TCP服务端(需要在后台线程)
new Thread(() -> {
    try {
        ServerSocket serverSocket = new ServerSocket(8080);
        while (true) {
            Socket clientSocket = serverSocket.accept();
            // 处理客户端
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}).start();

总结:

  • 创建ServerSocket并监听
  • 接受客户端连接
  • 多线程处理多个客户端
  • 处理完关闭客户端连接

16.15 TCP Socket的阻塞模式是什么?

答案:

TCP Socket的阻塞模式是默认模式,操作会阻塞直到完成

阻塞操作:

  • connect():阻塞直到连接建立或失败
  • accept():阻塞直到有客户端连接
  • read():阻塞直到有数据可读
  • write():阻塞直到数据写入缓冲区

阻塞模式特点:

  • 简单易用
  • 线程阻塞等待
  • 需要多线程处理多个连接

Android代码示例:

// 阻塞模式(默认)
Socket socket = new Socket("server.com", 8080);  // 阻塞直到连接
InputStream in = socket.getInputStream();
int data = in.read();  // 阻塞直到有数据

总结:

  • 阻塞模式是默认模式
  • 操作会阻塞直到完成
  • 简单但需要多线程

16.16 TCP Socket的非阻塞模式是什么?

答案:

TCP Socket的非阻塞模式是操作立即返回,不阻塞

非阻塞模式:

  • 使用NIO(New I/O)
  • 操作立即返回
  • 需要轮询或事件驱动

非阻塞模式特点:

  • 不阻塞线程
  • 可以单线程处理多个连接
  • 实现复杂

Android代码示例:

// 非阻塞模式(NIO)
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);  // 设置为非阻塞
channel.connect(new InetSocketAddress("server.com", 8080));

// 需要轮询或使用Selector
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_CONNECT);

总结:

  • 非阻塞模式不阻塞线程
  • 使用NIO实现
  • 可以单线程处理多个连接

16.17 阻塞和非阻塞的区别是什么?

答案:

阻塞和非阻塞的区别:

  1. 阻塞模式

    • 操作阻塞直到完成
    • 简单易用
    • 需要多线程
  2. 非阻塞模式

    • 操作立即返回
    • 不阻塞线程
    • 可以单线程处理多个连接

对比表:

特性阻塞模式非阻塞模式
操作方式阻塞直到完成立即返回
线程使用需要多线程可以单线程
实现复杂度简单复杂
性能一般好(高并发)

总结:

  • 阻塞:简单但需要多线程
  • 非阻塞:复杂但性能好
  • 根据需求选择

16.18 TCP Socket的NIO是什么?

答案:

TCP Socket的NIO(New I/O)是Java的非阻塞I/O API

NIO特点:

  • 非阻塞I/O
  • 通道(Channel)和缓冲区(Buffer)
  • 选择器(Selector)
  • 可以单线程处理多个连接

NIO组件:

  • SocketChannel:TCP Socket通道
  • ServerSocketChannel:TCP Server Socket通道
  • Selector:选择器,管理多个通道
  • Buffer:缓冲区

Android代码示例:

// NIO示例
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select();  // 阻塞直到有事件
    Set<SelectionKey> keys = selector.selectedKeys();
    for (SelectionKey key : keys) {
        if (key.isAcceptable()) {
            // 接受连接
        } else if (key.isReadable()) {
            // 读取数据
        }
    }
}

总结:

  • NIO是非阻塞I/O
  • 使用Channel和Selector
  • 可以单线程处理多个连接
  • 适合高并发场景

16.19 TCP的Keep-Alive机制是什么?

答案:

TCP的Keep-Alive机制是检测连接是否存活的机制

Keep-Alive作用:

  • 检测死连接
  • 保持连接活跃
  • 自动关闭死连接

Keep-Alive原理:

  • 定期发送探测包
  • 如果无响应,认为连接断开
  • 关闭连接

Android代码示例:

// 设置Keep-Alive
Socket socket = new Socket("server.com", 8080);
socket.setKeepAlive(true);  // 启用Keep-Alive

总结:

  • Keep-Alive检测连接存活
  • 定期发送探测包
  • 自动关闭死连接

16.20 如何设置TCP的Keep-Alive?

答案:

设置TCP Keep-Alive的方法:

// 设置Keep-Alive
Socket socket = new Socket("server.com", 8080);
socket.setKeepAlive(true);  // 启用Keep-Alive

// 设置超时
socket.setSoTimeout(5000);  // 5秒超时

系统参数:

  • tcp_keepalive_time:空闲时间(默认7200秒)
  • tcp_keepalive_intvl:探测间隔(默认75秒)
  • tcp_keepalive_probes:探测次数(默认9次)

总结:

  • 使用setKeepAlive(true)启用
  • 可以设置超时
  • 系统参数控制具体行为

16.21 如何设置TCP的缓冲区大小?

答案:

设置TCP缓冲区大小的方法:

// 设置发送缓冲区大小
Socket socket = new Socket("server.com", 8080);
socket.setSendBufferSize(8192);  // 8KB

// 设置接收缓冲区大小
socket.setReceiveBufferSize(8192);  // 8KB

// 获取缓冲区大小
int sendBufferSize = socket.getSendBufferSize();
int receiveBufferSize = socket.getReceiveBufferSize();

最佳实践:

  • 根据应用需求设置
  • 不要设置太小(影响性能)
  • 不要设置太大(浪费内存)

总结:

  • 使用setSendBufferSize和setReceiveBufferSize
  • 根据需求设置大小
  • 平衡性能和内存

16.22 如何处理TCP粘包问题?

答案:

处理TCP粘包问题的方法:

TCP粘包原因:

  • TCP是流式传输,无边界
  • 多个数据包可能粘在一起
  • 需要应用层处理边界

解决方法:

  1. 固定长度

    // 固定长度协议
    byte[] buffer = new byte[1024];  // 固定1024字节
    int length = in.read(buffer);
    
  2. 长度前缀

    // 长度前缀协议
    // 前4字节是长度,后面是数据
    byte[] lengthBytes = new byte[4];
    in.read(lengthBytes);
    int length = ByteBuffer.wrap(lengthBytes).getInt();
    byte[] data = new byte[length];
    in.read(data);
    
  3. 分隔符

    // 分隔符协议(如换行符)
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(in));
    String line = reader.readLine();  // 按行读取
    
  4. 协议头

    // 协议头包含长度信息
    // 读取协议头,获取数据长度,再读取数据
    

总结:

  • TCP粘包需要应用层处理
  • 使用固定长度、长度前缀、分隔符等方法
  • 根据协议选择合适方法

16.23 TCP的Nagle算法是什么?

答案:

Nagle算法是TCP的拥塞控制算法,减少小数据包

Nagle算法原理:

  • 如果数据小于MSS,等待
  • 等待ACK或数据积累到MSS
  • 减少小数据包,提高效率

Nagle算法作用:

  • 减少网络小包
  • 提高网络效率
  • 可能增加延迟

禁用Nagle算法:

// 禁用Nagle算法(TCP_NODELAY)
Socket socket = new Socket("server.com", 8080);
socket.setTcpNoDelay(true);  // 禁用Nagle算法

使用场景:

  • 实时应用:禁用Nagle(减少延迟)
  • 批量传输:启用Nagle(提高效率)

总结:

  • Nagle算法减少小数据包
  • 可以提高效率但可能增加延迟
  • 实时应用可以禁用

第十七章:TCP 与 UDP 对比(10 题)

17.1 TCP和UDP的定义是什么?

答案:

  • TCP(Transmission Control Protocol):传输控制协议,面向连接的可靠传输协议
  • UDP(User Datagram Protocol):用户数据报协议,无连接的快速传输协议

总结:

  • TCP:可靠,面向连接
  • UDP:快速,无连接

17.2 TCP和UDP属于OSI模型的哪一层?

答案:

TCP和UDP都属于OSI模型的第4层(传输层)

总结:

  • 都属于传输层
  • 提供端到端传输服务

17.3 TCP和UDP属于TCP/IP模型的哪一层?

答案:

TCP和UDP都属于TCP/IP模型的第3层(传输层)

总结:

  • 都属于传输层
  • 在IP层之上

17.4 TCP和UDP的区别是什么?

答案:

TCP和UDP的主要区别(详见15.9题答案):

  1. 可靠性:TCP可靠,UDP不可靠
  2. 连接:TCP面向连接,UDP无连接
  3. 传输:TCP流式,UDP数据报
  4. 性能:TCP较慢,UDP较快

总结:

  • TCP:可靠但慢
  • UDP:快速但不可靠
  • 根据需求选择

17.5 TCP和UDP的可靠性有什么区别?

答案:

TCP和UDP的可靠性区别:

  • TCP:可靠,保证数据到达和顺序
  • UDP:不可靠,不保证数据到达和顺序

总结:

  • TCP有确认、重传等机制保证可靠性
  • UDP没有可靠性保证
  • 需要可靠性时选择TCP

17.6 TCP和UDP的连接方式有什么区别?

答案:

TCP和UDP的连接方式区别:

  • TCP:面向连接,需要建立连接(三次握手)
  • UDP:无连接,直接发送数据

总结:

  • TCP需要建立连接
  • UDP直接发送
  • TCP有连接开销,UDP无连接开销

17.7 TCP和UDP的传输速度有什么区别?

答案:

TCP和UDP的传输速度区别:

  • TCP:较慢(有连接开销、确认重传等)
  • UDP:较快(无连接开销、无确认重传)

总结:

  • TCP有额外开销,速度较慢
  • UDP无额外开销,速度较快
  • 需要速度时选择UDP

17.8 TCP和UDP的适用场景有什么区别?

答案:

TCP和UDP的适用场景区别:

TCP适用场景:

  • 需要可靠性:文件传输、HTTP、数据库
  • 需要顺序:文本传输、数据同步

UDP适用场景:

  • 实时性要求高:视频直播、语音通话、游戏
  • 可以容忍丢失:DNS查询、广播

总结:

  • TCP:可靠性要求高的场景
  • UDP:实时性要求高的场景
  • 根据需求选择

17.9 什么时候使用TCP?

答案:

使用TCP的场景(详见15.12题答案):

  • 需要可靠性
  • 需要顺序
  • 长连接
  • HTTP、FTP、数据库等

总结:

  • 需要可靠性时使用TCP
  • 需要顺序时使用TCP
  • 长连接场景使用TCP

17.10 什么时候使用UDP?

答案:

使用UDP的场景(详见15.13题答案):

  • 实时性要求高
  • 可以容忍丢失
  • 低延迟要求
  • 视频直播、游戏、DNS等

总结:

  • 实时性要求高时使用UDP
  • 可以容忍丢失时使用UDP
  • 低延迟要求时使用UDP

第十八章:UDP Socket(17 题)

18.1 UDP是什么?它的全称是什么?

答案:

UDP(User Datagram Protocol):用户数据报协议,是无连接的、快速的传输层协议。

UDP定义:

  • 传输层协议
  • 无连接
  • 快速传输
  • 数据报传输

UDP特点:

  • 不可靠:不保证数据到达
  • 无连接:直接发送数据
  • 快速:无连接开销
  • 数据报:有边界的数据包

Android代码示例:

// UDP Socket
DatagramSocket socket = new DatagramSocket();
// 使用UDP协议

总结:

  • UDP是用户数据报协议
  • 无连接、快速传输
  • 适合实时应用

18.2 UDP属于OSI模型的哪一层?

答案:

UDP属于OSI模型的第4层(传输层)

总结:

  • UDP属于传输层
  • 与TCP同层

18.3 UDP属于TCP/IP模型的哪一层?

答案:

UDP属于TCP/IP模型的第3层(传输层)

总结:

  • UDP属于传输层
  • 在IP层之上

18.4 UDP的特点是什么?

答案:

UDP的特点(详见15.11题答案):

  1. 不可靠:不保证数据到达和顺序
  2. 无连接:不需要建立连接
  3. 数据报传输:有边界的数据包
  4. 快速:无连接开销

总结:

  • 不可靠但快速
  • 无连接
  • 数据报传输
  • 适合实时应用

18.5 UDP的适用场景是什么?

答案:

UDP的适用场景(详见15.13题答案):

  • 实时性要求高:视频直播、语音通话、游戏
  • 可以容忍丢失:DNS查询、广播
  • 低延迟要求:游戏、实时通信

总结:

  • 实时性要求高时使用UDP
  • 可以容忍丢失时使用UDP
  • 低延迟要求时使用UDP

18.6 UDP的数据包大小限制是什么?

答案:

UDP数据包大小限制:

  1. 理论最大

    • UDP头部:8字节
    • IP头部:20字节(IPv4)或40字节(IPv6)
    • 最大数据:65507字节(IPv4)或65527字节(IPv6)
  2. 实际限制

    • MTU限制:通常1500字节(以太网)
    • 实际数据:约1472字节(1500 - 20 - 8)
    • 超过MTU会分片
  3. 最佳实践

    • 建议不超过1472字节
    • 避免分片
    • 提高效率

Android代码示例:

// UDP数据包大小
byte[] data = new byte[1024];  // 建议不超过1472字节
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);

总结:

  • 理论最大65507字节
  • 实际建议不超过1472字节
  • 避免分片

18.7 UDP的可靠性如何保证?

答案:

UDP本身不保证可靠性,需要在应用层实现:

  1. 应用层实现

    • 序列号
    • 确认机制
    • 重传机制
  2. 使用可靠UDP库

    • 使用成熟的UDP库
    • 库实现可靠性
  3. 混合方案

    • 关键数据使用TCP
    • 非关键数据使用UDP

总结:

  • UDP本身不保证可靠性
  • 需要在应用层实现
  • 或使用可靠UDP库

18.8 UDP的优缺点是什么?

答案:

UDP的优缺点:

优点:

  • 快速:无连接开销
  • 简单:实现简单
  • 低延迟:适合实时应用

缺点:

  • 不可靠:不保证数据到达
  • 无顺序:不保证数据顺序
  • 无流量控制:可能丢包

总结:

  • 优点:快速、简单、低延迟
  • 缺点:不可靠、无顺序、无流量控制

18.9 Java中如何创建UDP Socket?

答案:

Java中创建UDP Socket的方法:

// 创建UDP Socket
DatagramSocket socket = new DatagramSocket();

// 绑定端口
DatagramSocket socket = new DatagramSocket(8080);

// 绑定地址和端口
DatagramSocket socket = new DatagramSocket(
    new InetSocketAddress("192.168.1.100", 8080));

Android代码示例:

// Android UDP Socket
DatagramSocket socket = new DatagramSocket(8080);

总结:

  • 使用DatagramSocket创建UDP Socket
  • 可以绑定端口
  • 可以绑定地址和端口

18.10 如何实现UDP客户端?

答案:

实现UDP客户端的方法:

// UDP客户端
public class UDPClient {
    public static void main(String[] args) {
        try {
            // 1. 创建UDP Socket
            DatagramSocket socket = new DatagramSocket();
            
            // 2. 准备数据
            String message = "Hello Server";
            byte[] data = message.getBytes();
            InetAddress serverAddress = InetAddress.getByName("server.com");
            int serverPort = 8080;
            
            // 3. 创建数据包并发送
            DatagramPacket packet = new DatagramPacket(
                data, data.length, serverAddress, serverPort);
            socket.send(packet);
            
            // 4. 接收响应
            byte[] buffer = new byte[1024];
            DatagramPacket responsePacket = new DatagramPacket(buffer, buffer.length);
            socket.receive(responsePacket);
            
            String response = new String(responsePacket.getData(), 
                0, responsePacket.getLength());
            System.out.println("Response: " + response);
            
            // 5. 关闭Socket
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Android代码示例:

// Android UDP客户端
new Thread(() -> {
    try {
        DatagramSocket socket = new DatagramSocket();
        byte[] data = "Hello".getBytes();
        DatagramPacket packet = new DatagramPacket(data, data.length, 
            InetAddress.getByName("server.com"), 8080);
        socket.send(packet);
        socket.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}).start();

总结:

  • 创建DatagramSocket
  • 创建DatagramPacket并发送
  • 接收响应
  • 关闭Socket

18.11 如何实现UDP服务端?

答案:

实现UDP服务端的方法:

// UDP服务端
public class UDPServer {
    public static void main(String[] args) {
        try {
            // 1. 创建UDP Socket并绑定端口
            DatagramSocket socket = new DatagramSocket(8080);
            
            while (true) {
                // 2. 接收数据包
                byte[] buffer = new byte[1024];
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                socket.receive(packet);  // 阻塞接收
                
                // 3. 获取客户端信息
                InetAddress clientAddress = packet.getAddress();
                int clientPort = packet.getPort();
                String request = new String(packet.getData(), 0, packet.getLength());
                
                // 4. 处理请求并发送响应
                String response = "Hello Client";
                byte[] responseData = response.getBytes();
                DatagramPacket responsePacket = new DatagramPacket(
                    responseData, responseData.length, clientAddress, clientPort);
                socket.send(responsePacket);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        socket.close();
    }
}

Android代码示例:

// Android UDP服务端(需要在后台线程)
new Thread(() -> {
    try {
        DatagramSocket socket = new DatagramSocket(8080);
        while (true) {
            byte[] buffer = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
            socket.receive(packet);
            // 处理数据
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}).start();

总结:

  • 创建DatagramSocket并绑定端口
  • 循环接收数据包
  • 处理请求并发送响应
  • 可以处理多个客户端

18.12 UDP的DatagramPacket是什么?

答案:

DatagramPacket是UDP数据包的封装类

DatagramPacket作用:

  • 封装UDP数据包
  • 包含数据和目标地址
  • 用于发送和接收

DatagramPacket组成:

  • 数据:byte[]
  • 数据长度:int
  • 目标地址:InetAddress
  • 目标端口:int

Android代码示例:

// 创建发送数据包
byte[] data = "Hello".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, 
    InetAddress.getByName("server.com"), 8080);

// 创建接收数据包
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);

总结:

  • DatagramPacket封装UDP数据包
  • 包含数据和地址信息
  • 用于发送和接收

18.13 UDP的DatagramSocket是什么?

答案:

DatagramSocket是UDP Socket的封装类

DatagramSocket作用:

  • 创建UDP Socket
  • 发送和接收数据包
  • 管理UDP连接

DatagramSocket方法:

  • send(DatagramPacket):发送数据包
  • receive(DatagramPacket):接收数据包
  • close():关闭Socket

Android代码示例:

// 创建DatagramSocket
DatagramSocket socket = new DatagramSocket(8080);

// 发送数据包
socket.send(packet);

// 接收数据包
socket.receive(packet);

// 关闭Socket
socket.close();

总结:

  • DatagramSocket是UDP Socket封装
  • 用于发送和接收数据包
  • 管理UDP通信

18.14 UDP的广播是什么?

答案:

UDP广播是向同一网络内所有主机发送数据

广播特点:

  • 向所有主机发送
  • 使用广播地址(255.255.255.255)
  • 只在本地网络有效

广播地址:

  • 255.255.255.255:本地网络广播
  • 192.168.1.255:子网广播

Android代码示例:

// UDP广播
DatagramSocket socket = new DatagramSocket();
byte[] data = "Broadcast Message".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length,
    InetAddress.getByName("255.255.255.255"), 8080);
socket.setBroadcast(true);  // 启用广播
socket.send(packet);

总结:

  • UDP广播向所有主机发送
  • 使用广播地址
  • 只在本地网络有效

18.15 如何实现UDP广播?

答案:

实现UDP广播的方法:

// UDP广播发送
DatagramSocket socket = new DatagramSocket();
socket.setBroadcast(true);  // 启用广播

byte[] data = "Broadcast Message".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length,
    InetAddress.getByName("255.255.255.255"), 8080);
socket.send(packet);

// UDP广播接收
DatagramSocket socket = new DatagramSocket(8080);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);  // 接收广播消息

总结:

  • 设置setBroadcast(true)
  • 使用广播地址发送
  • 绑定端口接收

18.16 UDP的组播(多播)是什么?

答案:

UDP组播(Multicast)是向一组主机发送数据

组播特点:

  • 向组播组发送
  • 使用组播地址(224.0.0.0 - 239.255.255.255)
  • 只有加入组的主机接收

组播地址:

  • 224.0.0.0 - 224.0.0.255:本地网络
  • 224.0.1.0 - 238.255.255.255:全局
  • 239.0.0.0 - 239.255.255.255:本地管理

Android代码示例:

// UDP组播发送
MulticastSocket socket = new MulticastSocket();
byte[] data = "Multicast Message".getBytes();
InetAddress group = InetAddress.getByName("230.0.0.1");
DatagramPacket packet = new DatagramPacket(data, data.length, group, 8080);
socket.send(packet);

// UDP组播接收
MulticastSocket socket = new MulticastSocket(8080);
InetAddress group = InetAddress.getByName("230.0.0.1");
socket.joinGroup(group);  // 加入组播组
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
socket.leaveGroup(group);  // 离开组播组

总结:

  • UDP组播向组播组发送
  • 使用组播地址
  • 需要加入组播组才能接收

18.17 如何实现UDP组播?

答案:

实现UDP组播的方法(详见18.16题答案):

// 使用MulticastSocket
MulticastSocket socket = new MulticastSocket(8080);
InetAddress group = InetAddress.getByName("230.0.0.1");
socket.joinGroup(group);  // 加入组播组
// 发送和接收数据
socket.leaveGroup(group);  // 离开组播组

总结:

  • 使用MulticastSocket
  • 加入组播组
  • 发送和接收数据
  • 离开组播组

第十九章:Socket 高级特性(10 题)

19.1 Socket的状态有哪些?

答案:

Socket的状态(详见16.10题答案):

  • CLOSED、LISTEN、SYN_SENT、SYN_RCVD、ESTABLISHED
  • FIN_WAIT_1、FIN_WAIT_2、CLOSE_WAIT、CLOSING、LAST_ACK、TIME_WAIT

总结:

  • Socket有多种状态
  • 反映连接生命周期
  • 系统自动管理

19.2 如何检测Socket连接状态?

答案:

检测Socket连接状态的方法:

// 方法1:检查Socket是否关闭
boolean isClosed = socket.isClosed();

// 方法2:检查Socket是否连接
boolean isConnected = socket.isConnected();

// 方法3:发送心跳包检测
try {
    socket.sendUrgentData(0);  // 发送紧急数据
    // 如果成功,连接正常
} catch (IOException e) {
    // 连接断开
}

// 方法4:尝试读取(设置超时)
socket.setSoTimeout(1000);
try {
    int data = socket.getInputStream().read();
    // 连接正常
} catch (SocketTimeoutException e) {
    // 超时,但连接可能正常
} catch (IOException e) {
    // 连接断开
}

总结:

  • 使用isClosed()和isConnected()
  • 发送心跳包检测
  • 尝试读取检测

19.3 Socket连接异常如何处理?

答案:

Socket连接异常处理(详见15.22题答案):

  • 捕获IOException、SocketException等
  • 使用try-finally确保关闭
  • 记录日志和显示错误提示

总结:

  • 捕获和处理各种异常
  • 使用try-finally确保资源释放
  • 记录日志和显示错误提示

19.4 Socket超时如何设置?

答案:

设置Socket超时的方法:

// 设置连接超时
Socket socket = new Socket();
socket.connect(new InetSocketAddress("server.com", 8080), 5000);  // 5秒超时

// 设置读取超时
socket.setSoTimeout(5000);  // 5秒超时

// 设置发送超时(通过SocketOptions)
socket.setOption(StandardSocketOptions.SO_SNDTIMEO, Duration.ofSeconds(5));

Android代码示例:

// Android Socket超时设置
Socket socket = new Socket();
socket.connect(new InetSocketAddress("server.com", 8080), 5000);
socket.setSoTimeout(5000);

总结:

  • 设置连接超时
  • 设置读取超时
  • 设置发送超时
  • 避免无限等待

19.5 Socket的心跳机制是什么?

答案:

Socket心跳机制是定期发送数据检测连接是否存活

心跳机制作用:

  • 检测连接是否断开
  • 保持连接活跃
  • 防止连接被中间设备关闭

实现方法:

// 心跳机制
new Thread(() -> {
    while (socket.isConnected()) {
        try {
            Thread.sleep(30000);  // 30秒
            socket.sendUrgentData(0);  // 发送心跳
        } catch (Exception e) {
            // 连接断开
            break;
        }
    }
}).start();

总结:

  • 定期发送心跳包
  • 检测连接状态
  • 保持连接活跃

19.6 如何优化Socket性能?

答案:

优化Socket性能的方法:

  1. 使用连接池

    • 复用连接
    • 减少连接建立开销
  2. 调整缓冲区大小

    • 设置合适的缓冲区
    • 平衡性能和内存
  3. 使用NIO

    • 非阻塞I/O
    • 单线程处理多个连接
  4. 批量操作

    • 批量发送数据
    • 减少系统调用

总结:

  • 使用连接池
  • 调整缓冲区
  • 使用NIO
  • 批量操作

19.7 Socket的连接池是什么?

答案:

Socket连接池是管理和复用Socket连接的机制

连接池作用:

  • 复用连接
  • 减少连接建立开销
  • 提高性能

实现方法:

// 简单的连接池
public class SocketPool {
    private Queue<Socket> pool = new LinkedList<>();
    private int maxSize = 10;
    
    public Socket getSocket(String host, int port) throws IOException {
        Socket socket = pool.poll();
        if (socket == null || socket.isClosed()) {
            socket = new Socket(host, port);
        }
        return socket;
    }
    
    public void returnSocket(Socket socket) {
        if (socket != null && socket.isConnected() && pool.size() < maxSize) {
            pool.offer(socket);
        } else {
            try {
                socket.close();
            } catch (IOException e) {
                // 忽略
            }
        }
    }
}

总结:

  • 连接池管理和复用连接
  • 减少连接建立开销
  • 提高性能

19.8 如何实现Socket连接池?

答案:

实现Socket连接池的方法(详见19.7题答案):

  • 使用队列管理连接
  • 获取时从池中取,没有则创建
  • 归还时放回池中
  • 管理连接生命周期

总结:

  • 使用队列管理
  • 复用连接
  • 管理生命周期

19.9 Socket的复用是什么?

答案:

Socket复用是复用同一个Socket连接发送多个请求

Socket复用方式:

  • 连接复用:多个请求使用同一个连接
  • 端口复用:SO_REUSEADDR选项

端口复用:

// 设置端口复用
ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);
serverSocket.bind(new InetSocketAddress(8080));

总结:

  • Socket复用减少连接开销
  • 连接复用和端口复用
  • 提高性能

19.10 Socket编程的最佳实践是什么?

答案:

Socket编程的最佳实践:

  1. 资源管理

    • 使用try-finally确保关闭
    • 及时释放资源
  2. 异常处理

    • 捕获和处理异常
    • 记录日志
  3. 超时设置

    • 设置连接和读取超时
    • 避免无限等待
  4. 多线程

    • 服务器使用多线程处理
    • 避免阻塞主线程
  5. 性能优化

    • 使用连接池
    • 调整缓冲区
    • 使用NIO(高并发)

总结:

  • 资源管理、异常处理、超时设置
  • 多线程、性能优化
  • 遵循最佳实践

第四部分:WebSocket 协议答案

第二十章:WebSocket 基础(17 题)

20.1 WebSocket是什么?它的特点是什么?

答案:

WebSocket基于TCP的全双工通信协议,提供持久的连接和实时通信能力。

WebSocket特点:

  1. 全双工通信

    • 客户端和服务器可以同时发送数据
    • 双向实时通信
  2. 持久连接

    • 建立一次连接,持续使用
    • 不需要每次请求都建立连接
  3. 低开销

    • 握手后只有数据帧
    • 比HTTP轮询效率高
  4. 实时性

    • 服务器可以主动推送数据
    • 适合实时应用

WebSocket vs HTTP:

  • HTTP:请求-响应模式,服务器不能主动推送
  • WebSocket:全双工通信,服务器可以主动推送

Android代码示例:

// 使用OkHttp WebSocket
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url("ws://www.example.com/websocket")
    .build();
WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        // 连接打开
    }
    
    @Override
    public void onMessage(WebSocket webSocket, String text) {
        // 接收文本消息
    }
    
    @Override
    public void onMessage(WebSocket webSocket, ByteString bytes) {
        // 接收二进制消息
    }
    
    @Override
    public void onClosing(WebSocket webSocket, int code, String reason) {
        // 正在关闭
    }
    
    @Override
    public void onClosed(WebSocket webSocket, int code, String reason) {
        // 连接关闭
    }
    
    @Override
    public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        // 连接失败
    }
});

总结:

  • WebSocket是全双工通信协议
  • 持久连接,实时通信
  • 服务器可以主动推送
  • 适合实时应用

20.2 WebSocket属于OSI模型的哪一层?

答案:

WebSocket属于OSI模型的第7层(应用层)

详细说明:

  • WebSocket是应用层协议
  • 基于TCP(传输层)
  • 在HTTP之上

OSI模型分层:

7. 应用层:WebSocket、HTTP
6. 表示层
5. 会话层
4. 传输层:TCP
3. 网络层:IP
2. 数据链路层
1. 物理层

总结:

  • WebSocket属于应用层
  • 基于TCP传输层
  • 与HTTP同层

20.3 WebSocket属于TCP/IP模型的哪一层?

答案:

WebSocket属于TCP/IP模型的第4层(应用层)

TCP/IP模型分层:

4. 应用层:WebSocket、HTTP
3. 传输层:TCP
2. 网络层:IP
1. 网络接口层

总结:

  • WebSocket属于应用层
  • 基于TCP传输层

20.4 WebSocket和HTTP的区别是什么?

答案:

WebSocket和HTTP的主要区别:

  1. 通信模式

    • HTTP:请求-响应模式
    • WebSocket:全双工通信
  2. 连接方式

    • HTTP:短连接(每次请求建立连接)
    • WebSocket:长连接(建立一次持续使用)
  3. 服务器推送

    • HTTP:服务器不能主动推送
    • WebSocket:服务器可以主动推送
  4. 协议升级

    • HTTP:标准HTTP协议
    • WebSocket:通过HTTP升级
  5. 数据格式

    • HTTP:请求头、响应头、正文
    • WebSocket:数据帧

对比表:

特性HTTPWebSocket
通信模式请求-响应全双工
连接方式短连接长连接
服务器推送不支持支持
协议HTTPWebSocket(基于HTTP升级)
数据格式请求/响应数据帧

总结:

  • HTTP:请求-响应,短连接
  • WebSocket:全双工,长连接,支持服务器推送

20.5 WebSocket的适用场景是什么?

答案:

WebSocket的适用场景:

  1. 实时通信

    • 聊天应用
    • 即时消息
    • 实时通知
  2. 实时数据推送

    • 股票行情
    • 实时监控
    • 数据流
  3. 游戏

    • 实时游戏
    • 多人游戏
    • 实时同步
  4. 协作应用

    • 实时协作编辑
    • 实时白板
    • 实时共享

使用场景:

  • 聊天应用
  • 实时通知
  • 股票行情
  • 游戏
  • 实时监控
  • 协作应用

总结:

  • 实时通信场景
  • 服务器需要主动推送的场景
  • 需要低延迟的场景

20.6 WebSocket的协议是什么?

答案:

WebSocket的协议是WebSocket协议(ws://或wss://)

协议标识:

  • ws://:WebSocket协议(非加密)
  • wss://:WebSocket Secure协议(加密,基于TLS)

协议关系:

  • WebSocket基于TCP
  • 通过HTTP升级到WebSocket
  • wss://基于TLS加密

URL格式:

ws://www.example.com/websocket
wss://www.example.com/websocket

Android代码示例:

// WebSocket URL
String wsUrl = "ws://www.example.com/websocket";
String wssUrl = "wss://www.example.com/websocket";  // 加密

总结:

  • WebSocket协议:ws://或wss://
  • 基于TCP
  • wss://基于TLS加密

20.7 WebSocket的默认端口号是什么?

答案:

WebSocket的默认端口号:

  • ws://:默认端口 80(与HTTP相同)
  • wss://:默认端口 443(与HTTPS相同)

端口使用:

ws://www.example.com        # 默认80端口
ws://www.example.com:80     # 显式指定80端口
ws://www.example.com:8080   # 使用8080端口

wss://www.example.com       # 默认443端口
wss://www.example.com:443   # 显式指定443端口
wss://www.example.com:8443  # 使用8443端口

Android代码示例:

// WebSocket默认端口
String wsUrl = "ws://www.example.com/websocket";  // 80端口
String wssUrl = "wss://www.example.com/websocket";  // 443端口

总结:

  • ws://默认80端口
  • wss://默认443端口
  • 可以指定其他端口

20.8 WebSocket的优势是什么?

答案:

WebSocket的优势:

  1. 实时性

    • 服务器可以主动推送
    • 低延迟
    • 实时通信
  2. 效率高

    • 长连接,减少连接开销
    • 数据帧开销小
    • 比HTTP轮询效率高
  3. 全双工

    • 双向通信
    • 客户端和服务器可以同时发送
  4. 协议简单

    • 数据帧格式简单
    • 易于实现

对比HTTP轮询:

  • HTTP轮询:需要不断请求,效率低
  • WebSocket:建立一次连接,持续通信,效率高

总结:

  • 实时性、效率高、全双工、协议简单
  • 比HTTP轮询效率高
  • 适合实时应用

20.9 WebSocket的协议版本有哪些?

答案:

WebSocket的协议版本:

  1. RFC 6455(当前标准)

    • WebSocket协议标准
    • 2011年发布
    • 当前使用版本
  2. 草案版本

    • 多个草案版本
    • 已废弃
    • 不再使用

协议版本标识:

  • 握手时通过Sec-WebSocket-Version头指定
  • 当前标准:13(RFC 6455)

Android代码示例:

// WebSocket协议版本(自动处理)
// OkHttp自动使用RFC 6455
Request request = new Request.Builder()
    .url("ws://www.example.com/websocket")
    .build();
// 自动使用Sec-WebSocket-Version: 13

总结:

  • 当前标准:RFC 6455(版本13)
  • 草案版本已废弃
  • 系统自动处理版本

20.10 WebSocket的握手过程是什么?

答案:

WebSocket的握手过程:

客户端请求:

GET /websocket HTTP/1.1
Host: www.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

服务器响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

握手流程:

1. 客户端发送HTTP升级请求
2. 服务器验证请求
3. 服务器返回101 Switching Protocols
4. 连接升级为WebSocket
5. 开始WebSocket通信

Android代码示例:

// WebSocket握手(自动处理)
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url("ws://www.example.com/websocket")
    .build();
WebSocket webSocket = client.newWebSocket(request, listener);
// OkHttp自动处理握手

总结:

  • 通过HTTP升级请求握手
  • 服务器返回101 Switching Protocols
  • 连接升级为WebSocket
  • 系统自动处理

20.11 WebSocket握手和HTTP的关系是什么?

答案:

WebSocket握手和HTTP的关系:

  1. 基于HTTP升级

    • WebSocket握手使用HTTP请求
    • 通过Upgrade头升级协议
    • 从HTTP升级到WebSocket
  2. HTTP 101响应

    • 服务器返回101 Switching Protocols
    • 表示协议升级成功
    • 之后使用WebSocket协议
  3. 兼容性

    • 使用HTTP端口(80/443)
    • 可以通过HTTP代理
    • 兼容HTTP基础设施

关系说明:

HTTP请求(握手) → HTTP 101响应 → WebSocket协议

总结:

  • WebSocket握手基于HTTP
  • 通过HTTP升级到WebSocket
  • 兼容HTTP基础设施

20.12 WebSocket的Upgrade头是什么?

答案:

WebSocket的Upgrade头用于请求协议升级

Upgrade头:

Upgrade: websocket

作用:

  • 客户端请求升级到WebSocket
  • 服务器确认升级
  • 协议从HTTP升级到WebSocket

握手示例:

# 客户端请求
GET /websocket HTTP/1.1
Upgrade: websocket
Connection: Upgrade

# 服务器响应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

总结:

  • Upgrade头请求协议升级
  • 值为websocket
  • 用于WebSocket握手

20.13 WebSocket的Sec-WebSocket-Key是什么?

答案:

Sec-WebSocket-Key是客户端生成的随机密钥,用于WebSocket握手验证

Sec-WebSocket-Key作用:

  • 客户端生成随机Base64编码的密钥
  • 服务器验证并返回Sec-WebSocket-Accept
  • 防止缓存代理误解WebSocket握手

握手过程:

1. 客户端生成随机密钥
2. Base64编码得到Sec-WebSocket-Key
3. 发送给服务器
4. 服务器计算Sec-WebSocket-Accept
5. 返回给客户端验证

计算过程:

服务器计算:
  key = Sec-WebSocket-Key
  magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  accept = base64(sha1(key + magic))
  Sec-WebSocket-Accept = accept

Android代码示例:

// Sec-WebSocket-Key(自动生成)
// OkHttp自动生成和处理
Request request = new Request.Builder()
    .url("ws://www.example.com/websocket")
    .build();
// 自动生成Sec-WebSocket-Key

总结:

  • Sec-WebSocket-Key是客户端随机密钥
  • 用于握手验证
  • 防止缓存代理误解
  • 系统自动生成和处理

20.14 WebSocket的连接状态有哪些?

答案:

WebSocket的连接状态:

  1. CONNECTING(0)

    • 正在连接
    • 握手阶段
  2. OPEN(1)

    • 连接已打开
    • 可以发送和接收数据
  3. CLOSING(2)

    • 正在关闭
    • 关闭握手阶段
  4. CLOSED(3)

    • 连接已关闭
    • 不能再使用

状态转换:

CONNECTING → OPEN → CLOSINGCLOSED

Android代码示例:

// WebSocket状态
WebSocket webSocket = ...;
// 状态通过回调通知
// onOpen: OPEN状态
// onClosing: CLOSING状态
// onClosed: CLOSED状态

总结:

  • CONNECTING、OPEN、CLOSING、CLOSED
  • 状态转换反映连接生命周期
  • 通过回调通知状态变化

20.15 如何建立WebSocket连接?

答案:

建立WebSocket连接的方法:

使用OkHttp(推荐):

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url("ws://www.example.com/websocket")
    .build();

WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        // 连接建立成功
        webSocket.send("Hello Server");
    }
    
    @Override
    public void onMessage(WebSocket webSocket, String text) {
        // 接收消息
    }
    
    @Override
    public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        // 连接失败
    }
});

连接流程:

1. 创建WebSocket请求
2. 发送HTTP升级请求
3. 服务器返回101响应
4. 连接升级为WebSocket
5. onOpen回调通知连接成功

总结:

  • 使用OkHttp建立WebSocket连接
  • 自动处理握手
  • onOpen回调通知连接成功

20.16 如何关闭WebSocket连接?

答案:

关闭WebSocket连接的方法:

// 关闭WebSocket连接
webSocket.close(1000, "Normal closure");

// 关闭代码:
// 1000: 正常关闭
// 1001: 端点离开
// 1002: 协议错误
// 1003: 数据类型错误
// 1006: 异常关闭

关闭流程:

1. 发送关闭帧
2. 等待对方关闭帧
3. 关闭TCP连接
4. onClosed回调通知

Android代码示例:

// 关闭WebSocket
webSocket.close(1000, "Normal closure");

// 在onClosing回调中处理
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
    // 正在关闭
    webSocket.close(code, reason);
}

@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
    // 连接已关闭
}

总结:

  • 使用close()关闭连接
  • 可以指定关闭代码和原因
  • onClosing和onClosed回调通知

20.17 WebSocket的连接生命周期是什么?

答案:

WebSocket的连接生命周期:

  1. 连接建立

    • 发送HTTP升级请求
    • 服务器返回101响应
    • 连接升级为WebSocket
  2. 数据传输

    • 发送和接收数据帧
    • 双向通信
    • 实时数据交换
  3. 连接关闭

    • 发送关闭帧
    • 接收关闭帧
    • 关闭TCP连接

生命周期流程:

CONNECTING(握手)
  ↓
OPEN(数据传输)
  ↓
CLOSING(关闭握手)
  ↓
CLOSED(连接关闭)

Android代码示例:

// WebSocket生命周期
WebSocketListener listener = new WebSocketListener() {
    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        // 1. 连接建立
    }
    
    @Override
    public void onMessage(WebSocket webSocket, String text) {
        // 2. 数据传输
    }
    
    @Override
    public void onClosing(WebSocket webSocket, int code, String reason) {
        // 3. 开始关闭
    }
    
    @Override
    public void onClosed(WebSocket webSocket, int code, String reason) {
        // 4. 连接关闭
    }
};

总结:

  • CONNECTING → OPEN → CLOSING → CLOSED
  • 通过回调通知状态变化
  • 完整的生命周期管理

第二十一章:WebSocket 数据帧(15 题)

21.1 WebSocket数据帧的结构是什么?

答案:

WebSocket数据帧的结构:

数据帧格式:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

数据帧字段:

  • FIN(1位):是否最后一帧
  • RSV1-3(各1位):保留位
  • Opcode(4位):操作码
  • Mask(1位):是否掩码
  • Payload len(7位):负载长度
  • Masking-key(32位):掩码密钥(如果Mask=1)
  • Payload Data:实际数据

总结:

  • 数据帧包含FIN、RSV、Opcode、Mask等字段
  • 包含负载长度和数据
  • 客户端发送需要掩码

21.2 数据帧的FIN位是什么?

答案:

数据帧的FIN位表示是否是消息的最后一帧

FIN位:

  • FIN=1:最后一帧
  • FIN=0:还有后续帧(分片)

分片消息:

消息1: FIN=0, Opcode=Text, Data="Hello "
消息2: FIN=0, Opcode=Continuation, Data="World"
消息3: FIN=1, Opcode=Continuation, Data="!"

总结:

  • FIN=1表示最后一帧
  • FIN=0表示还有后续帧
  • 用于分片消息

21.3 数据帧的RSV位是什么?

答案:

数据帧的RSV位是保留位,用于扩展

RSV位:

  • RSV1、RSV2、RSV3:各1位
  • 当前必须为0
  • 用于未来扩展

总结:

  • RSV是保留位
  • 当前必须为0
  • 用于未来扩展

21.4 数据帧的Opcode是什么?

答案:

数据帧的Opcode是操作码,表示数据帧类型

Opcode类型:

  • 0x0:Continuation(继续帧)
  • 0x1:Text(文本帧)
  • 0x2:Binary(二进制帧)
  • 0x8:Close(关闭帧)
  • 0x9:Ping(Ping帧)
  • 0xA:Pong(Pong帧)

总结:

  • Opcode表示数据帧类型
  • 0x1-0x2:数据帧
  • 0x8-0xA:控制帧

21.5 Opcode的类型有哪些?

答案:

Opcode的类型(详见21.4题答案):

  1. 数据帧

    • 0x0:Continuation
    • 0x1:Text
    • 0x2:Binary
  2. 控制帧

    • 0x8:Close
    • 0x9:Ping
    • 0xA:Pong

总结:

  • 数据帧:0x0、0x1、0x2
  • 控制帧:0x8、0x9、0xA

21.6 数据帧的Mask位是什么?

答案:

数据帧的Mask位表示是否对负载数据进行掩码

Mask位:

  • Mask=1:客户端发送的数据必须掩码
  • Mask=0:服务器发送的数据不掩码

掩码作用:

  • 防止缓存代理误解数据
  • 客户端必须掩码
  • 服务器不掩码

总结:

  • 客户端发送必须Mask=1
  • 服务器发送Mask=0
  • 防止缓存代理误解

21.7 文本帧(Text Frame)是什么?

答案:

文本帧(Text Frame)是包含文本数据的WebSocket数据帧

文本帧特点:

  • Opcode=0x1
  • 数据是UTF-8编码的文本
  • 用于发送文本消息

Android代码示例:

// 发送文本帧
webSocket.send("Hello Server");  // 自动封装为文本帧

总结:

  • 文本帧Opcode=0x1
  • 数据是UTF-8文本
  • 用于文本消息

21.8 二进制帧(Binary Frame)是什么?

答案:

二进制帧(Binary Frame)是包含二进制数据的WebSocket数据帧

二进制帧特点:

  • Opcode=0x2
  • 数据是二进制
  • 用于发送二进制消息

Android代码示例:

// 发送二进制帧
byte[] data = {0x01, 0x02, 0x03};
webSocket.send(ByteString.of(data));  // 自动封装为二进制帧

总结:

  • 二进制帧Opcode=0x2
  • 数据是二进制
  • 用于二进制消息

21.9 关闭帧(Close Frame)是什么?

答案:

关闭帧(Close Frame)是用于关闭WebSocket连接的控制帧

关闭帧特点:

  • Opcode=0x8
  • 可以包含关闭代码和原因
  • 用于关闭连接

关闭代码:

  • 1000:正常关闭
  • 1001:端点离开
  • 1002:协议错误
  • 1003:数据类型错误
  • 1006:异常关闭

总结:

  • 关闭帧Opcode=0x8
  • 包含关闭代码和原因
  • 用于关闭连接

21.10 Ping帧和Pong帧的作用是什么?

答案:

Ping帧和Pong帧的作用:

  1. 心跳检测

    • Ping帧:检测连接是否存活
    • Pong帧:响应Ping帧
    • 保持连接活跃
  2. 连接保活

    • 定期发送Ping
    • 接收Pong确认
    • 防止连接被关闭

Ping/Pong帧:

  • Ping:Opcode=0x9
  • Pong:Opcode=0xA
  • Pong必须响应Ping

Android代码示例:

// Ping/Pong(OkHttp自动处理)
// 可以手动发送Ping
webSocket.send(ByteString.EMPTY);  // 发送Ping(需要底层支持)

总结:

  • Ping/Pong用于心跳检测
  • 保持连接活跃
  • 系统可以自动处理

21.11 控制帧和数据帧的区别是什么?

答案:

控制帧和数据帧的区别:

  1. 数据帧

    • Opcode:0x0、0x1、0x2
    • 用于传输数据
    • 可以分片
  2. 控制帧

    • Opcode:0x8、0x9、0xA
    • 用于控制连接
    • 不能分片

对比表:

特性数据帧控制帧
Opcode0x0、0x1、0x20x8、0x9、0xA
用途传输数据控制连接
分片可以不可以

总结:

  • 数据帧:传输数据,可以分片
  • 控制帧:控制连接,不能分片

21.12 如何解析WebSocket数据帧?

答案:

解析WebSocket数据帧的方法:

手动解析:

// 解析WebSocket数据帧
ByteBuffer buffer = ByteBuffer.wrap(data);
byte firstByte = buffer.get();
boolean fin = (firstByte & 0x80) != 0;
int opcode = firstByte & 0x0F;

byte secondByte = buffer.get();
boolean mask = (secondByte & 0x80) != 0;
long payloadLength = secondByte & 0x7F;

if (payloadLength == 126) {
    payloadLength = buffer.getShort() & 0xFFFF;
} else if (payloadLength == 127) {
    payloadLength = buffer.getLong();
}

byte[] maskingKey = null;
if (mask) {
    maskingKey = new byte[4];
    buffer.get(maskingKey);
}

byte[] payload = new byte[(int) payloadLength];
buffer.get(payload);

// 如果掩码,需要解码
if (mask) {
    for (int i = 0; i < payload.length; i++) {
        payload[i] ^= maskingKey[i % 4];
    }
}

使用库(推荐):

// 使用OkHttp(自动解析)
// OkHttp自动解析数据帧
@Override
public void onMessage(WebSocket webSocket, String text) {
    // 文本帧已解析
}

@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
    // 二进制帧已解析
}

总结:

  • 可以手动解析数据帧
  • 使用库自动解析(推荐)
  • OkHttp自动处理

21.13 如何构造WebSocket数据帧?

答案:

构造WebSocket数据帧的方法:

手动构造:

// 构造WebSocket数据帧
ByteBuffer buffer = ByteBuffer.allocate(1024);

// 第一个字节
byte firstByte = (byte) (0x80 | 0x01);  // FIN=1, Opcode=Text
buffer.put(firstByte);

// 第二个字节
byte[] payload = "Hello".getBytes();
byte secondByte = (byte) payload.length;
buffer.put(secondByte);

// 负载数据
buffer.put(payload);

buffer.flip();
byte[] frame = new byte[buffer.remaining()];
buffer.get(frame);

使用库(推荐):

// 使用OkHttp(自动构造)
webSocket.send("Hello");  // 自动构造文本帧
webSocket.send(ByteString.of(data));  // 自动构造二进制帧

总结:

  • 可以手动构造数据帧
  • 使用库自动构造(推荐)
  • OkHttp自动处理

21.14 数据帧的分片是什么?

答案:

数据帧的分片是将大消息分成多个数据帧发送

分片原因:

  • 消息太大,需要分片
  • 流式传输
  • 提高效率

分片规则:

  • 第一帧:FIN=0,Opcode=Text/Binary
  • 中间帧:FIN=0,Opcode=Continuation
  • 最后一帧:FIN=1,Opcode=Continuation

分片示例:

消息:"Hello World"
帧1: FIN=0, Opcode=Text, Data="Hello "
帧2: FIN=1, Opcode=Continuation, Data="World"

总结:

  • 分片将大消息分成多帧
  • 第一帧使用数据Opcode,后续使用Continuation
  • 最后一帧FIN=1

21.15 如何处理分片数据帧?

答案:

处理分片数据帧的方法:

手动处理:

// 处理分片数据帧
StringBuilder message = new StringBuilder();
boolean isFirstFrame = true;

while (true) {
    WebSocketFrame frame = receiveFrame();
    
    if (isFirstFrame) {
        // 第一帧
        message.append(frame.getPayload());
        isFirstFrame = false;
    } else {
        // 后续帧
        message.append(frame.getPayload());
    }
    
    if (frame.isFin()) {
        // 最后一帧
        break;
    }
}

String completeMessage = message.toString();

使用库(推荐):

// 使用OkHttp(自动处理分片)
// OkHttp自动处理分片,直接返回完整消息
@Override
public void onMessage(WebSocket webSocket, String text) {
    // 完整消息(已自动合并分片)
}

总结:

  • 需要收集所有分片帧
  • 合并成完整消息
  • 使用库自动处理(推荐)

第二十二章:WebSocket 应用(10 题)

22.1 Android中如何使用WebSocket?

答案:

Android中使用WebSocket的方法:

使用OkHttp(推荐):

// 添加依赖
// implementation 'com.squareup.okhttp3:okhttp:4.x.x'

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
    .url("ws://www.example.com/websocket")
    .build();

WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
    @Override
    public void onOpen(WebSocket webSocket, Response response) {
        // 连接打开
    }
    
    @Override
    public void onMessage(WebSocket webSocket, String text) {
        // 接收文本消息
    }
    
    @Override
    public void onMessage(WebSocket webSocket, ByteString bytes) {
        // 接收二进制消息
    }
    
    @Override
    public void onClosing(WebSocket webSocket, int code, String reason) {
        // 正在关闭
    }
    
    @Override
    public void onClosed(WebSocket webSocket, int code, String reason) {
        // 连接关闭
    }
    
    @Override
    public void onFailure(WebSocket webSocket, Throwable t, Response response) {
        // 连接失败
    }
});

总结:

  • 使用OkHttp WebSocket
  • 实现WebSocketListener
  • 处理各种回调

22.2 如何实现WebSocket客户端?

答案:

实现WebSocket客户端的方法(详见22.1题答案):

// WebSocket客户端实现
public class WebSocketClient {
    private WebSocket webSocket;
    
    public void connect(String url) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(url)
            .build();
        
        webSocket = client.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                // 连接成功
            }
            
            @Override
            public void onMessage(WebSocket webSocket, String text) {
                // 接收消息
            }
            
            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                // 连接失败,重连
                reconnect();
            }
        });
    }
    
    public void send(String message) {
        if (webSocket != null) {
            webSocket.send(message);
        }
    }
    
    public void close() {
        if (webSocket != null) {
            webSocket.close(1000, "Normal closure");
        }
    }
}

总结:

  • 创建WebSocket连接
  • 实现回调处理
  • 发送和接收消息
  • 管理连接生命周期

22.3 如何处理WebSocket连接?

答案:

处理WebSocket连接的方法:

连接管理:

// WebSocket连接管理
public class WebSocketManager {
    private WebSocket webSocket;
    private boolean isConnected = false;
    
    public void connect(String url) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
            .url(url)
            .build();
        
        webSocket = client.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                isConnected = true;
                // 连接成功处理
            }
            
            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                isConnected = false;
                // 连接失败处理
                reconnect();
            }
            
            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                isConnected = false;
                // 连接关闭处理
            }
        });
    }
    
    public boolean isConnected() {
        return isConnected;
    }
}

总结:

  • 管理连接状态
  • 处理连接成功、失败、关闭
  • 实现重连机制

22.4 如何处理WebSocket消息?

答案:

处理WebSocket消息的方法:

// WebSocket消息处理
WebSocketListener listener = new WebSocketListener() {
    @Override
    public void onMessage(WebSocket webSocket, String text) {
        // 处理文本消息
        try {
            JSONObject json = new JSONObject(text);
            String type = json.getString("type");
            // 根据类型处理消息
        } catch (JSONException e) {
            // 处理错误
        }
    }
    
    @Override
    public void onMessage(WebSocket webSocket, ByteString bytes) {
        // 处理二进制消息
        byte[] data = bytes.toByteArray();
        // 处理二进制数据
    }
};

总结:

  • 处理文本消息(JSON等)
  • 处理二进制消息
  • 根据消息类型处理

22.5 如何发送文本消息?

答案:

发送文本消息的方法:

// 发送文本消息
webSocket.send("Hello Server");

// 发送JSON消息
JSONObject json = new JSONObject();
json.put("type", "message");
json.put("content", "Hello");
webSocket.send(json.toString());

总结:

  • 使用send()发送文本
  • 可以发送JSON等格式
  • 自动封装为文本帧

22.6 如何发送二进制消息?

答案:

发送二进制消息的方法:

// 发送二进制消息
byte[] data = {0x01, 0x02, 0x03};
webSocket.send(ByteString.of(data));

// 或
ByteString bytes = ByteString.of(data);
webSocket.send(bytes);

总结:

  • 使用ByteString发送二进制
  • 自动封装为二进制帧

22.7 WebSocket的心跳机制是什么?

答案:

WebSocket心跳机制是定期发送Ping帧保持连接活跃

心跳机制作用:

  • 检测连接是否存活
  • 保持连接活跃
  • 防止连接被关闭

实现方法:

// WebSocket心跳
private Handler handler = new Handler();
private Runnable heartbeatRunnable = new Runnable() {
    @Override
    public void run() {
        if (webSocket != null && isConnected) {
            // 发送Ping(需要底层支持)
            // 或发送自定义心跳消息
            webSocket.send("ping");
            handler.postDelayed(this, 30000);  // 30秒
        }
    }
};

// 启动心跳
handler.post(heartbeatRunnable);

总结:

  • 定期发送Ping或心跳消息
  • 保持连接活跃
  • 检测连接状态

22.8 如何实现WebSocket心跳?

答案:

实现WebSocket心跳的方法(详见22.7题答案):

  • 使用Handler定期发送
  • 发送Ping或自定义消息
  • 检测响应

总结:

  • 定期发送心跳
  • 检测响应
  • 保持连接活跃

22.9 如何实现WebSocket重连?

答案:

实现WebSocket重连的方法:

// WebSocket重连
private void reconnect() {
    if (!isReconnecting) {
        isReconnecting = true;
        handler.postDelayed(() -> {
            connect(url);  // 重新连接
            isReconnecting = false;
        }, 3000);  // 3秒后重连
    }
}

// 指数退避重连
private int reconnectDelay = 1000;
private void reconnectWithBackoff() {
    handler.postDelayed(() -> {
        connect(url);
        reconnectDelay = Math.min(reconnectDelay * 2, 30000);  // 最大30秒
    }, reconnectDelay);
}

总结:

  • 检测连接失败
  • 延迟重连
  • 可以使用指数退避

22.10 WebSocket的性能优化有哪些?

答案:

WebSocket性能优化的方法:

  1. 连接复用

    • 复用WebSocket连接
    • 减少连接开销
  2. 消息压缩

    • 压缩消息数据
    • 减少传输量
  3. 批量发送

    • 批量发送消息
    • 减少系统调用
  4. 心跳优化

    • 合理设置心跳间隔
    • 避免过于频繁

总结:

  • 连接复用、消息压缩
  • 批量发送、心跳优化
  • 提高性能

第五部分:网络协议对比与应用答案

第二十三章:协议对比(10 题)

23.1 HTTP和Socket的区别是什么?

答案:

HTTP和Socket的区别(详见15.5题答案):

  1. 协议层次:HTTP应用层,Socket传输层接口
  2. 连接方式:HTTP请求-响应,Socket灵活
  3. 数据格式:HTTP结构化,Socket原始数据
  4. 使用场景:HTTP Web应用,Socket实时通信

总结:

  • HTTP是应用层协议,Socket是传输层接口
  • 不同层次,不同用途

23.2 什么时候使用HTTP?

答案:

使用HTTP的场景:

  • Web应用
  • RESTful API
  • 文件下载
  • 不需要实时性的场景

总结:

  • Web应用和API使用HTTP
  • 不需要实时性时使用HTTP

23.3 HTTP和Socket的性能差异是什么?

答案:

HTTP和Socket的性能差异:

  • HTTP:每次请求建立连接,开销大
  • Socket:长连接,开销小,性能好

总结:

  • Socket性能更好(长连接)
  • HTTP有连接开销

23.4 HTTP和Socket的适用场景是什么?

答案:

HTTP和Socket的适用场景(详见23.2和23.3题答案):

  • HTTP:Web应用、API
  • Socket:实时通信、游戏

总结:

  • 根据需求选择协议
  • HTTP用于Web应用,Socket用于实时通信

23.5 Socket和WebSocket的区别是什么?

答案:

Socket和WebSocket的区别:

  1. 协议层次

    • Socket:传输层接口
    • WebSocket:应用层协议
  2. 协议标准

    • Socket:底层接口,需要自定义协议
    • WebSocket:标准协议,有规范
  3. 使用场景

    • Socket:需要完全控制
    • WebSocket:Web实时通信

总结:

  • Socket是底层接口,WebSocket是应用层协议
  • WebSocket更适合Web应用

23.6 Socket和WebSocket的性能差异是什么?

答案:

Socket和WebSocket的性能差异:

  • Socket:性能最好(底层,无协议开销)
  • WebSocket:性能好(有协议开销,但较小)

总结:

  • Socket性能最好
  • WebSocket性能好,但有一定开销

23.7 Socket和WebSocket的适用场景是什么?

答案:

Socket和WebSocket的适用场景(详见23.7和23.8题答案):

  • Socket:需要完全控制、非Web应用
  • WebSocket:Web实时通信、需要服务器推送

总结:

  • 根据需求选择
  • Socket用于完全控制,WebSocket用于Web实时通信

23.8 HTTP和WebSocket的区别是什么?

答案:

HTTP和WebSocket的区别(详见20.4题答案):

  1. 通信模式:HTTP请求-响应,WebSocket全双工
  2. 连接方式:HTTP短连接,WebSocket长连接
  3. 服务器推送:HTTP不支持,WebSocket支持

总结:

  • HTTP:请求-响应,短连接
  • WebSocket:全双工,长连接,支持服务器推送

23.9 HTTP和WebSocket的性能差异是什么?

答案:

HTTP和WebSocket的性能差异:

  • HTTP:每次请求建立连接,开销大
  • WebSocket:长连接,开销小,性能好

总结:

  • WebSocket性能更好(长连接)
  • HTTP有连接开销

23.10 HTTP和WebSocket的适用场景是什么?

答案:

HTTP和WebSocket的适用场景(详见23.12和23.13题答案):

  • HTTP:Web应用、API
  • WebSocket:Web实时通信、服务器推送

总结:

  • 根据需求选择
  • HTTP用于一般Web应用,WebSocket用于实时通信

第二十四章:协议选择与应用场景(15 题)

24.1 如何选择合适的网络协议?

答案:

选择合适的网络协议的原则:

  1. 需求分析

    • 是否需要实时性
    • 是否需要可靠性
    • 是否需要服务器推送
  2. 协议选择

    • 一般Web应用:HTTP/HTTPS
    • 实时通信:WebSocket
    • 完全控制:Socket
    • 实时性要求高:UDP Socket
  3. 性能考虑

    • 连接开销
    • 传输效率
    • 延迟要求

选择指南:

需要实时性?
  ├─ 是 → WebSocket或Socket
  └─ 否 → HTTP/HTTPS

需要服务器推送?
  ├─ 是 → WebSocket
  └─ 否 → HTTP/HTTPS

需要完全控制?
  ├─ 是 → Socket
  └─ 否 → HTTP/WebSocket

总结:

  • 根据需求选择协议
  • 考虑实时性、可靠性、推送需求
  • 平衡性能和复杂度

24.2 RESTful API应该使用什么协议?

答案:

RESTful API应该使用HTTP/HTTPS协议

原因:

  • RESTful基于HTTP
  • 使用HTTP方法(GET、POST、PUT、DELETE)
  • 使用HTTP状态码
  • 使用HTTP头部

最佳实践:

  • 生产环境使用HTTPS
  • 使用标准HTTP方法
  • 使用RESTful设计原则

总结:

  • RESTful API使用HTTP/HTTPS
  • 遵循RESTful设计原则

24.3 实时通信应该使用什么协议?

答案:

实时通信应该使用WebSocket或Socket协议

选择:

  • WebSocket:Web应用实时通信(推荐)
  • Socket:非Web应用或需要完全控制

总结:

  • 实时通信使用WebSocket或Socket
  • Web应用优先使用WebSocket

24.4 文件传输应该使用什么协议?

答案:

文件传输应该使用HTTP/HTTPS或FTP协议

选择:

  • HTTP/HTTPS:Web文件传输(常用)
  • FTP:传统文件传输
  • TCP Socket:自定义文件传输协议

总结:

  • 文件传输使用HTTP/HTTPS或FTP
  • Web应用使用HTTP/HTTPS

24.5 游戏开发应该使用什么协议?

答案:

游戏开发应该使用UDP Socket或TCP Socket协议

选择:

  • UDP Socket:实时性要求高的游戏(推荐)
  • TCP Socket:需要可靠性的游戏
  • WebSocket:Web游戏

总结:

  • 游戏开发使用UDP或TCP Socket
  • 实时性要求高使用UDP

24.6 HTTP适用于哪些场景?

答案:

HTTP适用的场景(详见23.2题答案):

  • Web应用
  • RESTful API
  • 文件下载
  • 不需要实时性的场景

总结:

  • HTTP适用于一般Web应用
  • 不需要实时性时使用HTTP

24.7 HTTPS适用于哪些场景?

答案:

HTTPS适用的场景(详见9.15题答案):

  • 生产环境
  • 用户登录
  • 支付交易
  • 敏感数据传输

总结:

  • HTTPS适用于需要安全性的场景
  • 生产环境必须使用HTTPS

24.8 TCP Socket适用于哪些场景?

答案:

TCP Socket适用的场景(详见15.12题答案):

  • 需要可靠性
  • 需要顺序
  • 长连接
  • 文件传输、数据库连接

总结:

  • TCP Socket适用于需要可靠性的场景
  • 需要顺序时使用TCP

24.9 UDP Socket适用于哪些场景?

答案:

UDP Socket适用的场景(详见15.13题答案):

  • 实时性要求高
  • 可以容忍丢失
  • 低延迟要求
  • 视频直播、游戏、DNS

总结:

  • UDP Socket适用于实时性要求高的场景
  • 可以容忍丢失时使用UDP

24.10 WebSocket适用于哪些场景?

答案:

WebSocket适用的场景(详见20.5题答案):

  • Web实时通信
  • 聊天应用
  • 实时数据推送
  • 需要服务器主动推送的Web应用

总结:

  • WebSocket适用于Web实时通信
  • 需要服务器推送时使用WebSocket

24.11 如何选择HTTP还是HTTPS?

答案:

选择HTTP还是HTTPS(详见9.15题答案):

  • 生产环境:必须使用HTTPS
  • 敏感数据:必须使用HTTPS
  • 开发环境:可以使用HTTP,但建议HTTPS

总结:

  • 生产环境必须使用HTTPS
  • 敏感数据必须使用HTTPS
  • 默认选择HTTPS

24.12 如何选择TCP还是UDP?

答案:

选择TCP还是UDP(详见17.9和17.10题答案):

  • 需要可靠性:使用TCP
  • 实时性要求高:使用UDP
  • 需要顺序:使用TCP
  • 可以容忍丢失:使用UDP

总结:

  • 根据可靠性需求和实时性需求选择
  • 需要可靠性用TCP,实时性要求高用UDP

24.13 如何选择HTTP还是WebSocket?

答案:

选择HTTP还是WebSocket(详见23.11-23.15题答案):

  • 需要实时性:使用WebSocket
  • 需要服务器推送:使用WebSocket
  • 一般Web应用:使用HTTP
  • RESTful API:使用HTTP

总结:

  • 实时通信使用WebSocket
  • 一般Web应用使用HTTP
  • 根据需求选择

24.14 如何选择Socket还是WebSocket?

答案:

选择Socket还是WebSocket(详见23.6-23.10题答案):

  • Web应用:使用WebSocket
  • 需要完全控制:使用Socket
  • 非Web应用:使用Socket
  • 标准协议:使用WebSocket

总结:

  • Web应用使用WebSocket
  • 需要完全控制使用Socket
  • 根据应用类型选择

24.15 网络协议选择的综合指南是什么?

答案:

网络协议选择的综合指南:

选择流程:

1. 确定应用类型
   ├─ Web应用 → HTTP/HTTPS或WebSocket
   └─ 非Web应用 → Socket

2. 确定实时性需求
   ├─ 需要实时性 → WebSocket或Socket
   └─ 不需要实时性 → HTTP/HTTPS

3. 确定安全性需求
   ├─ 需要安全性 → HTTPS或WSS
   └─ 不需要安全性 → HTTP或WS

4. 确定可靠性需求
   ├─ 需要可靠性 → TCP
   └─ 可以容忍丢失 → UDP

5. 确定服务器推送需求
   ├─ 需要推送 → WebSocket
   └─ 不需要推送 → HTTP

协议选择表:

场景推荐协议
Web应用、APIHTTP/HTTPS
Web实时通信WebSocket/WSS
需要完全控制Socket
需要可靠性TCP Socket
实时性要求高UDP Socket或WebSocket
文件传输HTTP/HTTPS或FTP
游戏开发UDP Socket或TCP Socket

总结:

  • 根据应用类型、实时性、安全性、可靠性需求选择
  • 参考协议选择表
  • 平衡性能和复杂度