一、端口重用
这本是没什么意思的一个东西,只是别人那么随便一问,自己也是没啥事情,就看了一下,可能没什么实际意义。从名字上看,两者都是端口重用的表示,可能是和socket的发送接收缓冲区一个,一个是全局的,一个是实例私有的。但是看了一下代码,感觉好像不是这样。
二、SO_REUSEADDR选项的使用
int sock_setsockopt(struct socket *sock, int level, int optname,
char __user *optval, int optlen)
case SO_REUSEADDR:
sk->sk_reuse = valbool;
当socket绑定端口时,该选项被检测使用
static int tcp_v4_get_port(struct sock *sk, unsigned short snum)
{
return inet_csk_get_port(&tcp_hashinfo, sk, snum,
inet_csk_bind_conflict);
}
tb_found:
if (!hlist_empty(&tb->owners)) {
if (sk->sk_reuse > 1) 如果reuse的值大于1,则不管是否被占用,直接返回端口可用。
goto success;
if (tb->fastreuse > 0 &&
sk->sk_reuse && sk->sk_state != TCP_LISTEN) {
goto success;
} else {
ret = 1;
if (bind_conflict(sk, tb))
goto fail_unlock;
}
}
/*
* This array holds the first and last local port number.
* For high-usage systems, use sysctl to change this to
* 32768-61000
*/
int sysctl_local_port_range[2] = { 1024, 4999 };
int inet_csk_bind_conflict(const struct sock *sk,
const struct inet_bind_bucket *tb)
{
const __be32 sk_rcv_saddr = inet_rcv_saddr(sk);
struct sock *sk2;
struct hlist_node *node;
int reuse = sk->sk_reuse;
sk_for_each_bound(sk2, node, &tb->owners) {
if (sk != sk2 &&
!inet_v6_ipv6only(sk2) &&
(!sk->sk_bound_dev_if ||
!sk2->sk_bound_dev_if ||
sk->sk_bound_dev_if == sk2->sk_bound_dev_if)) {
if (!reuse || !sk2->sk_reuse ||
sk2->sk_state == TCP_LISTEN) {
const __be32 sk2_rcv_saddr = inet_rcv_saddr(sk2);
if (!sk2_rcv_saddr || !sk_rcv_saddr ||
sk2_rcv_saddr == sk_rcv_saddr)
break;
}
}
}
return node != NULL;
}
所有使用该端口的socket都必须设置为可重用(包括新的申请该地址的socket),并且之前端口没有处于listening状态,则该端口可以给新申请者使用。
三、proc文件选项使用
tcp_v4_connect--->>inet_hash_connect--->>>__inet_check_established--->>tcp_twsk_unique
int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
{
const struct tcp_timewait_sock *tcptw = tcp_twsk(sktw);
struct tcp_sock *tp = tcp_sk(sk);
/* With PAWS, it is safe from the viewpoint
of data integrity. Even without PAWS it is safe provided sequence
spaces do not overlap i.e. at data rates <= 80Mbit/sec.
Actually, the idea is close to VJ's one, only timestamp cache is
held not per host, but per port pair and TW bucket is used as state
holder.
If TW bucket has been already destroyed we fall back to VJ's scheme
and use initial timestamp retrieved from peer table.
*/
if (tcptw->tw_ts_recent_stamp &&
(twp == NULL || (sysctl_tcp_tw_reuse &&
xtime.tv_sec - tcptw->tw_ts_recent_stamp > 1))) {
tp->write_seq = tcptw->tw_snd_nxt + 65535 + 2;
if (tp->write_seq == 0)
tp->write_seq = 1;
tp->rx_opt.ts_recent = tcptw->tw_ts_recent;
tp->rx_opt.ts_recent_stamp = tcptw->tw_ts_recent_stamp;
sock_hold(sktw);
return 1;
}
return 0;
}
也就是proc中参数只有在执行connect操作时才会生效,而server如果bind一个地址,即使系统设置为tcp_tw_reuse为1,而sol_sockreuse没有设置,端口依然无法绑定成功。
4、验证一下
下面测试代码修改自google搜索到的一个文件http://blog.csdn.net/zxcasdqwe123asd/article/details/8184647
[root@Harry tcpsvr]# netstat -anp | grep tcps
tcp 0 0 0.0.0.0:46175 0.0.0.0:* LISTEN 3030/tcpsvr.c.out
[root@Harry tcpsvr]# vi tcpsvr.c
[root@Harry tcpsvr]# cat tcpsvr.c
#include<stdio.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<errno.h>
#include<stdlib.h>
#include<strings.h>
#include<string.h>
#define TRY_TIME 128
#define MAX_CONNECT 12
#define BUFFER_SIZE 1024
//端口
#define SERPORT 2222
void printerr()
{
fprintf(stderr,"server error %s",strerror(errno));
}
int main(int argc,char *argv){
struct sockaddr_in serv;
struct sockaddr_in client;
char addr[INET_ADDRSTRLEN];
int fd;
char buf[BUFFER_SIZE];
int accept_fd;
int reuse=1;
int len=sizeof(struct sockaddr_in);
bzero(&serv,sizeof(struct sockaddr_in));
serv.sin_family=AF_INET;
serv.sin_addr.s_addr=htonl(INADDR_ANY);
serv.sin_port=htons(SERPORT);
//创建一个socket;
if((fd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("socket");
printerr();
return -1;
}
/*
if(setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(int))<0)
{
printerr();
return -1;
}
*/
//绑定
if(bind(fd,(struct sockaddr *)&serv,sizeof(struct sockaddr)))
{
printf("bind");
printerr();
return -1;
}
//监听
if(listen(fd,MAX_CONNECT)){
printf("listen");
printerr();
return -1;
}
//不断地接受连接
while(1){
bzero(buf,BUFFER_SIZE);
if((accept_fd=accept(fd,(struct sockaddr *)&client,&len))<0)
{
printerr();
return -1;
}
inet_ntop(AF_INET,&client.sin_addr,addr,INET_ADDRSTRLEN);
printf("accept from %s\n",addr);
recv(accept_fd,buf,BUFFER_SIZE,0);
printf("%s\n",buf);
//close(accept_fd);
sleep(1000);
}
close(fd);
return 0;
}
[root@Harry tcpsvr]# gcc tcpsvr.c -o tcpsvr.c.out
[root@Harry tcpsvr]# ./tcpsvr.c.out &
[1] 3068
[root@Harry tcpsvr]# cat /proc/sys/net/ipv4/tcp_tw_reuse
1
[root@Harry tcpsvr]# ./tcpsvr.c.out &
[1] 3082
该步骤完成之后在另一个终端中telnet 127.0.0.1 2222 之后不作任何操作,也就是只链接到该端口,建立一个完成三次握手的tcp链路。之后快速杀死服务器。
[root@Harry tcpsvr]# accept from 127.0.0.1
[root@Harry tcpsvr]# kill 3082 ; sleep 1 ; ./tcpsvr.c.out
[1]+ Terminated ./tcpsvr.c.out
server error Address already in usebind[root@Har
可以看到,依然提示该端口已经被占用。