网络中connect的作用

目录

connect作用

函数调用关系

关键函数解析 

tcp_connect

tcp_connect的作用

总结


connect作用

客户端在执行connect时,

  1. 把本地socket状态设置成TCP_SYN_SENT
  2. 选择一个可用的端口,发出SYN握手请求并重置定时器

函数调用关系

connect -> __sys_connect-> __sys_connect_file
    
int __sys_connect_file(struct file *file, struct sockaddr_storage *address,
		       int addrlen, int file_flags)
{
    //根据file查找sock对象
	sock = sock_from_file(file, &err);
	if (!sock)
		goto out;

    //connect
	err = sock->ops->connect(sock, (struct sockaddr *)address, addrlen,
				 sock->file->f_flags | file_flags);
}

inet_stream_connect

int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
			int addr_len, int flags)
{
	err = __inet_stream_connect(sock, uaddr, addr_len, flags, 0);
}


int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
			  int addr_len, int flags, int is_sendmsg)
{
    //刚创建完的socket状态SS_UNCONNECTED
	case SS_UNCONNECTED:
		if (sk->sk_state != TCP_CLOSE)
			goto out;

		err = sk->sk_prot->connect(sk, uaddr, addr_len);
		sock->state = SS_CONNECTING;

		break;
	}
}

tcp_v4_connect

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
    //设置socket状态TCP_SYN_SENT
	tcp_set_state(sk, TCP_SYN_SENT);

	//动态选择一个端口
	err = inet_hash_connect(tcp_death_row, sk);
    
	//根据sk中的信息,构建一个SYNC的报文,并将它发送出去
	err = tcp_connect(sk);
}

inet_hash_connect

int __inet_hash_connect(struct inet_timewait_death_row *death_row,
		struct sock *sk, u32 port_offset,
		int (*check_established)(struct inet_timewait_death_row *,
			struct sock *, __u16, struct inet_timewait_sock **))
{

	int port = inet_sk(sk)->inet_num;//获取端口

	//是否绑定端口
	if (port) {
		head = &hinfo->bhash[inet_bhashfn(net, port,
						  hinfo->bhash_size)];
		tb = inet_csk(sk)->icsk_bind_hash;
		spin_lock_bh(&head->lock);
		if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) {
			inet_ehash_nolisten(sk, NULL);
			spin_unlock_bh(&head->lock);
			return 0;
		}
		spin_unlock(&head->lock);
		ret = check_established(death_row, sk, port, NULL);
		local_bh_enable();
		return ret;
	}

	l3mdev = inet_sk_bound_l3mdev(sk);

	//获取本地端口配置
	inet_get_local_port_range(net, &low, &high);
	high++; /* [32768, 60999] -> [32768, 61000[ */
	remaining = high - low;
	if (likely(remaining > 1))
		remaining &= ~1U;

	offset = (hint + port_offset) % remaining;
	offset &= ~1U;

other_parity_scan:
	port = low + offset;//如何确定端口可用
	for (i = 0; i < remaining; i += 2, port += 2) {
		if (unlikely(port >= high))
			port -= remaining;
		//是否是保留端口, net.ipv4.ip_local_reversed_ports
		if (inet_is_local_reserved_port(net, port))
			continue;
		//查找已经使用的端口的hash表
		head = &hinfo->bhash[inet_bhashfn(net, port,
						  hinfo->bhash_size)];
		spin_lock_bh(&head->lock);

		inet_bind_bucket_for_each(tb, &head->chain) {
			//如果端口被使用
			if (net_eq(ib_net(tb), net) && tb->l3mdev == l3mdev &&
			    tb->port == port) {
				if (tb->fastreuse >= 0 ||
				    tb->fastreuseport >= 0)
					goto next_port;
				WARN_ON(hlist_empty(&tb->owners));
				//检测是否有可用继续使用,检测四元组是否一样
				if (!check_established(death_row, sk,
						       port, &tw))
					goto ok;
				goto next_port;
			}
		}
		//没有被使用情况
		tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
					     net, head, port, l3mdev);
		if (!tb) {
			spin_unlock_bh(&head->lock);
			return -ENOMEM;
		}
		tb->fastreuse = -1;
		tb->fastreuseport = -1;
		goto ok;
