Linux网络协议栈从应用层到内核层②

文章目录

  • 1、bind 源码剖析
  • 2、listen 源码剖析
  • 3、accept 源码剖析
  • 4、connect 源码剖析
    • 客户端调用connect成功,但三次握手并未完成,进程是如何阻塞自己
    • 客户端在connect时,如何选择源端口
    • 客户发送syn封包以及重传
    • 服务端收到syn封包,如何处理
    • 客户端收到syn+ack封包的处理过程
    • 服务端收到第三次握手(ACK)如何处理
  • 5、整体流程图

1、bind 源码剖析

系统调用原型

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:文件描述符,通常为socket函数的返回值
addr:一个指向 sockaddr 结构的指针,该结构包含了要绑定的地址和端口信息
addrlen:addr 结构的大小

bind 系统调用源码

SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
	struct socket *sock;
	struct sockaddr_storage address;
	int err, fput_needed;

	//通过fd获得struct file,再通过struct file再获得struct socket
	//它们三者的关系在创建socket时,就链接好了
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (sock) {
		//将用户态的数据拷贝到内核中
		err = move_addr_to_kernel(umyaddr, addrlen, &address);
		if (err >= 0) {
			err = security_socket_bind(sock,
						   (struct sockaddr *)&address,
						   addrlen);
			if (!err)
				//在创建套接字时,已经将inet_protosw数组的第一个元素的ops赋值给sock的ops
				//然而inet_protosw数组的第一个元素就是tcp
				/*{
				.type =       SOCK_STREAM,
				.protocol =   IPPROTO_TCP,
				.prot =       &tcp_prot,
				.ops =        &inet_stream_ops,
				.flags =      INET_PROTOSW_PERMANENT |
			      INET_PROTOSW_ICSK,
				}*/
				//因此这里的sock->ops 等于 inet_stream_ops
				//这里调用的bind最终会调用到af_inet.c中的inet_bind函数
				err = sock->ops->bind(sock,
						      (struct sockaddr *)
						      &address, addrlen);
		}
		fput_light(sock->file, fput_needed);
	}
	return err;
}

sockfd_lookup_light函数

struct fd {
	struct file *file;
	unsigned int flags;
};

static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
{
	//获取fd对应的file
	struct fd f = fdget(fd);
	struct socket *sock;

	*err = -EBADF;
	if (f.file) {
		//通过file获取sock
		sock = sock_from_file(f.file, err); //内部return file->private_data  
		if (likely(sock)) {
			*fput_needed = f.flags;
			return sock;
		}
		fdput(f);
	}
	return NULL;
}

inet_bind函数

int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
	//进行相应数据的初始化
	struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
	struct sock *sk = sock->sk;
	struct inet_sock *inet = inet_sk(sk);
	struct net *net = sock_net(sk);
	unsigned short snum;
	int chk_addr_ret;
	u32 tb_id = RT_TABLE_LOCAL;
	int err;

	//因为inet_bind是上层的抽象,此时还需要调用具体协议栈内的bind函数,也就是tcp
	//如果tcp实现了自己的bind,就进行调用
	//在创建套接字时,已经将inet_protosw数组的第一个元素的prot赋值给sock的prot
	//然而inet_protosw数组的第一个元素就是tcp,prot就是tcp_prot
	//但是在tcp_ipv4.c中可以查看到tcp并没有自己的bind函数,因此对应tcp只需要使用上层的inet_bind函数就够用了
	if (sk->sk_prot->bind) {
		err = sk->sk_prot->bind(sk, uaddr, addr_len);
		goto out;
	}
	err = -EINVAL;
	//进行地址长度的校验
	if (addr_len < sizeof(struct sockaddr_in))
		goto out;
	//进行协议族的校验
	if (addr->sin_family != AF_INET) {
		/* Compatibility games : accept AF_UNSPEC (mapped to AF_INET)
		 * only if s_addr is INADDR_ANY.
		 */
		err = -EAFNOSUPPORT;
		if (addr->sin_family != AF_UNSPEC ||
		    addr->sin_addr.s_addr != htonl(INADDR_ANY))
			goto out;
	}

	tb_id = l3mdev_fib_table_by_index(net, sk->sk_bound_dev_if) ? : tb_id;
	chk_addr_ret = inet_addr_type_table(net, addr->sin_addr.s_addr, tb_id);

	err = -EADDRNOTAVAIL;

	if (!net->ipv4.sysctl_ip_nonlocal_bind &&
	    !(inet->freebind || inet->transparent) &&
	    addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
	    chk_addr_ret != RTN_LOCAL &&
	    chk_addr_ret != RTN_MULTICAST &&
	    chk_addr_ret != RTN_BROADCAST)
		goto out;

	snum = ntohs(addr->sin_port);//将网络字节序转换为主机字节序
	err = -EACCES;
	if (snum && snum < PROT_SOCK &&
	    !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE))
		goto out;

	lock_sock(sk);

	err = -EINVAL;
	if (sk->sk_state != TCP_CLOSE || inet->inet_num)
		goto out_release_sock;

	//struct inet_sock *inet是struct sock 的进一步包装
	//内部包含通信四元组(本地地址,目的地址,本地端口,目的端口)
	/*struct inet_sock {
	struct sock		sk;
		#define inet_daddr		sk.__sk_common.skc_daddr  //目的地址
		#define inet_rcv_saddr		sk.__sk_common.skc_rcv_saddr //本地地址
		#define inet_dport		sk.__sk_common.skc_dport  //目的端口
		#define inet_num		sk.__sk_common.skc_num   //本地端口
		...
	}*/
	inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;   //绑定本地地址
	if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
		inet->inet_saddr = 0;  
	
	//确保端口能被绑定
	if ((snum || !inet->bind_address_no_port) &&
	    sk->sk_prot->get_port(sk, snum)) {
		inet->inet_saddr = inet->inet_rcv_saddr = 0;
		err = -EADDRINUSE;
		goto out_release_sock;
	}

	if (inet->inet_rcv_saddr)
		sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
	if (snum)
		sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
	inet->inet_sport = htons(inet->inet_num);//绑定本地端口
	//目的地址和目的端口未确定
	inet->inet_daddr = 0;
	inet->inet_dport = 0;
	sk_dst_reset(sk);
	err = 0;
out_release_sock:
	release_sock(sk);
out:
	return err;
}

整体流程

在这里插入图片描述

2、listen 源码剖析

系统调用原型

int listen(int sockfd, int backlog);

sockfd:文件描述符,指向类型为SOCK_STREAM或SOCK_SEQPACKET
backlog:全链接队列的大小,三次握手完成,服务端就会将链接放在全链接队列当中,上层再通过accept获取链接。如果服务端的全链接队列已经满了,那么其他的客户端就无法链接了

listen系统调用源码

SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
	struct socket *sock;
	int err, fput_needed;
	int somaxconn;

	//通过fd获取socket
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (sock) {
		//获取系统配置的全链接大小
		somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
		//我们传入的backlog和系统配置的backlog进行比较,取较小值
		//因此我们在调用listen时,可以限定全链接队列的大小,但是也不能完全限定,因为还要和系统配置比较
		if ((unsigned int)backlog > somaxconn)
			backlog = somaxconn;

		err = security_socket_listen(sock, backlog);
		if (!err)
			//调用到af_inet.c中的inet_stream_ops中的inet_listen函数
			err = sock->ops->listen(sock, backlog);

		fput_light(sock->file, fput_needed);
	}
	return err;
}

inet_stream_ops中的inet_listen函数

int inet_listen(struct socket *sock, int backlog)
{
	struct sock *sk = sock->sk;
	unsigned char old_state;
	int err;

	lock_sock(sk);

	err = -EINVAL;
	//如果sock没有处于未连接的状态或者sock的type为SOCK_STREAM,则返回
	if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
		goto out;

	//如果sock不是关闭或者监听,则返回
	old_state = sk->sk_state;
	if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
		goto out;

	if (old_state != TCP_LISTEN) {
		//涉及到TCP Fast Open的选项,它是一种加速TCP连接建立的技术
		if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) &&
		    (sysctl_tcp_fastopen & TFO_SERVER_ENABLE) &&
		    !inet_csk(sk)->icsk_accept_queue.fastopenq.max_qlen) {
			fastopen_queue_tune(sk, backlog);
			tcp_fastopen_init_key_once(true);
		}
		//将sock从非listen到listen状态的初始化
		err = inet_csk_listen_start(sk, backlog);
		if (err)
			goto out;
	}
	//更改sock的
	sk->sk_max_ack_backlog = backlog;
	err = 0;

out:
	release_sock(sk);
	return err;
}

inet_csk_listen_start函数

int inet_csk_listen_start(struct sock *sk, int backlog)
{
	struct inet_connection_sock *icsk = inet_csk(sk);//面向连接的sock
	struct inet_sock *inet = inet_sk(sk);
	int err = -EADDRINUSE;

	reqsk_queue_alloc(&icsk->icsk_accept_queue);//申请全连接队列

	sk->sk_max_ack_backlog = backlog;//更改全链接队列的最大值
	sk->sk_ack_backlog = 0;//因为当前正在处于更改listen状态,所以不可能有链接到来,因此当前全链接队列的大小为0
	inet_csk_delack_init(sk);

	//更改状态
	sk_state_store(sk, TCP_LISTEN);
	//检测端口是冲突
	if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
		//赋值
		inet->inet_sport = htons(inet->inet_num);

		sk_dst_reset(sk);
		// 当sock保存到全局的哈希列表里
		err = sk->sk_prot->hash(sk);

		if (likely(!err))
			return 0;
	}
	//如果失败,那么就把状态该为close
	sk->sk_state = TCP_CLOSE;
	return err;
}

整体流程

在这里插入图片描述

3、accept 源码剖析

系统调用原型

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd:文件描述符,指向类型为SOCK_STREAM或SOCK_SEQPACKET
addr:一个指向 sockaddr 结构的指针,该结构包含了要绑定的地址和端口信息
addrlen:addr 结构的大小

accept会从全链接队列中取出第一个链接,如果成功,就会返回一个文件描述符

accept系统调用源码

SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
		int __user *, upeer_addrlen)
{
	return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}

SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
		int __user *, upeer_addrlen, int, flags)
{
	struct socket *sock, *newsock;
	struct file *newfile;
	int err, len, newfd, fput_needed;
	struct sockaddr_storage address;

	//进行相应的检测
	if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
		return -EINVAL;

	if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
		flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;

	//根据listen fd 获取对应的socket
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (!sock)
		goto out;

	err = -ENFILE;
	newsock = sock_alloc(); //为新链接申请socket
	if (!newsock)
		goto out_put;

	//将listen fd所对应的socket的type和ops赋值给新链接的socket
	newsock->type = sock->type;
	newsock->ops = sock->ops;

	__module_get(newsock->ops->owner);
	//为新链接申请fd
	newfd = get_unused_fd_flags(flags);
	if (unlikely(newfd < 0)) {
		err = newfd;
		sock_release(newsock);
		goto out_put;
	}
	//为新链接申请file,并和新链接的socket建立联系
	newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
	if (IS_ERR(newfile)) {
		err = PTR_ERR(newfile);
		put_unused_fd(newfd);
		sock_release(newsock);
		goto out_put;
	}

	err = security_socket_accept(sock, newsock);
	if (err)
		goto out_fd;

	//调用到af_inet.c中的inet_stream_ops中的inet_accept函数
	err = sock->ops->accept(sock, newsock, sock->file->f_flags);
	if (err < 0)
		goto out_fd;

	//获取对端地址
	if (upeer_sockaddr) {
		if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
					  &len, 2) < 0) {
			err = -ECONNABORTED;
			goto out_fd;
		}
		err = move_addr_to_user(&address,
					len, upeer_sockaddr, upeer_addrlen);
		if (err < 0)
			goto out_fd;
	}


	//新链接的fd和新链接的file建立联系
	fd_install(newfd, newfile);
	err = newfd;
	//新链接的fd,file,socket之间建立联系,跟创建socket源码时类似
