端口号重复

时间:2022-04-06 23:57 | 分类: 句子大全 | 作者:解Bug之路 | 评论: 次 | 点击:

端口号重复

1. 手机刷机时出现端口号重复或无效怎么处理

刷机有风险,刷前请注意!不过对于某些刷机失败,手机变砖头了,也不是完全木有办法!

手机关机,同时按住音量上键+电源键+HOME键进入recovery(恢复)模式界面(当然,不同型号的安卓手机,进入recovery的按键可能不同,请先查询自己的手机如何进入recovery)。

如果手机还能进入recovery界面,那么恭喜你!你的手机还没有完全变砖,下载官方ROM,在recover模式下重新刷新。

如果你的手机无法进入recovery模式或者你的手机也不支持TF卡,那么只能通过官方线刷工具修复,这个不同平台不同手机差别较大,建议可以在线咨询“闪电蜂”官网或者咨询距离你较近的“闪电蜂”门店。

2. 数据库端口重复问题如何解决

错误】的问题其实与com.microsoft.sqlserver.jdbc.SQLServerException: Software caused connection abort: recv failed 是一回事!都是端口问题 下面讲讲怎么回事吧,数据sql2005中的错误SQL Server 2005连接数据库出现的问题,很可能是你数据库连接端口的问题。

SQL Server 2005数据库默认端口为1433,但是通常情况下,这个默认端口是关闭的。很多人就连接1434端口,1434端口是udp的端口,你再用1434端口连接SQL Server 2005,就会发生冲突。

这个时候就会报。注意:只有是你用1434端口连接数据库的时候,出现才可以用下面的方法解决错误。

如果你是用1433端口连接数据库,那应该就不会出现这个问题啦。出现其它问题的话,还可以测试一下你的1433端口是否打开。

解决办法:第一步:测试端口开始菜单—>运行cmd—>在命令提示符下输入:telnet 127.0.0.1 1433出现以下问题-------------------------------------------------------------------------------------------------------C:\Documents and Settings\Administrator>telnet 127.0.0.1 1433正在连接到127.0.0.1。不能打开到主机的连接, 在端口 1433: 连接失败C:\Documents and Settings\Administrator>-------------------------------------------------------------------------------------------------------则说明1433端口没有打开。

第二步:打开1433端口开始菜单—>程序—>Microsoft SQL Server 2005—>配置工具—>SQL Server外围应用配置器—>服务和连接的外围应用配置器—>Datebase Engine—>远程连接—>选择—>选择—>点击应用确定—>断开SQL Server 2005连接,重新连接SQL Server 2005 —>停止服务—>启动服务—>OK!第三步:再次测试1433端口重复第一步操作就可以啦,如果没有出现第一步中出现的问题,那就是打开啦。

3. 判断输入数据是否重复的函数(整理)

一、用Dlookup函数判断输入的数据是否重复1)单条件Private Sub 占用端口_BeforeUpdate(Cancel As Integer)If (Not IsNull(DLookup("[用户ID]", _ "用户资料", "[占用端口] ='" _ & Me![ 占用端口] & "'"))) Then MsgBox "占用端口号输入重复," & _ "请核对后重新输入占用端口号", vbInformation, "警告" Me.Form.Undo '删除新输入的内容vbCritical Exit Sub End IfEnd Sub注:用户ID为主键;用户资料为表名称。

或(注:在声明中不能使用Option Explicit)Private Sub占用端口_BeforeUpdate(Cancel As Integer) D = DLookup("占用端口", "网络交换机", "占用端口= '" & Me![ 占用端口] & "'") If IsNull(D) = False Then MsgBox "占用端口输入重复," & _ "请核对后重新输入占用端口", vbInformation, "警告" Me.Form.Undo '删除新输入的内容vbCritical Exit Sub End IfEnd Sub 二、用Dlookup函数判断输入的数据是否重复1)单条件Private Sub 占用端口_BeforeUpdate(Cancel As Integer)If DCount("占用端口", "用户资料", "占用端口='" & Me.占用端口 & "'") > 0 Then MsgBox "你输入的数据已经存在,请重新输入!", vbCritical, "警告" Cancel = True Me!占用端口.Undo '删除新输入的内容 Exit Sub End IfEnd Sub说明:将Me.Form.Undo或Me!占用端口.Undo改为:Cancel = True '全选有重复的内容:适用于编辑窗口。

从Linux源码看TCP Client端的Connect

从Linux源码看Socket(TCP)Client端的Connect

前言

笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情。今天笔者就来从Linux源码的角度看下Client端的Socket在进行Connect的时候到底做了哪些事情。由于篇幅原因,关于Server端的Accept源码讲解留给下一篇博客。(基于Linux 3.10内核)

一个最简单的Connect例子