next_port:
		spin_unlock_bh(&head->lock);
		cond_resched();
	}

	return -EADDRNOTAVAIL;//cannot assign requested address
}

关键函数解析 

  • 首先判断inet_sk(sk)->inet_num,如果调用过bind,这个函数会选择好端口并设置在inet_num上。如果没有bind,port为0;
  • inet_get_local_port_range这个函数获取net.ipv4.ip_local_port_range内核参数;
  • inet_is_local_reserved_port判断要选择的端口是否在net.ipv4.ip_local_reserved_local_port中;
  • 在hash表中查找端口,如果没有找到则可以使用;inet_bind_bucket_for_each申请一个inet_bind_bucket来记录端口使用,并用hash表形式管理起来;
  • 如果已经被使用,通过check_established继续检测是否可用,如果返回0则仍然可用;
  • 如果未使用,则inet_bind_bucket_create 创建一个
  • 如果没有找到端口,则返回EADDRNOTAVAIL,应用层提示 Cannot assign requested address
//检测是否和现有ESTABLISH状态连接冲突
static int __inet_check_established(struct inet_timewait_death_row *death_row,
				    struct sock *sk, __u16 lport,
				    struct inet_timewait_sock **twp)
{

	//查找hash
	struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash);


	//查找四元组有没有一样的
	sk_nulls_for_each(sk2, node, &head->chain) {
		if (sk2->sk_hash != hash)
			continue;
		//对saddr,daddr,port进行了比较
		if (likely(INET_MATCH(sk2, net, acookie,
					 saddr, daddr, ports, dif, sdif))) {
			if (sk2->sk_state == TCP_TIME_WAIT) {
				tw = inet_twsk(sk2);
				if (twsk_unique(sk, sk2, twp))
					break;
			}
			goto not_unique;
		}
	}

	return 0;//有可用的

not_unique:
	return -EADDRNOTAVAIL;//没有可用的
}

INET_MATCH源码如下,将__saddr,__daddr,__ports都进行了比较。如果匹配则端口不可以用,如果不匹配,端口可用。

#define INET_MATCH(__sk, __net, __cookie, __saddr, __daddr, __ports, __dif, __sdif) \
	(((__sk)->sk_portpair == (__ports))		&&		\
	 ((__sk)->sk_daddr	== (__saddr))		&&		\
	 ((__sk)->sk_rcv_saddr	== (__daddr))		&&		\
	 (((__sk)->sk_bound_dev_if == (__dif))		||		\
	  ((__sk)->sk_bound_dev_if == (__sdif)))	&&		\
	 net_eq(sock_net(__sk), (__net)))
#endif /* 64-bit arch */

tcp_connect

/* Build a SYN and send it off. */
int tcp_connect(struct sock *sk)
{

	//申请skb
	buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);

	//添加到发送队列sk_write_queue
	tcp_connect_queue_skb(sk, buff);
	tcp_ecn_send_syn(sk, buff);
	tcp_rbtree_insert(&sk->tcp_rtx_queue, buff);

	//发送syn  tcp_transmit_skb
	err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
	      tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);


	//重启定时器
	/* Timer for repeating the SYN until an answer. */
	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
				  inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
	return 0;
}

//超时时间1s
#define TCP_TIMEOUT_INIT ((unsigned)(1*HZ))	/* RFC6298 2.1 initial RTO value	*/

//初始化时 时间设置
void tcp_init_sock(struct sock *sk)
{
    ..

	icsk->icsk_rto = TCP_TIMEOUT_INIT;
}

tcp_connect的作用

  1. 申请skb,并将其设置为SKB包
  2. 添加到发送队列上
  3. 调用tcp_transmit_skb将该包发送出去
  4. 重启定时器,超时后会重发

总结

  1. Cannot assign requested address 在端口没有找到可用的时候发出的错误;可用通过调整net.ipv4.ip_local_port_range参数,放开一些端口;
  2. 如果某些不想被使用的端口,内核参数ip_local_reserved_ports设置。

 参考

 https://www.kernel.org/doc/html/v5.6/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为了维护世界和平_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值