下面我将为你提供一个完整、详细的步骤指南,包括代码示例和解释。

核心概念
- Socket (套接字):是网络编程的 API,它提供了两台计算机之间通信的端点,我们可以把它想象成一个电话,你(客户端)通过这个号码(IP 地址和端口号)来拨通服务器(如
www.google.com的 80 端口)进行通话。 - HTTP 协议:超文本传输协议,当你在浏览器中输入
www.google.com并回车时,你的浏览器会:- 通过 DNS 查询获取
www.google.com的 IP 地址。 - 与该 IP 地址的 80 端口建立一个 TCP 连接(这就是 Socket)。
- 向服务器发送一个 HTTP 请求 报文。
- 服务器收到请求后,会返回一个 HTTP 响应 报文。
- 浏览器解析响应报文,将 HTML 内容显示出来。
- 通过 DNS 查询获取
我们的任务就是手动完成浏览器的前两步和最后一步。
获取服务器的 IP 地址
我们不能直接使用域名(如 www.google.com)来创建 Socket,需要先将其转换为 IP 地址,在 Android 中,我们可以使用 InetAddress 类。
创建 Socket 并建立连接
使用 InetAddress 和服务器的端口号(HTTP 默认为 80,HTTPS 为 443)来创建一个 Socket 对象,构造函数会尝试与服务器建立 TCP 连接。
构造并发送 HTTP 请求
这是最关键的一步,我们需要按照 HTTP/1.1 协议的格式,手动构造一个请求字符串,并通过 Socket 的输出流发送给服务器。

一个简单的 HTTP GET 请求格式如下:
GET / HTTP/1.1 Host: www.google.com Connection: close User-Agent: MyCoolAndroidApp/1.0
GET / HTTP/1.1:请求方法(GET)、请求路径(根目录 )、HTTP 协议版本。Host: www.google.com:必须包含,告诉服务器我们要访问的是哪个域名下的资源。Connection: close:告诉服务器,在本次请求响应后,请关闭 TCP 连接。User-Agent: ...:标识客户端的软件信息,很多网站会根据这个信息返回不同格式的页面。- 一个空行 (
\r\n\r\n):非常重要,它标志着请求头部的结束,后面可以跟着请求体(GET 请求通常没有请求体)。
接收并解析 HTTP 响应
服务器收到请求后,会返回一个 HTTP 响应,响应也由三部分组成:状态行、响应头、响应体(即网页的 HTML 内容)。
- 状态行:
HTTP/1.1 200 OK。200表示成功。 - 响应头:
Content-Type: text/html; charset=UTF-8,Content-Length: 1234等。 - 空行:同样,
\r\n\r\n分隔头部和响应体。 - 响应体:我们最终需要的 HTML 数据。
我们的程序需要从输入流中读取数据,先读取头部,直到遇到空行,然后读取剩下的所有数据作为响应体。
完整代码实现
下面是一个完整的 Android Activity 示例,它使用 Socket 下载 httpbin.org 的 HTML 内容(这个网站非常适合测试 HTTP 请求)。