out_put:
	fput_light(sock->file, fput_needed);
out:
	return err;
out_fd:
	fput(newfile);
	put_unused_fd(newfd);
	goto out_put;
}


int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
	struct sock *sk1 = sock->sk;//listen的sock
	int err = -EINVAL;
	//sk2是新链接的sock
	//调用tcp实现的accept获取sock赋值给listen的sock
	//tcp.ipv4.c中的inet_csk_accept
	struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);

	if (!sk2)
		goto do_err;

	lock_sock(sk2);

	sock_rps_record_flow(sk2);
	WARN_ON(!((1 << sk2->sk_state) &
		  (TCPF_ESTABLISHED | TCPF_SYN_RECV |
		  TCPF_CLOSE_WAIT | TCPF_CLOSE)));

	sock_graft(sk2, newsock);//建立起新链接的socket 和 sock之间的联系

	newsock->state = SS_CONNECTED;
	err = 0;
	release_sock(sk2);
do_err:
	return err;
}

tcp.ipv4.c中的inet_csk_accept

struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
	//struct inet_connection_sock是内核中实现有链接的socket
	//inet_connection_sock-->inet_sock-->sock
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct request_sock_queue *queue = &icsk->icsk_accept_queue; //全链接队列
	/*struct request_sock_queue {
		...
		u32			synflood_warned;
		atomic_t		qlen;  //半链接队列sk个数
		...
		全链接队列的队列头和队列尾
		struct request_sock	*rskq_accept_head; //头
		struct request_sock	*rskq_accept_tail; //尾
		...
	};*/
	struct request_sock *req; //用来存放已经到来的链接
	struct sock *newsk;
	int error;

	lock_sock(sk);

	/* We need to make sure that this socket is listening,
	 * and that it has something pending.
	 */
	error = -EINVAL;
	//传进来的sock不处于listen状态
	if (sk->sk_state != TCP_LISTEN)
		goto out_err;

	/* Find already established connection */
	//如果全链接队列为空
	if (reqsk_queue_empty(queue)) {
		//获取阻塞的时间,如果sock为非阻塞,timeo就为0
		//return noblock ? 0 : sk->sk_rcvtimeo;
		long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);

		/* If this is a non blocking socket don't sleep */
		error = -EAGAIN;
		//sock为非阻塞,返回
		if (!timeo)
			goto out_err;

		//sock为阻塞,在这个函数内部会进一步调用schedule_timeout进行睡眠,直到超时,或者有链接到来才会被唤醒
		error = inet_csk_wait_for_connect(sk, timeo);
		//如果超时了,或者出错了,返回
		if (error)
			goto out_err;
	}
	//执行到这里,说明全链接队列里一定有链接
	//去除链接
	req = reqsk_queue_remove(queue, sk);
	newsk = req->sk;
	...
}

reqsk_queue_remove函数取链接

static inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue,
						      struct sock *parent)
{
	struct request_sock *req;

	spin_lock_bh(&queue->rskq_lock);
	req = queue->rskq_accept_head; //取出队头元素
	if (req) {
		sk_acceptq_removed(parent); //sk->sk_ack_backlog--; 全链接队列的个数减1
		queue->rskq_accept_head = req->dl_next; //rskq_accept_head=rskq_accept_head->dl_next 删除队头元素
		//如果只有一个元素,取完之后,头尾都是NULL
		if (queue->rskq_accept_head == NULL)
			queue->rskq_accept_tail = NULL;
	}
	spin_unlock_bh(&queue->rskq_lock);
	return req;
}

4、connect 源码剖析

系统调用原型

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

sockfd:文件描述符,指向类型为SOCK_STREAM或SOCK_SEQPACKET
addr:一个指向 sockaddr 结构的指针,该结构包含了要绑定的地址和端口信息
addrlen:addr 结构的大小

如果连接成功,则返回0,否则返回-1

系统调用源码

SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
		int, addrlen)
{
	struct socket *sock;
	struct sockaddr_storage address;
	int err, fput_needed;
	//通过fd获得与之对应socket
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (!sock)
		goto out;
	//将用户层的数据拷贝至内核
	err = move_addr_to_kernel(uservaddr, addrlen, &address);
	if (err < 0)
		goto out_put;

	err = security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
	if (err)
		goto out_put;

	//调用af_inet.c中的inet_stream_ops中的inet_stream_connect函数
	err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
				 sock->file->f_flags);
out_put:
	fput_light(sock->file, fput_needed);
out:
	return err;
}

af_inet.c中的inet_stream_ops中的inet_stream_connect函数

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

	lock_sock(sock->sk);
	//进一步的调用
	err = __inet_stream_connect(sock, uaddr, addr_len, flags);
	release_sock(sock->sk);
	return err;
}


int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
			  int addr_len, int flags)
{
	struct sock *sk = sock->sk;
	int err;
	long timeo;

	//做相应的检测
	if (addr_len < sizeof(uaddr->sa_family))
		return -EINVAL;

	if (uaddr->sa_family == AF_UNSPEC) {
		err = sk->sk_prot->disconnect(sk, flags);
		sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
		goto out;
	}

	//只有当socket的状态为SS_UNCONNECTED并且sock的状态为TCP_CLOSE后,最终才会调用到tcp的connect函数
	switch (sock->state) {
	default:
		err = -EINVAL;
		goto out;
	case SS_CONNECTED:
		err = -EISCONN;
		goto out;
	case SS_CONNECTING:
		err = -EALREADY;
		break;
	case SS_UNCONNECTED:
		err = -EISCONN;
		if (sk->sk_state != TCP_CLOSE)
			goto out;

		//最终会调用到tcp_ipv4.c中tcp_v4_connect函数(tcp实现的connect)
		//进行三次握手
		err = sk->sk_prot->connect(sk, uaddr, addr_len);
		if (err < 0)
			goto out;

		//更改状态为SS_CONNECTING,表示链接成功
		sock->state = SS_CONNECTING;

		err = -EINPROGRESS;
		break;
	}

	//判断上层的文件描述符有没有指定非阻塞
	//return noblock ? 0 : sk->sk_sndtimeo;
	timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
	
	//判断sk中的状态是否为TCPF_SYN_SENT或者TCPF_SYN_RECV
	//因为sk的状态会随着三次握手的变化而变化
	if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
		int writebias = (sk->sk_protocol == IPPROTO_TCP) &&
				tcp_sk(sk)->fastopen_req &&
				tcp_sk(sk)->fastopen_req->data ? 1 : 0;

		//如果没超时,就是调用inet_wait_for_connect进行等待 
		if (!timeo || !inet_wait_for_connect(sk, timeo, writebias))
			goto out;

		err = sock_intr_errno(timeo);
		if (signal_pending(current))
			goto out;
	}

	if (sk->sk_state == TCP_CLOSE)
		goto sock_error;

	sock->state = SS_CONNECTED;
	err = 0;
out:
	return err;

sock_error:
	err = sock_error(sk) ? : -ECONNABORTED;
	sock->state = SS_UNCONNECTED;
	if (sk->sk_prot->disconnect(sk, flags))
		sock->state = SS_DISCONNECTING;
	goto out;
}

客户端调用connect成功,但三次握手并未完成,进程是如何阻塞自己

看看在inet_wait_for_connect函数内部,进程是如何阻塞自己的

static long inet_wait_for_connect(struct sock *sk, long timeo, int writebias)
{
	//初始化一个等待队列(wait),并指定了一个唤醒函数woken_wake_function
	//当条件满足时,内核就会调用woken_wake_function将其唤醒
	DEFINE_WAIT_FUNC(wait, woken_wake_function);

	//将wait加入到等待sock的等待队列中
	add_wait_queue(sk_sleep(sk), &wait);
	sk->sk_write_pending += writebias;

	//当sk的sk_state为TCPF_SYN_SENT或者TCPF_SYN_RECV,都表示三次握手还没完成
	while ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
		release_sock(sk);
		//调用wait_woken进行睡眠
		timeo = wait_woken(&wait, TASK_INTERRUPTIBLE, timeo);
		lock_sock(sk);
		//退出来的原因如果是被信号中断或者超时,那么就退出循环,否则再次进行睡眠
		if (signal_pending(current) || !timeo)
			break;
	}
	//移除等待队列
	remove_wait_queue(sk_sleep(sk), &wait);
	sk->sk_write_pending -= writebias;
	return timeo;
}


//DEFINE_WAIT_FUNC等待队列
//function就是内核唤醒该进程时需要执行的函数
#define DEFINE_WAIT_FUNC(name, function)				\
	wait_queue_t name = {						\
		//current表示当前进程的task_struct
		//在设置等待队列时,当前进程就会把自己的task_struct放在等待队列中,方便内核唤醒
		.private	= current,				\
		.func		= function,				\
		.task_list	= LIST_HEAD_INIT((name).task_list),	\
	}


//woken_wake_function唤醒函数
int woken_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
	smp_wmb(); /* C */
	wait->flags |= WQ_FLAG_WOKEN;

	return default_wake_function(wait, mode, sync, key);
}

int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,
			  void *key)
{
						//curr->private 就表示被挂起的进程的task_struct
	return try_to_wake_up(curr->private, mode, wake_flags);
}

