当前位置: 首页 > news >正文

公司建立网站的作用有教育网站集约化建设

公司建立网站的作用有,教育网站集约化建设,微网站制作工具,seo职位文章目录 1. 前言2. 背景3. TCP连接的建立和断开3.1 TCP协议状态机3.2 TCP的三握四挥3.2.1 TCP 连接建立的三次握手过程分析3.2.1.1 服务端和客户端套接字的创建3.2.1.2 服务端进入 LISTEN 状态3.2.1.3 服务端在 LISTEN 状态等待客户端的 SYN 请求3.2.1.4 客户端向服务端发送 S… 文章目录 1. 前言2. 背景3. TCP连接的建立和断开3.1 TCP协议状态机3.2 TCP的三握四挥3.2.1 TCP 连接建立的三次握手过程分析3.2.1.1 服务端和客户端套接字的创建3.2.1.2 服务端进入 LISTEN 状态3.2.1.3 服务端在 LISTEN 状态等待客户端的 SYN 请求3.2.1.4 客户端向服务端发送 SYN 请求建立连接3.2.1.5 TCP 连接建立过程小结 3.2.2 TCP 连接断开的四次挥手过程分析3.2.2.1 客户端通过 close() 向服务端发送 FIN进入 FIN-WAIT-1 状态3.2.2.2 服务端收取客户端 FIN回以 ACK进入 CLOSE-WAIT 状态3.2.2.3 客户端收取服务端对 FIN 的回应 ACK进入 FIN-WAIT-2 状态3.2.2.4 服务端通过 close() 向客户端发 FIN进入 LAST-ACK 状态3.2.2.5 客户端收取服务端 FIN回以 ACK进入 TIME-WAIT超时后进入 CLOSED 终态3.2.2.6 服务端收取客户端对 FIN 的回应 ACK进入 CLOSED 终态 3.2.3 三握四挥小结 4. 抓包三握四挥过程示例5. 参考资料 1. 前言 限于作者能力水平本文可能存在谬误因此而给读者带来的损失作者不做任何承诺。 2. 背景 本文基于 linux-4.14.132 内核代码进行分析。 3. TCP连接的建立和断开 3.1 TCP协议状态机 3.2 TCP的三握四挥 在后面的分析中我们将始终参考 3.13.2 两小节中的状态图。 3.2.1 TCP 连接建立的三次握手过程分析 3.2.1.1 服务端和客户端套接字的创建 通过 socket() 系统调用创建套接字后套接字初始状态为 CLOSED 状态(即 TCP_CLOSE)。来看代码实现细节 /* 应用层 通过系统调用 sys_socket() 创建套接字 */ server_fd socket(AF_INET, SOCK_STREAM, 0); // 服务端 remote_fd socket(AF_INET, SOCK_STREAM, 0); // 客户端/* 内核空间初始创建时套接字为 CLOSED 状态(即 TCP_CLOSE) */ sys_socket(AF_INET, SOCK_STREAM, 0) // net/socket.csock_create(family, type, protocol, sock)__sock_create(current-nsproxy-net_ns, family, type, protocol, res, 0);sock sock_alloc();sock-type type; // sock-type SOCK_STREAM;...pf rcu_dereference(net_families[family]); // 获取协议簇接口...// 进入协议簇(family)的套接字创建过程pf-create(net, sock, protocol, kern) inet_create() // net/ipv4/af_inet.cstruct sock *sk;sock-state SS_UNCONNECTED; /* socket 初始为[未连接状态 (SS_UNCONNECTED)] */...list_for_each_entry_rcu(answer, inetsw[sock-type], list) {if (protocol answer-protocol) { /* 显式指定了 protocol */...} else { /* 非显式指定 protocol *//* Check for the two wild cases. */if (IPPROTO_IP protocol) { /* protocol 0 意味着创建各 type 下缺省协议的套接字 */protocol answer-protocol;break;}...}...}.../* 设定套接字对应协议接口 */sock-ops answer-ops; /* 设定套接字对应协议接口: inet_stream_ops */answer_prot answer-prot; /* tcp_prot */.../* 创建套接字[网络层管理数据]对象 */sk sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);struct sock *sk;sk sk_prot_alloc(prot, priority | __GFP_ZERO, family);if (sk) {sk-sk_family family; // sk-sk_family PF_INET;sk-sk_prot sk-sk_prot_creator prot; /* tcp_prot */...}return sk;.../* 初始化套接字[网络层管理数据]: 如 type, 状态等 (TCP_CLOSE) */sock_init_data(sock, sk);...//sk-sk_rcvbuf sysctl_rmem_default;//sk-sk_sndbuf sysctl_wmem_default;//sk-sk_state TCP_CLOSE; /* 设定套接字初始状态为 CLOSE */sk_set_socket(sk, sock); /* 绑定网络层管理数据到 socket */sk_tx_queue_clear(sk);sk-sk_socket sock;...if (sock) {sk-sk_type sock-type; // sk-sk_type SOCK_STREAM;sk-sk_wq sock-wq;sock-sk sk; /* 设定套接字的网络层管理数据对象 */...}...sk-sk_state_change sock_def_wakeup;sk-sk_data_ready sock_def_readable;//sk-sk_write_space sock_def_write_space;...sk-sk_destruct inet_sock_destruct;sk-sk_protocol protocol; // IPPROTO_TCPsk-sk_backlog_rcv sk-sk_prot-backlog_rcv;.../** 前面完成的是 IPv4 协议簇套接字公共初始化。* 这里是 IPv4 协议簇下的子类型 type 和 子协议 protocol 套接字的特定初始化。* 套接字的初始化是一个个层层递进的过程有点类似于 C 子类对象的构建过程:* 先调用父类的构造函数然后在逐级调用子类的构造函数。** 这里是 TCP 类型(IPPROTO_TCP) 套接字 初始化。*/if (sk-sk_prot-init) {err sk-sk_prot-init(sk); /* tcp_v4_init_sock() */...}.../* TCP 类型套接字初始化 */ tcp_v4_init_sock() // net/ipv4/tcp_ipv4.cstruct inet_connection_sock *icsk inet_csk(sk);/* TCP 套接字初始化 */tcp_init_sock(sk);...sk-sk_state TCP_CLOSE; /* TCP 套接字创建时初始状态为 TCP_CLOSE */sk-sk_write_space sk_stream_write_space;...sk-sk_sndbuf sysctl_tcp_wmem[1];sk-sk_rcvbuf sysctl_tcp_rmem[1];.../* 设定 IPv4 TCP 套接字操作接口 */icsk-icsk_af_ops ipv4_specific;3.2.1.2 服务端进入 LISTEN 状态 服务端调用 listen() 后服务端套接字 由 CLOSED 状态进入 LISTEN CLOSED LISTEN 。 // 用户空间struct sockaddr_in server_addr; int backlog 8;memset(server_addr, 0, sizeof(struct sockaddr_in)); server_addr.sin_family AF_INET; server_addr.sin_port htons(8888); // 服务端 端口号 server_addr.sin_addr.s_addr inet_addr(192.168.1.123); // 服务端 IP 地址 bind(server_fd, (struct sockaddr *)server_addr, sizeof(server_addr));listen(server_fd, backlog); // 服务端: CLOSED LISTEN// 内核空间sys_listen(fd, backlog) // net/socket.cstruct socket *sock;sock sockfd_lookup_light(fd, err, fput_needed);if (sock) {...err sock-ops-listen(sock, backlog); /* inet_listen() */...}inet_listen() // net/ipv4/af_inet.cstruct sock *sk sock-sk;unsigned char old_state;err -EINVAL;// 处于未连接状态的、 SOCK_STREAM 类型套接字 才能监听if (sock-state ! SS_UNCONNECTED || sock-type ! SOCK_STREAM)goto out;old_state sk-sk_state;/* 只有对 TCP_CLOSE 或 TCP_LISTEN 态套接字 listen 才是合法的 */if (!((1 old_state) (TCPF_CLOSE | TCPF_LISTEN)))goto out;if (old_state ! TCP_LISTEN) {.../** . accept 队列初始化* . backlog 初始化* . 套接字由 TCP_CLOSE 转为 TCP_LISTEN 态 (CLOSED LISTEN)*/err inet_csk_listen_start(sk, backlog);if (err)goto out;}sk-sk_max_ack_backlog backlog;err 0;out:return err;inet_csk_listen_start() // net/ipv4/inet_connection_sock.cstruct inet_connection_sock *icsk inet_csk(sk);reqsk_queue_alloc(icsk-icsk_accept_queue); // 创建和初始化 accept 队列sk-sk_max_ack_backlog backlog;sk-sk_ack_backlog 0;inet_csk_delack_init(sk);sk_state_store(sk, TCP_LISTEN); // 由 TCP_CLOSE 转为 TCP_LISTEN 态 (CLOSED LISTEN)smp_store_release(sk-sk_state, newstate);...3.2.1.3 服务端在 LISTEN 状态等待客户端的 SYN 请求 // 服务端的套接字 server_fd 在 LISTEN 状态等待客户端的 SYN 连接请求 client_fd accept(server_fd, NULL, NULL);sys_accept(server_fd, NULL, NULL) // net/socket.csys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);struct socket *sock, *newsock;sock sockfd_lookup_light(fd, err, fput_needed);/* 为可能连接的客户端准备一个 sock 对象 */newsock sock_alloc();...newsock-type sock-type; // SOCK_STREAMnewsock-ops sock-ops; // inet_stream_ops.../** 为客户端 sock 分配一个 fd 。* * 注意* 这和用来监听的套接字 server_fd 不是同一个这个是用来管理新连接的* 客户端套接字也即前面代码中 accept() 返回的 client_fd 。*/newfd get_unused_fd_flags(flags);/* 为客户端 sock 分配一个文件对象 (struct file) */newfile sock_alloc_file(newsock, flags, sock-sk-sk_prot_creator-name);...// 等待客户端的 SYN 请求err sock-ops-accept(sock, newsock, sock-file-f_flags, false); /* inet_accept() *//* 连接客户端的 fd 放入进程的 fd 表 */fd_install(newfd, newfile);err newfd; /* 返回客户端句柄到用户空间 */...out:return err;inet_accept() // net/ipv4/af_inet.cstruct sock *sk2 sk1-sk_prot-accept(sk1, flags, err, kern); /* inet_csk_accept() */inet_csk_accept() // net/ipv4/inet_connection_sock.cstruct inet_connection_sock *icsk inet_csk(sk);struct request_sock_queue *queue icsk-icsk_accept_queue;struct request_sock *req;struct sock *newsk;/* Find already established connection */if (reqsk_queue_empty(queue)) {long timeo sock_rcvtimeo(sk, flags O_NONBLOCK); // 这里只讨论阻塞方式不关心非阻塞方式的逻辑/* If this is a non blocking socket dont sleep */error -EAGAIN;if (!timeo)goto out_err;// 等待连接请求 SYN 数据包error inet_csk_wait_for_connect(sk, timeo);if (error)goto out_err;}req reqsk_queue_remove(queue, sk); // 从 accpet 队列(全连接队列)中取出/移除一个建立好的连接newsk req-sk; // 返回新建立的客户端连接......newsock-state SS_CONNECTED; // 等待到客户端连接后套接字标记为已连接状态 SS_CONNECTED// 等待连接请求 SYN 数据包 inet_csk_wait_for_connect() // net/ipv4/inet_connection_sock.cstruct inet_connection_sock *icsk inet_csk(sk);DEFINE_WAIT(wait);int err;for (;;) {prepare_to_wait_exclusive(sk_sleep(sk), wait,TASK_INTERRUPTIBLE);...if (reqsk_queue_empty(icsk-icsk_accept_queue)) // 没有准备好的连接陷入睡眠等待客户端 SYN 连接请求进来timeo schedule_timeout(timeo);...if (!reqsk_queue_empty(icsk-icsk_accept_queue)) // 有准备好的连接了结束等待break;...if (signal_pending(current)) // 被信号中断break;err -EAGAIN;if (!timeo)break;}finish_wait(sk_sleep(sk), wait);return err;3.2.1.4 客户端向服务端发送 SYN 请求建立连接 到目前为止服务端处于 LISTEN 状态客户端处于 CLOSED 状态。接下来客户端通过 connect() 调用向服务端发送 SYN 请求然后自身进入到 SYN-SENT 状态等待服务端对 SYN 请求 的 SYN ACK 回复客户端在收到服务端的 SYN ACK 回复后也对服务端的 SYN 回复一个 ACK之后自身进入到 ESTABLISHED 状态并从 connect() 调用返回在 accept() 中等待、处于 LISTEN 状态的服务端套接字(前面代码中的 server_fd 指代的套接字)收到客户端的 SYN 请求后回复 SYN ACK 给客户端之后用刚进入 accept() 时、为新连接的客户端准备的套接字(注意这是个新的套接字和用来监听的套接字 server_fd 不是同一个详见 3.2.1.3 的分析)管理新的客户端连接并将该套接字状态置为 ESTABLISHED 最终返回(从 accept() 返回)新套接字的句柄(即 accept() 的返回值)给用户空间使用。细心的读者注意到了吧用来监听的服务端套接字 server_fd 的状态不会发生变化仍然处于 LISTEN 状态服务端处于 ESTABLISHED 状态的套接字是 accept() 返回的、用来管理客户端新连接的套接字这从 3.13.2 小节中的状态转换图是看不出来的而且还会误解是监听套接字 server_fd 的状态发生了变化。 上面用文字描述了从客户端发 SYN 包开始直到最终整个连接的建立过程下面来看代码实现的细节。当前服务端正在 accept() 中等待客户端的 SYN 请求于是客户端向服务端发起了 SYN 请求 // 客户端 int remote_fd; struct sockaddr_in server_addr;remote_fd socket(AF_INET, SOCK_STREAM, 0);memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(SERVER_PORT); // 服务端端口号 server_addr.sin_addr.s_addr inet_addr(192.168.1.188); // 假设服务端 IP 为 192.168.1.188 connect(remote_fd, (struct sockaddr *)server_addr, sizeof(struct sockaddr))); // 连接服务端sys_connect(remote_fd, (struct sockaddr *)server_addr, sizeof(struct sockaddr))) // net/socket.cstruct socket *sock;sock sockfd_lookup_light(fd, err, fput_needed);.../* uservaddr: 客户端想连接的目标地址 */err move_addr_to_kernel(uservaddr, addrlen, address);...err sock-ops-connect(sock, (struct sockaddr *)address, addrlen, sock-file-f_flags); // inet_stream_connect()inet_stream_connect() // net/ipv4/af_inet.c__inet_stream_connect(sock, uaddr, addr_len, flags, 0);switch (sock-state) {...// 发送 SYN 请求包到服务端case SS_UNCONNECTED:...err sk-sk_prot-connect(sk, uaddr, addr_len); /* tcp_v4_connect() */sock-state SS_CONNECTING; /* 套接字标记为 正在连接状态 */...break;}tcp_v4_connect() // net/ipv4/tcp_ipv4.c// 一些路由等相关的其它处理...tcp_set_state(sk, TCP_SYN_SENT); /* 客户端套接字状态由 CLOSED 转为 SYN-SENT: CLOSED SYN-SENT */...err tcp_connect(sk); /* 发送 SYN 包 */struct tcp_sock *tp tcp_sk(sk);struct sk_buff *buff;.../* 为 SYN 包分配空间 */buff sk_stream_alloc_skb(sk, 0, sk-sk_allocation, true);...tcp_init_nondata_skb(buff, tp-write_seq, TCPHDR_SYN); /* 构建 SYN 包 */.../* 发送 SYN 包 */err tp-fastopen_req ? tcp_send_syn_data(sk, buff) :tcp_transmit_skb(sk, buff, 1, sk-sk_allocation);...// 接前面的 inet_stream_connect() 流程 inet_stream_connect() // net/ipv4/af_inet.c...switch (sock-state) {// 发送 SYN 请求包到服务端case SS_UNCONNECTED:...err sk-sk_prot-connect(sk, uaddr, addr_len); /* tcp_v4_connect() */sock-state SS_CONNECTING; /* 套接字标记为 已连接状态 */...break;}...if ((1 sk-sk_state) (TCPF_SYN_SENT | TCPF_SYN_RECV)) {.../** 已向服务端发送 SYN 包陷入睡眠等待服务端回复 SYN ACK. * 在收到服务端的 SYN ACK 后, 内核再回复 ACK 给服务端,* 然后唤醒等待在此处的进程.*/if (!timeo || !inet_wait_for_connect(sk, timeo, writebias))goto out;...}...sock-state SS_CONNECTED; /* 到此, socket 进入已连接状态 (SS_CONNECTED) */// 等待服务端会送 SYN ACK 然后再回复服务端 ACK inet_wait_for_connect() // net/ipv4/af_inet.cDEFINE_WAIT_FUNC(wait, woken_wake_function);// sk_sleep() // return rcu_dereference_raw(sk-sk_wq)-wait;add_wait_queue(sk_sleep(sk), wait);...while ((1 sk-sk_state) (TCPF_SYN_SENT | TCPF_SYN_RECV)) {...timeo wait_woken(wait, TASK_INTERRUPTIBLE, timeo); // 等待服务端回复: SYN ACK...}...从上面的分析我们没有看到客户端(套接字)是怎么接收服务端的 SYN ACK 的也没有看到客户端(套接字)在收到服务端的 SYN ACK 后回应服务端 ACK 的过程。事实上就算翻遍整个 connect() 的代码也找不到这些逻辑因为处理这个逻辑的是内核网络协议栈的代码。分析这些逻辑要从网络协议栈接收数据的流程中去找 // 从网卡数据接收中断入口开始 xxx_nic_interrput()napi_gro_receive()napi_skb_finish()netif_receive_skb_internal()__netif_receive_skb()__netif_receive_skb_core()pt_prev-func() ip_rcv()ip_rcv_finish()dst_input()ip_local_deliver()ip_local_deliver_finish()ipprot-handler() tcp_v4_rcv()tcp_v4_rcv() // net/ipv4/tcp_ipv4.cstruct sock *sk;... lookup:/** 用 【源、目标IP】和【源、目标端口】等找到接收 skb 的目标通信套接字。* 在我们的分析上下文中当前接收的数据报是服务端应客户端 SYN 请求回复* 的 SYN ACK 数据包该数据包发往前面在 inet_wait_for_connect() 中* 等待的客户端套接字 sk但 SYN ACK 会由网路协议栈的 TCP 协议层处理* 而非客户端套接字本身。*/sk __inet_lookup_skb(tcp_hashinfo, skb, __tcp_hdrlen(th), th-source,th-dest, sdif, refcounted); ...ret 0;if (!sock_owned_by_user(sk)) {ret tcp_v4_do_rcv(sk, skb);} else if (tcp_add_backlog(sk, skb)) {...}...tcp_v4_do_rcv(sk, skb)...if (tcp_rcv_state_process(sk, skb)) {...}tcp_rcv_state_process(sk, skb)...switch (sk-sk_state) {...case TCP_SYN_SENT: /* 已往服务端发送了 SYN 的客户端处于 TCP_SYN_SENT (connect() 调用) */...queued tcp_rcv_synsent_state_process(sk, skb, th); /* 收取服务端发送的 SYN ACK */...return 0;...}tcp_rcv_synsent_state_process(sk, skb, th)...if (th-ack) { // ACK 包标记...if (!th-syn) // SYN ACKgoto discard_and_undo;...tcp_init_wl(tp, TCP_SKB_CB(skb)-seq);tcp_ack(sk, skb, FLAG_SLOWPATH); // 处理服务端回复包中的 ACK// 包序列号的一些处理tp-rcv_nxt TCP_SKB_CB(skb)-seq 1;tp-rcv_wup TCP_SKB_CB(skb)-seq 1;...// MTU, MSS 处理tcp_mtup_init(sk);tcp_sync_mss(sk, icsk-icsk_pmtu_cookie);tcp_initialize_rcv_mss(sk);...tcp_finish_connect(sk, skb); // 连接建立完成套接字 sk 由 SYN-SENT 转为 ESTABLISHEDtcp_set_state(sk, TCP_ESTABLISHED);...sk_state_store(sk, state); // sk-state TCP_ESTABLISHED......if (!sock_flag(sk, SOCK_DEAD)) {/* 唤醒等待 server 端 SYN ACK 的 connect() */sk-sk_state_change(sk) sock_def_wakeup(sk)struct socket_wq *wq;...wq rcu_dereference(sk-sk_wq);if (skwq_has_sleeper(wq))wake_up_interruptible_all(wq-wait); // 唤醒 connect() 调用链中在 inet_wait_for_connect() 中等待连接建立完成的进程...sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);}...if (sk-sk_write_pending ||icsk-icsk_accept_queue.rskq_defer_accept ||icsk-icsk_ack.pingpong) {...} else {tcp_send_ack(sk); /* 客户端 构建 发送 ACK 包给服务端 */}}...到此客户端的整个建立流程已经完成。接下来我们看从服务端收到客户端的 SYN 开始然后服务端回复客户端以 SYN ACK并最终收到客户端对自身 SYN 回复建立新的客户端连接套接字的过程 // 上接前面的 sys_accept4() 此时客户端为可能的新连接准备好了一个新的套接字 // 并睡眠等待服务端发送的 SYN 连接请求 sys_accept4(server_fd, NULL, NULL) // net/socket.cstruct socket *sock, *newsock;sock sockfd_lookup_light(fd, err, fput_needed);/* 为可能连接的客户端准备一个 sock 对象 */newsock sock_alloc();...// 等待客户端的 SYN 请求err sock-ops-accept(sock, newsock, sock-file-f_flags, false);inet_accept() // net/ipv4/af_inet.cinet_csk_accept() // net/ipv4/inet_connection_sock.cinet_csk_wait_for_connect()...for (;;) {prepare_to_wait_exclusive(sk_sleep(sk), wait,TASK_INTERRUPTIBLE);...// 如果 accept 队列(全连接队列)一直为空则一直等待(只讨论阻塞模式)if (reqsk_queue_empty(icsk-icsk_accept_queue))timeo schedule_timeout(timeo);...}...如同客户端接收来自服务端的 SYN ACK 回复以及对服务端的 SYN 回复 ACK 一样这些在服务端也都是由协议栈完成的而非 accept() 调用来看代码实现细节 // 从网卡数据接收中断入口开始 xxx_nic_interrput()...tcp_v4_rcv()...lookup:/* 用 【源、目标IP】和【源、目标端口】等找到接收 skb 的目标通信套接字 */sk __inet_lookup_skb(tcp_hashinfo, skb, __tcp_hdrlen(th), th-source,th-dest, sdif, refcounted);...if (sk-sk_state TCP_LISTEN) {ret tcp_v4_do_rcv(sk, skb);goto put_and_return;}...tcp_v4_do_rcv()...if (sk-sk_state TCP_LISTEN) {/* * 为了防止 SYN flood 攻击减少连接阶段的资源消耗, 建立了 SYN cookies .* 我们这里假定没有开启 SYN cookies (CONFIG_SYN_COOKIES 配置项关闭)。*/struct sock *nsk tcp_v4_cookie_check(sk, skb);...} else...if (tcp_rcv_state_process(sk, skb)) {rsk sk;goto reset;}return 0;...tcp_rcv_state_process()switch (sk-sk_state) {...case TCP_LISTEN:if (th-syn) { /* skb 为 SYN 报文 */...acceptable icsk-icsk_af_ops-conn_request(sk, skb) 0; // tcp_v4_conn_request()...if (!acceptable) /* sk 不接收 skb, 回发给源头 RESET */return 1;/* sk 正常接收 SYN skb */consume_skb(skb);return 0;}...}tcp_v4_conn_request(sk, skb) // net/ipv4/tcp_ipv4.creturn tcp_conn_request(tcp_request_sock_ops,tcp_request_sock_ipv4_ops, sk, skb);tcp_conn_request() // net/ipv4/tcp_input.c...struct request_sock *req;...if (sk_acceptq_is_full(sk)) { // 套接字的 sk accept 队列(全连接队列)已满NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);goto drop;}.../** 为连接请求, 分配轻量级的套接字数据结构 request_sock (指代 server_fd 套接字), * 并设定操作接口 (tcp_request_sock_ops) 。*/req inet_reqsk_alloc(rsk_ops, sk, !want_cookie);struct request_sock *req reqsk_alloc(ops, sk_listener, attach_listener);req kmem_cache_alloc(ops-slab, GFP_ATOMIC | __GFP_NOWARN);if (attach_listener) {...req-rsk_listener sk_listener; /* 监听套接字 sock: 当前分配的连接请求套接字的完整版 */}req-rsk_ops ops; /* req-rsk_ops tcp_request_sock_ops */req_to_sk(req)-sk_prot sk_listener-sk_prot;...if (req) {struct inet_request_sock *ireq inet_rsk(req);...// 标记服务端轻量级代理套接字(struct request_sock)为已接收 SYN-RECEIVED 状态。// TCP_NEW_SYN_RECV 是高内核版本新引入的套接字状态TCP_SYN_RECV 状态被 TFO 特性使用。ireq-ireq_state TCP_NEW_SYN_RECV; ...ireq-ireq_family sk_listener-sk_family;} tcp_rsk(req)-af_specific af_ops; /* tcp_rsk(req)-af_specific tcp_request_sock_ipv4_ops */...af_ops-init_req(req, sk, skb); /* 设置轻量级套接字的 源、目的 IP: 同 sk 的 源、目的 IP */tcp_v4_init_req()...if (fastopen_sk) { /* TCP Fast Open(TFO) 特性 */...} else {tcp_rsk(req)-tfo_listener false;if (!want_cookie)/* 添加连接请求 SYN 到 sk 的 ehash 表(半连接队列) inet_connection_sock::icsk_accept_queue */inet_csk_reqsk_queue_hash_add(sk, req, tcp_timeout_init((struct sock *)req));reqsk_queue_hash_req(req, timeout); // 将轻量级套接字插入到 ehash 表inet_csk_reqsk_queue_added(sk);reqsk_queue_added(inet_csk(sk)-icsk_accept_queue);atomic_inc(queue-young);atomic_inc(queue-qlen);/* 服务端收到 SYN 后回送 SYN ACK */af_ops-send_synack(sk, dst, fl, req, foc, // tcp_v4_send_synack()!want_cookie ? TCP_SYNACK_NORMAL : TCP_SYNACK_COOKIE);...struct sk_buff *skb;skb tcp_make_synack(sk, dst, req, foc, synack_type);if (skb) {...err ip_build_and_send_pkt(skb, sk, ireq-ir_loc_addr,ireq-ir_rmt_addr,rcu_dereference(ireq-ireq_opt));...}}reqsk_put(req); /* 释放 Internet 连接请求sock对象(request_sock) */return 0;到此服务端回应客户端 SYN 请求以 SYN ACK 的过程已经完成从前面对客户端的代码分析客户端收到服务端的 SYN ACK 后会回应服务端的 SYN 请求一个 ACK看下面的代码细节 // 从网卡数据接收中断入口开始 xxx_nic_interrput()...tcp_v4_rcv()...lookup:/* 用 【源、目标IP】和【源、目标端口】等找到接收 skb 的目标通信套接字 */sk __inet_lookup_skb(tcp_hashinfo, skb, __tcp_hdrlen(th), th-source,th-dest, sdif, refcounted);...process:.../** 已经收到了客户端 SYN 并回应了 SYN ACK 的服务端轻量级代理套接字: * . 服务端轻量级代理套接字 sk / req 处于 TCP_NEW_SYN_RECV 状态* . 服务端轻量级代理套接字 sk / req 指代的服务端套接字 req-rsk_listener 处于 TCP_LISTEN 状态*/if (sk-sk_state TCP_NEW_SYN_RECV) {struct request_sock *req inet_reqsk(sk); // 服务端轻量级代理套接字struct sock *nsk;sk req-rsk_listener;...if (unlikely(sk-sk_state ! TCP_LISTEN)) {inet_csk_reqsk_queue_drop_and_put(sk, req);goto lookup;}...if (!tcp_filter(sk, skb)) { /* 如果数据包没有被 eBPF 过滤掉 */...// 为新的连接建立完整的套接字: 建立 TCP 的套接字数据 (struct sock)// 并将连接数据添加到 accept 队列 (全连接队列)nsk tcp_check_req(sk, skb, req, false);}...if (nsk sk) {...} else if (tcp_child_process(sk, nsk, skb)) { // 新连接初始化(MTU、缓冲等)然后唤醒在 accept() 调用中阻塞等待新连接的进程...} else {sock_put(sk);return 0;}...}// 看 // 【为新的连接建立完整的套接字: 建立 TCP 的套接字数据 (struct sock) // 并将连接数据添加到 accept 队列(全连接队列)】 // 的细节 nsk tcp_check_req(sk, skb, req, false); // net/ipv4/tcp_minisocks.cchild inet_csk(sk)-icsk_af_ops-syn_recv_sock(sk, skb, req, NULL, req, own_req);tcp_v4_syn_recv_sock() // net/ipv4/tcp_ipv4.cstruct sock *newsk;...newsk tcp_create_openreq_child(sk, req, skb);struct sock *newsk inet_csk_clone_lock(sk, req, GFP_ATOMIC);struct sock *newsk sk_clone_lock(sk, priority);if (newsk) {...newsk-sk_state TCP_SYN_RECV;...}........./* 将新连接数据放到 accept 队列(全连接队列) */return inet_csk_complete_hashdance(sk, child, req, own_req);if (own_req) {inet_csk_reqsk_queue_drop(sk, req);// 将建立好连接的套接字从 ehash 表(半连接队列)移除reqsk_queue_removed(inet_csk(sk)-icsk_accept_queue, req);if (inet_csk_reqsk_queue_add(sk, req, child))return child;}...inet_csk_reqsk_queue_add()struct request_sock_queue *queue inet_csk(sk)-icsk_accept_queue;...if (unlikely(sk-sk_state ! TCP_LISTEN)) {...} else {req-sk child;req-dl_next NULL;if (queue-rskq_accept_head NULL)queue-rskq_accept_head req;elsequeue-rskq_accept_tail-dl_next req;queue-rskq_accept_tail req;sk_acceptq_added(sk);sk-sk_ack_backlog;}...// 继续看 // 【新连接初始化(MTU、缓冲等)然后唤醒在 accept() 调用中阻塞等待新连接的进程】 // 的细节 tcp_child_process(sk, nsk, skb)int state child-sk_state;...if (!sock_owned_by_user(child)) {ret tcp_rcv_state_process(child, skb);...switch (sk-sk_state) {case TCP_SYN_RECV:...tcp_set_state(sk, TCP_ESTABLISHED); /* 与客户端的通信的套接字装换为已连接状态 ESTABLISHED */...tcp_init_wl(tp, TCP_SKB_CB(skb)-seq);...tcp_initialize_rcv_mss(sk);...break;}.../* Wakeup parent, send SIGIO */if (state TCP_SYN_RECV child-sk_state ! state)/* 唤醒在 accept() 中等待连接的进程 */parent-sk_data_ready(parent) sock_def_readable()...wq rcu_dereference(sk-sk_wq);if (skwq_has_sleeper(wq))wake_up_interruptible_sync_poll(wq-wait, POLLIN | POLLPRI |POLLRDNORM | POLLRDBAND);sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);...} else {...}...return 0;到此3.2 小节中图示的 TCP 连接建立的三次握手过程 已经全部分析完成。当然这里分析的只是 TCP 连接建立的诸多可能序列当中的一个其它可能的建立过程感兴趣的读者可以自行分析源码。另外对于 SYN Cookies TFO(TCP Fast Open)端口重用 等特性本文也未加讨论。 3.2.1.5 TCP 连接建立过程小结 从上面的分析中应该了解到的是TCP 连接的建立是一个双向的过程不管是服务端还是客户端都会向对端通过 SYN 发起连接请求而对方也会在收到 SYN 后回应一个 ACK 这样一个双向连接就建立好了。以这样的方式建立连接的基本理由是因为 TCP 是全双工通信。从后面的章节也可以看到拆除连接(四次挥手) 也是一个双向拆除的过程。 另外连接建立的过程是由内核协议栈完成的。细心的童鞋会发现在调用 listen() 后即使服务端不调用 accept() 客户端照样可以通过 connect() 建立连接只是这样的连接无法正常和服务端进行数据通信。这是因为 listen() 已经建立好了 accept 队列当客户端发起连接时内核协议栈会把建立好的连接信息放入 accept 队列而 accept() 只是从该队列中取出建立好的连接信息它本身并不参与连接的建立过程。用 netstat 观察可以发现这些 TCP 连接没有进程名信息取而代之的是 -- 。这样的现象可能会让初接触 TCP 编程的童鞋大吃一惊。 3.2.2 TCP 连接断开的四次挥手过程分析 当我们的通信完毕可以通过调用 close() 关闭连接。和连接的建立一样连接的关闭过程也存在多种可能的序列本文以 3.2 小节中的连接关闭过程为例来进行分析。 3.2.2.1 客户端通过 close() 向服务端发送 FIN进入 FIN-WAIT-1 状态 // 客户端发起本端的连接断开请求 close(remote_fd);sys_close() // fs/open.c__close_fd(current-files, fd)filp_close(file, files)// 中间过程有点小复杂不是这里关注的重点...sock_close()sock_close() // net/socket.c__sock_release(SOCKET_I(inode), inode)if (sock-ops) {...sock-ops-release(sock) inet_release() // net/ipv4/af_inet.cstruct sock *sk sock-sk;if (sk) {...sk-sk_prot-close(sk, timeout) tcp_close()}return 0;...}tcp_close(sk, timeout) // net/ipv4/tcp.c...sk-sk_shutdown SHUTDOWN_MASK;.../** 关闭连接时需处理接收缓冲里还没有被应用层读取的数据* 我们假定关闭连接时客户端应用已经拿走了接收缓冲里的* 所有数据。* 对于客户端套接字缓冲还有未读取数据的情形读者可自行分析。*/...sk_mem_reclaim(sk);if (unlikely(tcp_sk(sk)-repair)) {...} else if (data_was_unread) {...} else if (sock_flag(sk, SOCK_LINGER) !sk-sk_lingertime) {...} else if (tcp_close_state(sk)) { // 套接字由 ESTABLISHED 态转为 FIN-WAIT-1tcp_send_fin(sk); // 向服务端发送 FIN 包}...3.2.2.2 服务端收取客户端 FIN回以 ACK进入 CLOSE-WAIT 状态 客户端发送 FIN 包给服务端服务端收到客户端的 FIN 包后回应以一个 ACK xxx_nic_interrput()...tcp_v4_rcv()...lookup:/* 用 【源、目标IP】和【源、目标端口】等找到接收 skb 的目标通信套接字 */sk __inet_lookup_skb(tcp_hashinfo, skb, __tcp_hdrlen(th), th-source,th-dest, sdif, refcounted);...process:...if (!sock_owned_by_user(sk)) {ret tcp_v4_do_rcv(sk, skb);...if (sk-sk_state TCP_ESTABLISHED) {...tcp_rcv_established(sk, skb, tcp_hdr(skb)); // net/ipv4/tcp_input.c...tcp_send_ack(sk); // 回应 FIN 一个 ACK...tcp_data_queue(sk, skb);if (TCP_SKB_CB(skb)-seq tp-rcv_nxt) {...// 服务端收到客户端的 FIN 除了回一个 ACK 外// 还要针对 FIN 包做一些和客户端连接的套接字的特定处理:// 状态由 ESTABLISHED 转为 CLOSE-WAIT 等等...if (TCP_SKB_CB(skb)-tcp_flags TCPHDR_FIN)tcp_fin(sk);sk-sk_shutdown | RCV_SHUTDOWN;sock_set_flag(sk, SOCK_DONE);switch (sk-sk_state) {case TCP_SYN_RECV:case TCP_ESTABLISHED:// ESTABLISHED CLOSE-WAITtcp_set_state(sk, TCP_CLOSE_WAIT);inet_csk(sk)-icsk_ack.pingpong 1;break;...}......}...return 0;}} else if (...) {...}3.2.2.3 客户端收取服务端对 FIN 的回应 ACK进入 FIN-WAIT-2 状态 客户端在调用 close() 向服务端发送 FIN 包后当前处于 FIN-WAIT-1 状态(如果没有设置SOCK_LINGER close() 调用也已经返回)服务端对客户端的 FIN 包回应了一个 ACK自身进入 CLOSE-WAIT 状态客户端收到这个 ACK 后进入 FIN-WAIT-2 状态这时从服务端向客户端发送数据的通道就已经关闭了。看客户端处理 FIN 的 ACK 包后进入 FIN-WAIT-2 状态这一过程的代码实现细节 xxx_nic_interrput()...tcp_v4_rcv()...lookup:/* 用 【源、目标IP】和【源、目标端口】等找到接收 skb 的目标通信套接字 */sk __inet_lookup_skb(tcp_hashinfo, skb, __tcp_hdrlen(th), th-source,th-dest, sdif, refcounted);...process:...if (!sock_owned_by_user(sk)) {ret tcp_v4_do_rcv(sk, skb);...if (tcp_rcv_state_process(sk, skb)) {rsk sk;goto reset;}return 0;...} else if (tcp_add_backlog(sk, skb)) {...}...tcp_rcv_state_process(sk, skb)...switch (sk-sk_state) {...case TCP_FIN_WAIT1: {...tcp_set_state(sk, TCP_FIN_WAIT2); /* 客户端: FIN-WAIT-1 FIN-WAIT-2 */sk-sk_shutdown | SEND_SHUTDOWN;...}...}...3.2.2.4 服务端通过 close() 向客户端发 FIN进入 LAST-ACK 状态 此时TCP 连接已经处于半关闭状态(客户端接收数据通道已经关闭)。同样的在服务端不再想接收客户端的数据时调用 close() 向客户端发送 FIN 包然后服务端套接字由 CLOSE-WAIT 进入 LAST-ACK 状态 // 关闭服务端和客户端通信的套接字注意这里关闭的不是 server_fd close(client_fd);sys_close() // fs/open.c...sock_close() // net/socket.c__sock_release(SOCKET_I(inode), inode)if (sock-ops) {...sock-ops-release(sock) inet_release() // net/ipv4/af_inet.cstruct sock *sk sock-sk;if (sk) {...sk-sk_prot-close(sk, timeout) tcp_close()}return 0;...}tcp_close(sk, timeout) // net/ipv4/tcp.c...sk-sk_shutdown SHUTDOWN_MASK;...sk_mem_reclaim(sk);...if (unlikely(tcp_sk(sk)-repair)) {...} else if (data_was_unread) {...} else if (sock_flag(sk, SOCK_LINGER) !sk-sk_lingertime) {...} else if (tcp_close_state(sk)) { // 套接字由 CLOSE-WAIT 态转为 LAST-ACKtcp_send_fin(sk); // 向客户端发送 FIN 包}...3.2.2.5 客户端收取服务端 FIN回以 ACK进入 TIME-WAIT超时后进入 CLOSED 终态 客户端当前处于 FIN-WAIT-2 状态收到服务端的 FIN 包后回复服务端一个 ACK然后自身进入 TIME-WAIT 状态 xxx_nic_interrput()...tcp_v4_rcv()...lookup:/* 用 【源、目标IP】和【源、目标端口】等找到接收 skb 的目标通信套接字 */sk __inet_lookup_skb(tcp_hashinfo, skb, __tcp_hdrlen(th), th-source,th-dest, sdif, refcounted);...process:...if (!sock_owned_by_user(sk)) {ret tcp_v4_do_rcv(sk, skb);...if (tcp_rcv_state_process(sk, skb)) {rsk sk;goto reset;}return 0;...} else if (tcp_add_backlog(sk, skb)) {...}...tcp_rcv_state_process(sk, skb).../* step 7: process the segment text */switch (sk-sk_state) {...case TCP_FIN_WAIT1:case TCP_FIN_WAIT2: // 客户端当前处于 FIN-WAIT-2 状态.../* Fall through */case TCP_ESTABLISHED:tcp_data_queue(sk, skb); // 处理服务端发送的 FIN , 并进入 TIME-WAIT 状态queued 1;break;}...// 处理服务端发送的 FIN , 并进入 TIME-WAIT 状态 tcp_data_queue(sk, skb)...if (TCP_SKB_CB(skb)-seq tp-rcv_nxt) {...if (TCP_SKB_CB(skb)-tcp_flags TCPHDR_FIN)tcp_fin(sk);switch (sk-sk_state) {...case TCP_FIN_WAIT2:/* Received a FIN -- send ACK and enter TIME_WAIT. */tcp_send_ack(sk); // 回复给服务端 ACKtcp_time_wait(sk, TCP_TIME_WAIT, 0); // 进入 TIME-WAIT 态: FIN-WAIT-2 TIME-WAITbreak;...}...}...// 进入 TIME-WAIT 态: FIN-WAIT-2 TIME-WAIT tcp_time_wait(sk, TCP_TIME_WAIT, 0);...struct inet_timewait_sock *tw;.../* 分配 TIME-WAIT 套接字数据初始化包括 TIME-WAIT 超时定时器 等 */tw inet_twsk_alloc(sk, tcp_death_row, state);...tw kmem_cache_alloc(sk-sk_prot_creator-twsk_prot-twsk_slab, GFP_ATOMIC);...if (tw) {...tw-tw_state TCP_TIME_WAIT;tw-tw_substate state; // tw-tw_substate TCP_TIME_WAIT...// 初始化 TIME-WAIT 超时定时器setup_pinned_timer(tw-tw_timer, tw_timer_handler, (unsigned long)tw);...}if (tw) {...// TIME-WATI 超时时间设置tw-tw_timeout TCP_TIMEWAIT_LEN;if (state TCP_TIME_WAIT)timeo TCP_TIMEWAIT_LEN;...inet_twsk_schedule(tw, timeo); /* 启动 TIME-WAIT 超时定时器 */__inet_twsk_schedule(tw, timeo, false);tw-tw_kill timeo 4*HZ;if (!rearm) {BUG_ON(mod_timer(tw-tw_timer, jiffies timeo)); // 启动 TIME-WAIT 超时定时器atomic_inc(tw-tw_dr-tw_count);} else {...}...}...tcp_done(sk);...tcp_set_state(sk, TCP_CLOSE);...// TIME-WAIT 定时器超时触发 tw_timer_handler() // 回收 TIME-WAIT (struct inet_timewait_sock) 套接字资源 tw_timer_handler()struct inet_timewait_sock *tw (struct inet_timewait_sock *)data;...inet_twsk_kill(tw); // 回收 TIME-WAIT (struct inet_timewait_sock) 套接字资源3.2.2.6 服务端收取客户端对 FIN 的回应 ACK进入 CLOSED 终态 上面分析中客户端收到服务端的 FIN 后回复服务端一个 ACK然后自身进入由 FIN-WAIT-2 进入到 TIME-WAIT 并在超时时间到达后进入终态 CLOSED 。服务端收到客户端对 FIN 的 ACK 后也由 LAST-ACK 转入终态 CLOSED 到此整个连接的双向关闭过程终结了。下面看服务端收到客户端对 FIN 的 ACK由 LAST-ACK 转入终态 CLOSED 的代码细节 xxx_nic_interrput()...tcp_v4_rcv()...lookup:/* 用 【源、目标IP】和【源、目标端口】等找到接收 skb 的目标通信套接字 */sk __inet_lookup_skb(tcp_hashinfo, skb, __tcp_hdrlen(th), th-source,th-dest, sdif, refcounted);...process:...if (!sock_owned_by_user(sk)) {ret tcp_v4_do_rcv(sk, skb);...if (tcp_rcv_state_process(sk, skb)) {rsk sk;goto reset;}return 0;...} else if (tcp_add_backlog(sk, skb)) {...}...// 服务端: LAST-ACK CLOSED tcp_rcv_state_process(sk, skb)...switch (sk-sk_state) {...case TCP_LAST_ACK:if (tp-snd_una tp-write_seq) {tcp_update_metrics(sk);tcp_done(sk);tcp_set_state(sk, TCP_CLOSE); // 服务端: LAST-ACK CLOSEDtcp_clear_xmit_timers(sk);...goto discard;}break;...}...到此TCP 连接断开的四次挥手过程已经分析完毕。细心的读者可能会发现示例代码中的 server_fd 还没有关闭。是的但本文不打算对此进行分析相信有了前面的基础读者自行分析有不会是多困难的事情。 3.2.3 三握四挥小结 3.2.1 和 3.2.2 两个小节中的分析采用了不同排列方式3.2.1 是先分析了服务端的所有阶段然后再分析了客户端的所有阶段3.2.2 是按事件发生的先后顺序交叉的分析服务端和客户端。这一点读者阅读的时候需要引起注意。这是笔者组织时没有统一规划好造成的写完后又不想再改了。 4. 抓包三握四挥过程示例 可通过工具 tcpdump 观察 TCP 三握四挥 的过程请参考博文 Linux: tcpdump抓包示例 。 当然Wireshark 可能是更好的选择。 5. 参考资料 rfc793: https://www.rfc-editor.org/rfc/rfc793.html https://mp.weixin.qq.com/s/tCXH8BTrgYaVmwVx_Ek1qA
http://www.w-s-a.com/news/322479/

