内核版本:2.6.21.5
相信接触linux网络编程工程师对这个函数也很熟悉,还是让我们从源代码的角度看看这个函数到底做了什么,函数注释是这么说的:
Bind a name to a socket. Nothing much to do here since it's the protocol's responsibility to handle the local address. 在看这个函数之前,先看一下怎么使用这个api:
struct _in servaddr;
sockfd = (, , 0); /* create a */
/* init servaddr */ (&servaddr, sizeof(servaddr));
servaddr.sin_family = ;
servaddr.s.s_addr = (); //通常我们用
下面来具体看一下这个函数吧
1.
-
/*
-
* Bind a name to a socket. Nothing much to do here since it's
-
* the protocol's responsibility to handle the local address.
-
*
-
* We move the socket address to kernel space before we call
-
* the protocol layer (having also checked the address is ok).
-
*/
-
-
asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
-
{
-
struct socket *sock;
-
char address[MAX_SOCK_ADDR];
-
int err, fput_needed;
-
-
sock = sockfd_lookup_light(fd, &err, &fput_needed); //不具体看这个函数了,但我们知道通过fd,我们得到了这个socket结构
-
if(sock) {
-
err = move_addr_to_kernel(umyaddr, addrlen, address); //这个函数是怎么实现的,还是以后再说
-
if (err >= 0) {
-
err = security_socket_bind(sock,
-
(struct sockaddr *)address,
-
addrlen);
-
if (!err)
-
err = sock->ops->bind(sock, //还记得在创建socket的时候,挂入的回调函数吧:inet_bind
-
(struct sockaddr *)
-
address, addrlen);
-
}
-
fput_light(sock->file, fput_needed);
-
}
-
return err;
-
}
从上面的代码可以看到,进入bind系统调用后,根据fd得到socket,直接调用bind的回调函数,直接看inet_bind:
2.
-
int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
-
{
-
struct sockaddr_in *addr = (struct sockaddr_in *)uaddr; //这个是我们调用bind api 传入的
-
-
struct sock *sk = sock->sk; //通过struct socket结构,直接得到struct sock和struct inet_sock结构
-
struct inet_sock *inet = inet_sk(sk);
-
unsigned short snum;
-
int chk_addr_ret;
-
int err;
-
-
/* If the socket has its own bind function then use it. (RAW) */
-
if (sk->sk_prot->bind) { //如果创建的是RAW类型的socket
-
err = sk->sk_prot->bind(sk, uaddr, addr_len);
-
goto out;
-
}
-
err = -EINVAL;
-
if (addr_len < sizeof(struct sockaddr_in))
-
goto out;
-
-
chk_addr_ret = inet_addr_type(addr->sin_addr.s_addr); //通过这个函数能得到传入IP地址的类型,是单播地址,广播地址,多播地址等,有好多类型呢
-
-
/* Not specified by any standard per-se, however it breaks too
-
* many applications when removed. It is unfortunate since
-
* allowing applications to make a non-local bind solves
-
* several problems with systems using dynamic addressing.
-
* (ie. your servers still start up even if your ISDN link
-
* is temporarily down)
-
*/
-
err = -EADDRNOTAVAIL;
-
if (!sysctl_ip_nonlocal_bind && //这个从注释上看,没明白,但从代码上看,我们设置一下sysctl_ip_nonlocal_bind等参数在这里bind就失败了
-
!inet->freebind &&
-
addr->sin_addr.s_addr != INADDR_ANY &&
-
chk_addr_ret != RTN_LOCAL &&
-
chk_addr_ret != RTN_MULTICAST &&
-
chk_addr_ret != RTN_BROADCAST)
-
goto out;
-
-
snum = ntohs(addr->sin_port); //取得端口号
-
err = -EACCES;
-
if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE)) //在这里我们看到,端口号是不能随便填的,得给知名端口号让路...
-
goto out;
-
-
/* We keep a pair of addresses. rcv_saddr is the one
-
* used by hash lookups, and saddr is used for transmit.
-
*
-
* In the BSD API these are the same except where it
-
* would be illegal to use them (multicast/broadcast) in
-
* which case the sending device address is used.
-
*/
-
lock_sock(sk);
-
-
/* Check these errors (active socket, double bind). */
-
err = -EINVAL;
-
if (sk->sk_state != TCP_CLOSE || inet->num) //这里检查连接的状态,对TCP来说在timewait状态时就不能绑定了,所以
-
//在2MSL时间后才可以绑定,这就是在server端你close socket后,过一段时间才可以绑定的原因
-
//inet->num 这个是检查是否重复绑定,现在内核TCP/IP协议栈好像已支持端口重复绑定了,以后再分析,bind失败,大部分都是在这里引起的
-
goto out_release_sock;
-
-
inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr; //在这里一般是0,也就是调用bind api的时候我们用了servaddr.s.s_addr = ();
-
//从注释上看rcv_saddr is the one used by hash lookups, and saddr is used for transmit.
-
//当我们填上本机IP地址的时候,这2个地址就用到了,用的时候再分析
-
if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
-
inet->saddr = 0; /* Use device */
-
-
/* Make sure we are allowed to bind here. */
-
if (sk->sk_prot->get_port(sk, snum)) { //bind这个函数的作用体现在这里,这个函数一调用,我们就能通过端口号找到这个socket了,稍后我们分析这个回调函数是怎么个情况
-
inet->saddr = inet->rcv_saddr = 0;
-
err = -EADDRINUSE;
-
goto out_release_sock;
-
}
-
-
if (inet->rcv_saddr)
-
sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
-
if (snum)
-
sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
-
inet->sport = htons(inet->num); //这个是服务端的端口号,你绑定的
-
inet->daddr = 0; //这个是要从对端发来的数据包中提取的IP地址
-
inet->dport = 0; //这个是要从对端发来的数据包中提取的端口号
-
sk_dst_reset(sk);
-
err = 0;
-
out_release_sock:
-
release_sock(sk);
-
out:
-
return err;
-
}
从上面的代码中,我们看到sk
->sk_prot
->get_port
(sk
, snum
) 这里是关键,看看这个回调函数udp_v4_get_port:
3.
static inline int udp_v4_get_port(struct sock *sk, unsigned short snum)
{
return udp_get_port(sk, snum, ipv4_rcv_saddr_equal);
}
__inline__ int udp_get_port(struct sock *sk, unsigned short snum,
int (*scmp)(const struct sock *, const struct sock *))
{
return __udp_lib_get_port(sk, snum,
udp_hash, &udp_port_rover, scmp);
}
还是直接分析__udp_lib_get_port函数,但我们要注意第3个参数udp_hash,先看看这个东东:
#define UDP_HTABLE_SIZE 128
struct hlist_head udp_hash[UDP_HTABLE_SIZE]; //是个哈希数组
DEFINE_RWLOCK(udp_hash_lock);
-
/**
-
* __udp_lib_get_port - UDP/-Lite port lookup for IPv4 and IPv6
-
*
-
* @sk: socket struct in question
-
* @snum: port number to look up
-
* @udptable: hash list table, must be of UDP_HTABLE_SIZE
-
* @port_rover: pointer to record of last unallocated port
-
* @saddr_comp: AF-dependent comparison of bound local IP addresses
-
*/
-
int __udp_lib_get_port(struct sock *sk, unsigned short snum,
-
struct hlist_head udptable[], int *port_rover,
-
int (*saddr_comp)(const struct sock *sk1,
-
const struct sock *sk2 ) )
-
{
-
struct hlist_node *node;
-
struct hlist_head *head;
-
struct sock *sk2;
-
int error = 1;
-
-
write_lock_bh(&udp_hash_lock);
-
if (snum == 0) { //先不看这里
-
int best_size_so_far, best, result, i;
-
-
if (*port_rover > sysctl_local_port_range[1] ||
-
*port_rover < sysctl_local_port_range[0])
-
*port_rover = sysctl_local_port_range[0];
-
best_size_so_far = 32767;
-
best = result = *port_rover;
-
for (i = 0; i < UDP_HTABLE_SIZE; i++, result++) {
-
int size;
-
-
head = &udptable[result & (UDP_HTABLE_SIZE - 1)];
-
if (hlist_empty(head)) {
-
if (result > sysctl_local_port_range[1])
-
result = sysctl_local_port_range[0] +
-
((result - sysctl_local_port_range[0]) &
-
(UDP_HTABLE_SIZE - 1));
-
goto gotit;
-
}
-
size = 0;
-
sk_for_each(sk2, node, head) {
-
if (++size >= best_size_so_far)
-
goto next;
-
}
-
best_size_so_far = size;
-
best = result;
-
next:
-
;
-
}
-
result = best;
-
for(i = 0; i < (1 << 16) / UDP_HTABLE_SIZE; i++, result += UDP_HTABLE_SIZE) {
-
if (result > sysctl_local_port_range[1])
-
result = sysctl_local_port_range[0]
-
+ ((result - sysctl_local_port_range[0]) &
-
(UDP_HTABLE_SIZE - 1));
-
if (! __udp_lib_lport_inuse(result, udptable))
-
break;
-
}
-
if (i >= (1 << 16) / UDP_HTABLE_SIZE)
-
goto fail;
-
gotit:
-
*port_rover = snum = result;
-
} else {
-
head = &udptable[snum & (UDP_HTABLE_SIZE - 1)]; //以端口号为key找到head
-
-
sk_for_each(sk2, node, head)
-
if (sk2->sk_hash == snum && //在这里哈希冲突了(当我们用创建的不同socket去bind相同的端口号就出现了此情况),就判断这个端口号是否已经绑定了等其他条件,如还冲突就goto fail;
-
sk2 != sk && //这个比较就看这2个socket是否是同一个socket
-
(!sk2->sk_reuse || !sk->sk_reuse) && //这个比较就是对应的SO_REUSEADDR 端口复用属性,如果不设置这个属性,2个有相同端口号的socket第二个bind会失败
-
(!sk2->sk_bound_dev_if || !sk->sk_bound_dev_if
-
|| sk2->sk_bound_dev_if == sk->sk_bound_dev_if) &&
-
(*saddr_comp)(sk, sk2) )
-
goto fail;
-
}
-
inet_sk(sk)->num = snum; //在这里把端口号赋值给inet_sk->num字段,也就是我们说的bind端口号了
-
sk->sk_hash = snum; //赋值给哈希key
-
if (sk_unhashed(sk)) {
-
head = &udptable[snum & (UDP_HTABLE_SIZE - 1)];
-
sk_add_node(sk, head); //在这里把挂入sock节点,也就是通过端口号把socket关联起来了,在前面文章的分析中我们知道通过接收到的skb中的目的端口号我们就可以找到处理这个skb的socket
-
//然后就把这个skb挂入到这个socket接收队列中,在应用层我们就可以recv_from了。
-
sock_prot_inc_use(sk->sk_prot);
-
}
-
error = 0;
-
fail:
-
write_unlock_bh(&udp_hash_lock);
-
return error;
-
}
还是来张图吧:
over......
阅读(2881) | 评论(0) | 转发(0) |