# Linux 端口绑定原理
我们在创建了socket fd后,需要绑定监听地址,代码如下,本节将详细介绍绑定过程,请读者务必弄懂socket创建原理后再观看,因为绑定时调用的函数将会在创建时设置。本节较为简单,没有过多的操作,仅仅只是将用户层传递的数据保存到对应结构而已。
int serverFD = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr; // 端口绑定信息
addr.sin_family = AF_INET; // 指定协议簇
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8080); // 将端口信息转为网络字节序(大端序)
int ret = bind(serverFD, (sockaddr*)&addr, sizeof(addr)); // 开始绑定
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
sys_bind 函数
该函数用于根据fd找到socket,然后调用socket的bind操作完成绑定。源码如下。
asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];// 保存用户空间传递的绑定地址信息
int err;
if((sock = sockfd_lookup(fd,&err))!=NULL) // 根据fd描述符找到socket
{
if((err=move_addr_to_kernel(umyaddr,addrlen,address))>=0) { // 将用户层传递的 sockaddr 信息复制到 char address[MAX_SOCK_ADDR] 中
...
err = sock->ops->bind(sock, (struct sockaddr *)address, addrlen); // 执行 bind 操作
}
...
}
return err;
}
// 根据fd描述符找到socket
struct socket *sockfd_lookup(int fd, int *err){
struct file *file;
struct inode *inode;
struct socket *sock;
if (!(file = fget(fd))) // 获取fd下标处的file结构
{
*err = -EBADF;
return NULL;
}
inode = file->f_dentry->d_inode; // 从file结构中获取inode信息
if (!inode->i_sock || !(sock = SOCKET_I(inode))) // 前面我们说到socket 和 inode 放在一起(struct socket_alloc结构)所以我们只需要inode地址 就可以根据偏移量获取 socket的地址
{
*err = -ENOTSOCK;
fput(file);
return NULL;
}
...
return sock;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
inet_bind 函数
在创建 tcp 协议时,将会把该函数作为 bind 函数指针的实现,所以我们跟进该函数即可。我们看到,在该函数中将会通过 socket 获取到 sock 结构,然后将该结构强转为 inet_sock 结构 ,随后将用户态传递的绑定 IP 和 端口 信息保存在 inet_sock 的 inet_opt 信息 中。源码如下。
// 描述 IP socket 地址结构。注意:该结构为内核绑定时使用
#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */
struct sockaddr_in {
sa_family_t sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
// 描述 inet sock 的信息(在sock 结构基础上增加 inet_opt,用于表示socket套接字的元数据)
struct inet_sock {
struct sock sk;
struct inet_opt inet;
};
// 宏定义,用于从sock结构中获取到inet_sock结构指针
#define inet_sk(__sk) (&((struct inet_sock *)__sk)->inet)
int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len){
struct sockaddr_in *addr = (struct sockaddr_in *)uaddr; // 指针强转,还记得混沌学堂说的么?对内存不同的解释
struct sock *sk = sock->sk; // 从socket 中获取 底层网络操作 sock
struct inet_opt *inet = inet_sk(sk); // 从socket中获取 inet_opt socket 描述结构
unsigned short snum;
int chk_addr_ret;
int err;
// 对于RAW格式而言,存在自己的绑定操作,那么执行它自己的
if (sk->sk_prot->bind) {
err = sk->sk_prot->bind(sk, uaddr, addr_len);
goto out;
}
...
snum = ntohs(addr->sin_port); // 端口号设置为 用户空间传递的 portNum
...
// 否则执行通用绑定操作
if (sk->sk_prot->get_port(sk, snum)) { // 若端口信息存在,那么绑定失败
inet->saddr = inet->rcv_saddr = 0;
err = -EADDRINUSE;
goto out_release_sock;
}
...
inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr; // 保存监听IP地址
...
inet->sport = htons(inet->num); // 保存端口信息(source port 源端口,也即输入端口,这里为server 的监听端口)
// 服务端不需要连接,所以没有目的IP和目的端口,也即对端信息
inet->daddr = 0;
inet->dport = 0;
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
sock 与 inet_sock 内存转换原理
读者可能会好奇:为啥 sock 可以直接强转为 inet_sock 。我们来看以下代码。可以看到其实开辟的 sock 内存后面已经包含了 ip 和 tcp 层的信息空间,所以我们可以随意强转,这时,是不是更能体现:C语言中指针的强转变化,仅仅只是对内存空间的数据不同解释罢了。
static int inet_create(struct socket *sock, int protocol){ // 前面描述sys_socket时讲过
...
sk = sk_alloc(PF_INET, GFP_KERNEL, inet_sk_size(protocol),
inet_sk_slab(protocol));
...
}
// 表示TCP协议的 sock 元信息
struct tcp_sock {
struct sock sk; // sock 信息
struct inet_opt inet; // ip 层信息
struct tcp_opt tcp; // tcp 层信息
};
static __inline__ int inet_sk_size(int protocol) // 通常我们指定为0,至于UDP和原始sock类型,读者自行查看
{
int rc = sizeof(struct tcp_sock);
if (protocol == IPPROTO_UDP)
rc = sizeof(struct udp_sock);
else if (protocol == IPPROTO_RAW)
rc = sizeof(struct raw_sock);
return rc;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45