看看tcp_ipv4.c中tcp_v4_connect函数是如何进行三次握手操作的

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
	struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
	struct inet_sock *inet = inet_sk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	__be16 orig_sport, orig_dport;
	__be32 daddr, nexthop;
	struct flowi4 *fl4;
	struct rtable *rt;
	int err;
	struct ip_options_rcu *inet_opt;

	//检查地址的有效性
	if (addr_len < sizeof(struct sockaddr_in))
		return -EINVAL;

	if (usin->sin_family != AF_INET)
		return -EAFNOSUPPORT;

	//获取目标地址和下一跳地址
	nexthop = daddr = usin->sin_addr.s_addr;
	inet_opt = rcu_dereference_protected(inet->inet_opt,
					     lockdep_sock_is_held(sk));
	if (inet_opt && inet_opt->opt.srr) {
		if (!daddr)
			return -EINVAL;
		nexthop = inet_opt->opt.faddr;
	}

	//保存原始的源端口号和目的端口号
	orig_sport = inet->inet_sport;
	orig_dport = usin->sin_port;
	//获取并设置用于连接的路由
	fl4 = &inet->cork.fl.u.ip4;

	rt = ip_route_connect(fl4, nexthop, inet->inet_saddr,
			      RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
			      IPPROTO_TCP,
			      orig_sport, orig_dport, sk);
	//检查路由连接的结果
	if (IS_ERR(rt)) {
		err = PTR_ERR(rt);
		if (err == -ENETUNREACH)
			IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
		return err;
	}

	//检查路由是否指向多播或广播地址
	if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
		ip_rt_put(rt);
		return -ENETUNREACH;
	}

	//更新目标地址为路由的目标地址
	if (!inet_opt || !inet_opt->opt.srr)
		daddr = fl4->daddr;

	//更新源 IP 地址
	if (!inet->inet_saddr)
		inet->inet_saddr = fl4->saddr;
	sk_rcv_saddr_set(sk, inet->inet_saddr);

	//重置 TCP 相关的状态
	if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) {
		/* Reset inherited state */
		tp->rx_opt.ts_recent	   = 0;
		tp->rx_opt.ts_recent_stamp = 0;
		if (likely(!tp->repair))
			tp->write_seq	   = 0;
	}

	if (tcp_death_row.sysctl_tw_recycle &&
	    !tp->rx_opt.ts_recent_stamp && fl4->daddr == daddr)
		tcp_fetch_timewait_stamp(sk, &rt->dst);

	
	inet->inet_dport = usin->sin_port;//设置目的端口号
	sk_daddr_set(sk, daddr);//设置目标地址

	//设置扩展头长度
	inet_csk(sk)->icsk_ext_hdr_len = 0;
	if (inet_opt)
		inet_csk(sk)->icsk_ext_hdr_len = inet_opt->opt.optlen;

	//设置最大报文段长度
	tp->rx_opt.mss_clamp = TCP_MSS_DEFAULT;

	//设置套接字的状态为 SYN-SENT,并调用inet_hash_connect设置源端口号

	tcp_set_state(sk, TCP_SYN_SENT);
	err = inet_hash_connect(&tcp_death_row, sk);//确定源端口号
	if (err)
		goto failure;

	sk_set_txhash(sk);

	//设置新的端口并更新路由表
	rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
			       inet->inet_sport, inet->inet_dport, sk);
	if (IS_ERR(rt)) {
		err = PTR_ERR(rt);
		rt = NULL;
		goto failure;
	}
	/* OK, now commit destination to socket.  */
	//设置套接字的 GSO 类型并设置能力
	sk->sk_gso_type = SKB_GSO_TCPV4;
	sk_setup_caps(sk, &rt->dst);

	//设置初始化的 TCP 序列号
	if (!tp->write_seq && likely(!tp->repair))
		// TCP 初始化的序列号 跟四元组和时间有关,最大程度上保证序列号不重复
		tp->write_seq = secure_tcp_sequence_number(inet->inet_saddr,
							   inet->inet_daddr,
							   inet->inet_sport,
							   usin->sin_port);
	//为套接字分配一个随机的标识符
	inet->inet_id = tp->write_seq ^ jiffies;

	//发送 TCP 连接请求(发送syn包)
	err = tcp_connect(sk);

	rt = NULL;
	if (err)
		goto failure;

	return 0;

failure:
	//失败时处理的操作,将状态改为TCP_CLOSE
	tcp_set_state(sk, TCP_CLOSE);
	ip_rt_put(rt);
	sk->sk_route_caps = 0;
	inet->inet_dport = 0;
	return err;
}

客户端在connect时,如何选择源端口

在前面的代码中,会调用inet_hash_connect(&tcp_death_row, sk)函数选择源端口,通过分析源码,看看是如何选择的?

int inet_hash_connect(struct inet_timewait_death_row *death_row,
		      struct sock *sk)
{
	u32 port_offset = 0;

	if (!inet_sk(sk)->inet_num)
		port_offset = inet_sk_port_offset(sk);
	return __inet_hash_connect(death_row, sk, port_offset,
				   __inet_check_established);
}

//进一步调用__inet_hash_connect(death_row, sk, port_offset, __inet_check_established);
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 **))
{
	//相应结构的初始化
	//在内核当中,有一个全局的连接信息 inet_hashinfo
	/*struct inet_hashinfo {
		...
		struct inet_ehash_bucket	*ehash; //存放拥有完整连接信息的连接,key,四元组结构体,value:表示该连接的sock
		struct inet_bind_hashbucket	*bhash; //存放所有绑定某端号的连接,key:本地端口,value:绑定该端口的sock
		struct inet_listen_hashbucket	listening_hash[INET_LHTABLE_SIZE]
					____cacheline_aligned_in_smp;
		//存放所有监听sock,key:本地端口,value:对应listen状态下的sock
		...
	};*/
	
	/*struct inet_bind_hashbucket {
		spinlock_t		lock;
		struct hlist_head	chain;
	};*/
	
	/*struct hlist_head {
		struct hlist_node *first;
	};*/
	
	/*struct inet_bind_bucket {
		possible_net_t		ib_net;
		unsigned short		port;//对应的端口
		signed char		fastreuse;
		signed char		fastreuseport;
		kuid_t			fastuid;
		int			num_owners;
		struct hlist_node	node;
		struct hlist_head	owners; //用于保存sock中的skc_bind_node,就将端口和sock关联在一起
	};*/
	
	//通过port就能找到对应的inet_bind_hashbucket,从而找到hlist_head,进一步再找到hlist_node
	//hlist_node node 又指向了sock中的skc_bind_node,此时就能找到正在使用port的sock
	
	
	struct inet_hashinfo *hinfo = death_row->hashinfo;
	struct inet_timewait_sock *tw = NULL;
	struct inet_bind_hashbucket *head;
	int port = inet_sk(sk)->inet_num;
	struct net *net = sock_net(sk);
	struct inet_bind_bucket *tb;
	u32 remaining, offset;
	int ret, i, low, high;
	static u32 hint;

	//如果端口存在,直接返回
	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;
	}
	//获取可用的端口范围
	//该参数的默认值是 32768 61000,意味着端口总可用的数量是61000 - 32768 = 28232个 
	inet_get_local_port_range(net, &low, &high);
	high++; /* [32768, 60999] -> [32768, 61000[ */
	remaining = high - low; //28232
	if (likely(remaining > 1))
		remaining &= ~1U; //remaining必为偶数

	//选择一个随机的偏移量
	offset = (hint + port_offset) % remaining;
	//保证offset为奇数
	offset &= ~1U;
other_parity_scan:
	port = low + offset;//port也为奇数,port一定介于low 和 high之间
	//因为port为奇数,每次加2,还是奇数,因此先找奇数端口
	//如果奇数端口遍历完成,都还没有获取到可用的端口,会在后面再次 goto other_parity_scan,此时奇偶性反转,遍历偶数端口
	for (i = 0; i < remaining; i += 2, port += 2) {
		//unlikely为编译优化,表示不太可能成立
		if (unlikely(port >= high))
			port -= remaining;
		//如果是本地保留端口(给系统使用),则继续判断下一个端口
		if (inet_is_local_reserved_port(net, port))
			continue;
		//查看已经使用该端口的hash表
		//bhash存放所有绑定某端号的连接,key:本地端口,value:绑定该端口的sock
		head = &hinfo->bhash[inet_bhashfn(net, port,
						  hinfo->bhash_size)];
		spin_lock_bh(&head->lock);

		head = &hinfo->bhash[inet_bhashfn(net, port, hinfo->bhash_size)];
		inet_bind_bucket_for_each(tb, &head->chain) {
			//如果端口被使用了
			if (net_eq(ib_net(tb), net) && tb->port == port) {
				if (tb->fastreuse >= 0 ||
				    tb->fastreuseport >= 0)
					goto next_port;
				WARN_ON(hlist_empty(&tb->owners));
				//检测端口是否能复用,等于检测四元组是否相同(检测ehash中是否有相同的四元组),
				//如果存在,并且sock处于time_wait状态,表示可以复用,此时还会把处于time_wait状态的sock赋值给tw
				if (!check_established(death_row, sk,
						       port, &tw))
					goto ok;
				goto next_port;
			}
		}

		//如果端口没被别人使用,并且本次连接可以使用,就会创建inet_bind_bucket
		//然后加入到全局的连接信息(inet_hashinfo->bhash)
		tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,
					     net, head, port);
		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();
	}

	offset++; //为了让奇数端口变为偶数端口
	if ((offset & 1) && remaining > 1)
		goto other_parity_scan;

	return -EADDRNOTAVAIL;

ok:
	hint += i + 2;

	inet_bind_hash(sk, tb, port); //将port和sock绑定起来
	if (sk_unhashed(sk)) {
		inet_sk(sk)->inet_sport = htons(port); //赋值源端口(已确定)
		inet_ehash_nolisten(sk, (struct sock *)tw);//将sock添加到ehash中
		//并且把之前绑定这个端口,但现在处于time_wait状态的sock从ehash中移除
	}
	if (tw)
		inet_twsk_bind_unhash(tw, hinfo);
	spin_unlock(&head->lock);
	if (tw)
		inet_twsk_deschedule_put(tw);
	local_bh_enable();
	return 0;
}

客户发送syn封包以及重传

确定四元组,设置初始化的 TCP 序列号后,就会调用tcp_output.c中的tcp_connect(struct sock *sk)函数,构建syn包并发送

tcp_connect源码

int tcp_connect(struct sock *sk)
{
	//tcp_sock对inet_connection_sock的进一步组合
	struct tcp_sock *tp = tcp_sk(sk);
	//无论是接受还是发送,在内核中都是通过sk_buff进行分包的构建和传输的
	struct sk_buff *buff;
	int err;

	if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
		return -EHOSTUNREACH; /* Routing failure or similar. */

	//对sock进行初始化化
	tcp_connect_init(sk);

	if (unlikely(tp->repair)) {
		tcp_finish_connect(sk, NULL);
		return 0;
	}

	//申请sk_buff
	buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);
	if (unlikely(!buff))
		return -ENOBUFS;

	//sk_buff初始化,write_seq就是初始序列号,TCPHDR_SYN就是tcp的标志(syn,ack,rst...)
	//这里其实就是将sk_buff声明成一个syn包
	tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
	//更新最近一次的发送数据报的时间
	tp->retrans_stamp = tcp_time_stamp;
	//把sk_buff放到sock的发送缓冲队列,当tcp发送数据时,就会从这个缓冲队列里面取数据,然后再发送出去
	tcp_connect_queue_skb(sk, buff);
	tcp_ecn_send_syn(sk, buff);

	//针对于Fast Open,采用tcp_send_syn_data,否则采用tcp_transmit_skb发送数据
	err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
	      tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
	if (err == -ECONNREFUSED)
		return err;

	//数据发送完成后(异步发送),需要更改对应的序号
	//snd_nxt表示发送窗口中可以窗口的第一个字节
	tp->snd_nxt = tp->write_seq;
	tp->pushed_seq = tp->write_seq;
	buff = tcp_send_head(sk);
	if (unlikely(buff)) {
		tp->snd_nxt	= TCP_SKB_CB(buff)->seq;
		tp->pushed_seq	= TCP_SKB_CB(buff)->seq;
	}
	TCP_INC_STATS(sock_net(sk), TCP_MIB_ACTIVEOPENS);
	//重启一个定时器,直到syn发送成功
	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
				  inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
	return 0;
}

如果syn发送失败,或者没有接受到对应的ack,还会涉及到syn包的重传
inet_csk_reset_xmit_timer源码

