socket简单实现(Linux附Android源码)
6400 Words|Read in about 30 Min|本文总阅读量次
在Linux中的socket用法,它是一种RPC的机制,通过通过客户端和服务端相连,产生socket句柄,通过句柄完成数据的传输通信。
1Linux socket
socket是一类接口,在Linux中,一切皆为文件,socket亦然。因此,在Linux中的socket用法,它是一种RPC的机制,通过通过客户端和服务端相连,产生socket句柄,通过句柄完成数据的传输通信。
2 socket用法
为创建一个套接字,调用socket函数
2.1函数原型
1#include <sys/types.h> /* See NOTES */
2#include <sys/socket.h>
3
4int socket(int domain, int type, int protocol);
2.2 描述
socket()为通信创建一个端点并返回一个描述符。
2.2.1第一个参数
domain参数指定通信域;这将选择用于通信的协议族。这些族定义在<sys/socket.h>中。目前理解的格式包括
Name | Purpose | Man page |
---|---|---|
AF_UNIX, AF_LOCAL | 本地通信 | unix(7) |
AF_INET | IPv4网络通信协议 | ip(7) |
AF_INET6 | IPv6网络通信协议 | ipv6(7) |
AF_IPX | IPX - Novell专用协议 | |
AF_NETLINK | 内核用户界面设备通信 | netlink(7) |
AF_PACKET | 低层分组接口通信 | packet(7) |
2.2.2第二个参数
套接字具有指定的类型,该类型指定通信语义。当前定义的类型有
类型 | 含义 |
---|---|
SOCK_STREAM | (TCP)提供有序的、可靠的、双向的、基于连接的字节流。可以支持带外数据传输机制。 |
SOCK_DGRAM | (UDP)支持数据报(无连接,固定最大长度的不可靠消息)。 |
SOCK_SEQPACKET | 为固定最大长度的数据报提供一个有序的、可靠的、基于双向连接的数据传输路径消费者在每次输入系统调用时都需要读取整个数据包。 |
SOCK_RAW | 提供原始网络协议访问。 |
SOCK_RDM | 提供一个不保证排序的可靠数据报层。 |
从Linux 2.6.27开始,type参数还有第二个用途:除了指定套接字类型外,它还可以包括按位的修改socket()的行为
SOCK_NONBLOCK
在新打开的文件描述中设置
O_NONBLOCK
文件状态标志。使用这个标志可以节省对fcntl(2)
的额外调用,以达到相同的结果。SOCK_CLOEXEC
在进程执行exec系统调用时关闭此打开的文件描述符。在子进程没有相应权限的情况下,防止父进程将打开的文件描述符泄露给子进程,防止子进程间接获得权限。
2.2.3第三个参数
协议指定了套接字使用的特定协议。在给定的协议族中,通常只有一个协议支持特定的套接字类型,在这种情况下,protocol可以指定为0。然而,可能存在许多协议,在这种情况下,必须以这种方式指定特定的协议。要使用的协议号是特定于将要发生通信的“通信域”的;看到协议(5)。关于如何将协议名字符串映射到协议号,请参见getprotoent(3)。
1)SOCK_STREAM
这个类型的套接字是全双工字节流。它们不保留记录边界。流套接字在发送或接收任何数据之前必须处于连接状态。到另一个套接字的连接是通过connect(2)
调用创建的。一旦连接,数据可以使用read(2)
和write(2)
调用或send(2)
和recv(2)
调用的一些变体来传输。当一个会话完成时,可以执行close(2)
。带外数据也可以像send(2)中描述的那样传输,并像recv(2)中描述的那样接收。
实现SOCK_STREAM的通信协议确保数据不会丢失或重复。如果对端协议有缓冲空间的一段数据不能在合理的时间内成功传输,则认为该连接已经死亡。当在套接字上启用SO_KEEPALIVE时,协议以特定于协议的方式检查另一端是否仍然活跃。如果进程在中断的流上发送或接收,则引发SIGPIPE信号;这将导致不处理信号的naive进程退出。
2)SOCK_SEQPACKET
这个套接字使用与SOCK_STREAM套接字相同的系统调用。唯一的区别是read(2)调用将只返回所请求的数据量,而到达数据包中剩余的任何数据将被丢弃。此外,传入数据报中的所有消息边界都被保留。
3)SOCK_DGRAM和SOCK_RAW
这两个套接字允许向**sendto(2)调用中命名的通讯员发送数据报。数据报通常使用recvfrom(2)**接收,它返回下一个数据报及其发送者的地址。
fcntl(2) F_SETOWN操作可用于指定进程或进程组在带外数据到达时接收SIGURG信号或在SOCK_STREAM连接意外中断时接收SIGPIPE信号。该操作还可用于设置通过SIGIO接收I/O和I/O事件异步通知的进程或进程组。使用F_SETOWN相当于使用FIOSETOWN或SIOCSPGRP参数进行ioctl(2)调用。
4)其他
套接字的操作由套接字级别的选项控制中定义了这些选项。函数**setsockopt(2)和getsockopt(2)**分别用于设置和获取选项。
关于setsockopt,具体可以点击这里。
setsockopt
1#include <sys/types.h> 2#include <sys/socket.h> 3int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
- sockfd:标识一个套接口的描述字。
- level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
- optname:需设置的选项。
- optval:指针,指向存放选项待设置的新值的缓冲区。
- optlen:optval缓冲区长度。
1optname定义如下: 2 3#define SO_DEBUG 1 -- 打开或关闭调试信息(BOOL) 4#define SO_REUSEADDR 2 -- 打开或关闭地址复用功能(BOOL) 5#define SO_TYPE 3 -- 6#define SO_ERROR 4 7#define SO_DONTROUTE 5 -- 禁止选径;直接传送。(BOOL) 8#define SO_BROADCAST 6 -- 允许套接口传送广播信息(BOOL) 9#define SO_SNDBUF 7 -- 设置发送缓冲区的大小(int) 10#define SO_RCVBUF 8 -- 设置接收缓冲区的大小(int) 11#define SO_KEEPALIVE 9 -- 套接字保活(BOOL) 12#define SO_OOBINLINE 10 -- 在常规数据流中接收带外数据(BOOL) 13#define SO_NO_CHECK 11 14#define SO_PRIORITY 12 -- 设置在套接字发送的所有包的协议定义优先权 15#define SO_LINGER 13 -- 如关闭时有未发送数据,则逗留(struct linger FAR*) 16#define SO_BSDCOMPAT 14 17#define SO_REUSEPORT 15 18#define SO_PASSCRED 16 19#define SO_PEERCRED 17 20#define SO_RCVLOWAT 18 21#define SO_SNDLOWAT 19 22#define SO_RCVTIMEO 20 -- 设置接收超时时间 23#define SO_SNDTIMEO 21 -- 设置发送超时时间 24#define SO_ACCEPTCONN 30 25#define SO_SNDBUFFORCE 32 26#define SO_RCVBUFFORCE 33 27#define SO_PROTOCOL 38 28#define SO_DOMAIN 39
举例说明
1BOOL bReuseaddr = TRUE; 2setsockopt( s, SOL_SOCKET, SO_REUSEADDR, ( const char* )&bReuseaddr, sizeof( BOOL ) );
2.2.4sock协议族
关于socketaddr,sockaddr_un和sockaddr_in的关系和区别
1)socketaddr
基本地址结构,本身没有意义,仅用于泛型化参数,结构体总共16个字节
1typedef unsigned short sa_family_t;
2
3struct sockaddr
4{
5 sa_family_t sa_family;//地址族
6 char sa_data[14];//地址值
7};
2)sockaddr_un
本地地址结构,用于AF_LOCAL/AF_UNIX域的本地通信,,结构体总共110个字节
1struct sockaddr_un
2{
3 sa_family_t sun_family;//地址族(AF_LOCAL/AF_UNIX)
4 char sun_path[108];//本地套接字文件的路径,通常路径字符串不能超过108
5};
3)sockaddr_in
网络地址结构,用于AF_INET域的IPv4网络通信,结构体总共16个字节
1typedef uint16_t in_port_t;//无符号16位整数
2typedef uint32_t in_addr_t;//无符号32位整数
3
4struct in_addr
5{
6 in_addr_t s_addr;
7};
8
9struct sockaddr_in
10{
11 sa_family_t sin_family;//地址族(AF_INET)
12 int_port_t sin_port;//端口号(0~65535)
13 struct in_addr sin_addr;//IP地址
14 unsigned char sin_zero[8];//在linux中会有填充字段,sin_zero[8],全部置为0
15};
2.2.5demo
2.2.5.1基于TCP协议的demo
2.2.5.1.1服务端
1//基于tcp协议的服务器
2#include<stdio.h>
3#include<stdlib.h>
4#include<unistd.h>
5#include<string.h>
6#include<sys/socket.h>
7#include<sys/types.h>
8#include<arpa/inet.h>
9#include<ctype.h>
10#include<errno.h>
11#include<signal.h>
12#include<sys/wait.h>
13//信号处理函数
14void sigchild(int signum)
15{
16 for(;;)
17 {
18 pid_t pid = waitpid(-1,NULL,WNOHANG);//非阻塞方式回收子进程
19 if(pid == -1)
20 {
21 if(errno == ECHILD)
22 {
23 printf("没有子进程可回收\n");
24 break;
25 }
26 else
27 {
28 perror("waitpid");
29 exit(-1);
30 }
31 }
32 else if(pid == 0)
33 {
34 printf("子进程在运行,没法收\n");
35 break;
36 }
37 else
38 {
39 printf("回收%d进程僵尸\n",getpid());
40 }
41 }
42}
43
44int main(void)
45{
46 //捕获17号信号
47 if(signal(SIGCHLD,sigchild) == SIG_ERR)
48 {
49 perror("signal");
50 return -1;
51 }
52 //创建侦听套接字
53 printf("服务器:创建套接字\n");
54 int sockfd = socket(AF_INET,SOCK_STREAM,0);//返回侦听套接字
55 if(sockfd == -1)
56 {
57 perror("socket");
58 return -1;
59 }
60 printf("sockfd:%d\n",sockfd);
61 //组织地址结构,代表服务器
62 printf("服务器:组织地址结构\n");
63 struct sockaddr_in ser;
64 ser.sin_family = AF_INET;
65 ser.sin_port = htons(8888);//注意大小端问题,存是小端,服务器大端方式拿。
66 //ser.sin_addr.s_addr = inet_addr("127.0.0.1")
67 ser.sin_addr.s_addr = INADDR_ANY;//表示接受任意IP下的地址
68 //绑定套接字和地址结构
69 printf("服务器:绑定套接字和地址结构\n");
70 int sockbind = bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
71 if(sockbind == -1)
72 {
73 perror("bind");
74 return -1;
75 }
76 //开启侦听功能
77 printf("服务器:开启侦听功能\n");
78 int socklisten = listen(sockfd,1024);
79 if(socklisten == -1)
80 {
81 perror("listen");
82 return -1;
83 }
84 //等待并接收客户端连接
85 for(;;)
86 { //父进程负责和客户端建立通信
87 printf("服务器:等待并接收客户端连接\n");
88 struct sockaddr_in cli;//用来输出客户端的地址结构
89 socklen_t len = sizeof(cli);
90 int conn = accept(sockfd,(struct sockaddr*)&cli,&len);//返回后续用来通信的通信套接字
91 if(conn == -1)
92 {
93 perror("accept");
94 return -1;
95 }
96 printf("服务器:接收到%s:%hu的客户端\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
97 //子进程负责和客户端通信
98 pid_t pid = fork();
99 if(pid == -1)
100 {
101 perror("fork");
102 return -1;
103 }
104 if(pid == 0)
105 {
106 close(sockfd);//子进程不用侦听套接字,关闭
107 //业务处里(接发数据)
108 printf("服务器:接发数据\n");
109 //接收客户端发来的小写的串
110 while(1)
111 {
112 char buf[128] = {};
113 ssize_t size = read(conn,buf,sizeof(buf) - sizeof(buf[0]));
114 if(size == -1)
115 {
116 perror("read");
117 return -1;
118 }
119 if(size == 0)
120 {
121 printf("服务端:客户端断开连接\n");
122 break;
123 }
124 for(int i = 0; i < size; i++)
125 {
126 //转换大写
127 buf[i] = toupper(buf[i]);
128 }
129 //发送给客户端
130 if(write(conn,buf,size) ==-1)
131 {
132 perror("write");
133 return -1;
134 }
135 }
136 //关闭套接字
137 printf("服务器:关闭套接字\n");
138 close(conn);
139 return 0;
140 }
141 close(conn);//父进程关闭通信套接字
142 }
143 close(sockfd);
144 return 0;
145}
2.2.5.1.2客户端
1//基于TCP协议的客户端
2#include<stdio.h>
3#include<unistd.h>
4#include<string.h>
5#include<sys/socket.h>
6#include<sys/types.h>
7#include<arpa/inet.h>
8
9int main()
10{
11 //创建服务器套接字
12 printf("客户端:创建套接字\n");
13 int sockfd = socket(AF_INET,SOCK_STREAM,0);
14 if(sockfd ==-1)
15 {
16 perror("socket");
17 return -1;
18 }
19 //组织服务器的地址结构
20 printf("客户端:组织服务器的地址结构\n");
21 struct sockaddr_in ser;
22 ser.sin_family = AF_INET;
23 ser.sin_port = htons(8888);
24 ser.sin_addr.s_addr = inet_addr("127.0.0.1");//不能像服务器宏一样
25 //发起连接
26 printf("客户端:发起连接\n");
27 if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1 )
28 {
29 perror("connect");
30 return -1;
31 }
32 //业务处理
33 printf("客户端:业务处理\n");
34 for(;;)
35 {
36 char buf[128] = {};
37 fgets(buf,sizeof(buf),stdin);
38 //循环退出条件
39 if(strcmp(buf,"!\n") == 0)
40 {
41 break;
42 }
43 //将小写传发送给服务器
44 if(send(sockfd,buf,strlen(buf),0) == -1 )
45 {
46 perror("send");
47 return -1;
48 }
49
50 //接收服务器回传的大写的串
51 if(recv(sockfd,buf,sizeof(buf) - sizeof(buf[0]),0) == -1)
52 {
53 perror("recv");
54 return -1;
55 }
56 printf(">>%s",buf);
57 }
58 //关闭套接字
59 printf("客户端:关闭套接字\n");
60 close(sockfd);
61}
2.2.5.2基于UDP协议的demo
2.2.5.2.1服务端
1//基于UDP的服务器
2#include<stdio.h>
3#include<unistd.h>
4#include<ctype.h>
5#include<sys/socket.h>
6#include<arpa/inet.h>
7#include<sys/types.h>
8
9int main()
10{
11 //创建套接字
12 printf("服务器:创建套接字\n");
13 int sockfd = socket(PF_INET,SOCK_DGRAM,0);
14 if(sockfd == -1)
15 {
16 perror("socket");
17 return -1;
18 }
19 //组织地址结构
20 printf("服务器:组织地址结构\n");
21 struct sockaddr_in ser;
22 ser.sin_family = AF_INET;
23 ser.sin_port = htons(9999);
24 ser.sin_addr.s_addr = INADDR_ANY;//接受任意IP地址数据
25 //绑定套接字和地址结构
26 printf("服务器:绑定套接字和地址结构\n");
27 if(bind(sockfd,(struct sockaddr *)&ser,sizeof(ser)) == -1)
28 {
29 perror("bind");
30 return -1;
31 }
32 //处理数据
33 printf("服务器:处理数据\n");
34 while(1)
35 {
36 //接收客户端的串
37 char buf[128] = {};
38 struct sockaddr_in cli;//用来输出客户端的地址结构
39 socklen_t len = sizeof(cli);
40 ssize_t size = recvfrom(sockfd,buf,sizeof(buf)-sizeof(buf[0]),0,(struct sockaddr*)&cli,&len);
41 if(size == -1)
42 {
43 perror("recvfrom");
44 return -1;
45 }
46 //转成大写
47 for(int i = 0;i < size;i++)
48 {
49 buf[i] = toupper(buf[i]);
50 }
51 //发送客户端
52 if(sendto(sockfd,buf,size,0,(struct sockaddr *)&cli,len) == -1)
53 {
54 perror("sendto");
55 return -1;
56 }
57 }
58 //关闭套接字
59 printf("服务器:关闭套接字\n");
60 close(sockfd);
61 return 0;
62}
2.2.5.2.2客户端
1//客户端
2#include<stdio.h>
3#include<unistd.h>
4#include<sys/socket.h>
5#include<string.h>
6#include<sys/types.h>
7#include<arpa/inet.h>
8
9int main()
10{
11 //创建套接字
12 printf("客户端:创建套接字\n");
13 int sockfd = socket(PF_INET,SOCK_DGRAM,0);
14 if(sockfd == -1)
15 {
16 perror("socket");
17 return -1;
18 }
19 //组织服务器地址结构
20 printf("客户端:组织地址结构\n");
21 struct sockaddr_in ser;
22 ser.sin_family = PF_INET;
23 ser.sin_port = htons(9999);
24 ser.sin_addr.s_addr = inet_addr("127.0.0.1");
25 //数据处理
26 printf("客户端:数据处理\n");
27 for(;;)
28 {
29 //通过键盘获取小写的串
30 char buf[128] = {};
31 fgets(buf,sizeof(buf),stdin);
32 //循环退出条件
33 if(strcmp(buf,"!\n") == 0)
34 {
35 break;
36 }
37
38 //发送给服务器
39 if(sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&ser,sizeof(ser)) == -1)
40 {
41 perror("sendto");
42 return -1;
43 }
44 //接收服务器发送回来的大写的串
45 if(recv(sockfd,buf,sizeof(buf)-sizeof(buf[0]),0) == -1)
46 {
47 perror("recv");
48 return -1;
49 }
50 //打印输出
51 printf("%s\n",buf);
52 }
53 //关闭套接字
54 close(sockfd);
55 return 0;
56}
2.2.6socket一些补充
本文篇幅有限,主要做一个大致的介绍,具体内容还需要查看**《UNIX环境高级编程手册》**
2.2.6.1关于socket中的write和send的区别
1ssize_t write(int fd, const void*buf,size_t nbytes); 2 3#include <sys/types.h> 4#include <sys/socket.h> 5ssize_t send(int sockfd, const void *buf, size_t len, int flags);
尽管可以通过read和write交换数据,但这就是这两个函数所能做的一切。如果想指定选项,从多个客户端接收数据包,或者发送带外数据,就需要使用6个为数据传递而设计的套接字函数中的一个。3个函数用来发送数据,3个用于接收数据。最简单的是send,它和write很像,但是可以指定标志来改变处理传输数据的方式。即send()和write之间的唯一区别是是否存在标志。使用零标志参数,send()等效于write。
类似write,使用send时套接字必须已经连接。参数buf和nbytes的含义与write中的一致。 然而,与write不同的是,send支持第4个参数args。
flags 含义 MSG_CONFIRM
告诉链接层:你从另一端得到了一个成功的回复。如果链路层没有得到这一点,它将定期重新探测邻居(例如,通过单播ARP)。仅对 SOCK_DGRAM
和SOCK_RAW
套接字有效,目前仅对IPv4
和IPv6
实现。MSG_DONTROUTE
不要使用网关发送数据包,只发送到直连网络上的主机。这通常只被诊断程序或路由程序使用。 MSG_DONTWAIT
启用非阻塞操作;如果操作将阻塞,则返回 EAGAIN
或EWOULDBLOCK
。这提供了**类似于设置O_NONBLOCK
**标志的行为(通过fcntl(2) F_SETFL
操作),但不同之处在于MSG_DONTWAIT
是每个调用的选项,而O_NONBLOCK
是对打开的文件描述的设置(参见open(2)
),这将影响调用进程中的所有线程,以及其他持有引用相同打开文件描述的文件描述符的进程。MSG_EOR
终止一条记录(当支持此概念时,例如 SOCK_SEQPACKET
类型的套接字)。MSG_MORE
调用方有更多数据要发送。此标志用于 TCP
套接字以获得与TCP_CORK
套接字选项相同的效果(参见TCP(7)
),不同的是此标志可以在每次调用的基础上设置。自Linux 2.6以来,这个标志也支持UDP
套接字,并通知内核将所有在调用中发送的数据打包成一个单一的数据报,该数据报仅在执行不指定此标志的调用时传输。(请参见udp(7)
中描述的UDP_CORK
套接字选项。)MSG_NOSIGNAL
如果对等端在面向流的套接字上关闭了连接,则不要生成 SIGPIPE
信号。仍然返回EPIPE
错误。这提供了类似于使用sigaction(2)
忽略SIGPIPE
的行为,但是,MSG_NOSIGNAL
是每个调用的特性,忽略SIGPIPE
会设置一个影响进程中所有线程的进程属性。MSG_OOB
在支持此概念的套接字(例如 SOCK_STREAM
类型)上发送带外数据;底层协议还必须支持带外数据。紧急消息,会优先处理。
2.2.6.2关于socket中的write和writev的区别
1#include <sys/uio.h> 2struct iovec { 3 void *iov_base; /* Starting address */ 4 size_t iov_len; /* Number of bytes to transfer */ 5}; 6ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
writev 函数的功能可概括为:对数据进行整合接收及发送的函数。也就是说,通过 writev 函数可以将分散保存在多个缓冲中的数据一并发送,通过 readv 函数可以由多个缓冲分别接收。writev()在继续到iov[1]之前写出iov[0]的全部内容,writev()执行的数据传输是原子的。因此,适当使用这两个函数可以减少 I/O 函数的调用次数。
write和writev区别
对于socket IO而言,write经常不能够一次写完,好在它会返回已经写了多少字节,如果继续写,此时就会阻塞;对于非阻塞socket而言,write会在buf不可写时返回的EAGAIN,那么在下一次write时,便可通过之前返回的值重新确定基址和长度。
manual中对于writev的相关描述为:和write类似。也就是说,它也会返回已经写入的长度或者EAGAIN(errno)。千万不可天真地认为,每次传同样的iovec就能解决问题,writev并不会为你做任何事情,重新处理iovec是调用者的任务。
问题是,这个返回值“实用性”并不高,因为参数传入的是iovec数组,计量单位是iovcnt,而不是字节数,用户依旧需要通过遍历iovec来计算新的基址,另外写入数据的“结束点”可能位于一个iovec的中间某个位置,因此需要调整临界iovec的io_base和io_len。
对于socket,尤其是非阻塞socket,还是尽可能避免writev的好,实现连续的内存块反而可以简化实现。
2.2.6.3关于socket中recvmsg 和 sendmsg 函数
具体内容可以点击这里
1#include <sys/types.h> 2#include <sys/socket.h> 3 4ssize_t send(int sockfd, const void *buf, size_t len, int flags); 5 6ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, 7 const struct sockaddr *dest_addr, socklen_t addrlen); 8 9ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
主要是有两个结构体msghdr和cmsghdr。
这两个函数的参数中都有一个指向msghdr结构的指针,该结构包含了所有关于要发送或要接收的消息的信息。该结构的定义大致如下:
1struct msghdr { 2 void *msg_name; // protocol address 3 socklen_t msg_namelen; // size of protocol address 4 struct iovec *msg_iov; // scatter/gather array 5 int msg_iovlen; // elements in msg_iov 6 void *msg_control; // ancillary data (cmsghdr struct) 7 socklen_t msg_controllen; // length of ancillary data 8 int msg_flags; // flags returned by recvmsg() 9}; 10 11struct cmsghdr { 12 size_t cmsg_len; 13 int cmsg_level; 14 int cmsg_type; 15};
另外还有几个宏定义用于上面结构体的计算
1#define CMSG_NXTHDR(mhdr, cmsg) __cmsg_nxthdr((mhdr), (cmsg)) 2#define CMSG_ALIGN(len) ( ((len)+sizeof(long)-1) & ~(sizeof(long)-1) ) 3#define CMSG_DATA(cmsg) (((unsigned char*)(cmsg) + CMSG_ALIGN(sizeof(struct cmsghdr)))) 4#define CMSG_SPACE(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + CMSG_ALIGN(len)) 5#define CMSG_LEN(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + (len)) 6#define CMSG_FIRSTHDR(msg) \ 7 ((msg)->msg_controllen >= sizeof(struct cmsghdr) \ 8 ? (struct cmsghdr*) (msg)->msg_control : (struct cmsghdr*) NULL) 9#define CMSG_OK(mhdr, cmsg) ((cmsg)->cmsg_len >= sizeof(struct cmsghdr) && (cmsg)->cmsg_len <= (unsigned long) ((mhdr)->msg_controllen - ((char*)(cmsg) - (char*)(mhdr)->msg_control)))
关于结构体msghdr
关于结构体msghdr和cmsghdr联系
send,sendto和sendmsg区别
即使send成功返回,也并不表示连接的另一端的进程就一定接收了数据。我们所能保证的只是当send成功返回时,数据已经被无错误地发送到网络驱动程序上。当消息没有放入套接字的发送缓冲区时,send()通常会阻塞,除非套接字已被置于非阻塞I/O模式。在非阻塞模式下,它会失败,在这种情况下会出现EAGAIN或EWOULDBLOCK错误。
对于支持报文边界的协议,如果尝试发送的单个报文的长度超过协议所支持的最大长度,那么send会失败,并将errno设为EMSGSIZE。对于字节流协议,send会阻塞直到整个数据传输完成。函数sendto和send很类似。区别在于sendto可以在无连接的套接字上指定一个目标地址。
对于面向连接的套接字,目标地址是被忽略的,因为连接中隐含了目标地址。对于无连接的套接字,除非先调用connect设置了目标地址,否则不能使用send。sendto提供了发送报文的另一种方式。
通过套接字发送数据时,还有一个选择。可以调用带有msghdr结构的sendmsg来指定多重缓冲区传输数据,这和writev函数很相似。sendmsg()调用还允许发送辅助数据(也称为控制信息)。
1//下面两种方式一致 2send(sockfd, buf, len, flags); 3sendto(sockfd, buf, len, flags, NULL, 0);
如果sendto()用于连接模式套接字(SOCK_STREAM, SOCK_SEQPACKET),则参数dest_addr和addrlen将被忽略(当它们不为NULL和0时可能返回EISCONN错误),并且当套接字未实际连接时返回错误ENOTCONN。否则,目标的地址由dest_addr给出,addrlen指定其大小。对于sendmsg(),目标地址由msg给出。Msg_name,加上msg。msg_namelen指定它的大小。
2.2.6.4socket发送函数总结
应用层的五个发送函数,实际上对应系统调用最终都回到sock_sendmsg,即把应用层的数据封装成msghdr,然后继续发送到协议栈。write可以看作是单个iovec的数据,且flags为0;writev可以看作是不定项数个iovec的数据,且flags为0;send可以看作是单个iovec的数据,且flags可以是send falgs表中任意值;sendto就是封装了send,多了flags值和sockaddr;sendmsg比较特殊,默认就构造了msghdr,直接发送到sock_sendmsg即可。
3 源码中的socket
先介绍一下源码中封装的socket,由于Android系统中很多都是本地通信,回连127.0.0.1,所以单独封装了相对应的源码。
3.0.1客户端连接
根据上面篇章可以知道客户端连接只需要创建socket,等待与服务端连接connect
1//system/core/libcutils/socket_local_client_unix.cpp
2#define ANDROID_SOCKET_DIR "/dev/socket"
3// Linux "abstract" (non-filesystem) namespace
4#define ANDROID_SOCKET_NAMESPACE_ABSTRACT 0
5// Android "reserved" (/dev/socket) namespace
6#define ANDROID_SOCKET_NAMESPACE_RESERVED 1
7// Normal filesystem namespace
8#define ANDROID_SOCKET_NAMESPACE_FILESYSTEM 2
9
10int socket_local_client(const char *name, int namespaceId, int type)
11{
12 int s;
13 //1.创建socket,AF_LOCAL指定本地连接
14 s = socket(AF_LOCAL, type, 0);
15 if(s < 0) return -1;
16
17 if ( 0 > socket_local_client_connect(s, name, namespaceId, type)) {
18 close(s);
19 return -1;
20 }
21
22 return s;
23}
24
25int socket_local_client_connect(int fd, const char* name, int namespaceId, int /*type*/) {
26 struct sockaddr_un addr;
27 socklen_t alen;
28 int err;
29 //这里的addr是协议族,主要包含本地连接的路径名
30 err = socket_make_sockaddr_un(name, namespaceId, &addr, &alen);
31
32 if (err < 0) {
33 goto error;
34 }
35 //2.等待与服务端连接
36 if(connect(fd, (struct sockaddr *) &addr, alen) < 0) {
37 goto error;
38 }
39
40 return fd;
41
42error:
43 return -1;
44}
45
46int socket_make_sockaddr_un(const char *name, int namespaceId,
47 struct sockaddr_un *p_addr, socklen_t *alen)
48{
49 memset (p_addr, 0, sizeof (*p_addr));
50 size_t namelen;
51 //根据传入的id来区分sockaddr_un族中的socket文件路径
52 switch (namespaceId) {
53 //这个协议通常是bt中使用比较多,类似在/data/misc/bluedroid/.sco_ctrl
54 case ANDROID_SOCKET_NAMESPACE_ABSTRACT:
55 namelen = strlen(name);
56
57 if ((namelen + 1) > sizeof(p_addr->sun_path)) {
58 goto error;
59 }
60 p_addr->sun_path[0] = 0;
61 memcpy(p_addr->sun_path + 1, name, namelen);
62 break;
63 //这个协议/dev/socket文件夹下面的socket文件,有日志相关的logd等
64 case ANDROID_SOCKET_NAMESPACE_RESERVED:
65 namelen = strlen(name) + strlen(ANDROID_RESERVED_SOCKET_PREFIX);
66 if (namelen > sizeof(*p_addr)
67 - offsetof(struct sockaddr_un, sun_path) - 1) {
68 goto error;
69 }
70
71 strcpy(p_addr->sun_path, ANDROID_RESERVED_SOCKET_PREFIX);
72 strcat(p_addr->sun_path, name);
73 break;
74 //这个协议是正常文件系统的协议,比如/data/system/ndebugsocket
75 case ANDROID_SOCKET_NAMESPACE_FILESYSTEM:
76 namelen = strlen(name);
77 if (namelen > sizeof(*p_addr)
78 - offsetof(struct sockaddr_un, sun_path) - 1) {
79 goto error;
80 }
81
82 strcpy(p_addr->sun_path, name);
83 break;
84 default:
85 return -1;
86 }
87
88 p_addr->sun_family = AF_LOCAL;
89 *alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;
90 return 0;
91error:
92 return -1;
93}
3.0.2服务端连接
根据上面篇章可以知道服务端连接需要创建socket,关联地址和套接字与创建监听队列。
1//system/core/libcutils/socket_local_server_unix.cpp
2#define LISTEN_BACKLOG 4
3#define SOCK_TYPE_MASK 0xf
4int socket_local_server(const char *name, int namespaceId, int type)
5{
6 int err;
7 int s;
8 //1.创建socket,AF_LOCAL指定本地连接
9 s = socket(AF_LOCAL, type, 0);
10 if (s < 0) return -1;
11
12 err = socket_local_server_bind(s, name, namespaceId);
13
14 if (err < 0) {
15 close(s);
16 return -1;
17 }
18 //SOCK_STREAM表示这个服务端是以TCP协议的传输层的socket通信,否则就是UDP协议
19 if ((type & SOCK_TYPE_MASK) == SOCK_STREAM) {
20 int ret;
21 //3.如果是TCP协议,那么创建监听队列,默认LISTEN_BACKLOG为4
22 ret = listen(s, LISTEN_BACKLOG);
23
24 if (ret < 0) {
25 close(s);
26 return -1;
27 }
28 }
29
30 return s;
31}
32
33int socket_local_server_bind(int s, const char *name, int namespaceId)
34{
35 struct sockaddr_un addr;
36 socklen_t alen;
37 int n;
38 int err;
39 //同客户端类似,这里的服务端主要是生成addr
40 err = socket_make_sockaddr_un(name, namespaceId, &addr, &alen);
41
42 if (err < 0) {
43 return -1;
44 }
45 //因为是本地通信,所以当id是RESERVED和FILESYSTEM的时候,删除addr协议族的路径
46 if (namespaceId == ANDROID_SOCKET_NAMESPACE_RESERVED
47 || namespaceId == ANDROID_SOCKET_NAMESPACE_FILESYSTEM) {
48 unlink(addr.sun_path);
49 }
50
51 n = 1;
52 //设置调用close(socket)后,仍可继续重用该socket
53 //调用close(socket)一般不会立即关闭socket,而经历TIME_WAIT的过程
54 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));
55 //2.关联地址和套接字
56 if(bind(s, (struct sockaddr *) &addr, alen) < 0) {
57 return -1;
58 }
59
60 return s;
61}
3.1demo1
这个是Java端服务端和C/C++层为客户端进行TCP协议的连接
在AF_INET通信域中,套接字类型SOCK_STREAM的默认协议是传输控制协议(Transmission Control Protocol,TCP)。字节流(SOCK_STREAM)要求在交换数据之前,在本地套接字和通信的对等进程的套接字之间建立一个逻辑连接。使用面向连接的协议通信就像与对方打电话。首先,需要通过电话建立一个连接,连接建立好之后,彼此能双向地通信。每个连接是端到端的通信链路。对话中不包含地址信息,就像呼叫两端存在一个点对点虚拟连接,并且连接本身暗示特定的源和目的地。
SOCK_STREAM套接字提供字节流服务,所以应用程序分辨不出报文的界限。这意味着从SOCK_STREAM套接字读数据时,它也许不会返回所有由发送进程所写的字节数。最终可以获得发送过来的所有数据,但也许要通过若干次函数调用才能得到。
3.1.1服务端
1//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
2public void startObservingNativeCrashes() {
3 final NativeCrashListener ncl = new NativeCrashListener(this);
4 ncl.start();
5}
NativeCrashListener的Thread启动
1//frameworks/base/services/core/java/com/android/server/am/NativeCrashListener.java
2final class NativeCrashListener extends Thread {
3 static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket";
4 @Override
5 public void run() {
6 //这里是响应客户端的ack,为一个0值
7 final byte[] ackSignal = new byte[1];
8
9 if (DEBUG) Slog.i(TAG, "Starting up");
10 {
11 //创建一个socket文件,DEBUGGERD_SOCKET_PATH为/data/system/ndebugsocket
12 File socketFile = new File(DEBUGGERD_SOCKET_PATH);
13 if (socketFile.exists()) {
14 socketFile.delete();
15 }
16 }
17
18 try {
19 //1.创建服务端socket句柄,AF_UNIX为本地通信,SOCK_STREAM为TCP协议
20 FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0);
21 //获取DEBUGGERD_SOCKET_PATH,转化为UnixSocketAddress
22 final UnixSocketAddress sockAddr = UnixSocketAddress.createFileSystem(
23 DEBUGGERD_SOCKET_PATH);
24 //2.关联地址和套接字
25 Os.bind(serverFd, sockAddr);
26 //3.如果是TCP协议,那么创建监听队列,这里队列只有一个,同时只能处理一个IO
27 Os.listen(serverFd, 1);
28 //设置socket文件权限为可读可写可执行
29 Os.chmod(DEBUGGERD_SOCKET_PATH, 0777);
30 //这里就是为了保证可以循环处理多个IO,服务端不能直接关闭
31 while (true) {
32 FileDescriptor peerFd = null;
33 try {
34 if (MORE_DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
35 //4.服务端获取连接请求并建立连接
36 peerFd = Os.accept(serverFd, null /* peerAddress */);
37 if (peerFd != null) {
38 //服务端处理客户端的请求
39 consumeNativeCrashData(peerFd);
40 }
41 } catch (Exception e) {
42 Slog.w(TAG, "Error handling connection", e);
43 } finally {
44 if (peerFd != null) {
45 try {
46 //5.存在客户端,那么发送一个响应
47 Os.write(peerFd, ackSignal, 0, 1);
48 } catch (Exception e) {
49 ...
50 }
51 try {
52 //6.关闭客户端句柄
53 Os.close(peerFd);
54 } catch (ErrnoException e) {
55 ...
56 }
57 }
58 }
59 }
60 } catch (Exception e) {
61 Slog.e(TAG, "Unable to init native debug socket!", e);
62 }
63 }
3.1.2客户端
1//system/core/debuggerd/crash_dump.cpp
2static bool activity_manager_notify(pid_t pid, int signal, const std::string& amfd_data) {
3 //获取句柄amfd,
4 // "/data/system/ndebugsocket"名称
5 //ANDROID_SOCKET_NAMESPACE_FILESYSTEM类型为普通文件系统
6 //SOCK_STREAM为TCP连接
7 android::base::unique_fd amfd(socket_local_client(
8 "/data/system/ndebugsocket", ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM));
9 if (amfd.get() == -1) {
10 PLOG(ERROR) << "unable to connect to activity manager";
11 return false;
12 }
13 //设置一个收发时间1s
14 struct timeval tv = {
15 .tv_sec = 1,
16 .tv_usec = 0,
17 };
18 //发送时限为1s
19 if (setsockopt(amfd.get(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) {
20 PLOG(ERROR) << "failed to set send timeout on activity manager socket";
21 return false;
22 }
23 tv.tv_sec = 3;
24 //接收时限为3s
25 if (setsockopt(amfd.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
26 PLOG(ERROR) << "failed to set receive timeout on activity manager socket";
27 return false;
28 }
29
30 // 活动管理器协议:二进制32位网络字节顺序整数为pid和信号号,然后是转储的原始文本,最后是一个零字节,标志着数据结束。
31 //TCP/IP协议栈使用大端字节序,地址用网络字节序来表示,所以应用程序有时需要在处理器的字节序与网络字节序之间转换
32 //这里分别表示两个量,一个是pid,另外一个是信号量,分别以四个字节保存到网络字节序中
33 //比如pid是2023,信号是11,那么表示为0x 00 00 07 E7 00 00 00 0B
34 uint32_t datum = htonl(pid);
35 if (!android::base::WriteFully(amfd, &datum, 4)) {
36 PLOG(ERROR) << "AM pid write failed";
37 return false;
38 }
39 datum = htonl(signal);
40 if (!android::base::WriteFully(amfd, &datum, 4)) {
41 PLOG(ERROR) << "AM signal write failed";
42 return false;
43 }
44 //amfd_data为打印的错误信息log
45 if (!android::base::WriteFully(amfd, amfd_data.c_str(), amfd_data.size() + 1)) {
46 PLOG(ERROR) << "AM data write failed";
47 return false;
48 }
49
50 // 3s之后接收来自服务端的内容,这里是一个值为0的ack
51 char ack;
52 android::base::ReadFully(amfd, &ack, 1);
53 return true;
54}
3.2demo2
一个具有报文服务的客户端和服务端传输
SOCK_SEQPACKET套接字和SOCK_STREAM套接字很类似,只是从该套接字得到的是基于报文的服务而不是字节流服务。这意味着从SOCK_SEQPACKET套接字接收的数据量与对方所发送的一致。流控制传输协议(Stream Control Transmission Protocol,SCTP)提供了因特网域上的顺序数据包服务。
3.2.1服务端
服务端的底层原理比较复杂,这里涉及到event原理,笔者不做展开。主要对socket是做一个逻辑上的梳理。
1//system/core/debuggerd/tombstoned/tombstoned.cpp
2constexpr char kTombstonedCrashSocketName[] = "tombstoned_crash";
3int main(int, char* []) {
4 //1.生成一个socket句柄
5 int crash_socket = android_get_control_socket(kTombstonedCrashSocketName);
6
7 if (intercept_socket == -1 || crash_socket == -1) {
8 PLOG(FATAL) << "failed to get socket from init";
9 }
10 //设置服务端的socket句柄不阻塞
11 evutil_make_socket_nonblocking(crash_socket);
12
13 event_base* base = event_base_new();
14 if (!base) {
15 LOG(FATAL) << "failed to create event_base";
16 }
17 intercept_manager = new InterceptManager(base, intercept_socket);
18 //2.建立客户端与服务端的连接,将句柄和函数crash_accept_cb绑定,一旦有客户端连接调用该函数
19 evconnlistener* tombstone_listener =
20 evconnlistener_new(base, crash_accept_cb, CrashQueue::for_tombstones(), LEV_OPT_CLOSE_ON_FREE,
21 -1 /* backlog */, crash_socket);
22 if (!tombstone_listener) {
23 LOG(FATAL) << "failed to create evconnlistener for tombstones.";
24 }
25 ...
26 LOG(INFO) << "tombstoned successfully initialized";
27 //循环等待客户端连接
28 event_base_dispatch(base);
29}
30//客户端连接成功,调用到这里
31static void crash_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int,
32 void*) {
33 //3.设置一个监听
34 event_base* base = evconnlistener_get_base(listener);
35 Crash* crash = new Crash();
36 //设置接收的超时时间1s
37 struct timeval timeout = { 1, 0 };
38 //创建接收函数,重新创建一个函数用于接收
39 event* crash_event = event_new(base, sockfd, EV_TIMEOUT | EV_READ, crash_request_cb, crash);
40 crash->crash_socket_fd.reset(sockfd);
41 crash->crash_event = crash_event;
42 event_add(crash_event, &timeout);
43}
44//接收packet报文函数
45static void crash_request_cb(evutil_socket_t sockfd, short ev, void* arg) {
46 ssize_t rc;
47 Crash* crash = static_cast<Crash*>(arg);
48
49 TombstonedCrashPacket request = {};
50
51 if ((ev & EV_TIMEOUT) != 0) {
52 LOG(WARNING) << "crash request timed out";
53 goto fail;
54 } else if ((ev & EV_READ) == 0) {
55 LOG(WARNING) << "tombstoned received unexpected event from crash socket";
56 goto fail;
57 }
58 //3.接收packet的报文
59 rc = TEMP_FAILURE_RETRY(read(sockfd, &request, sizeof(request)));
60 if (rc == -1) {
61 PLOG(WARNING) << "failed to read from crash socket";
62 goto fail;
63 }
64
65 if (request.packet_type != CrashPacketType::kDumpRequest) {
66 LOG(WARNING) << "unexpected crash packet type, expected kDumpRequest, received "
67 << StringPrintf("%#2hhX", request.packet_type);
68 }
69
70 crash->crash_type = request.packet.dump_request.dump_type;
71 if (crash->crash_type < 0 || crash->crash_type > kDebuggerdAnyIntercept) {
72 LOG(WARNING) << "unexpected crash dump type: " << crash->crash_type;
73 }
74
75 if (crash->crash_type != kDebuggerdJavaBacktrace) {
76 crash->crash_pid = request.packet.dump_request.pid;
77 }
78
79 return;
80}
3.2.2客户端
1//system/core/debuggerd/tombstoned/tombstoned_client.cpp
2constexpr char kTombstonedCrashSocketName[] = "tombstoned_crash";
3enum class CrashPacketType : uint8_t {
4 kDumpRequest = 0,
5 kCompletedDump,
6 kPerformDump = 128,
7 kAbortDump,
8};
9
10struct DumpRequest {
11 DebuggerdDumpType dump_type;
12 int32_t pid;
13};
14//报文的结构体
15struct TombstonedCrashPacket {
16 CrashPacketType packet_type;
17 union {
18 DumpRequest dump_request;
19 } packet;
20};
21
22bool tombstoned_connect(pid_t pid, unique_fd* tombstoned_socket, unique_fd* output_fd,
23 DebuggerdDumpType dump_type) {
24 //1.socket连接
25 //name为tombstoned_crash
26 //id为ANDROID_SOCKET_NAMESPACE_RESERVED,socket路径为/dev/socket/tombstoned_crash
27 //SOCK_SEQPACKET为基于报文的服务
28 unique_fd sockfd(
29 socket_local_client((dump_type != kDebuggerdJavaBacktrace ? kTombstonedCrashSocketName
30 : kTombstonedJavaTraceSocketName),
31 ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
32 if (sockfd == -1) {
33 async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to connect to tombstoned: %s",
34 strerror(errno));
35 return false;
36 }
37
38 TombstonedCrashPacket packet = {};
39 packet.packet_type = CrashPacketType::kDumpRequest;
40 packet.packet.dump_request.pid = pid;
41 packet.packet.dump_request.dump_type = dump_type;
42 //2.发送packet的报文
43 if (TEMP_FAILURE_RETRY(write(sockfd, &packet, sizeof(packet))) != sizeof(packet)) {
44 async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to write DumpRequest packet: %s",
45 strerror(errno));
46 return false;
47 }
48 ...
49 return true;
50}
3.3demo3
这个demo是Android中的日志系统,主要是有两个线程,一个线程用于把日志写入到缓冲区,另一个线程用于把缓冲区的日志读出来。
写的时候使用的是使用的是SOCK_DGRAM,读的时候用到的是SOCK_SEQPACKET,这个demo2已经使用过,就不再介绍。
在AF_INET通信域中,套接字类型SOCK_DGRAM的默认协议是UDP。对于数据报(SOCK_DGRAM)接口,两个对等进程之间通信时不需要逻辑连接。只需要向对等进程所使用的套接字送出一个报文。因此数据报提供了一个无连接的服务。数据报是自包含报文。发送数据报近似于给某人邮寄信件。你能邮寄很多信,但你不能保证传递的次序,并且可能有些信件会丢失在路上。每封信件包含接收者地址,使这封信件独立于所有其他信件。每封信件可能送达不同的接收者。
3.3.1服务端
这里的服务端主要是以logd进程开启的两个线程作为服务端,分别是使用UDP协议的logd.writer线程和使用SEQPACKET基于报文的传输的logd.reader线程。
1//system/core/logd/main.cpp
2int main(int argc, char* argv[]) {
3 ...
4 // LogReader监听/dev/socket/logdr .当客户端连接时,LogBuffer中的日志条目被写入客户端。
5 LogReader* reader = new LogReader(logBuf);
6 if (reader->startListener()) {
7 return EXIT_FAILURE;
8 }
9 // LogListener监听/dev/socket/logdw上客户端发起的日志消息。
10 //新的日志条目被添加到LogBuffer中,LogReader被通知向连接的客户端发送更新。
11 LogListener* swl = new LogListener(logBuf, reader);
12 // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
13 if (swl->startListener(600)) {
14 return EXIT_FAILURE;
15 }
16 ...
17}
3.3.1.1LogReader服务端
1)构造LogReader
1//system/core/logd/LogReader.cpp
2LogReader::LogReader(LogBuffer* logbuf)
3 : SocketListener(getLogSocket(), true), mLogbuf(*logbuf) {
4}
5//根据上面可以得知实际上是开启一个logdr的socket服务端
6int LogReader::getLogSocket() {
7 static const char socketName[] = "logdr";
8 sock = socket_local_server(
9 socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET);
10 return sock;
11}
2)基类最先完成构造
1//system/core/libsysutils/src/SocketListener.cpp
2SocketListener::SocketListener(int socketFd, bool listen) {
3 init(nullptr, socketFd, listen, false);
4}
5
6void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) {
7 mListen = listen;
8 mSocketName = socketName;
9 mSock = socketFd;
10 mUseCmdNum = useCmdNum;
11 pthread_mutex_init(&mClientsLock, nullptr);
12}
3)LogReader开启logd.reader线程
1//system/core/libsysutils/src/SocketListener.cpp
2int SocketListener::startListener() {
3 return startListener(4);
4}
5
6//这里的mListen构造时候传入就是true
7int SocketListener::startListener(int backlog) {
8 ...
9 //在 Linux 内核 2.2 之后,backlog 是已完成连接建立的队列长度,所以现在通常认为 backlog 是 accept 队列。
10 //这里使用的是默认值4
11 if (mListen && listen(mSock, backlog) < 0) {
12 SLOGE("Unable to listen on socket (%s)", strerror(errno));
13 return -1;
14 }
15 //管道创建,最终用于关闭子线程使用
16 if (pipe2(mCtrlPipe, O_CLOEXEC)) {
17 SLOGE("pipe failed (%s)", strerror(errno));
18 return -1;
19 }
20 //开启一个子线程
21 if (pthread_create(&mThread, nullptr, SocketListener::threadStart, this)) {
22 SLOGE("pthread_create (%s)", strerror(errno));
23 return -1;
24 }
25
26 return 0;
27}
28//开启一个线程
29void *SocketListener::threadStart(void *obj) {
30 SocketListener *me = reinterpret_cast<SocketListener *>(obj);
31
32 me->runListener();
33 pthread_exit(nullptr);
34 return nullptr;
35}
36//这里使用了poll机制,使得客户端连接能够IO多路复用
37void SocketListener::runListener() {
38 while (true) {
39 std::vector<pollfd> fds;
40
41 pthread_mutex_lock(&mClientsLock);
42 fds.reserve(2 + mClients.size());
43 fds.push_back({.fd = mCtrlPipe[0], .events = POLLIN});
44 if (mListen) fds.push_back({.fd = mSock, .events = POLLIN});
45 for (auto pair : mClients) {
46 const int fd = pair.second->getSocket();
47 fds.push_back({.fd = fd, .events = POLLIN});
48 }
49 pthread_mutex_unlock(&mClientsLock);
50
51 int rc = TEMP_FAILURE_RETRY(poll(fds.data(), fds.size(), -1));
52 //轮询是否收到管道的关闭或者是唤醒
53 if (fds[0].revents & (POLLIN | POLLERR)) {
54 char c = CtrlPipe_Shutdown;
55 TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
56 if (c == CtrlPipe_Shutdown) {
57 break;
58 }
59 continue;
60 }
61 //这里的fds[1]实际上是logdr的服务端的socket句柄,用来接收客户端的队列,并将队列存在客户端关联容器mClients中
62 if (mListen && (fds[1].revents & (POLLIN | POLLERR))) {
63 int c = TEMP_FAILURE_RETRY(accept4(mSock, nullptr, nullptr, SOCK_CLOEXEC));
64 if (c < 0) {
65 SLOGE("accept failed (%s)", strerror(errno));
66 sleep(1);
67 continue;
68 }
69 pthread_mutex_lock(&mClientsLock);
70 mClients[c] = new SocketClient(c, true, mUseCmdNum);
71 pthread_mutex_unlock(&mClientsLock);
72 }
73 //最终从fds的句柄中找出对应的accept句柄,并且在关联容器mClients中将句柄对应的SocketClient数据结构保存起来
74 std::vector<SocketClient*> pending;
75 pthread_mutex_lock(&mClientsLock);
76 const int size = fds.size();
77 for (int i = mListen ? 2 : 1; i < size; ++i) {
78 const struct pollfd& p = fds[i];
79 if (p.revents & (POLLIN | POLLERR)) {
80 auto it = mClients.find(p.fd);
81 if (it == mClients.end()) {
82 SLOGE("fd vanished: %d", p.fd);
83 continue;
84 }
85 SocketClient* c = it->second;
86 pending.push_back(c);
87 c->incRef();
88 }
89 }
90 pthread_mutex_unlock(&mClientsLock);
91
92 for (SocketClient* c : pending) {
93 //最终每一个客户端连接对应获取的accept句柄,都会有独立访问onDataAvailable函数
94 SLOGV("processing fd %d", c->getSocket());
95 if (!onDataAvailable(c)) {
96 release(c, false);
97 }
98 c->decRef();
99 }
100 }
101}
1)关于backlog相关,可以点击这里
补充关于tcp相关的backlog
2)另外关于poll的IO多路复用机制
上面的poll机制如下图所示
poll() 的机制与 select() 类似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理。
1#include <poll.h> 2int poll(struct pollfd *fds, nfds_t nfds, int timeout);
定义的结构体
- fd:每一个 pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示 poll() 监视多个文件描述符。
- events:表示要告诉操作系统需要监测fd的事件(输入、输出、错误),每一个事件有多个取值
- revents:revents 域是文件描述符的操作结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回。
1struct pollfd { 2 int fd; /* file descriptor */ 3 short events; /* requested events */ 4 short revents; /* returned events */ 5};
成员变量说明:
- fd:每一个 pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示 poll() 监视多个文件描述符。
- events:表示要告诉操作系统需要监测fd的事件(输入、输出、错误),每一个事件有多个取值
- revents:revents 域是文件描述符的操作结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回。
events&revents的取值如下:
事件 描述 是否可作为输入(events) 是否可作为输出(revents) POLLIN 数据可读(包括普通数据&优先数据) 是 是 POLLOUT 数据可写(普通数据&优先数据) 是 是 POLLRDNORM 普通数据可读 是 是 POLLRDBAND 优先级带数据可读(linux不支持) 是 是 POLLPRI 高优先级数据可读,比如TCP带外数据 是 是 POLLWRNORM 普通数据可写 是 是 POLLWRBAND 优先级带数据可写 是 是 POLLRDHUP TCP连接被对端关闭,或者关闭了写操作,由GNU引入 是 是 POPPHUP 挂起 否 是 POLLERR 错误 否 是 注意:
每个结构体的 events 域是由用户来设置,告诉内核我们关注的是什么,而 revents 域是返回时内核设置的,以说明对该描述符发生了什么事件
4)onDataAvailable
1//system/core/logd/LogReader.cpp
2bool LogReader::onDataAvailable(SocketClient* cli) {
3 static bool name_set;
4 if (!name_set) {
5 prctl(PR_SET_NAME, "logd.reader");
6 name_set = true;
7 }
8 //读取对应的客户端发送的类似stream lids=0,1,2,3,4,6 timeout=xx start=xx.xx pid=xx
9 char buffer[255];
10 int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1);
11 buffer[len] = '\0';
12 //这里开始解析收到的socket数据
13 static const char _tail[] = " tail=";
14 char* cp = strstr(buffer, _tail);
15 tail = atol(cp + sizeof(_tail) - 1);
16 static const char _start[] = " start=";
17 cp = strstr(buffer, _start);
18 start.strptime(cp + sizeof(_start) - 1, "%s.%q");
19 static const char _timeout[] = " timeout=";
20 cp = strstr(buffer, _timeout);
21 timeout = atol(cp + sizeof(_timeout) - 1) * NS_PER_SEC +
22 log_time(CLOCK_REALTIME).nsec();
23 static const char _logIds[] = " lids=";
24 cp = strstr(buffer, _logIds);
25 logMask = 0;
26 cp += sizeof(_logIds) - 1;
27 while (*cp && *cp != '\0') {
28 int val = 0;
29 while (isdigit(*cp)) {
30 val = val * 10 + *cp - '0';
31 ++cp;
32 }
33 logMask |= 1 << val;
34 if (*cp != ',') {
35 break;
36 }
37 ++cp;
38 }
39 static const char _pid[] = " pid=";
40 cp = strstr(buffer, _pid);
41 pid = atol(cp + sizeof(_pid) - 1);
42 ...
43 //开始发送数据给客户端,客户端就可以收到对应的日志了
44 logbuf().flushTo(cli, sequence, nullptr, FlushCommand::hasReadLogs(cli),
45 FlushCommand::hasSecurityLogs(cli),
46 logFindStart.callback, &logFindStart);
47 ...
48 return true;
49}
5)flushTo
1//system/core/logd/LogBufferElement.cpp
2log_time LogBufferElement::flushTo(SocketClient* reader, LogBuffer* parent, bool lastSame) {
3 struct logger_entry entry = {};
4
5 entry.hdr_size = sizeof(struct logger_entry);
6 entry.lid = mLogId;
7 entry.pid = mPid;
8 entry.tid = mTid;
9 entry.uid = mUid;
10 entry.sec = mRealTime.tv_sec;
11 entry.nsec = mRealTime.tv_nsec;
12
13 struct iovec iovec[2];
14 iovec[0].iov_base = &entry;
15 iovec[0].iov_len = entry.hdr_size;
16
17 char* buffer = nullptr;
18
19 if (mDropped) {
20 entry.len = populateDroppedMessage(buffer, parent, lastSame);
21 if (!entry.len) return mRealTime;
22 iovec[1].iov_base = buffer;
23 } else {
24 entry.len = mMsgLen;
25 iovec[1].iov_base = mMsg;
26 }
27 iovec[1].iov_len = entry.len;
28 //最终在这里发送给客户端
29 log_time retval = reader->sendDatav(iovec, 1 + (entry.len != 0))
30 ? FLUSH_ERROR
31 : mRealTime;
32
33 if (buffer) free(buffer);
34
35 return retval;
36}
3.3.1.2LogListener服务端
1)构造LogListener
1//system/core/logd/LogListener.cpp
2//与上面的区别是这里在基类构造SocketListener传入false,等同mListener为false
3LogListener::LogListener(LogBuffer* buf, LogReader* reader)
4 : SocketListener(getLogSocket(), false), logbuf(buf), reader(reader) {}
5//根据上面可以得知实际上是开启一个logdw的socket服务端
6int LogListener::getLogSocket() {
7 static const char socketName[] = "logdw";
8 sock = socket_local_server(
9 socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_DGRAM);
10 int on = 1;
11 if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) {
12 return -1;
13 }
14 return sock;
15}
UDP的连接,设置SO_PASSCRED
在辅助消息中启用接收发送过程的凭据。当设置了此选项并且套接字尚未连接时,将自动生成抽象名称空间中的唯一名称。期望一个整数布尔标志。
2)LogListener开启logd.writer线程
1//system/core/libsysutils/src/SocketListener.cpp
2//与logd.reader线程区别是backlog这里传入了600,且传入的mListen=false
3int SocketListener::startListener(int backlog) {
4 if (!mListen)
5 //这里默认就会创建一个SocketClient对象作为客户端接收的句柄
6 mClients[mSock] = new SocketClient(mSock, false, mUseCmdNum);
7 //管道创建,最终用于关闭子线程使用
8 if (pipe2(mCtrlPipe, O_CLOEXEC)) {
9 SLOGE("pipe failed (%s)", strerror(errno));
10 return -1;
11 }
12 //创建子线程
13 if (pthread_create(&mThread, nullptr, SocketListener::threadStart, this)) {
14 SLOGE("pthread_create (%s)", strerror(errno));
15 return -1;
16 }
17
18 return 0;
19}
20
21...
22//这个线程中没有accept,因为是UDP协议,另外多路复用主要是监听管道句柄和线程创建前的一个SocketClient对象作为客户端接收的句柄
23void SocketListener::runListener() {
24 while (true) {
25 std::vector<pollfd> fds;
26
27 pthread_mutex_lock(&mClientsLock);
28 fds.reserve(2 + mClients.size());
29 fds.push_back({.fd = mCtrlPipe[0], .events = POLLIN});
30 for (auto pair : mClients) {
31 const int fd = pair.second->getSocket();
32 fds.push_back({.fd = fd, .events = POLLIN});
33 }
34 pthread_mutex_unlock(&mClientsLock);
35
36 int rc = TEMP_FAILURE_RETRY(poll(fds.data(), fds.size(), -1));
37 if (fds[0].revents & (POLLIN | POLLERR)) {
38 char c = CtrlPipe_Shutdown;
39 TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
40 if (c == CtrlPipe_Shutdown) {
41 break;
42 }
43 continue;
44 }
45
46 std::vector<SocketClient*> pending;
47 pthread_mutex_lock(&mClientsLock);
48 //实际上size只有2,位置0位管道句柄,位置1为线程创建前的一个SocketClient对象作为客户端接收的句柄
49 const int size = fds.size();
50 for (int i = mListen ? 2 : 1; i < size; ++i) {
51 const struct pollfd& p = fds[i];
52 if (p.revents & (POLLIN | POLLERR)) {
53 auto it = mClients.find(p.fd);
54 if (it == mClients.end()) {
55 SLOGE("fd vanished: %d", p.fd);
56 continue;
57 }
58 SocketClient* c = it->second;
59 pending.push_back(c);
60 c->incRef();
61 }
62 }
63 pthread_mutex_unlock(&mClientsLock);
64
65 for (SocketClient* c : pending) {
66 // Process it, if false is returned, remove from the map
67 SLOGV("processing fd %d", c->getSocket());
68 if (!onDataAvailable(c)) {
69 release(c, false);
70 }
71 c->decRef();
72 }
73 }
74}
3)onDataAvailable
1//system/core/logd/LogListener.cpp
2bool LogListener::onDataAvailable(SocketClient* cli) {
3 static bool name_set;
4 if (!name_set) {
5 prctl(PR_SET_NAME, "logd.writer");
6 name_set = true;
7 }
8
9 char buffer[sizeof(android_log_header_t) + LOGGER_ENTRY_MAX_PAYLOAD + 1];
10 struct iovec iov = { buffer, sizeof(buffer) - 1 };
11 alignas(4) char control[CMSG_SPACE(sizeof(struct ucred))];
12 struct msghdr hdr = {
13 nullptr, 0, &iov, 1, control, sizeof(control), 0,
14 };
15
16 int socket = cli->getSocket();
17 //接收来自客户端的数据
18 ssize_t n = recvmsg(socket, &hdr, 0);
19 //处理保存数据到缓存中
20 int res = logbuf->log(logId, header->realtime, cred->uid, cred->pid, header->tid, msg,
21 ((size_t)n <= UINT16_MAX) ? (uint16_t)n : UINT16_MAX);
22 if (res > 0) {
23 //通知另外一个logd.reader线程开始读取缓存数据
24 reader->notifyNewLog(static_cast<log_mask_t>(1 << logId));
25 }
26
27 return true;
28}
3.3.2客户端
客户端其实就是进程使用liblog动态库
3.3.2.1日志从缓存中读取
读取通常是用户通过logcat来主动获取缓存中的logcat数据
1//system/core/logcat/logcat.cpp
2int Logcat::Run(int argc, char** argv)
3{
4 ...
5 while (!max_count_ || print_count_ < max_count_) {
6 ...
7 int ret = android_logger_list_read(logger_list, &log_msg);
8 ...
9 }
10 ...
11}
1)android_logger_list_read
1//system/core/liblog/logger_read.cpp
2int android_logger_list_read(struct logger_list* logger_list, struct log_msg* log_msg) {
3 ...
4 if (logger_list->mode & ANDROID_LOG_PSTORE) {
5 ret = PmsgRead(logger_list, log_msg);
6 } else {
7 //读取logcat LOG_ID_MAIN主日志
8 ret = LogdRead(logger_list, log_msg);
9 }
10 ...
11 return ret;
12}
2)LogdRead
这里主要有两个操作
- logdOpen获取路径为/dev/socket/logdr的socket句柄,并发送一次数据给服务端
- recv接收数据结构为log_msg的logcat数据
1//system/core/liblog/logd_reader.cpp
2int LogdRead(struct logger_list* logger_list, struct log_msg* log_msg) {
3 int ret = logdOpen(logger_list);
4 /* NOTE: SOCK_SEQPACKET guarantees we read exactly one full entry */
5 ret = TEMP_FAILURE_RETRY(recv(ret, log_msg, LOGGER_ENTRY_MAX_LEN, 0));
6 ...
7 return ret;
8}
logdOpen
1static int logdOpen(struct logger_list* logger_list) {
2 char buffer[256], *cp, c;
3 int ret, remaining, sock;
4
5 sock = atomic_load(&logger_list->fd);
6 if (sock > 0) {
7 return sock;
8 }
9 //创建以报文传输的socket句柄的客户端,路径为/dev/socket/logdr
10 sock = socket_local_client("logdr", SOCK_SEQPACKET);
11 //开始拼接字符串,这里以stream开头,类似stream lids=0,1,2,3,4,6 timeout=xx start=xx.xx pid=xx
12 strcpy(buffer, (logger_list->mode & ANDROID_LOG_NONBLOCK) ? "dumpAndClose" : "stream");
13 cp = buffer + strlen(buffer);
14 strcpy(cp, " lids");
15 cp += 5;
16 c = '=';
17 remaining = sizeof(buffer) - (cp - buffer);
18 for (size_t log_id = 0; log_id < LOG_ID_MAX; ++log_id) {
19 if ((1 << log_id) & logger_list->log_mask) {
20 ret = snprintf(cp, remaining, "%c%zu", c, log_id);
21 ret = MIN(ret, remaining);
22 remaining -= ret;
23 cp += ret;
24 c = ',';
25 }
26 }
27
28 if (logger_list->tail) {
29 ret = snprintf(cp, remaining, " tail=%u", logger_list->tail);
30 ret = MIN(ret, remaining);
31 remaining -= ret;
32 cp += ret;
33 }
34
35 if (logger_list->start.tv_sec || logger_list->start.tv_nsec) {
36 if (logger_list->mode & ANDROID_LOG_WRAP) {
37 // ToDo: alternate API to allow timeout to be adjusted.
38 ret = snprintf(cp, remaining, " timeout=%u", ANDROID_LOG_WRAP_DEFAULT_TIMEOUT);
39 ret = MIN(ret, remaining);
40 remaining -= ret;
41 cp += ret;
42 }
43 ret = snprintf(cp, remaining, " start=%" PRIu32 ".%09" PRIu32, logger_list->start.tv_sec,
44 logger_list->start.tv_nsec);
45 ret = MIN(ret, remaining);
46 remaining -= ret;
47 cp += ret;
48 }
49
50 if (logger_list->pid) {
51 ret = snprintf(cp, remaining, " pid=%u", logger_list->pid);
52 ret = MIN(ret, remaining);
53 cp += ret;
54 }
55 //发送上述字符串给服务端
56 ret = TEMP_FAILURE_RETRY(write(sock, buffer, cp - buffer));
57 int write_errno = errno;
58 ...
59 return sock;
60}
3.3.2.2日志写入到缓存中
1//system/core/liblog/logger_write.cpp
2//LOG_ID_MAIN为主日志,prio为日志的等级,Verbose、Debug、Info、Warning、Error、Fatal、Silent(不输出)
3int __android_log_write(int prio, const char* tag, const char* msg) {
4 return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
5}
6
7int __android_log_buf_write(int bufID, int prio, const char* tag, const char* msg) {
8 ...
9 __android_log_message log_message = {
10 sizeof(__android_log_message), bufID, prio, tag, nullptr, 0, msg};
11 __android_log_write_log_message(&log_message);
12 return 1;
13}
14
15void __android_log_write_log_message(__android_log_message* log_message) {
16 ...
17 logger_function(log_message);
18}
又因为logger_function在初始化的时候就已经赋值,这里直接找到赋值的地方
1//system/core/liblog/logger_write.cpp
2#ifdef __ANDROID__
3static __android_logger_function logger_function = __android_log_logd_logger;
4#else
5static __android_logger_function logger_function = __android_log_stderr_logger;
6#endif
最终调用__android_log_logd_logger
1//system/core/liblog/logger_write.cpp
2void __android_log_logd_logger(const struct __android_log_message* log_message) {
3 int buffer_id = log_message->buffer_id == LOG_ID_DEFAULT ? LOG_ID_MAIN : log_message->buffer_id;
4
5 struct iovec vec[3];
6 vec[0].iov_base =
7 const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(&log_message->priority));
8 vec[0].iov_len = 1;
9 vec[1].iov_base = const_cast<void*>(static_cast<const void*>(log_message->tag));
10 vec[1].iov_len = strlen(log_message->tag) + 1;
11 vec[2].iov_base = const_cast<void*>(static_cast<const void*>(log_message->message));
12 vec[2].iov_len = strlen(log_message->message) + 1;
13 //buffer_id传入的就是LOG_ID_MAIN
14 write_to_log(static_cast<log_id_t>(buffer_id), vec, 3);
15}
16
17static int write_to_log(log_id_t log_id, struct iovec* vec, size_t nr) {
18 ...
19 ret = LogdWrite(log_id, &ts, vec, nr);
20 ...
21 return ret;
22}
LogdWrite
1//system/core/liblog/logd_writer.cpp
2int LogdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
3 ssize_t ret;
4 static const unsigned headerLength = 1;
5 struct iovec newVec[nr + headerLength];
6 android_log_header_t header;
7 size_t i, payloadSize;
8 static atomic_int dropped;
9 static atomic_int droppedSecurity;
10 //获取客户端的socket句柄
11 GetSocket();
12
13 header.tid = gettid();
14 header.realtime.tv_sec = ts->tv_sec;
15 header.realtime.tv_nsec = ts->tv_nsec;
16
17 newVec[0].iov_base = (unsigned char*)&header;
18 newVec[0].iov_len = sizeof(header);
19
20 int32_t snapshot = atomic_exchange_explicit(&droppedSecurity, 0, memory_order_relaxed);
21 if (snapshot) {
22 android_log_event_int_t buffer;
23
24 header.id = LOG_ID_SECURITY;
25 buffer.header.tag = LIBLOG_LOG_TAG;
26 buffer.payload.type = EVENT_TYPE_INT;
27 buffer.payload.data = snapshot;
28
29 newVec[headerLength].iov_base = &buffer;
30 newVec[headerLength].iov_len = sizeof(buffer);
31 //客户端发送给服务端
32 ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, 2));
33 if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
34 atomic_fetch_add_explicit(&droppedSecurity, snapshot, memory_order_relaxed);
35 }
36 }
37 ...
38 return ret;
39}
40//获取客户端的socket句柄
41static void GetSocket() {
42 int new_socket =
43 TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
44 LogdConnect();
45}
46
47static void LogdConnect() {
48 //使用UDP协议的本地socket,路径为/dev/socket/logdw
49 sockaddr_un un = {};
50 un.sun_family = AF_UNIX;
51 strcpy(un.sun_path, "/dev/socket/logdw");
52 TEMP_FAILURE_RETRY(connect(logd_socket, reinterpret_cast<sockaddr*>(&un), sizeof(sockaddr_un)));
53}
3.4demo4
这个demo是SElinux中的日志打印相关的socket,使用用户空间和内核空间的通信,且使用数据报接口用于直接访问网络层。
SOCK RAW套接字提供一个数据报接口,用于直接访问下面的网络层(即因特网域中的P层)。使用这个接口时,应用程序负责构造自己的协议头部,这是因为传输协议(如TCP和UDP)被绕过了。当创建一个原始套接字时,需要有超级用户特权,这样可以防止恶意应用程序绕过内建安全机制来创建报文。
3.4.1服务端
1//kernel/audit.c
2static int __net_init audit_net_init(struct net *net)
3{
4 struct netlink_kernel_cfg cfg = {
5 //2.这里的input就是用来接收用户空间的数据
6 .input = audit_receive,
7 .bind = audit_multicast_bind,
8 .unbind = audit_multicast_unbind,
9 .flags = NL_CFG_F_NONROOT_RECV,
10 .groups = AUDIT_NLGRP_MAX,
11 };
12
13 struct audit_net *aunet = net_generic(net, audit_net_id);
14 //1.建立连接NETLINK_AUDIT,采用了异步方式
15 aunet->sk = netlink_kernel_create(net, NETLINK_AUDIT, &cfg);
16 if (aunet->sk == NULL) {
17 audit_panic("cannot initialize netlink socket in namespace");
18 return -ENOMEM;
19 }
20 /* limit the timeout in case auditd is blocked/stopped */
21 aunet->sk->sk_sndtimeo = HZ / 10;
22
23 return 0;
24}
其中NETLINK_AUDIT的定义
1//include/uapi/linux/netlink.h
2#define NETLINK_AUDIT 9 /* auditing */
netlink_unicast采用的是单播消息
1//kernel/audit.c
2static int audit_multicast_bind(struct net *net, int group)
3{
4 int err = 0;
5
6 if (!capable(CAP_AUDIT_READ))
7 err = -EPERM;
8 audit_log_multicast(group, "connect", err);
9 return err;
10}
11
12
13static void audit_receive(struct sk_buff *skb)
14{
15 struct nlmsghdr *nlh;
16 int len;
17 int err;
18
19 nlh = nlmsg_hdr(skb);
20 len = skb->len;
21
22 audit_ctl_lock();
23 while (nlmsg_ok(nlh, len)) {
24 err = audit_receive_msg(skb, nlh);
25 /* if err or if this message says it wants a response */
26 if (err || (nlh->nlmsg_flags & NLM_F_ACK))
27 netlink_ack(skb, nlh, err, NULL);
28
29 nlh = nlmsg_next(nlh, &len);
30 }
31 audit_ctl_unlock();
32 ...
33}
34//这里通常只有三种常见的AVC,初始化的时候用得AUDIT_SET(1001),与属性相关的AUDIT_USER_AVC(1107),其他AUDIT_AVC(1400)
35static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
36{
37 u32 seq;
38 void *data;
39 int data_len;
40 int err;
41 struct audit_buffer *ab;
42 u16 msg_type = nlh->nlmsg_type;
43 struct audit_sig_info *sig_data;
44 char *ctx = NULL;
45 u32 len;
46
47 err = audit_netlink_ok(skb, msg_type);
48 if (err)
49 return err;
50
51 seq = nlh->nlmsg_seq;
52 data = nlmsg_data(nlh);
53 data_len = nlmsg_len(nlh);
54
55 switch (msg_type) {
56 case AUDIT_GET: {
57 struct audit_status s;
58 memset(&s, 0, sizeof(s));
59 s.enabled = audit_enabled;
60 s.failure = audit_failure;
61 /* NOTE: use pid_vnr() so the PID is relative to the current
62 * namespace */
63 s.pid = auditd_pid_vnr();
64 s.rate_limit = audit_rate_limit;
65 s.backlog_limit = audit_backlog_limit;
66 s.lost = atomic_read(&audit_lost);
67 s.backlog = skb_queue_len(&audit_queue);
68 s.feature_bitmap = AUDIT_FEATURE_BITMAP_ALL;
69 s.backlog_wait_time = audit_backlog_wait_time;
70 s.backlog_wait_time_actual = atomic_read(&audit_backlog_wait_time_actual);
71 audit_send_reply(skb, seq, AUDIT_GET, 0, 0, &s, sizeof(s));
72 break;
73 }
74 case AUDIT_SET: {
75 ...
76 break;
77 }
78 case AUDIT_USER:
79 case AUDIT_FIRST_USER_MSG ... AUDIT_LAST_USER_MSG:
80 case AUDIT_FIRST_USER_MSG2 ... AUDIT_LAST_USER_MSG2:
81 ...
82 break;
83 default:
84 err = -EINVAL;
85 break;
86 }
87 return err < 0 ? err : 0;
88}
3.4.2客户端
随便的一句log输出
1//external/selinux/libselinux/src/selinux_restorecon.c
2selinux_log(SELINUX_INFO,
3 "filespec hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n",
4 nel, used, HASH_BUCKETS, longest);
或者是AVC log
1//external/selinux/libselinux/src/avc.c
2avc_log(SELINUX_AVC, "%s", avc_audit_buf);
avc_log调用
1//external/selinux/libselinux/src/avc_internal.h
2//因为设置了avc_func_log实际上就是selinux_log
3#define avc_log(type, format...) \
4 if (avc_func_log) \
5 avc_func_log(format); \
6 else \
7 selinux_log(type, format);
反推到是否设置callback,没有设置按照默认stderr输出,设置则按照设置的输出
1//external/selinux/libselinux/src/callbacks.c
2//如果callback没有设置任何值,默认按照stderr标准错误来输出
3static int __attribute__ ((format(printf, 2, 3)))
4default_selinux_log(int type __attribute__((unused)), const char *fmt, ...)
5{
6 int rc;
7 va_list ap;
8 va_start(ap, fmt);
9 rc = vfprintf(stderr, fmt, ap);
10 va_end(ap);
11 return rc;
12}
13
14static int
15default_selinux_audit(void *ptr __attribute__((unused)),
16 security_class_t cls __attribute__((unused)),
17 char *buf __attribute__((unused)),
18 size_t len __attribute__((unused)))
19{
20 return 0;
21}
22
23int (*selinux_audit) (void *, security_class_t, char *, size_t) =
24 default_selinux_audit;
25int __attribute__ ((format(printf, 2, 3)))
26(*selinux_log)(int, const char *, ...) =
27 default_selinux_log;
28
29/* callback setting function */
30void selinux_set_callback(int type, union selinux_callback cb)
31{
32 switch (type) {
33 case SELINUX_CB_LOG:
34 selinux_log = cb.func_log;
35 break;
36 case SELINUX_CB_AUDIT:
37 selinux_audit = cb.func_audit;
38 break;
39 case SELINUX_CB_VALIDATE:
40 selinux_validate = cb.func_validate;
41 break;
42 case SELINUX_CB_SETENFORCE:
43 selinux_netlink_setenforce = cb.func_setenforce;
44 break;
45 case SELINUX_CB_POLICYLOAD:
46 selinux_netlink_policyload = cb.func_policyload;
47 break;
48 }
49}
继续反推,Android会在开机的时候设置callback,所以日志打印会在SelinuxKlogCallback体现
1//system/core/init/selinux.cpp
2如果callback设置为func_log
3void SelinuxSetupKernelLogging() {
4 selinux_callback cb;
5 cb.func_log = SelinuxKlogCallback;
6 selinux_set_callback(SELINUX_CB_LOG, cb);
7}
8//日志打印会在这里体现
9int SelinuxKlogCallback(int type, const char* fmt, ...) {
10 android::base::LogSeverity severity = android::base::ERROR;
11 if (type == SELINUX_WARNING) {
12 severity = android::base::WARNING;
13 } else if (type == SELINUX_INFO) {
14 severity = android::base::INFO;
15 }
16 char buf[kKlogMessageSize];
17 va_list ap;
18 va_start(ap, fmt);
19 int length_written = vsnprintf(buf, sizeof(buf), fmt, ap);
20 va_end(ap);
21 if (length_written <= 0) {
22 return 0;
23 }
24 //如果是AVC log
25 if (type == SELINUX_AVC) {
26 SelinuxAvcLog(buf, sizeof(buf));
27 } else {
28 //如果是其他的log
29 android::base::KernelLogger(android::base::MAIN, severity, "selinux", nullptr, 0, buf);
30 }
31 return 0;
32}
1)如果是AVC log
1//system/core/init/selinux.cpp
2constexpr size_t kKlogMessageSize = 1024;
3void SelinuxAvcLog(char* buf, size_t buf_len) {
4 CHECK_GT(buf_len, 0u);
5
6 size_t str_len = strnlen(buf, buf_len);
7 // trim newline at end of string
8 if (buf[str_len - 1] == '\n') {
9 buf[str_len - 1] = '\0';
10 }
11
12 struct NetlinkMessage {
13 nlmsghdr hdr;
14 char buf[kKlogMessageSize];
15 } request = {};
16
17 request.hdr.nlmsg_flags = NLM_F_REQUEST;
18 request.hdr.nlmsg_type = AUDIT_USER_AVC;
19 request.hdr.nlmsg_len = sizeof(request);
20 strlcpy(request.buf, buf, sizeof(request.buf));
21 //1.创建socket句柄
22 //PF_NETLINK是是一种在内核和用户应用间进行双向数据传输的非常好的方式
23 //SOCK_RAW用于直接访问网络层
24 //NETLINK_AUDIT定义的一个方式,NETLINK_AUDIT=9
25 auto fd = unique_fd{socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_AUDIT)};
26 if (!fd.ok()) {
27 return;
28 }
29 //2.数据报接口发送
30 TEMP_FAILURE_RETRY(send(fd, &request, sizeof(request), 0));
31}
其中nlmsghdr的定义
1//bionic/libc/kernel/uapi/linux/netlink.h 2struct nlmsghdr { 3 __u32 nlmsg_len; 4 __u16 nlmsg_type; 5 __u16 nlmsg_flags; 6 __u32 nlmsg_seq; 7 __u32 nlmsg_pid; 8};
2)如果是其他的log
1//system/core/base/logging.cpp
2void KernelLogger(android::base::LogId, android::base::LogSeverity severity, const char* tag,
3 const char*, unsigned int, const char* full_message) {
4 SplitByLines(full_message, KernelLogLine, severity, tag);
5}
6
7static void KernelLogLine(const char* msg, int length, android::base::LogSeverity severity,
8 const char* tag) {
9 // 设置日志等级
10 static constexpr int kLogSeverityToKernelLogLevel[] = {
11 [android::base::VERBOSE] = 7, // KERN_DEBUG (there is no verbose kernel log
12 // level)
13 [android::base::DEBUG] = 7, // KERN_DEBUG
14 [android::base::INFO] = 6, // KERN_INFO
15 [android::base::WARNING] = 4, // KERN_WARNING
16 [android::base::ERROR] = 3, // KERN_ERROR
17 [android::base::FATAL_WITHOUT_ABORT] = 2, // KERN_CRIT
18 [android::base::FATAL] = 2, // KERN_CRIT
19 };
20 // clang-format on
21 static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1,
22 "Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity");
23 //1.打开Kmsg句柄,这个并不是socket句柄,是linux中的文件句柄
24 static int klog_fd = OpenKmsg();
25 if (klog_fd == -1) return;
26
27 int level = kLogSeverityToKernelLogLevel[severity];
28 char buf[1024] __attribute__((__uninitialized__));
29 size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %.*s\n", level, tag, length, msg);
30 if (size > sizeof(buf)) {
31 size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\n",
32 level, tag, size);
33 }
34
35 iovec iov[1];
36 iov[0].iov_base = buf;
37 iov[0].iov_len = size;
38 //2.文件中写入内容
39 TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));
40}
41
42static int OpenKmsg() {
43 // pick up 'file w /dev/kmsg' environment from daemon's init rc file
44
45 const auto val = getenv("ANDROID_FILE__dev_kmsg");
46 if (val != nullptr) {
47 int fd;
48 if (android::base::ParseInt(val, &fd, 0)) {
49 auto flags = fcntl(fd, F_GETFL);
50 if ((flags != -1) && ((flags & O_ACCMODE) == O_WRONLY)) return fd;
51 }
52 }
53 //1.连接句柄,找到/dev/kmsg
54 return TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC));
55}
调用SplitByLines函数
1//system/core/base/logging_splitters.h
2template <typename F, typename... Args>
3static void SplitByLines(const char* msg, const F& log_function, Args&&... args) {
4 const char* newline = strchr(msg, '\n');
5 while (newline != nullptr) {
6 log_function(msg, newline - msg, args...);
7 msg = newline + 1;
8 newline = strchr(msg, '\n');
9 }
10 //log_function最终会调用到KernelLogLine中
11 log_function(msg, -1, args...);
12}
4总结
本文介绍了Linux socket,包含以TCP和UDP协议传输层的socket用法,还介绍了几种Android中的socket用法,包含Java层,native层,还衍生出了一套封装的函数socket_local_client和socket_local_server,可以更简单的使用socket。
参考
[1] yjy239, Android socket源码解析(一)socket的初始化原理, 2021.
[2] yjy239, Android socket源码解析(二)socket的绑定与监听, 2021.
[3] yjy239, Android socket源码解析(三)socket的connect源码解析, 2021.
[4] 段细狗, Linux中的socket编程, 2022.
[5] 李白不喝酒777777, 进程间通信——SOCKET(TCP), 2022.
[6] 李白不喝酒777777, 进程间通信——SOCKET(UDP), 2022.
[7] jiatingqiang, sock结构和socket结构详解, 2011.
[9] winsonCCCC, Linux网络编程之sockaddr与sockaddr_in,sockaddr_un分析, 2021.
[10] iteye_19603, 整理:Linux网络编程之sockaddr与sockaddr_in,sockaddr_un结构体详细讲解, 2013.
[11] 低调小一, UNIX Domain Socket IPC, 2015.
[12] 低调小一 Linux C编程一站式学习读书笔记——socket编程, 2013.
[13] linglongbayinhe, AF_INET与套接字, 2018.
[14] 一口Linux, Linux SOCKET介绍, 2021.
[15] 转角心静了, Linux下的Socket通信, 2021.
[16] penguin1990, LocalSocket实现进程间通信, 2016.
[17] IT先森, Android Java层和Native层通信实战大荟萃之原生socket实现通信, 2020.
[18] a370352701, 人工智能 图解linux netlink, 2019.
[19] Shining-LY, poll方法的基本概念, 2018.
[21] yunfan188, Linux网络编程 - 多种 I/O 函数(send、recv、readv、writev), 2022.