int clientSocket;if((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {    //  创建socket失败失败     return -1;}......if(connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {    // connect 失败    return -1;}.......

首先我们通过socket系统调用创建了一个socket,其中指定了SOCK_STREAM,而且最后一个参数为0,也就是建立了一个通常所有的TCP Socket。在这里,我们直接给出TCP Socket所对应的ops也就是操作函数。

从Linux源码看TCP Client端的Connect

如果你想知道上图中的结构是怎么来的,可以看下笔者以前的博客:

https://my.oschina.net/alchemystar/blog/1791017

值得注意的是,由于socket系统调用操作做了如下两个代码的判断

sock_map_fd    |->get_unused_fd_flags            |->alloc_fd                |->expand_files (ulimit)    |->sock_alloc_file            |->alloc_file            |->get_empty_filp (/proc/sys/fs/max_files)

第一个判断,ulmit超限:

int expand_files(struct files_struct *files, int nr{    ......    if (nr >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur)        return -EMFILE;    ......}

这边的判断即是ulimit的限制!在这里返回-EMFILE对应的描述就是“Too many open files”

从Linux源码看TCP Client端的Connect

第二个判断max_files超限

struct file *get_empty_filp(void){ ......    /*     * 由此可见,特权用户可以无视文件数最大大小的限制!     */    if (get_nr_files() >= files_stat.max_files && !capable(CAP_SYS_ADMIN)) {        /*         * percpu_counters are inaccurate.  Do an expensive check before         * we go and fail.         */        if (percpu_counter_sum_positive(&nr_files) >= files_stat.max_files)            goto over;    } ......}

所以在文件描述符超过所有进程能打开的最大文件数量限制(/proc/sys/fs/file-max)的时候会返回-ENFILE,对应的描述就是”Too many open files in system”,但是特权用户确可以无视这一限制,如下图所示:

从Linux源码看TCP Client端的Connect

connect系统调用

我们再来看一下connect系统调用:

int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen)

这个系统调用有三个参数,那么依据规则,它肯定在内核中的源码长下面这个样子

SYSCALL_DEFINE3(connect, ......

笔者全文搜索了下,就找到了具体的实现:

socket.cSYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,        int, addrlen){   ......    err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,                 sock->file->f_flags);    ......}

前面图给出了在TCP下的sock->ops == inet_stream_ops,然后再陷入到更进一步的调用栈中,即下面的:

SYSCALL_DEFINE3(connect    |->inet_stream_ops        |->inet_stream_connect            |->tcp_v4_connect                |->tcp_set_state(sk, TCP_SYN_SENT);设置状态为TCP_SYN_SENT                 |->inet_hash_connect                |->tcp_connect

首先,我们来看一下inet_hash_connect这个函数,里面有一个端口号的搜索过程,搜索不到可用端口号就会导致创建连接失败!内核能够建立一个连接也是跋涉了千山万水的!我们先看一下搜索端口号的逻辑,如下图所示:

从Linux源码看TCP Client端的Connect

获取端口号范围

首先,我们从内核中获取connect能够使用的端口号范围,在这里采用了Linux中的顺序锁(seqlock)

void inet_get_local_port_range(int *low, int *high){    unsigned int seq;    do {        // 顺序锁        seq = read_seqbegin(&sysctl_local_ports.lock);        *low = sysctl_local_ports.range[0];        *high = sysctl_local_ports.range[1];    } while (read_seqretry(&sysctl_local_ports.lock, seq));}

顺序锁事实上就是结合内存屏障等机制的一种乐观锁,主要依靠一个序列计数器。在读取数据之前和之后,序列号都被读取,如果两者的序列号相同,说明在读操作的时候没有被写操作打断过。这也保证了上面的读取变量都是一致的,也即low和high不会出现low是改前值而high是改后值得情况。low和high要么都是改之前的,要么都是改之后的!内核中修改的地方为:

cat /proc/sys/net/ipv4/ip_local_port_range 32768 61000

通过hash决定端口号起始搜索范围

在Linux上进行connect,内核给其分配的端口号并不是线性增长的,但是也符合一定的规律。先来看下代码:

int __inet_hash_connect(...){        // 注意,这边是static变量        static u32 hint;        // 这边的port_offset是用对端ip:port hash的一个值        // 也就是说对端ip:port固定,port_offset固定        u32 offset = hint + port_offset;        for (i = 1; i <= remaining; i++) {            port = low + (i + offset) % remaining;            /* port是否占用check */            ....            goto ok;        }        .......ok:        hint += i;        ......}

这里面有几个小细节,为了安全原因,Linux本身用对端ip:port做了一次hash作为搜索的初始offset,所以不同远端ip:port初始搜索范围可以基本是不同的!但同样的对端ip:port初始搜索范围是相同的!

从Linux源码看TCP Client端的Connect

在笔者机器上,一个完全干净的内核里面,不停的对同一个远端ip:port,其以2进行稳定增长,也即38742->38744->38746,如果有其它的干扰,就会打破这个规律。

端口号范围限制

由于我们指定了端口号返回ip_local_port_range是不是就意味着我们最多创建high-low+1个连接呢?当然不是,由于检查端口号是否重复是将(网络命名空间,对端ip,对端port,本端port,Socket绑定的dev)当做唯一键进行重复校验,所以限制仅仅是在同一个网络命名空间下,连接同一个对端ip:port的最大可用端口号数为high-low+1,当然可能还要减去ip_local_reserved_ports。如下图所示:

从Linux源码看TCP Client端的Connect

检查端口号是否被占用

端口号的占用搜索分为两个阶段,一个是处于TIME_WAIT状态的端口号搜索,另一个是其它状态端口号搜索。

TIME_WAIT状态端口号搜索

众所周知,TIME_WAIT阶段是TCP主动close必经的一个阶段。如果Client采用短连接的方式和Server端进行交互,就会产生大量的TIME_WAIT状态的Socket。而这些Socket由占用端口号,所以当TIME_WAIT过多,打爆上面的端口号范围之后,新的connect就会返回错误码:

C语言connect返回错误码为-EADDRNOTAVAIL,对应描述为Cannot assign requested address 对应Java的异常为java.net.NoRouteToHostException: Cannot assign requested address (Address not available)

ip_local_reserved_ports。如下图所示:

从Linux源码看TCP Client端的Connect

由于TIME_WAIT大概一分钟左右才能消失,如果在一分钟内Client端和Server建立大量的短连接请求就容易导致端口号耗尽。而这个一分钟(TIME_WAIT的最大存活时间)是在内核(3.10)编译阶段就确定了的,无法通过内核参数调整。 如下代码所示:

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT                  * state, about 60 seconds    */

Linux自然也考虑到了这种情况,所以提供了一个tcp_tw_reuse参数使得在搜索端口号时可以在某些情况下重用TIME_WAIT。代码如下:

__inet_hash_connect    |->__inet_check_establishedstatic int __inet_check_established(......){    ......        /* Check TIME-WAIT sockets first. */    sk_nulls_for_each(sk2, node, &head->twchain) {        tw = inet_twsk(sk2);        // 如果在time_wait中找到一个match的port,就判断是否可重用        if (INET_TW_MATCH(sk2, net, hash, acookie,                    saddr, daddr, ports, dif)) {            if (twsk_unique(sk, sk2, twp))                goto unique;            else                goto not_unique;        }    }    ......}

如上面代码中写的那样,如果在一堆TIME-WAIT状态的Socket里面能够有当前要搜索的port,则判断是否这个port可以重复利用。如果是TCP的话这个twsk_unique的实现函数是:

int tcp_twsk_unique(......){    ......    if (tcptw->tw_ts_recent_stamp &&        (twp == NULL || (sysctl_tcp_tw_reuse &&                 get_seconds() - tcptw->tw_ts_recent_stamp > 1))) {        tp->write_seq = tcptw->tw_snd_nxt + 65535 + 2        ......        return 1;    }    return 0;    }

上面这段代码逻辑如下所示:

从Linux源码看TCP Client端的Connect

在开启了tcp_timestamp以及tcp_tw_reuse的情况下,在Connect搜索port时只要比之前用这个port的TIME_WAIT状态的Socket记录的最近时间戳>1s,就可以重用此port,即将之前的1分钟缩短到1s。同时为了防止潜在的序列号冲突,直接将write_seq加上在65537,这样,在单Socket传输速率小于80Mbit/s的情况下,不会造成序列号冲突。同时这个tw_ts_recent_stamp设置的时机如下图所示:

从Linux源码看TCP Client端的Connect

所以如果Socket进入TIME_WAIT状态后,如果一直有对应的包发过来,那么会影响此TIME_WAIT对应的port是否可用的时间。我们可以通过下面命令开始tcp_tw_reuse:

echo '1' > /proc/sys/net/ipv4/tcp_tw_reuse

ESTABLISHED状态端口号搜索

ESTABLISHED的端口号搜索就简单了许多

    /* And established part... */    sk_nulls_for_each(sk2, node, &head->chain) {        if (INET_MATCH(sk2, net, hash, acookie,                    saddr, daddr, ports, dif))            goto not_unique;    }

以(网络命名空间,对端ip,对端port,本端port,Socket绑定的dev)当做唯一键进行匹配,如果匹配成功,表明此端口无法重用。

端口号迭代搜索

Linux内核在[low,high]范围按照上述逻辑进行port的搜索,如果没有搜索到port,即port耗尽,就会返回-EADDRNOTAVAIL,也即Cannot assign requested address。但还有一个细节,如果是重用TIME_WAIT状态的Socket的端口的话,就会将对应的TIME_WAIT状态的Socket给销毁。

__inet_hash_connect(......){        ......        if (tw) {            inet_twsk_deschedule(tw, death_row);            inet_twsk_put(tw);        }        ......}

寻找路由表

在我们找到一个可用端口号port后,就会进入搜寻路由阶段:

ip_route_newports    |->ip_route_output_flow            |->__ip_route_output_key                |->ip_route_output_slow                    |->fib_lookup

这也是一个非常复杂的过程,限于篇幅,就不做详细阐述了。如果搜索不到路由信息的话,会返回。

-ENETUNREACH,对应描述为Network is unreachable

Client端的三次握手

在前面一大堆前置条件就绪后,才进入到真正的三次握手阶段。

tcp_connect    |->tcp_connect_init 初始化tcp socket    |->tcp_transmit_skb 发送SYN包    |->inet_csk_reset_xmit_timer 设置SYN重传定时器

tcp_connect_init初始化了一大堆TCP相关的设置,例如mss_cache/rcv_mss等一大堆。而且如果开启了TCP窗口扩大选项的话,其窗口扩大因子也在此函数里进行计算:

tcp_connect_init    |->tcp_select_initial_windowint tcp_select_initial_window(...){    ......    (*rcv_wscale) = 0;    if (wscale_ok) {        /* Set window scaling on max possible window         * See RFC1323 for an explanation of the limit to 14         */        space = max_t(u32, sysctl_tcp_rmem[2], sysctl_rmem_max);        space = min_t(u32, space, *window_clamp);        while (space > 65535 && (*rcv_wscale) < 14) {            space >>= 1;            (*rcv_wscale)++;        }    }    ......}

如上面代码所示,窗口扩大因子取决于Socket最大可允许的读缓冲大小和window_clamp(最大允许滑动窗口大小,动态调整)。搞完了一票初始信息设置后,才开始真正的三次握手。在tcp_transmit_skb中才真正发送SYN包,同时在紧接着的inet_csk_reset_xmit_timer里设置了SYN超时定时器。如果对端一直不发送SYN_ACK,将会返回-ETIMEDOUT。

从Linux源码看TCP Client端的Connect

重传的超时时间和

/proc/sys/net/ipv4/tcp_syn_retries

息息相关,Linux默认设置为5,建议设置成3,下面是不同设置的超时时间参照图。

从Linux源码看TCP Client端的Connect

在设置了SYN超时重传定时器后,tcp_connnect就返回,并一路返回到最初始的inet_stream_connect。在这里我们就等待对端返回SYN_ACK或者SYN定时器超时。

int __inet_stream_connect(struct socket *sock,...,){    // 如果设置了O_NONBLOCK则timeo为0    timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);    ......    // 如果timeo=0即O_NONBLOCK会立刻返回    // 否则等待timeo时间    if (!timeo || !inet_wait_for_connect(sk, timeo, writebias))        goto out;}

Linux本身提供一个SO_SNDTIMEO来控制对connect的超时,不过Java并没有采用这个选项。而是采用别的方式进行connect的超时控制。仅仅就C语言的connect系统调用而言,不设置SO_SNDTIMEO,就会将对应用户进程进行睡眠,直到SYN_ACK到达或者超时定时器超时才将次用户进程唤醒。

从Linux源码看TCP Client端的Connect

如果是NON_BLOCK的话,则是通过select/epoll等多路复用机制去捕获超时或者连接成功事件。

对端SYN_ACK到达

在Server端SYN_ACK到达之后会按照下面的代码路径传递,并唤醒用户态进程:

tcp_v4_rcv    |->tcp_v4_do_rcv        |->tcp_rcv_state_process            |->tcp_rcv_synsent_state_process                |->tcp_finish_connect                    |->tcp_init_metrics 初始化度量统计                    |->tcp_init_congestion_control 初始化拥塞控制                    |->tcp_init_buffer_space 初始化buffer空间                    |->inet_csk_reset_keepalive_timer 开启包活定时器                    |->sk_state_change(sock_def_wakeup) 唤醒用户态进程                |->tcp_send_ack 发送三次握手的最后一次握手给Server端            |->tcp_set_state(sk, TCP_ESTABLISHED) 设置为ESTABLISHED状态

公众号

关注笔者公众号《解Bug之路》,获取更多干货文章

总结

Client(TCP)端进行Connect的过程真是跋山涉水,从一开始文件描述符的限制到端口号的搜索再到路由表的搜索再到最后的三次握手,任何一个环节有问题就会导致创建连接失败,笔者详细的描述了这些机制的源码实现。希望本篇文章可以对读者在以后遇到Connect失败问题时候有所帮助。


  • 发表评论
【已经有()位大神发现了看法】

  • 匿名发表
  •  
人参与,条评论