static inline void inet_csk_reset_xmit_timer(struct sock *sk, const int what,
					     unsigned long when,
					     const unsigned long max_when)
{
	//传入进来的参数what就表示需要重启的定时器,因为定时器有多个
	struct inet_connection_sock *icsk = inet_csk(sk);

	//设置超时时间,when为自己设置的超时时间,max_when为最大的超时时间
	if (when > max_when) {
#ifdef INET_CSK_DEBUG
		pr_debug("reset_xmit_timer: sk=%p %d when=0x%lx, caller=%p\n",
			 sk, what, when, current_text_addr());
#endif
		when = max_when;
	}

	if (what == ICSK_TIME_RETRANS || what == ICSK_TIME_PROBE0 ||
	    what == ICSK_TIME_EARLY_RETRANS || what ==  ICSK_TIME_LOSS_PROBE) {
	    //将事件记录到icsk_pending中表示启动的定时器是超时重传定时器
		icsk->icsk_pending = what;
		//下次进行重传的时间,jiffis为当前的时间戳,when表示时间间隔
		icsk->icsk_timeout = jiffies + when;
		//重启定时器,超时后就会触发
		sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
	} else if (what == ICSK_TIME_DACK) {
		icsk->icsk_ack.pending |= ICSK_ACK_TIMER;
		icsk->icsk_ack.timeout = jiffies + when;
		sk_reset_timer(sk, &icsk->icsk_delack_timer, icsk->icsk_ack.timeout);
	}
#ifdef INET_CSK_DEBUG
	else {
		pr_debug("%s", inet_csk_timer_bug_msg);
	}
#endif
}

当超时后,就会执行定时器对应的执行函数,那这些函数又是如何被注册到对应的sock中的呢?

首先sock在初始化时,会调用到tcp.c中的tcp_init_sock,然后调用tcp_init_xmit_timers,再调用inet_csk_init_xmit_timers

//tcp.c
void tcp_init_sock(struct sock *sk)
{
	...
	tcp_init_xmit_timers(sk);
	...
}


//tcp_timer.c
void tcp_init_xmit_timers(struct sock *sk)
{
	inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer,
				  &tcp_keepalive_timer);
}

//inet_connection_sock.c
//在这里会注册三个定时器
void inet_csk_init_xmit_timers(struct sock *sk,
			       void (*retransmit_handler)(unsigned long),
			       void (*delack_handler)(unsigned long),
			       void (*keepalive_handler)(unsigned long))
{
	struct inet_connection_sock *icsk = inet_csk(sk);

	setup_timer(&icsk->icsk_retransmit_timer, retransmit_handler,
			(unsigned long)sk);
	setup_timer(&icsk->icsk_delack_timer, delack_handler,
			(unsigned long)sk);
	setup_timer(&sk->sk_timer, keepalive_handler, (unsigned long)sk);
	//由于同一个定时器函数可以处理多个定时器,它们也需要进行区分,pending参数表示
	//当前需要处理的是哪个定时事件,0表示没有事件需要处理
	icsk->icsk_pending = icsk->icsk_ack.pending = 0;
}

再看看这个重传数据的定时器tcp_write_timer,主要做了哪些事?

//tcp_timer.c
//重传数据
static void tcp_write_timer(unsigned long data)
{
	struct sock *sk = (struct sock *)data;
	
	//定时器函数在软中断中执行,这里先锁定套接字
	bh_lock_sock(sk);

	//如果TCB没有被进程上下文锁定,那么就调用tcp_write_timer_handler做进一步处理
	if (!sock_owned_by_user(sk)) {
		//进一步调用
		tcp_write_timer_handler(sk);
	} else {
		/* delegate our work to tcp_release_cb() */
		if (!test_and_set_bit(TCP_WRITE_TIMER_DEFERRED, &tcp_sk(sk)->tsq_flags))
			sock_hold(sk);
	}
	bh_unlock_sock(sk);
	sock_put(sk);
}


void tcp_write_timer_handler(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	int event;

	//如果套接字已经关闭或者定时器根本就没有启动,退出
	if (((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_LISTEN)) ||
	    !icsk->icsk_pending)
		goto out;
		
	//如果没有超时,重新设定超时时间
	if (time_after(icsk->icsk_timeout, jiffies)) {
		sk_reset_timer(sk, &icsk->icsk_retransmit_timer, icsk->icsk_timeout);
		goto out;
	}
	
	//event 表示需要处理的事件
	event = icsk->icsk_pending;
	
	switch (event) {
	case ICSK_TIME_EARLY_RETRANS:
		tcp_resume_early_retransmit(sk);
		break;
	case ICSK_TIME_LOSS_PROBE:
		tcp_send_loss_probe(sk);
		break;
	case ICSK_TIME_RETRANS:
		//即将处理事件,将icsk_pending置为0,表示清除事件,后面如果需要会重新设定
		icsk->icsk_pending = 0;
		//超时重传由该函数处理
		tcp_retransmit_timer(sk);
		break;
	case ICSK_TIME_PROBE0:
		icsk->icsk_pending = 0;
		tcp_probe_timer(sk);
		break;
	}

out:
	//内存回收
	sk_mem_reclaim(sk);
}



void tcp_retransmit_timer(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct net *net = sock_net(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);

	//暂时不考虑fastopen
	if (tp->fastopen_rsk) {
		WARN_ON_ONCE(sk->sk_state != TCP_SYN_RECV &&
			     sk->sk_state != TCP_FIN_WAIT1);
		tcp_fastopen_synack_timer(sk);
		/* Before we receive ACK to our SYN-ACK don't retransmit
		 * anything else (e.g., data or FIN segments).
		 */
		return;
	}

	//如果根本就没有发送数据何来超时处理
	if (!tp->packets_out)
		goto out;

	//同上,发送队列是空的
	WARN_ON(tcp_write_queue_empty(sk));

	tp->tlp_high_seq = 0;

	//发送窗口为0;socket没有关闭;当前不再三次握手阶段(说明在连接态)。
	//这种情况下发生了超时,需要检查是否需要关闭套接字
	if (!tp->snd_wnd && !sock_flag(sk, SOCK_DEAD) &&
	    !((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV))) {
		struct inet_sock *inet = inet_sk(sk);
		if (sk->sk_family == AF_INET) {
			net_dbg_ratelimited("Peer %pI4:%u/%u unexpectedly shrunk window %u:%u (repaired)\n",
					    &inet->inet_daddr,
					    ntohs(inet->inet_dport),
					    inet->inet_num,
					    tp->snd_una, tp->snd_nxt);
		}
#if IS_ENABLED(CONFIG_IPV6)
		else if (sk->sk_family == AF_INET6) {
			net_dbg_ratelimited("Peer %pI6:%u/%u unexpectedly shrunk window %u:%u (repaired)\n",
					    &sk->sk_v6_daddr,
					    ntohs(inet->inet_dport),
					    inet->inet_num,
					    tp->snd_una, tp->snd_nxt);
		}
#endif
		//如果这条连接上已经有很长时间(超过TCP_RTO_MAX=120s)没有收到对端
		//的确认了,认为连接异常了,直接关闭该套接字
		if (tcp_time_stamp - tp->rcv_tstamp > TCP_RTO_MAX) {
			tcp_write_err(sk);
			goto out;
		}
		//这种情况说明对方已经很拥塞了,进入LOSS状态
		tcp_enter_loss(sk);
		//重传第一包数据
		tcp_retransmit_skb(sk, tcp_write_queue_head(sk), 1);
		__sk_dst_reset(sk);
		goto out_reset_timer;
	}

	//超时重传也不能无限重传下去,必须有截止时间,
	//该函数判断重传的次数是否超过最大限制,是否继续执行超时重传
	if (tcp_write_timeout(sk))
		goto out;

	//如果是第一次超时重传
	if (icsk->icsk_retransmits == 0) {
		int mib_idx;

		if (icsk->icsk_ca_state == TCP_CA_Recovery) {
			if (tcp_is_sack(tp))
				mib_idx = LINUX_MIB_TCPSACKRECOVERYFAIL;
			else
				mib_idx = LINUX_MIB_TCPRENORECOVERYFAIL;
		} else if (icsk->icsk_ca_state == TCP_CA_Loss) {
			mib_idx = LINUX_MIB_TCPLOSSFAILURES;
		} else if ((icsk->icsk_ca_state == TCP_CA_Disorder) ||
			   tp->sacked_out) {
			if (tcp_is_sack(tp))
				mib_idx = LINUX_MIB_TCPSACKFAILURES;
			else
				mib_idx = LINUX_MIB_TCPRENOFAILURES;
		} else {
			mib_idx = LINUX_MIB_TCPTIMEOUTS;
		}
		__NET_INC_STATS(sock_net(sk), mib_idx);
	}

	//切换拥塞状态为LOSS
	tcp_enter_loss(sk);

	//尝试重传第一包数据,如果发送失败说明本地发生了拥塞,这时不执行指数退避算法
	//一旦允许超时重传,那么只重发当前发送队列中的第一个包,然后按照指数退避算法重启定时器
	if (tcp_retransmit_skb(sk, tcp_write_queue_head(sk), 1) > 0) {
		if (!icsk->icsk_retransmits)
			icsk->icsk_retransmits = 1;
		//再次设定超时定时器
		inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
					  min(icsk->icsk_rto, TCP_RESOURCE_PROBE_INTERVAL),
					  TCP_RTO_MAX);
		goto out;
	}
	//累加指数退避次数和发生超时重传次数
	icsk->icsk_backoff++;
	icsk->icsk_retransmits++;

out_reset_timer:
	if (sk->sk_state == TCP_ESTABLISHED &&
	    (tp->thin_lto || sysctl_tcp_thin_linear_timeouts) &&
	    tcp_stream_is_thin(tp) &&
	    icsk->icsk_retransmits <= TCP_THIN_LINEAR_RETRIES) {
		icsk->icsk_backoff = 0;
		//执行指数退避算法,更新下次超时间隔记录到icsk_rto
		icsk->icsk_rto = min(__tcp_set_rto(tp), TCP_RTO_MAX);
	} else {
		icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);
	}
	inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX);
	if (retransmits_timed_out(sk, net->ipv4.sysctl_tcp_retries1 + 1, 0, 0))
		__sk_dst_reset(sk);

out:;
}

小结一下:从上面可以看出,超时重传的处理逻辑还是很清晰的。1)首先检查是否还允许继续进行超时重传,这时综合考虑最大重传次数限制、系统拥塞限制、socket状态等因素;2)一旦允许超时重传,那么只重发当前发送队列中的第一个包,然后按照指数退避算法重启定时器。

服务端收到syn封包,如何处理

数据经过网卡,再到ip层,最终会调用到tcp_v4_rcv函数做处理

int tcp_v4_rcv(struct sk_buff *skb)
{
	struct net *net = dev_net(skb->dev);
	const struct iphdr *iph;
	const struct tcphdr *th;
	bool refcounted;
	struct sock *sk;
	int ret;

	//如果不是发往本机的就直接丢弃
	if (skb->pkt_type != PACKET_HOST)
		goto discard_it;

	/* Count it even if it's bad */
	__TCP_INC_STATS(net, TCP_MIB_INSEGS);

	/*如果一个TCP段在传输过程中被网络层分片,那么在目的端的网络层会重新组包,这会导致传给
	TCP的skb的分片结构中包含多个skb,这种情况下,该函数会将分片结构重组到线性数据区。如果发生异常,则丢弃该报文*/
	if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
		goto discard_it;

	//获得tcp头
	th = (const struct tcphdr *)skb->data;

	//如果 TCP 的首部长度小于不带数据的 TCP 的首部长度,则说明 TCP 数据异常。
	//统计相关信息后,丢弃。
	if (unlikely(th->doff < sizeof(struct tcphdr) / 4))
		goto bad_packet;
	//保证skb的线性区域至少包括实际的TCP首部
	if (!pskb_may_pull(skb, th->doff * 4))
		goto discard_it;


	//验证 TCP 首部中的校验和,如校验和有误,则说明报文已损坏,统计相关信息后丢弃
	if (skb_checksum_init(skb, IPPROTO_TCP, inet_compute_pseudo))
		goto csum_error;

	//tcp头
	th = (const struct tcphdr *)skb->data;
	iph = ip_hdr(skb);//ip头 skb->head(整个skb的起始位置) + skb->network_header(起始位置到ip头部的偏移量)

	memmove(&TCP_SKB_CB(skb)->header.h4, IPCB(skb),
		sizeof(struct inet_skb_parm));
	barrier();

	//初始化skb中的控制块
	TCP_SKB_CB(skb)->seq = ntohl(th->seq);
	TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
				    skb->len - th->doff * 4);
	TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
	TCP_SKB_CB(skb)->tcp_flags = tcp_flag_byte(th);
	TCP_SKB_CB(skb)->tcp_tw_isn = 0;
	TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph);
	TCP_SKB_CB(skb)->sacked	 = 0;