相关文章:

  • wordpress构建自定义设置页面seo培训学什么
  • 延安有哪些做网站的公司如何建设网站?
  • 网站建设者属于广告经营者吗网站管理程序
  • 网站内容优化方法深圳市宝安区怎么样
  • 视频网站开发视频公司网站制作多少钱
  • 单页简洁手机网站模板购物软件
  • 素材网站官网低价网站建设费用预算
  • 苏州网站设计kgwl个人网站有什么外国广告做
  • 浙江省网站建设报价简单网站开发工具
  • 物流网站的建设wordpress电视直播插件下载
  • 简述网站开发流程青岛做网站建设价格低
  • 网站开发的业务需求分析杭州推广公司
  • 网站建设技术实现难点app开发需要哪些软件
  • 响水建设局网站做网站需要会哪些知识
  • 企业制作企业网站个人网站可以做百度竞价
  • 做网站找投资人wordpress 5 主题教程
  • 做国外网站汇款用途是什么wordpress图片主题晨曦
  • 网站设计跟网站开发区别为什么网站需要维护
  • m 的手机网站怎么做网络推广方式和方法
  • wordpress图片自动轮播插件seo门户网站建设
  • 制作商业网站传奇网页游戏排名
  • 网站免费推广方案长沙房地产网站设计
  • 济南网站建设cnwenhui中交路桥建设网站
  • 韶关网站开发网站建设任务分解
  • 网站建设核心点阿根廷网站后缀
  • 哪些网站可以做招商广告语学校官网页面设计
  • 十堰城市建设网站网站开发流程宜春
  • 内江网站建设郑州网站优化外包
  • 土地流转网站建设项目云南抖音推广
  • 建设银行网站无法打开2021年有没有人给个网站