1、问题原因
svr挂掉了,端口释放了,cli去connect这个目的端口的时候正好选择了这个端口作为源端口,此时端口没人用,使用是合法的。于是自连接形成了。 就是出现源ip和源端口通目的ip和目的端口完全相同的情况,也就是在服务端没有启动,客户端也可以连接成功,但会造成服务端无法启动。
2、tcp连接分析
要建立一个tcp连接,首先svr要在b端口上listen,cli再使用a端口connect,端口选择一般是用户不显示bind,由内核代为选择一个空闲端口号,那么,即使svr和cli在一台机器上,因为svr已将b端口占用,cli不管用户bind还是内核选择,都不可能选到b。所以这样看来,同一个端口自连接就是一个伪命题。
同时打开
在同时打开的过程中,我们站在一方的角度想,它①先发了一个SYN,然后②收到了对端的SYN(而不是SYN+ACK),这时③回复SYN+ACK,当④再次收到SYN+ACK时就认为建连成功。对于自连接过程:为了建立连接,向destport发送一个SYN(完成了步骤①),因为目的ip是自己,因此会被loopback网络接口处理回送给本机TCP/IP协议栈,又因为port是自己,于是这个SYN给了正在等SYN的自己(完成了步骤②),这时需要执行步骤③发送SYN+ACK,同样原理,这个报文段会送给了自己(完成了步骤④),于是连接建立了。
3、自连接测试程序
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<errno.h>
#include<arpa/inet.h>
#define TRY_CNT 50000
void exec_cmd(char*cmd)
{
printf("start exec cmd: %s\n\n",cmd);
system(cmd);
printf("finish exec cmd:%s\n",cmd);
}
int main(int argc,char**argv)
{
int cnt;
int sock;
int ret;
struct sockaddr_in seraddr,cliaddr;
socklen_t addrlen;
char cmd[1024];
int port;
if(argc<2)
{
printf("need port arg.usage:\nselfconnection port\n");
return 1;
}
snprintf(cmd,sizeof(cmd),"netstat -npt GREP %s",argv[1]);
port=atoi(argv[1]);
if(port<1000 || port>65535)
{
printf("port should be 1000 -65535\n");
return 1;
}
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
printf("socket() error.error=%d,errstr=%s\n",errno,strerror(errno));
return -1;
}
memset(&seraddr,0,sizeof(seraddr));
seraddr.sin_family=AF_INET;
seraddr.sin_port=ntohs(port);
if(inet_pton(AF_INET,"127.0.0.1",&seraddr.sin_addr.s_addr)<0)
{
printf("inet_pton()error");
return -2;
}
printf("remote addr:\t ip = %X, port = %u\n",ntohl(seraddr.sin_addr.s_addr), ntohs(seraddr.sin_port));
for(cnt = 0; cnt < TRY_CNT; cnt++)
{
ret = connect(sock, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if( ret == 0 )
{
printf("try %d times, connect succ.\n", cnt + 1);
// get local addr info
addrlen = sizeof(cliaddr);
ret = getsockname(sock, (struct sockaddr *)&cliaddr, &addrlen);
if( ret < 0 )
{
printf("getsockname () error. errno = %d, errstr = %s\n",errno, strerror(errno));
}
else
{
printf("local addrr:\t ip = %X, port = %u\n",ntohl(cliaddr.sin_addr.s_addr), ntohs(cliaddr.sin_port));
exec_cmd(cmd);
}
// pause();
sleep(1);
break;
}
// only print error message of first time and last time
if( cnt == 0 || cnt == TRY_CNT - 1)
{
printf("connect () error. errno = %d, errstr = %s\n",errno, strerror(errno));
}
}
printf("cnt = %d\n", cnt+1);
close(sock);
sleep(2);
exec_cmd(cmd);
return 0;
}
4、自连接的避免
-
Client端连接成功后,然后判断是否是自连接。
每次connect成功后,调用getsockname(),获得本端的ip及port,判断是否等于server端端口,如果是,关闭该连接,继续循环 重试。存在的问题:因为是主动关闭,必然造成该TCP连接处于TIME_WAIT状态,如果Server端此时启动,仍然因为端口被占用导致不能启动成功。解决办法:可以通过设置socket选项,降低等待时间,服务端设置SO_REUSEADDR选项,并当发 现 端口被占用时,过一段时间重试。
-
Client使用固定端口,该端口与Server端不同。
- 查看系统配置文件。
在linux系统,系统随机分配未绑定的客户端的端口号,是有一定规律,并可以配置的,随机本地随机端口的分配的区间是ip_local_port_range。
查看 /etc/sysctl.conf ,看一下是否有对 net.ipv4.ip_local_port_range 以及 net.ipv4.ip_local_reserved_ports的设置,如果没有,再查看/proc/sys/net/ipv4/ip_local_port_range 以及 /proc/sys/net/ipv4/ip_local_reserved_ports中的值。
我机器上的值如下:
cat /proc/sys/net/ipv4/ip_local_port_range/ip_local_port_range
32768 61000
系统分配Client端的随机端口规律是:ip_local_port_range 区间的值 去掉 ip_local_reserved_ports 后剩下的端口。
Server端程序的端口号最好不要选择ip_local_port_range区间内的端口,这样Client如果使用随机端口是在ip_local_port_range区间内,这样也就不会发生本机上的自连接。
如果Server端的端口号已经固定,并在 ip_local_port_range区间内,那么可以设置 ip_local_reserved_ports 为该Server端的端口号,那么Client就不会使用ip_local_reserved_ports中的值作为随机端口。