lookup:
	/* 在 ehash 或 bhash 散列表中根据地址和端口来查找传
	输控制块。如果在 ehash 中找到,则表示已经经历了三次握手并且已建立了连接,可以
	进行正常的通信。如果在 bhash 中找到,则表示已经绑定已经绑定了端口,处于侦听
	状态。如果在两个散列表中都查找不到,说明此时对应的传输控制块还没有创建,跳转
	到no_tcp_socket 处处理。
	*/
	sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
			       th->dest, &refcounted);
	if (!sk)
		goto no_tcp_socket;

process:
	//TCP_TIME_WAIT需要做特殊处理,这里先不关注
	if (sk->sk_state == TCP_TIME_WAIT)
		goto do_time_wait;

	//如果服务端已经收到过客服端发的syn包
	if (sk->sk_state == TCP_NEW_SYN_RECV) {
		struct request_sock *req = inet_reqsk(sk);
		struct sock *nsk;

		sk = req->rsk_listener;
		if (unlikely(tcp_v4_inbound_md5_hash(sk, skb))) {
			sk_drops_add(sk, skb);
			reqsk_put(req);
			goto discard_it;
		}
		if (tcp_checksum_complete(skb)) {
			reqsk_put(req);
			goto csum_error;
		}
		if (unlikely(sk->sk_state != TCP_LISTEN)) {
			inet_csk_reqsk_queue_drop_and_put(sk, req);
			goto lookup;
		}

		sock_hold(sk);
		refcounted = true;
		nsk = tcp_check_req(sk, skb, req, false);
		if (!nsk) {
			reqsk_put(req);
			goto discard_and_relse;
		}
		if (nsk == sk) {
			reqsk_put(req);
		} else if (tcp_child_process(sk, nsk, skb)) {
			tcp_v4_send_reset(nsk, skb);
			goto discard_and_relse;
		} else {
			sock_put(sk);
			return 0;
		}
	}
	//ttl 小于给定的最小的 ttl
	if (unlikely(iph->ttl < inet_sk(sk)->min_ttl)) {
		__NET_INC_STATS(net, LINUX_MIB_TCPMINTTLDROP);
		goto discard_and_relse;
	}

	//查找 IPsec 数据库,如果查找失败,进行相应处理
	if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
		goto discard_and_relse;

	//md5 相关
	if (tcp_v4_inbound_md5_hash(sk, skb))
		goto discard_and_relse;

	nf_reset(skb);

	//TCP套接字过滤器,如果数据包被过滤掉了,结束处理过程
	if (tcp_filter(sk, skb))
		goto discard_and_relse;
	th = (const struct tcphdr *)skb->data;
	iph = ip_hdr(skb);

	//到了传输层,该字段已经没有意义,将其置为空
	skb->dev = NULL;

	//LISTEN 状态
	if (sk->sk_state == TCP_LISTEN) {
		ret = tcp_v4_do_rcv(sk, skb); //交由tcp_v4_do_rcv()处理
		goto put_and_return;
	}

	//先持锁,这样进程上下文和其它软中断则无法操作该TCB
	sk_incoming_cpu_update(sk);

	bh_lock_sock_nested(sk);
	tcp_segs_in(tcp_sk(sk), skb);
	ret = 0;
	//如果当前TCB没有被进程上下文锁定,首先尝试将数据包放入prequeue队列,
	//如果prequeue队列没有处理,再将其处理后放入receive队列。如果TCB已
	//经被进程上下文锁定,那么直接将数据包放入backlog队列
	if (!sock_owned_by_user(sk)) {
		if (!tcp_prequeue(sk, skb))
			ret = tcp_v4_do_rcv(sk, skb);
	} else if (tcp_add_backlog(sk, skb)) {//TCB被用户进程锁定,直接将数据包放入backlog队列
		goto discard_and_relse;
	}
	bh_unlock_sock(sk);//释放锁

put_and_return:
	if (refcounted)
		sock_put(sk);//释放TCB引用计数,当计数为 0 的时候,使用 sk_free 释放传输控制块

	return ret;//返回处理结果

//处理没有创建传输控制块收到报文,校验错误,坏包的情况,给对端发送 RST 报文。
no_tcp_socket:
	//查找 IPsec 数据库,如果查找失败,进行相应处理
	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
		goto discard_it;

	if (tcp_checksum_complete(skb)) {
csum_error:
		__TCP_INC_STATS(net, TCP_MIB_CSUMERRORS);
bad_packet:
		__TCP_INC_STATS(net, TCP_MIB_INERRS);
	} else {
		tcp_v4_send_reset(NULL, skb);
	}

discard_it:
	//丢弃帧
	kfree_skb(skb);
	return 0;

discard_and_relse:
	sk_drops_add(sk, skb);
	if (refcounted)
		sock_put(sk);
	goto discard_it;
//处理TIME_WAIT状态
do_time_wait:
	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
		inet_twsk_put(inet_twsk(sk));
		goto discard_it;
	}

	if (tcp_checksum_complete(skb)) {
		inet_twsk_put(inet_twsk(sk));
		goto csum_error;
	}
	//根据返回值进行相应处理
	switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
	case TCP_TW_SYN: {
		struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
							&tcp_hashinfo, skb,
							__tcp_hdrlen(th),
							iph->saddr, th->source,
							iph->daddr, th->dest,
							inet_iif(skb));
		if (sk2) {
			inet_twsk_deschedule_put(inet_twsk(sk));
			sk = sk2;
			refcounted = false;
			goto process;
		}
	}
	case TCP_TW_ACK:
		tcp_v4_timewait_ack(sk, skb);
		break;
	case TCP_TW_RST:
		tcp_v4_send_reset(sk, skb);
		inet_twsk_deschedule_put(inet_twsk(sk));
		goto discard_it;
	case TCP_TW_SUCCESS:;
	}
	goto discard_it;
}

因为服务端的sock处于listen状态,那么就会接着调用/net/ipv4/tcp_ipv4.c中的tcp_v4_do_rcv

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
	struct sock *rsk;

	//当状态为ESTABLISHED时,用tcp_rcv_established()接收处理
	if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
		struct dst_entry *dst = sk->sk_rx_dst;

		sock_rps_save_rxhash(sk, skb);
		sk_mark_napi_id(sk, skb);
		if (dst) {
			if (inet_sk(sk)->rx_dst_ifindex != skb->skb_iif ||
			    !dst->ops->check(dst, 0)) {
				dst_release(dst);
				sk->sk_rx_dst = NULL;
			}
		}
		//连接已建立时的处理路径
		tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len);
		return 0;
	}

	if (tcp_checksum_complete(skb))
		goto csum_err;

	//如果这个sock处于监听状态,被动打开时的处理,包括收到SYN或ACK
	if (sk->sk_state == TCP_LISTEN) {
		struct sock *nsk = tcp_v4_cookie_check(sk, skb);
		/*
		NULL,错误
		nsk == sk,没有找到新的TCB,所以收到的是第一次握手的SYN
		nsk != sk,找到了新的TCB,所以收到的是第三次握手的ACK
		*/
		if (!nsk)
			goto discard;
		if (nsk != sk) {
			sock_rps_save_rxhash(nsk, skb);
			sk_mark_napi_id(nsk, skb);
			if (tcp_child_process(sk, nsk, skb)) { //处理新的sock ,初始化子传输控制块
				rsk = nsk;
				goto reset; //失败时,给客户端发送 RST 段进行复位
			}
			return 0;
		}
	} else
		sock_rps_save_rxhash(sk, skb);
	
	//处理除了ESTABLISHED和TIME_WAIT之外的所有状态,处于TCP_LISTEN状态,也是有该函数处理
	if (tcp_rcv_state_process(sk, skb)) {
		rsk = sk;
		goto reset;
	}
	return 0;

reset:
	tcp_v4_send_reset(rsk, skb);//发送被动的RST包
discard:
	kfree_skb(skb);
	return 0;

csum_err:
	TCP_INC_STATS(sock_net(sk), TCP_MIB_CSUMERRORS);
	TCP_INC_STATS(sock_net(sk), TCP_MIB_INERRS);
	goto discard;
}

