usbmux是一套基于socket的通信协议,在macOS主要实现为usbmuxd,在macOS启动时随之启动,其主要涉及到以下文件:
1. /System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/Resources/usbmuxd
2. /var/run/usbmuxd
第一个为可执行文件,也就是守护进程usbmuxd的启动文件;usbmuxd进程启动之后会创建socket(Unix Domain Socket),对应的文件为第二个文件,macOS的所有App都是通过这个socket和usbmuxd进程通信。
设备间通信过程
按照官方说法,usbmuxd提供了一种TCP-Like的方式允许macOS App和iOS设备通信,比如上图,Xcode和iOS上的lockdown服务之间通过usbmuxd和USB Service建立了类似TCP链接的通信通道,这种通信方式类似C/S架构,其中Xcode是客户端(Client),lockdown是服务端(Server)。
lockdown服务
lockdown在iOS系统由launchd进程启动,它主要的角色是充当网关,协调macOS和iOS其他服务的通信。
lockdown启动之后会建立一个端口为62078的socket listen,监听相关连接请求。
比如Xcode要进行设备调试,需要先和lockdown服务通信,发起调试请求,lockdown再启动调试服务(debugserver),然后Xcode才能和debugserver建立连接。
usbmuxd服务
usbmuxd运行在macOS系统上,通过USB协议和iOS设备通信。
macOS App通过Unix Domain Socket和usbmuxd通信。
int connect_to_usbmuxd() {
// Create Unix domain socket
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
// prevent SIGPIPE
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
// Connect socket
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/var/run/usbmuxd");
socklen_t socklen = sizeof(addr);
if (connect(fd, (struct sockaddr *)&addr, socklen) == -1) {
printf("Connect failure, fd is: %d.\n", fd);
} else {
printf("Connect successifully, fd is: %d.\n", fd);
}
return fd;
}
连接过程
usbmux协议使用xml格式作为消息体,主要定义了下面这些消息来协商连接过程:
// usbmuxd的消息格式使用xml也就是plist
void send_packet(NSDictionary *packetDict, int tag, dispatch_io_t channel) {
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:packetDict format:NSPropertyListXMLFormat_v1_0 options:0 error:NULL];
int protocol = USBMuxPacketProtocolPlist;
int type = USBMuxPacketTypePlistPayload;
usbmux_packet_t *upacket = usbmux_packet_create(
protocol,
type,
tag,
plistData ? plistData.bytes : nil,
(uint32_t)(plistData.length)
);
dispatch_data_t data = dispatch_data_create((const void*)upacket, upacket->size, usbmuxd_io_queue, ^{
usbmux_packet_free(upacket);
});
dispatch_io_write(channel, 0, data, usbmuxd_io_queue, ^(bool done, dispatch_data_t data, int _errno) {
NSLog(@"dispatch_io_write: done=%d data=%p error=%d", done, data, _errno);
if (!done) { return; }
});
}
1. Listen
Xcode连接到usbmuxd进程之后,需要发送Listen消息来告诉usbmuxd开始监听iOS设备的连接通知,以便在iOS设备通过USB接入之后能够获取到设备信息,从而决定是否与设备建立连接。
void send_listen_packet() {
connect_channel = connect_to_usbmuxd_channel();
NSDictionary *packet = @{
@"ClientVersionString": @"1",
@"MessageType": @"Listen",
@"ProgName": @"Peertalk Example"
};
NSLog(@"send listen packet: %@", packet);
send_packet(packet, 0, listen_channel);
}
当有iOS设备连接,就会收到来自usbmuxd的设备消息“DeviceList”
主要的格式如下:
[{
MessageType = Attached",
DeviceID = 19,
Properties = {
ConnectionSpeed = 480000000,
DeviceID = 19,
LocationID = 338821120,
ProductID = 4776,
SerialNumber = ...,
USBSerialNumber = ...
}
}]
2. Connect
上一步我们已经拿到了设备相关信息,接下来就可以连接设备,通过发送Connect消息来建立设备连接。
void send_connect_usb_packet() {
connect_channel = connect_to_usbmuxd_channel();
port = ((port<<8) & 0xFF00) | (port>>8);
NSDictionary *packet = @{
@"ClientVersionString" : @"1",
@"DeviceID" : deviceID,
@"MessageType" : @"Connect",
@"PortNumber" : [NSNumber numberWithInt:port],
@"ProgName" : @"Peertalk Example"
};
NSLog(@"send connect to usb packet: %@", packet);
send_packet(packet, 1, connect_channel);
read_packet_on_channle(connect_channel);
}
其中PortNumber就是我们想要连接到的服务,比如lockdown服务的端口就是62078。
如果连接成功,就能收到结果消息:
{
MessageType = Result;
Number = 0;
}
至此,Xcode就建立了和iOS设备上的lockdown服务的TCP连接,就可以直接通过这个连接通道发送TCP数据了。
void send_msg(NSString *msg) {
dispatch_data_t payload = create_payload(msg);
dispatch_data_t frame = create_frame(101, 0, payload);
// send through connect channel, not tcp_channel
dispatch_io_write(connect_channel, 0, frame, usbmuxd_io_queue, ^(bool done, dispatch_data_t _Nullable data, int error) {
NSLog(@"error is: %d", error);
});
}
主要的过程总结如下:
1. macOS App通过Unix Domain Socket和usbmuxd进行通信(类似TCP);
2. macOS App向usbmuxd发送Listen命令,监听iOS连接状态;
3. macOS App接收iOS设备信息,发送Connect命令,建立与iOS端相关进程连接(基于端口号);
4. lockdown服务以TCP Server的方式存在;
5. usbmuxd作为代理通过usbmux协议为macOS App和iOS设备进程提供了类TCP的通信服务;