Linux Socket自连接现象

2020-07-02  本文已影响0人  Aska偶阵雨

一、问题描述

今天一个同事遇到一个问题,问题如下:

他的程序的运行操作系统是linux,作为TCP Client 端,需要连接本机上的另一程序(TCP Server端, 端口为39000)。TCP Server端程序不是并不是总是启动着,TCP Client端程序在连接不到Server端时,等待10分钟,再重新连接Server端,不断重试。昨天他遇到了这样的问题,Server端未启动,但他的程序Client端可以连接成功,造成Server端程序因为端口被占用,无法启动!

他描述完问题,我第一反应就是发生了自连接。关于自连接为什么是合法的,可以查看相关资料。

二、产生自连接程序测试程序

点击(此处)折叠或打开

#include <unistd.h>

#include <stdio.h>

#include <string.h>

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

#include <errno.h>

#include <arpa/inet.h>

#define TRY_CNT 60000

void exec_cmd(char *cmd) {

printf("\n**********start exec cmd: %s **********\n\n", cmd);

system(cmd);

printf("\n\n**********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:\nselfconntest 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. errno = %d, errstr = %s\n",

errno, strerror(errno));

return -1;

}

// init seraddr

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;

}

执行测试程序如下:

./selfconntest 39000

remote addr: ip = 7F000001, port = 39000

connect () error. errno = 111, errstr = Connection refused

try 1997 times, connect succ.

local addrr: ip = 7F000001, port = 39000

**********start exec cmd: netstat -npt | grep 39000 **********

(Not all processes could be identified, non-owned process info

will not be shown, you would have to be root to see it all.)

tcp 0 0 127.0.0.1:39000 127.0.0.1:39000 ESTABLISHED 8512/./selfconntest

**********finish exec cmd: netstat -npt | grep 39000 **********

cnt = 1997

**********start exec cmd: netstat -npt | grep 39000 **********

(Not all processes could be identified, non-owned process info

will not be shown, you would have to be root to see it all.)

tcp 0 0 127.0.0.1:39000 127.0.0.1:39000 TIME_WAIT -

**********finish exec cmd: netstat -npt | grep 39000 **********

三、如何避免自连接?

1. Client端连接成功后,然后判断是否是自连接。

该方法在一定程度上可行,每次connect成功后,调用getsockname(),获得本端的ip及port,判断是否等于server端端口,如果是,关闭该连接,继续循环重试。

但该方法仍然存在一个问题,就是判断出是自连接,并关闭,但因为是主动关闭,必然造成该TCP连接处于TIME_WAIT状态,如果Server端此时启动,仍然因为端口被占用导致不能启动成功。这个时间窗口,会因为你设置socket的选项,可以缩小窗口,降低发生Server端不能启动的概率,但仍然无法避免。所以Server端也应该进行相应的修改,设置SO_REUSEADDR套接口选项,并当发现端口被占用时,过一段时间重试。

这样一个问题,导致Client与Server都需要进行修改,而且Server端的开发人员还不是一个组的时候,确实有点大动干戈。

2. Client使用固定端口,该端口与Server端不同

Client端在连接前bind本地固定端口,然后再connect Server端。

这种方式能够解决问题。但仍然需要修改Client程序,如果有多个Client端,要申请多个固定的端口号。

3. 查看系统配置文件,未雨绸缪。

在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中的值作为随机端口。

上一篇下一篇

猜你喜欢

热点阅读