tcp_rcv_state_process函数在tcp_input.c中,它实现了TCP 状态机相对核心的一个部分。该函数可以处理除 ESTABLISHED 和 TIME_WAIT 状态以外的情况下的接收过程

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);
	const struct tcphdr *th = tcp_hdr(skb);
	struct request_sock *req;
	int queued = 0;
	bool acceptable;

	switch (sk->sk_state) {
	case TCP_CLOSE://CLOSE 状态的处理代码
		goto discard;

	//当前TCP套接字所在状态是LISTEN,说明这个套接字是一个服务器(server),它在等待一个连接请求
	case TCP_LISTEN://LISTEN 状态的处理代码
		//发送连接复位,因为客户端发送的包里只有syn为1,ack不可能为1,如果为1,则发生错误,需要服务端复位
		if (th->ack)
			return 1;

		//RST:连接由客户端复位,扔掉数据包
		if (th->rst)
			goto discard;

		//客户端来发送的连接请求,调用icsk_af_ops->conn_request函数完成连接请求处理。
		//将TCP连接状态切换至SYN_RECV。icsk_af_ops->conn_request函数指针在TCP协议实例中初始化为tcp_v4_conn_request
		if (th->syn) {
			if (th->fin)
				goto discard;
			rcu_read_lock();
			local_bh_disable();
			acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;//完成连接请求处理
			local_bh_enable();
			rcu_read_unlock();

			if (!acceptable)
				return 1;
			consume_skb(skb);
			return 0;
		}
		goto discard;

	//如果当前套接字状态是SYN_SENT,说明套接字为客户端,它发送了一个SYN数据包请求连接,并将自己设置为SYN_SENT状态
	case TCP_SYN_SENT:
		tp->rx_opt.saw_tstamp = 0;
		//这时我们必须查看输入数据段中的ACK或SYN标志,以确定是否将状态转换到ESTABLISHED
		queued = tcp_rcv_synsent_state_process(sk, skb, th);
		if (queued >= 0)
			return queued;

		/* Do step6 onward by hand. */
		tcp_urg(sk, skb, th);
		__kfree_skb(skb);
		tcp_data_snd_check(sk);
		return 0;
	}

	tp->rx_opt.saw_tstamp = 0;
	req = tp->fastopen_rsk;
	if (req) {
		WARN_ON_ONCE(sk->sk_state != TCP_SYN_RECV &&
		    sk->sk_state != TCP_FIN_WAIT1);

		if (!tcp_check_req(sk, skb, req, true))
			goto discard;
	}

	if (!th->ack && !th->rst && !th->syn)
		goto discard;

	//数据包有效性检查
	if (!tcp_validate_incoming(sk, skb, th, 0))
		return 0;

	//对收到的 ACK 段进行处理判断是否正确接收,如果正确接收就会发送返回非零值
	acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH |
				      FLAG_UPDATE_TS_RECENT |
				      FLAG_NO_CHALLENGE_ACK) > 0;
	//如果回答是可接收的数据包,则将TCP连接状态转换到ESTABLISHED状态
	if (!acceptable) {
		//连接处于SYN_RECV
		if (sk->sk_state == TCP_SYN_RECV)
			return 1;	/* send one RST */
		tcp_send_challenge_ack(sk, skb);
		goto discard;
	}
	switch (sk->sk_state) {
	case TCP_SYN_RECV://服务端发送SYN+ACK第二次握手,等待客户端回复ACK第三次握手
		if (!tp->srtt_us)
			tcp_synack_rtt_meas(sk, req);
		if (req) {
			inet_csk(sk)->icsk_retransmits = 0;
			reqsk_fastopen_remove(sk, req, false);
		} else {
			/* Make sure socket is routed, for correct metrics. */
			icsk->icsk_af_ops->rebuild_header(sk);
			tcp_init_congestion_control(sk);

			tcp_mtup_init(sk);
			tp->copied_seq = tp->rcv_nxt;
			tcp_init_buffer_space(sk);
		}
		smp_mb();
		//进行一系列的初始化,开启相应拥塞控制等,并且将 TCP 的状态置为 TCP_ESTABLISHED
		tcp_set_state(sk, TCP_ESTABLISHED);
		sk->sk_state_change(sk);

		//发信号给那些将通过该套接口发送数据的进程,通知它们套接口目前已经可以发送数据了
		if (sk->sk_socket)
			sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
			
		//设置传输控制块tp的发送未确认序列号snd_una为ACK序列号
		//snd_una表示发送端已经发送,但是还没有收到ack的数据的第一个字节
		tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
		//设置发送窗口大小snd_wnd
		tp->snd_wnd = ntohs(th->window) << tp->rx_opt.snd_wscale;
		//初始化发送窗口的左边界
		tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
		//如果启用了时间戳选项,则调整最大段大小advmss
		if (tp->rx_opt.tstamp_ok)
			tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;

		if (req) {
			tcp_rearm_rto(sk);
		} else
			tcp_init_metrics(sk);

		if (!inet_csk(sk)->icsk_ca_ops->cong_control)
			tcp_update_pacing_rate(sk);
		
		//更新最近一次的发送数据报的时间
		tp->lsndtime = tcp_time_stamp;

		//对端有效发送MSS估值的初始化
		tcp_initialize_rcv_mss(sk);
		tcp_fast_path_on(tp);
		break;

	case TCP_FIN_WAIT1: {//发送FIN+ACK第一次挥手后,等待对方回复ACK第二次挥手
		struct dst_entry *dst;
		int tmo;

		if (req) {
			/* We no longer need the request sock. */
			reqsk_fastopen_remove(sk, req, false);
			tcp_rearm_rto(sk);
		}
		if (tp->snd_una != tp->write_seq)
			break;
		//由FIN_WAIT_1切换到FIN_WAIT_2
		tcp_set_state(sk, TCP_FIN_WAIT2);
		sk->sk_shutdown |= SEND_SHUTDOWN;

		dst = __sk_dst_get(sk);
		if (dst)
			dst_confirm(dst);

		if (!sock_flag(sk, SOCK_DEAD)) {
			sk->sk_state_change(sk);
			break;
		}

		if (tp->linger2 < 0 ||
		    (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
		     after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))) {
			tcp_done(sk);
			NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
			return 1;
		}

		tmo = tcp_fin_time(sk);
		if (tmo > TCP_TIMEWAIT_LEN) {
			inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);
		} else if (th->fin || sock_owned_by_user(sk)) {
			inet_csk_reset_keepalive_timer(sk, tmo);
		} else {
			tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
			goto discard;
		}
		break;
	}

	case TCP_CLOSING:
		if (tp->snd_una == tp->write_seq) {
			//在TCP_CLOSING状态下,收到了ACK后套接字直接进入到TIME_WAIT状态,说明当前已经没有要发生的数据了
			tcp_time_wait(sk, TCP_TIME_WAIT, 0);
			goto discard;
		}
		break;

	case TCP_LAST_ACK:
		// 如果套接字被迫关闭,则响应应用程序的close调用。
        // 在这个状态上接收到ACK意味着可以关闭套接字,所以调用tcp_done函数。
		if (tp->snd_una == tp->write_seq) {
			tcp_update_metrics(sk);
			tcp_done(sk);
			goto discard;
		}
		break;
	}

	//紧急数据处理
	tcp_urg(sk, skb, th);
	//理段中的数据内容
	switch (sk->sk_state) {
	case TCP_CLOSE_WAIT:
	case TCP_CLOSING:
	case TCP_LAST_ACK:
		if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
			break;
	case TCP_FIN_WAIT1:
	case TCP_FIN_WAIT2:
		if (sk->sk_shutdown & RCV_SHUTDOWN) {
			if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
			    after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) {
				NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
				tcp_reset(sk);
				return 1;
			}
		}
	//套接字为ESTABLISHED状态时收到常规数据段的处理,它调用tcp_data_queue函数把数据段放入套接字的输入缓冲队列
	case TCP_ESTABLISHED://在建立连接阶段一般不会收到 TCP 段
		tcp_data_queue(sk, skb);
		queued = 1;
		break;
	}

	//此时状态不为 CLOSE,故而就回去检测是否数据和 ACK 要发送。
	//其次,根据 queue 标志来确定是否释放接收到的 TCP 段,如果接收到的 TCP 段已添加到接收队列中,则不释放
	if (sk->sk_state != TCP_CLOSE) {
		tcp_data_snd_check(sk);
		tcp_ack_snd_check(sk);
	}

	if (!queued) {
discard:
		tcp_drop(sk, skb);
	}
	return 0;
}

如果客户端发送的第一次握手携带数据,那么服务端将返回一个rst包
如果客户端发送的第一次握手是rst,那么服务端将直接丢弃

不管是服务端还是客户端,在创建socket的时候都会调用到sk_alloc(),在这个函数内核会对sock中的icsk_af_ops赋值,sk->icsk_af_ops=&ipv4_specific,ipv4_specific则在tcp_ipv4.c里

在这里插入图片描述

icsk->icsk_af_ops->conn_request(sk, skb)其实就是调用的tcp_v4_conn_request

tcp_v4_conn_request源码

int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
	/* Never answer to SYNs send to broadcast or multicast */
	if (skb_rtable(skb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
		goto drop;

	return tcp_conn_request(&tcp_request_sock_ops,
				&tcp_request_sock_ipv4_ops, sk, skb);

drop:
	tcp_listendrop(sk);
	return 0;
}

tcp_conn_request源码

int tcp_conn_request(struct request_sock_ops *rsk_ops,
		     const struct tcp_request_sock_ops *af_ops,
		     struct sock *sk, struct sk_buff *skb)
{
	struct tcp_fastopen_cookie foc = { .len = -1 };
	__u32 isn = TCP_SKB_CB(skb)->tcp_tw_isn;
	struct tcp_options_received tmp_opt;
	struct tcp_sock *tp = tcp_sk(sk);
	struct net *net = sock_net(sk);
	struct sock *fastopen_sk = NULL;
	struct dst_entry *dst = NULL;
	struct request_sock *req;
	bool want_cookie = false;
	struct flowi fl;

	//检测到可能的SYN洪水攻击时,通过生成SYN Cookie来减轻攻击的影响
	if ((net->ipv4.sysctl_tcp_syncookies == 2 ||
	     inet_csk_reqsk_queue_is_full(sk)) && !isn) {
		want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name);
		if (!want_cookie)
			goto drop;
	}


	//如果accept接收队列已满,并且SYN请求队列中至少有一个请求还没有重传过SYWN+ACK包,则丢弃该新的SYN请求
	//个人理解这样设计的考虑是:因为SYN请求队列中有这种"年轻的SYN请求”,而且当前accept队列已满,
	//那么这种年轻的SYN请求很可能很快就会完成三次握手,进而需要添加到accept队列中,
	//所以此时如果接受该新的SYN请求,那么很可能会导致由于无法加入到accept队列而导致已经完成三次握手的TCP连接失败
	if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
		NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
		goto drop;
	}

	//分配struct tcp_reqeust_sock对象(表示半链接),并将tcp_request_sock_ops赋值给其rsk_ops
	//后续连接建立过程中会调用该结构指定的函数,
	req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie);
	if (!req)
		goto drop;

	tcp_rsk(req)->af_specific = af_ops;

	//解析SYN包携带的TCP选项
	tcp_clear_options(&tmp_opt);
	tmp_opt.mss_clamp = af_ops->mss_clamp;
	tmp_opt.user_mss  = tp->rx_opt.user_mss;
	tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc);

	//SYN cookie相关
	if (want_cookie && !tmp_opt.saw_tstamp)
		tcp_clear_options(&tmp_opt);

	//时间戳选项处理
	tmp_opt.tstamp_ok = tmp_opt.saw_tstamp;
	//根据SYN请求段中的字段和选项来初始化连接请求块
	tcp_openreq_init(req, &tmp_opt, skb, sk);
	inet_rsk(req)->no_srccheck = inet_sk(sk)->transparent;

	inet_rsk(req)->ir_iif = inet_request_bound_dev_if(sk, skb);

	af_ops->init_req(req, sk, skb);

	if (security_inet_conn_request(sk, skb, req))
		goto drop_and_free;

	//根据不同情况生成服务器端的初始发送序号
	if (!want_cookie && !isn) {
		if (tcp_death_row.sysctl_tw_recycle) {
			bool strict;

			dst = af_ops->route_req(sk, &fl, req, &strict);

			if (dst && strict &&
			    !tcp_peer_is_proven(req, dst, true,
						tmp_opt.saw_tstamp)) {
				NET_INC_STATS(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
				goto drop_and_release;
			}
		}
		else if (!net->ipv4.sysctl_tcp_syncookies &&
			 (sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
			  (sysctl_max_syn_backlog >> 2)) &&
			 !tcp_peer_is_proven(req, dst, false,
					     tmp_opt.saw_tstamp)) {

			pr_drop_req(req, ntohs(tcp_hdr(skb)->source),
				    rsk_ops->family);
			goto drop_and_release;
		}

		isn = af_ops->init_seq(skb);
	}
	if (!dst) {
		dst = af_ops->route_req(sk, &fl, req, NULL);
		if (!dst)
			goto drop_and_free;
	}

	tcp_ecn_create_request(req, skb, sk, dst);

	if (want_cookie) {
		isn = cookie_init_sequence(af_ops, sk, skb, &req->mss);
		req->cookie_ts = tmp_opt.tstamp_ok;
		if (!tmp_opt.tstamp_ok)
			inet_rsk(req)->ecn_ok = 0;
	}

	//将确定的初始序列号记录到TCP控制块中
	tcp_rsk(req)->snt_isn = isn;
	tcp_rsk(req)->txhash = net_tx_rndhash();
	tcp_openreq_init_rwin(req, sk, dst);
	if (!want_cookie) {
		tcp_reqsk_record_syn(sk, req, skb);
		fastopen_sk = tcp_try_fastopen(sk, skb, req, &foc, dst);
	}
	//如果开启了fastopen,直接加入到全链接队列里(accept队列)
	if (fastopen_sk) {
		af_ops->send_synack(fastopen_sk, dst, &fl, req,
				    &foc, TCP_SYNACK_FASTOPEN);
		/* Add the child socket directly into the accept queue */
		inet_csk_reqsk_queue_add(sk, req, fastopen_sk);
		sk->sk_data_ready(sk);
		bh_unlock_sock(fastopen_sk);
		sock_put(fastopen_sk);
	} else {
		tcp_rsk(req)->tfo_listener = false;
		if (!want_cookie)
			//把新创建的半连接加入到半链接队列里面,并启动SYN+ACK超时重传定时器
			inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
		//会调用到tcp_ipv4.c中的tcp_v4_send_synack函数,从而构建synack包,并且发送回去
		af_ops->send_synack(sk, dst, &fl, req, &foc,
				    !want_cookie ? TCP_SYNACK_NORMAL :
						   TCP_SYNACK_COOKIE);
		if (want_cookie) {
			reqsk_free(req);
			return 0;
		}
	}
	reqsk_put(req);
	return 0;

drop_and_release:
	dst_release(dst);
drop_and_free:
	reqsk_free(req);
drop:
	tcp_listendrop(sk);
	return 0;
}

