一、PC 侧 adb server
我们在终端敲的 adb 命令,第一步不是直接发给板子,而是发给本机 adb server 的 127.0.0.1:5037。
1、adb server 是一个 TCP 服务端,持续监听 5037。
2、adb client(短进程)作为 TCP 客户端连接 5037,发送控制命令(文本格式,不是标准 adb 数据流包)
3、client 发给 server 的控制指令 例子
host:devices
host:transport:xxxxxx
host:shell
host:forward:tcp:8888;tcp:8888
host:开头:是发给 adb server 本身的控制命令,用来选择设备、管理转发;- server 解析完 host 指令之后,建立一条通往板子 adbd 的数据通道,之后才开始走标准 ADB 二进制协议包(
A_OPEN/A_OKAY/A_WRTE/A_CLSE)。
4、非常重要的分层
① 5037 端口:
PC 本地控制面,只负责管理设备、选设备、建立通道;
② 板子和 server 之间:
数据面,跑标准 ADB 多路复用协议。
5、执行 adb shell流程
① client → 127.0.0.1:5037,发 host:devices,拿到设备序列号
② client 发 host:transport:serial,告诉 server:我要切换到这台设备;
③ server 和 adbd 之间建立好数据流通道;
④ 通道建立完成,开始传输 ADB 协议包,发起 shell 服务。

二、传输层抽象
Transport,统一 USB 和 TCP
一个核心抽象:struct transport
上层协议代码(protocol、service)完全不关心底层是 USB 还是 TCP,只调用 transport 提供的 read /write 接口
1、USB transport(FunctionFS / F_ADB)
内核侧依赖:CONFIG_USB_CONFIGFS + CONFIG_USB_CONFIGFS_F_ADB(Linux5.4 内核配置项)
当板子作为 USB gadget 设备,内核会在 /dev/usb-ffs/adb/ 下面生成几个端点文件:
本质:USB 批量端点,是一种面向包的可靠传输通道,不是流式 socket,但 adbd 把它封装成和 socket 一样的读写接口。
- ep0:控制端点
- ep1:OUT(PC → 板子,写)
- ep2:IN(板子 → PC,读)
- adbd 直接 open 这两个批量端点,用 read/write 收发二进制数据包。
2、TCP transport(adb tcpip 5555)
adbd 在板子上监听 0.0.0.0:5555 的 TCP socket;
PC 的 adb server 主动连这个端口;底层就是普通字节流 TCP。
3、这样统一的好处
不管插 USB,还是连 wifi 5555,上层 ADB 协议包格式完全一模一样,这就是为什么一条命令 adb shell 两种方式都能用。
源码对应:
transport.c里面封装这两套读写实现。

三、ADB 核心协议
数据包结构 + 4 种基础包
ADB 协议是多路复用流协议:一条底层传输链路(USB 或者一条 TCP 连接),可以虚拟出很多独立的「通道(stream)」。
类比:一条网线(底层 transport),同时开多个网页、微信、文件下载;adb 的 channel 就是这个虚拟流。
1、数据包头部(每个 adb 包都带这个头)
struct adb_msg {
uint32_t command; // 包类型:A_OPEN, A_OKAY, A_WRTE, A_CLSE
uint32_t arg0; // local id:本地通道号
uint32_t arg1; // remote id:对端通道号
uint32_t data_length;// 后面payload的长度
uint32_t data_check; // 校验
uint32_t magic;
}
local id:发包方自己给这个通道分配的编号remote id:对方给这个通道分配的编号
通道是双向配对的:local_id ↔ remote_id,一一绑定。
2. 四个基础命令包
就这 4 个,所有功能都靠它们组合
① A_OPEN:
打开一个新通道,请求启动一个服务 payload:服务名字串,比如 shell:、sync:、tcp:8888
例子:PC 侧发 A_OPEN,payload="shell:" → 请求 adbd 拉起 shell 服务
② A_OKAY:
确认应答包 收到 A_OPEN 之后,adbd 同意创建通道,回复 A_OKAY;
通道正式就绪,可以收发数据。
③ A_WRTE:
写数据包,承载业务 payload shell 输入字符、命令输出、push 的文件数据,全部封装在 A_WRTE 里面传输。
④ A_CLSE:
关闭通道 一端发 A_CLSE,代表这个虚拟流结束;
adbd 这边就会关闭对应的子进程、管道。
重点:一条物理连接,可以同时存在几十组不同 id 的通道,互不干扰。
比如:通道 1 跑 adb shell,通道 2 跑 adb push,通道 3 跑 forward 端口转发,这就是多路复用。
3. 一次完整通道建立流程(shell 通道示例)
① PC(adb server)
发送:A_OPEN, arg0=1, arg1=0, payload="shell:"
含义:我申请新建通道,我这边编号是 1,请你打开 shell 服务。
② 板子 adbd
收到 A_OPEN,解析 service 名字 shell:
adbd 内部:fork () 创建子进程,dup2 管道绑定 /bin/sh 的 stdin/stdout/stderr
③ adbd 回复
A_OKAY, arg0=5, arg1=1
含义:同意,我这边给这个通道分配编号 5,你的编号是 1;
通道(1<->5)建立成功。
④ 双向互发 A_WRTE
PC 输入字符 → A_WRTE → adbd → 写入 sh 的 stdin
sh 输出结果 → adbd 打包 A_WRTE → 传回 PC 展示
⑤ 退出 shell(exit 或者 ctrl+c)
任意一端发送 A_CLSE,通道销毁,回收子进程。
这就是 adb shell 的内核流转逻辑,没有魔法,就是包 + 通道 + fork + 管道转发 IO。

四、两个核心服务:shell 服务、sync 文件服务
1、shell: 服务
就是流程,adbd fork exec /bin/sh;
老版本 android-tools-4.2.2 大坑:原生不带 PTY 伪终端
- 不带 PTY:只是普通管道;能收发字符,但是没有终端控制; 表现:没有回显、Tab 补全失效、Ctrl+C 无法中断进程
- 带 PTY:open /dev/pts/xx,把子进程绑定到 pty,才有完整终端交互。
2、sync: 服务(adb push /adb pull /adb sync)
payload 不再是终端字符,而是一套简单的 sync 子协议: SEND、RECV、DATA、DONE;
用来分片传输文件、文件属性、长度。 文件被切成多个 A_WRTE 包分片传输,zlib 用来做压缩(对应依赖 zlib 库)。
另外还有 tcp: 服务,就是 forward/reverse 端口转发:adbd 在板子上建立本地 socket,把通道的数据转发到这个 socket。
五、USB ADB 和 TCP ADB 的差异细节
| 项目 | USB FFS ADB | TCPIP adb(5555) |
|---|---|---|
| 底层介质 | USB 批量端点,面向包 | TCP 字节流,流式 |
| 内核依赖 | CONFIG_USB_CONFIGFS_F_ADB | 只需要网卡,不需要 gadget |
| adbd 启动方式 | adbd 打开 ep1/ep2 端点 | adbd 监听 socket 5555 |
| 优点 | 稳定、不需要 IP、不容易粘包 | 免数据线,远程调试 |
| 缺点 | 必须 USB 线;需要 gadget 配置 | 网络抖动、容易断、内网开放有安全风险 |
协议层:上层 A_OPEN/A_OKAY/A_WRTE/A_CLSE 完全一样,transport 层抹平差异。