前一段时间遇到一个地址绑定的问题,当通过killall 将进程杀死之后,然后进程立即起来总是会报Address already binded的错误,通过查看代码发现bind在绑定地址时总是不成功.查看代码没有通过setsockopt将socket设为地址重用.仔细查看了内核代码发现一个问题.
static 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; unsigned short snum;
int chk_addr_ret; int err; /* If the socket has its own bind function then use it. (RAW) */
if(sk->prot->bind) return sk->prot->bind(sk, uaddr, addr_len);//针对特殊协议调用相应的bind函数,在这里针对UDP,TCP协议是为空的.
if (addr_len < sizeof(struct sockaddr_in)) return -EINVAL;
chk_addr_ret = inet_addr_type(addr->sin_addr.s_addr);//取得绑定地址的类型 /* 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) */
if (sysctl_ip_nonlocal_bind == 0 && sk->protinfo.af_inet.freebind == 0 && addr->sin_addr.s_addr != INADDR_ANY && chk_addr_ret != RTN_LOCAL && chk_addr_ret != RTN_MULTICAST && chk_addr_ret != RTN_BROADCAST)
return -EADDRNOTAVAIL; //地址判断,是否非法 snum = ntohs(addr->sin_port);//获取绑定端口
if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
return -EACCES; /* 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->state != TCP_CLOSE) || //判断sock当前状态
(sk->num != 0)) goto out; sk->rcv_saddr = sk->saddr = addr->sin_addr.s_addr; //将sock与地址进行绑定
if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST) sk->saddr = 0; /* Use device */ //端口检查 /* Make sure we are allowed to bind here. */ if (sk->prot->get_port(sk, snum) != 0) { sk->saddr = sk->rcv_saddr = 0; err = -EADDRINUSE; //这里当端口被用时,返回Address already in used! goto out; } if (sk->rcv_saddr) sk->userlocks |= SOCK_BINDADDR_LOCK; if (snum) sk->userlocks |= SOCK_BINDPORT_LOCK; sk->sport = htons(sk->num); //将端口与socket进行绑定. sk->daddr = 0; sk->dport = 0; sk_dst_reset(sk); err = 0; out: release_sock(sk); return err; } 这里我们以UDP协议为例. static int udp_v4_get_port(struct sock *sk, unsigned short snum) { write_lock_bh(&udp_hash_lock); if (snum == 0) { //这里是没有指定端口的,会去为socket选一个端口.在这我们不讨论 int best_size_so_far, best, result, i; if (udp_port_rover > sysctl_local_port_range[1] || udp_port_rover < sysctl_local_port_range[0]) udp_port_rover = sysctl_local_port_range[0]; best_size_so_far = 32767; best = result = udp_port_rover; for (i = 0; i < UDP_HTABLE_SIZE; i++, result++) { struct sock *sk; int size; sk = udp_hash[result & (UDP_HTABLE_SIZE - 1)]; if (!sk) { 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; do { if (++size >= best_size_so_far) goto next; } while ((sk = sk->next) != NULL); 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_lport_inuse(result)) break; } if (i >= (1 << 16) / UDP_HTABLE_SIZE) goto fail; gotit: udp_port_rover = snum = result; } else {//我们指关注给定端口的情况. struct sock *sk2; for (sk2 = udp_hash[snum & (UDP_HTABLE_SIZE - 1)]; sk2 != NULL; //从hash表中根据端口选择socket sk2 = sk2->next) { if (sk2->num == snum && sk2 != sk && sk2->bound_dev_if == sk->bound_dev_if && (!sk2->rcv_saddr || !sk->rcv_saddr || sk2->rcv_saddr == sk->rcv_saddr) && (!sk2->reuse || !sk->reuse)) //判断socket是否已经被用,当没有设定端口重用的情况下就返回错误. goto fail; } } sk->num = snum; //端口绑定. if (sk->pprev == NULL) { struct sock **skp = &udp_hash[snum & (UDP_HTABLE_SIZE - 1)]; if ((sk->next = *skp) != NULL) (*skp)->pprev = &sk->next; *skp = sk; sk->pprev = skp; sock_prot_inc_use(sk->prot); sock_hold(sk); } write_unlock_bh(&udp_hash_lock); return 0; fail: write_unlock_bh(&udp_hash_lock); return 1; }
就是通过kill将进程杀死之后,如果重起进程会很快的情况下,在之前的socket没有释放完的情况下就有可能导致地址bind不成功,最终导致之前的进程被杀死了,新建的进程失败退出了.因此通常在进行网络编程时,最好能设定地址重用属性. if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n)) == -1) return -1;
阅读(4081) | 评论(0) | 转发(0) |