inet_csk_reqsk_queue_hash_add函数

void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
				   unsigned long timeout)
{
	reqsk_queue_hash_req(req, timeout); //把半连接插入到ehash中
	inet_csk_reqsk_queue_added(sk); //
}


static void reqsk_queue_hash_req(struct request_sock *req,
				 unsigned long timeout)
{
	req->num_retrans = 0;
	req->num_timeout = 0;
	req->sk = NULL;

	setup_pinned_timer(&req->rsk_timer, reqsk_timer_handler,
			    (unsigned long)req);
	mod_timer(&req->rsk_timer, jiffies + timeout);
	
	//将半连接所对应的sock插入到全局的ehash中
	inet_ehash_insert(req_to_sk(req), NULL);

	smp_wmb();
	atomic_set(&req->rsk_refcnt, 2 + 1);
}

static inline void inet_csk_reqsk_queue_added(struct sock *sk)
{
	reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue);
}

static inline void reqsk_queue_added(struct request_sock_queue *queue)
{
	atomic_inc(&queue->young);//SYN请求队列中至少有一个请求还没有重传过SYN+ACK包的半连接个数加1
	atomic_inc(&queue->qlen);//半连接队列的长度加1
}

//用来表示监听套接字下的连接
struct request_sock_queue {
	spinlock_t		rskq_lock;
	u8			rskq_defer_accept;

	u32			synflood_warned;
	atomic_t		qlen;  //半链接队列sk个数
	atomic_t		young; //还没有重传过syn+ack半连接的个数

	struct request_sock	*rskq_accept_head;//全链接的头
	struct request_sock	*rskq_accept_tail;//全链接的尾
	struct fastopen_queue	fastopenq;  
};

客户端收到syn+ack封包的处理过程

客户端收到syn+ack封包时的处理流程和服务端收到syn封包处理的流程大致一样,数据经过网卡,再到ip层,最终会调用到tcp_v4_rcv函数做处理,再调用tcp_v4_do_rcv函数,最后再调用tcp_rcv_state_process(处理tcp的状态机)

因为前面已经谈过tcp_rcv_state_process,这里将省略部分源码

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
	...
	//如果当前套接字状态是SYN_SENT,说明套接字为客户端,它发送了一个SYN数据包请求连接,并将自己设置为SYN_SENT状态
	case TCP_SYN_SENT:
		tp->rx_opt.saw_tstamp = 0;
		//这时我们必须查看输入数据段中的ACK或SYN标志,以确定是否将状态转换到ESTABLISHED
		queued = tcp_rcv_synsent_state_process(sk, skb, th);
		//如果queued>=0,就会返回并发送RST
		if (queued >= 0)
			return queued;

		/* Do step6 onward by hand. */
		tcp_urg(sk, skb, th);
		__kfree_skb(skb);
		tcp_data_snd_check(sk);
		return 0;
	}
	...
}

调用tcp_rcv_synsent_state_process函数做进一步处理

static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
					 const struct tcphdr *th)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	struct tcp_fastopen_cookie foc = { .len = -1 };
	int saved_clamp = tp->rx_opt.mss_clamp;
	bool fastopen_fail;

	//全面解析skb携带的TCP选项 
	tcp_parse_options(skb, &tp->rx_opt, 0, &foc);
	if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr)
		tp->rx_opt.rcv_tsecr -= tp->tsoffset;

	//如果携带ACK标志,那么有可能是SYNACK
	if (th->ack) {
		//检查ack_seq:snd_una < ack_seq <= snd_nxt。
        //如果SYN段没有携带数据,那么此时ack_seq应该为本端的ISN + 1
		if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) ||
		    after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt))
			goto reset_and_undo;

		//如果使用了时间戳选项,那么回显的时间戳,必须落在
		//第一次发送SYN段的时间和当前时间之间。
		if (tp->rx_opt.saw_tstamp && tp->rx_opt.rcv_tsecr &&
		    !between(tp->rx_opt.rcv_tsecr, tp->retrans_stamp,
			     tcp_time_stamp)) {
			NET_INC_STATS(sock_net(sk),
					LINUX_MIB_PAWSACTIVEREJECTED);
			goto reset_and_undo;
		}
		
		//如果既没有RST也没有SYN标志位,那么直接丢弃这个ACK
		if (th->rst) {
			tcp_reset(sk);
			goto discard;
		}

		if (!th->syn)
			goto discard_and_undo;

		//收到一个合法的SYNACK了,接下来要完成连接的建立了
		tcp_ecn_rcv_synack(tp, th);
		//记录最近更新发送窗口的ACK序号 
		tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
		//更新发送窗口,删除发送队列中已被确认的SYN段,并进行时延采样
		tcp_ack(sk, skb, FLAG_SLOWPATH);

		tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;//更新接收窗口的要接收的下一个序号
		tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;//更新接收窗口的左端

		//更新发送窗口,其实就是对端接收窗口的大小。在三次握手时,不使用窗口扩大因子
		tp->snd_wnd = ntohs(th->window);	

		//如果连接不支持窗口扩大因子选项
		if (!tp->rx_opt.wscale_ok) {
			tp->rx_opt.snd_wscale = tp->rx_opt.rcv_wscale = 0;
			tp->window_clamp = min(tp->window_clamp, 65535U);
		}

		//如果连接支持时间戳选项 
		if (tp->rx_opt.saw_tstamp) {
			tp->rx_opt.tstamp_ok	   = 1;
			tp->tcp_header_len =
				sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
			tp->advmss	    -= TCPOLEN_TSTAMP_ALIGNED;
			tcp_store_ts_recent(tp);//记录对端的时间戳,作为下次发送的回显值
		} else {
			tp->tcp_header_len = sizeof(struct tcphdr);
		}

		//使用SACK时,才能考虑是否使用FACK
		if (tcp_is_sack(tp) && sysctl_tcp_fack)
			tcp_enable_fack(tp);

		tcp_mtup_init(sk);//TCP的MTU初始化
		tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);//更新MSS
		tcp_initialize_rcv_mss(sk);//对端有效发送MSS估值的初始化

		tp->copied_seq = tp->rcv_nxt;//更新未读数据的左端

		smp_mb();

		
        /* 走到这里,连接算是成功建立了,接下来:
         * 把连接的状态设置为TCP_ESTABLISHED。
         * 唤醒调用connect()的进程。
         */
		tcp_finish_connect(sk, skb);

		//Fast Open选项处理 
		fastopen_fail = (tp->syn_fastopen || tp->syn_data) &&
				tcp_rcv_fastopen_synack(sk, skb, &foc);

		if (!sock_flag(sk, SOCK_DEAD)) {
			//指向sock_def_wakeup,唤醒调用connect()的进程
			sk->sk_state_change(sk);
			//如果使用了异步通知,则发送SIGIO通知进程可写
			sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
		}
		if (fastopen_fail)
			return -1;

		
        /* 符合以下任一条件,则使用延迟确认,不会马上发送ACK:
         * 目前有数据等待发送。
         * 使用TCP_DEFER_ACCEPT选项。
         * 延迟确认标志为1。
         */
		if (sk->sk_write_pending ||
		    icsk->icsk_accept_queue.rskq_defer_accept ||
		    icsk->icsk_ack.pingpong) {
			inet_csk_schedule_ack(sk);//设置ICSK_ACK_SCHED标志位,表示有ACK需要发送
			tcp_enter_quickack_mode(sk, TCP_MAX_QUICKACKS);//进入快速确认模式,之后会进行快速确认
			//激活延迟确认定时器,超时时间为200ms,也就是说最多延迟200ms
			inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
						  TCP_DELACK_MAX, TCP_RTO_MAX);

discard:
			tcp_drop(sk, skb);
			return 0;
		} else {
			tcp_send_ack(sk);//立即发送一个ACK,即三次握手的最后一个ACK
		}
		return -1;
	}

	//如果收到的段没有ACK标志,却设置了RST标志,那么直接丢掉
	if (th->rst) {

		goto discard_and_undo;
	}

	//PAWS check. 检查时间戳是否合法
	if (tp->rx_opt.ts_recent_stamp && tp->rx_opt.saw_tstamp &&
	    tcp_paws_reject(&tp->rx_opt, 0))
		goto discard_and_undo;

	//收到了SYN段,即同时打开
	if (th->syn) {
        /* 发送SYN后,状态为SYN_SENT,如果此时也收到SYN,
        * 状态则变为SYN_RECV。
        */
		tcp_set_state(sk, TCP_SYN_RECV);

		if (tp->rx_opt.saw_tstamp) {
			tp->rx_opt.tstamp_ok = 1;
			tcp_store_ts_recent(tp);//记录对端的时间戳,作为下次发送的回显值
			tp->tcp_header_len =
				sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
		} else {
			tp->tcp_header_len = sizeof(struct tcphdr);
		}

		tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;/* 更新接收窗口的要接收的下一个序号 */
		tp->copied_seq = tp->rcv_nxt;
		tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;/* 更新接收窗口的左端 */

		//更新对端接收窗口的大小。在三次握手时,不使用窗口扩大因子
		tp->snd_wnd    = ntohs(th->window);
		tp->snd_wl1    = TCP_SKB_CB(skb)->seq;
		tp->max_window = tp->snd_wnd;

		
        /* 如果对端支持ECN,SYN会同时设置ECE和CWR标志。
        * 否则,连接就不支持ECN显式拥塞通知了。
        */
		tcp_ecn_rcv_syn(tp, th);

		tcp_mtup_init(sk);/* TCP的MTU初始化 */
		tcp_sync_mss(sk, icsk->icsk_pmtu_cookie); /* 更新MSS */
		tcp_initialize_rcv_mss(sk);/* 对端有效发送MSS估值的初始化 */
		/* 构造和发送SYNACK */
		tcp_send_synack(sk);
#if 0
		return -1;
#else
		goto discard;
#endif
	}

discard_and_undo:
	tcp_clear_options(&tp->rx_opt);
	tp->rx_opt.mss_clamp = saved_clamp;
	goto discard;

reset_and_undo:
	tcp_clear_options(&tp->rx_opt);
	tp->rx_opt.mss_clamp = saved_clamp;
	return 1;
}

总的来说tcp_rcv_synsent_state_process()用于SYN_SENT状态的处理,具体分以下几种情况