添加网络权限
在 app/src/main/AndroidManifest.xml 文件中,必须添加 INTERNET 权限,由于网络操作不能在主线程进行,我们还需要声明 android:usesCleartextTraffic="true"(对于 Android 9+,默认禁止 HTTP,仅允许 HTTPS)。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.socketdownload">
<!-- 1. 添加网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SocketDownload"
android:usesCleartextTraffic="true"> <!-- 2. 允许明文流量 (HTTP) -->
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
布局文件 (activity_main.xml)
添加一个按钮和一个 TextView 来显示下载结果。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Download via Socket" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp">
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Result will be shown here..."
android:textSize="12sp"
android:fontFamily="monospace" />
</ScrollView>
</LinearLayout>
Java 代码 (MainActivity.java)
这是核心逻辑,我们使用 AsyncTask(为了简单演示,新项目推荐使用 ExecutorService 或 Coroutine)来执行网络耗时操作。
package com.example.socketdownload;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class MainActivity extends AppCompatActivity {
private static final String HOST = "httpbin.org";
private static final int PORT = 80;
private TextView tvResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvResult = findViewById(R.id.tv_result);
Button btnDownload = findViewById(R.id.btn_download);
btnDownload.setOnClickListener(v -> {
// 在新线程中执行下载任务
new DownloadWebPageTask().execute();
});
}
private class DownloadWebPageTask extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... voids) {
Socket socket = null;
StringBuilder response = new StringBuilder();
try {
// 1. 获取服务器IP地址
InetAddress address = InetAddress.getByName(HOST);
// 2. 创建Socket并连接
socket = new Socket(address, PORT);
tvResult.post(() -> tvResult.setText("Connecting to " + HOST + "..."));
// 3. 构造HTTP请求
String request = "GET /html HTTP/1.1\r\n" +
"Host: " + HOST + "\r\n" +
"Connection: close\r\n" +
"User-Agent: MyAndroidSocketApp/1.0\r\n" +
"\r\n"; // 空行结束请求头
// 发送请求
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write(request);
writer.flush();
tvResult.post(() -> tvResult.append("\nRequest sent! Waiting for response..."));
// 4. 接收HTTP响应
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
boolean inBody = false;
// 先读取响应头,直到遇到空行
while ((line = reader.readLine()) != null) {
if (line.isEmpty()) { // 空行表示头部结束
inBody = true;
break; // 开始读取响应体
}
}
// 读取响应体(HTML内容)
if (inBody) {
tvResult.post(() -> tvResult.append("\nReceiving HTML content..."));
while ((line = reader.readLine()) != null) {
response.append(line).append("\n");
}
}
} catch (UnknownHostException e) {
return "Error: Unknown host " + HOST;
} catch (IOException e) {
return "Error: " + e.getMessage();
} finally {
// 5. 关闭Socket
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return response.toString();
}
@Override
protected void onPostExecute(String result) {
// 在主线程更新UI
tvResult.setText(result);
}
}
}
代码解释
DownloadWebPageTask:继承自AsyncTask,用于在后台线程执行网络操作,避免阻塞UI线程。InetAddress.getByName(HOST):将域名httpbin.org解析为 IP 地址。new Socket(address, PORT):尝试与服务器建立 TCP 连接。BufferedWriter:用于高效地构造和发送 HTTP 请求字符串,注意write(request)后一定要调用flush()将数据真正发送出去。BufferedReader:用于高效地读取服务器返回的数据。- 读取响应头:我们循环
readLine(),直到读取到一个空行。readLine()会自动去掉行尾的\r\n,所以一个空行就是null。 - 读取响应体:一旦检测到空行,我们就进入读取响应体的循环,将所有剩余的行都追加到
StringBuilder中。 tvResult.post(...):这是在AsyncTask的后台线程中安全地更新 UI 的方法,它会将更新操作放到主线程的消息队列中执行。finally块:确保Socket被关闭,释放资源,防止内存泄漏。
运行结果
点击按钮后,TextView 会依次显示连接状态、请求已发送、正在接收内容,最后会完整地显示出 httpbin.org/html 页面的 HTML 代码。
重要注意事项和局限性
- 仅支持 HTTP:这个例子只实现了 HTTP 协议,HTTPS(SSL/TLS)要复杂得多,需要使用
SSLSocket并处理证书验证,这超出了简单 Socket 的范畴。 - 重定向:如果服务器返回
3xx状态码(如 301, 302),表示需要重定向,上面的代码无法处理这种情况,需要解析Location响应头,然后对新地址重新发起请求。 - 性能和效率:手动处理 Socket 和 HTTP 协议非常繁琐且容易出错,对于生产环境,强烈推荐使用成熟的网络库,如:
- OkHttp: 高效、现代,支持 HTTP/2、连接池、拦截器等。
- HttpURLConnection: Android SDK 自带,虽然功能相对简单,但对于大多数场景已经足够。
- 线程管理:
AsyncTask已被标记为过时,在现代 Android 开发中,应使用java.util.concurrent.ExecutorService和Future,或者更推荐的 Kotlin Coroutines 来处理异步任务。
这个 Socket 下载网页的例子,其最大价值在于帮助你理解网络通信的底层机制,而不是作为实际项目中的解决方案。