①收到的是syn+ack(合法)
检查ack_seq是否合法,如果使用了时间戳选项,检查回显的时间戳是否合法,检查TCP的标志位是否合法。如果都合法,更新sock的各种信息。把连接的状态设置为TCP_ESTABLISHED,唤醒调用connect()的进程。判断是马上发送ACK,还是延迟发送。

②收到的是syn+ack(合法)
也就是ack_seq不是本机的seq+1,或者如果使用了时间戳选项,syn+ack的时间戳不在本机第一次发送syn的时间和当前时间之间,该函数会返回1,上层会发送一个RST包

收到的是syn
把连接状态置为SYN_RECV,更新sock的各种信息,构造和发送syn+ack包

③收到的是rst+ack
则向用户发送error,然后进行链接重置

④收到的是rst
直接丢弃

⑤收到的是ack
直接丢弃

收到的是syn+ack(合法),最终会调用void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)函数将连接状态置为TCP_ESTABLISHED

void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);

	//连接状态从SYN_SENT变为ESTABLISHED
	tcp_set_state(sk, TCP_ESTABLISHED);
	icsk->icsk_ack.lrcvtime = tcp_time_stamp;

	if (skb) {
		icsk->icsk_af_ops->sk_rx_dst_set(sk, skb);
		security_inet_conn_established(sk, skb);
	}

	/* Make sure socket is routed, for correct metrics.  */
	icsk->icsk_af_ops->rebuild_header(sk);

	//根据路由缓存,初始化TCP相关变量
	tcp_init_metrics(sk);

	//获取默认的TCP拥塞控制算法
	tcp_init_congestion_control(sk);

	//更新最近一次的发送数据报的时间
	tp->lsndtime = tcp_time_stamp;

	//调整发送缓存和接收缓存的大小
	tcp_init_buffer_space(sk);

	//如果使用了SO_KEEPALIVE选项,激活保活定时器
	if (sock_flag(sk, SOCK_KEEPOPEN))
		inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp));

	//如果对端的窗口扩大因子为0
	if (!tp->rx_opt.snd_wscale)
		__tcp_fast_path_on(tp, tp->snd_wnd);
	else
		tp->pred_flags = 0;
}

服务端收到第三次握手(ACK)如何处理

服务端在收到第三次握手之前,所处状态为TCP_SYN_RECV,还是会通过tcp_rcv_state_process函数处理

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{
	...
	case TCP_SYN_RECV://服务端发送SYN+ACK第二次握手,等待客户端回复ACK第三次握手
		if (!tp->srtt_us)
			tcp_synack_rtt_meas(sk, req);

		if (req) {
			inet_csk(sk)->icsk_retransmits = 0;
			reqsk_fastopen_remove(sk, req, false);
		} else {
			icsk->icsk_af_ops->rebuild_header(sk);
			tcp_init_congestion_control(sk);

			tcp_mtup_init(sk);
			tp->copied_seq = tp->rcv_nxt;
			tcp_init_buffer_space(sk);
		}
		smp_mb();
		//进行一系列的初始化,开启相应拥塞控制等,并且将 TCP 的状态置为 TCP_ESTABLISHED
		tcp_set_state(sk, TCP_ESTABLISHED);
		sk->sk_state_change(sk);

		//发信号给那些将通过该套接口发送数据的进程,通知它们套接口目前已经可以发送数据了
		if (sk->sk_socket)
			sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
			
		//设置传输控制块tp的发送未确认序列号snd_una为ACK序列号
		//snd_una表示发送端已经发送,但是还没有收到ack的数据的第一个字节
		tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
		//设置发送窗口大小snd_wnd
		tp->snd_wnd = ntohs(th->window) << tp->rx_opt.snd_wscale;
		//初始化发送窗口的左边界
		tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
		//如果启用了时间戳选项,则调整最大段大小advmss
		if (tp->rx_opt.tstamp_ok)
			tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;

		if (req) {
			tcp_rearm_rto(sk);
		} else
			tcp_init_metrics(sk);

		if (!inet_csk(sk)->icsk_ca_ops->cong_control)
			tcp_update_pacing_rate(sk);

		//更新最近一次的发送数据报的时间
		tp->lsndtime = tcp_time_stamp;

		//对端有效发送MSS估值的初始化
		tcp_initialize_rcv_mss(sk);
		tcp_fast_path_on(tp);
		break;
	...
}

5、整体流程图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/479464.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

echarts 折线图 数据点过密,显示重叠该如何解决

echarts 折线图 数据点过密&#xff0c;显示重叠该如何解决 有这样一个图表&#xff0c;显示的数据比较多&#xff0c; 当把 label 显示出来的时候&#xff0c;这些 label 重叠了&#xff0c;我想让它间隔一下。 结果是这样的&#xff1a; 只需要在 label.formatter 上处理 …

Linux课程____Samba文件共享服务

一、 Samba服务基础 SMB协议&#xff0c;服务消息块 CIFS协议&#xff0c;通用互联网文件系统 1.Samba 服务器的主要程序 smbd:提供对服务器中文件、打印资源的共享访问 nmbd:提供基于 NetBlOS 主机名称的解析 2.目录文件 /etc/samba/smb.conf 检查工具&#xff1a;test…

408学习笔记-17-C-C/C++中程序内存区域划分

C/C中程序内存区域划分 C/C程序内存分配的几个区域&#xff1a; 1、栈区(stack)&#xff1a;在执行函数时&#xff0c;函数内局部变量的存储单元都可以在栈上创建&#xff0c;函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中&#xff0c;效率很高…

前端学习笔记 | Node.js

一、Node.js入门 1、什么是Node.js 定义&#xff1a;是跨平台JS运行环境&#xff08;可以独立执行JS的环境&#xff09;作用&#xff1a; 编写数据接口&#xff0c;提供网页资源功能等等前端工程化&#xff1a;为后续学Vue和React等框架做铺垫 2、Node.js为何能执行JS&#xff…

【Java反序列化】CommonsCollections-CC1链分析

前言 好几天没发博文了&#xff0c;偷偷憋了个大的——CC1链分析&#xff0c;手撸了一遍代码。虽然说&#xff0c;这个链很老了&#xff0c;但还是花费了我一段时间去消化吸收&#xff0c;那么接下来&#xff0c;我会简洁的介绍下整个链的利用过程&#xff0c;还有哪些不理解的…

2核4G服务器阿里云性能测评和优惠价格表

阿里云2核4G服务器租用优惠价格&#xff0c;轻量2核4G服务器165元一年、u1服务器2核4G5M带宽199元一年、云服务器e实例30元3个月&#xff0c;活动链接 aliyunfuwuqi.com/go/aliyun 活动链接如下图&#xff1a; 阿里云2核4G服务器优惠价格 轻量应用服务器2核2G4M带宽、60GB高效…

HTTP --- 上

目录 1. HTTP协议 2. 认识URL 2.1. URL中的四个主要字段 2.2. URL Encode && URL Decode 3. HTTP 协议格式 3.1. 快速构建 HTTP 请求和响应的报文格式 3.1.1. HTTP 请求格式 3.1.2. HTTP 响应格式 3.1.3. 关于 HTTP 请求 && 响应的宏观理解 3.2. 实现…

SpringBoot+Vue项目(后端项目搭建 + 添加家居)

文章目录 1.使用版本控制管理该项目1.创建远程仓库2.克隆到本地 2.后端项目环境搭建1.创建一个maven项目2.删除不必要的文件夹3.pom.xml文件引入依赖4.application.yml 配置数据源&#xff08;注意&#xff0c;数据库名还没写&#xff09;5.com/sun/furn/Application.java 编写…

2024年起重机司机(限桥式起重机)证考试题库及起重机司机(限桥式起重机)试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年起重机司机(限桥式起重机)证考试题库及起重机司机(限桥式起重机)试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作…

【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器

【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器 文章目录 【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器一、环境说明二、安装配置步骤1.下载完成后&#xff0c;传至服务器或者本机并解压至C:/Program Files/目录下2.打开PowerShell终端3.进入到包含ssh可执行exe文件的文…

每日五道java面试题之springboot篇(一)

目录&#xff1a; 第一题. 什么是 Spring Boot&#xff1f;第二题. Spring Boot 有哪些优点&#xff1f;第三题. Spring Boot 的核心注解是哪个&#xff1f;它主要由哪几个注解组成的&#xff1f;第四题. 什么是 JavaConfig&#xff1f;第五题. Spring Boot 自动配置原理是什么…

全流程ArcGIS Pro技术应用

GIS是利用电子计算机及其外部设备&#xff0c;采集、存储、分析和描述整个或部分地球表面与空间信息系统。简单地讲&#xff0c;它是在一定的地域内&#xff0c;将地理空间信息和 一些与该地域地理信息相关的属性信息结合起来&#xff0c;达到对地理和属性信息的综合管理。GIS的…

小本创业项目哪个比较赚钱?户用光伏加盟如何

随着不可再生能源的不断消耗和环保问题的日益重视&#xff0c;世界各国对新能源的需求都迫在眉睫。而光伏发电在新能源又有独特的优势。而光伏既改变了人们的生活方式也推动了社会的进步和市场经济的发展。那想做小本游戏创业的话可以选择户用光伏加盟吗&#xff1f; 首先要确认…

【微服务】Feign远程调用

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;微服务 ⛺️稳中求进&#xff0c;晒太阳 先来看我们以前利用RestTemplate发起远程调用的代码&#xff1a; 存在下面的问题&#xff1a;代码可读性差&#xff0c;编程体验不统一参数复杂URL…

332. 重新安排行程(力扣LeetCode)

文章目录 332. 重新安排行程题目描述思路如何理解死循环该记录映射关系 dfs&#xff08;回溯法&#xff09;代码 332. 重新安排行程 题目描述 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排…

6.2 感知器

感知器的概念由 罗森布拉特弗兰克 在1957年提出&#xff0c;它是一种监督训练的二元分类器。 一、单层感知器 考虑一个只包含一个神经元的神经网络。 这个神经元有两个输入x1&#xff0c;x2&#xff0c;权值为w1&#xff0c;w2。其激活函数为符号函数&#xff1a; 根据感知器…

Git浅谈配置文件和免密登录

一、文章内容 简述git三种配置ssh免密登录以及遇见的问题git可忽略文件git remote 相关操作 二、Git三种配置 项目配置文件(局部)&#xff1a;项目路径/.git/config 文件 git config --local user.name name git config --local user.email 123qq.cc全局配置文(所有用户): …

C++位运算符(<<,>>,|,^,)

简介 位运算符作用于整数类型的运算对象&#xff0c;并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能&#xff0c;一种名为bitset的标准库类型也可以表示任意大小的二进制集合&#xff0c;所以位运算符同样可以用于bitset类型。 如果运算对象是“小…

使用echart绘制拓扑图,树类型,自定义tooltip和label样式,可收缩

效果如图&#xff1a; 鼠标移上显示 vue3 - ts文件 “echarts”: “^5.4.3”, import { EChartsOption } from echarts import * as echarts from echarts/core import { TooltipComponent } from echarts/components import { TreeChart } from echarts/charts import { C…

【LeetCode】--- 动态规划 集训(一)

目录 一、1137. 第 N 个泰波那契数1.1 题目解析1.2 状态转移方程1.3 解题代码 二、面试题 08.01. 三步问题2.1 题目解析2.2 状态转移方程2.3 解题代码 三、746. 使用最小花费爬楼梯3.1 题目解析3.2 状态转移方程3.3 解题代码 一、1137. 第 N 个泰波那契数 题目地址&#xff1a…