TCP发送功能是指将从应用层通过打开的套接字写入的数据移入内核,通过TCP/IP协议栈,最终通过网络设备发送到远端接收主机。

TCP传送的特点如下:

  • 异步传送:TCP的实际传送独立于应用层。
  • 汇集从套接字传送的数据,形成TCP协议的数据段,复制到Socket Buffer。
  • 管理和维护Socket Buffer链表,形成Socket Buffer传送队列缓存传送数据,准备以后发送。
  • 管理Socket Buffer缓冲区分配,如果队列中最后一个Socket Buffer已满,但套接字缓冲区中还有新的数据要写入,则分配一个新的Socket Buffer以接收数据。

1. 用户socket–>内核socket(数据从用户地址空间复制到内核Socket Buffer)

TCP协议初始化时在协议函数块中注册发送函数。

static int __init inet_init(void){/*...*//* Register the socket-side information for inet_create. */for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)INIT_LIST_HEAD(r);for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)inet_register_protosw(q);
}
/* Upon startup we insert all the elements in inetsw_array[] into* the linked list inetsw.*/
static struct inet_protosw inetsw_array[] =
{{.type =       SOCK_STREAM,.protocol =   IPPROTO_TCP,.prot =       &tcp_prot,.ops =        &inet_stream_ops,   /*TCP socket操作注册*/.no_check =   0,.flags =      INET_PROTOSW_PERMANENT |INET_PROTOSW_ICSK,},{.type =       SOCK_DGRAM,.protocol =   IPPROTO_UDP,.prot =       &udp_prot,.ops =        &inet_dgram_ops,.no_check =   UDP_CSUM_DEFAULT,.flags =      INET_PROTOSW_PERMANENT,},{.type =       SOCK_RAW,.protocol =   IPPROTO_IP, /* wild card */.prot =       &raw_prot,.ops =        &inet_sockraw_ops,.no_check =   UDP_CSUM_DEFAULT,.flags =      INET_PROTOSW_REUSE,}
};const struct proto_ops inet_stream_ops = {.family           = PF_INET,.owner           = THIS_MODULE,.release     = inet_release,.bind           = inet_bind,.connect       = inet_stream_connect,.socketpair      = sock_no_socketpair,.accept           = inet_accept,.getname     = inet_getname,.poll           = tcp_poll,.ioctl          = inet_ioctl,.listen           = inet_listen,.shutdown    = inet_shutdown,.setsockopt    = sock_common_setsockopt,.getsockopt       = sock_common_getsockopt,.sendmsg      = tcp_sendmsg,  /*TCP socket 发送函数*/.recvmsg    = inet_recvmsg,.mmap           = sock_no_mmap,.sendpage       = tcp_sendpage,.splice_read    = tcp_splice_read,
#ifdef CONFIG_COMPAT.compat_setsockopt = compat_sock_common_setsockopt,.compat_getsockopt = compat_sock_common_getsockopt,
#endif
};

所有的tcp写操作在套接字层会调用tcp_sendmsg函数

int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,size_t size)
{/**************************1. 数据来源和局部变量初始化********************************/struct sock *sk = sock->sk;struct iovec *iov;  /*参数struct msghdr *msg的数据块链表的起始地址msg_iov。*/struct tcp_sock *tp = tcp_sk(sk);  struct sk_buff *skb;    /*指向新分配的Socket Buffer,用于存放要传送的数据。*/int iovlen, flags;int mss_now, size_goal;int sg, err, copied;long timeo; /*存放SO_SENDTIMEO(套接字设定的传送超时的时间)选项*//**************************2. 数据复制准备********************************//*等待连接建立 */if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)goto out_err;/* ... *//* 提取发送数据 */iovlen = msg->msg_iovlen;iov = msg->msg_iov;copied = 0;/**************************3. 从用户空间复制数据********************************/while (--iovlen >= 0) {int seglen = iov->iov_len;unsigned char __user *from = iov->iov_base;iov++;while (seglen > 0) {int copy = 0;int max = size_goal;skb = tcp_write_queue_tail(sk); /*指向队列最后一个skb, 如果等待队列中的最后一个skb的数据缓冲区中还有空间,则将数据放入队列的最后一个skb数据缓冲区中*/if (tcp_send_head(sk)) {if (skb->ip_summed == CHECKSUM_NONE)max = mss_now;copy = max - skb->len;}if (copy <= 0) {new_segment:/* Allocate new segment. If the interface is SG,* allocate skb fitting to single page.*/if (!sk_stream_memory_free(sk))goto wait_for_sndbuf;skb = sk_stream_alloc_skb(sk,           /*分配新的skb*/select_size(sk, sg),sk->sk_allocation);if (!skb)goto wait_for_memory;skb_entail(sk, skb);copy = size_goal;max = size_goal;}/* Try to append data to the end of skb. */if (copy > seglen)copy = seglen;/* Where to copy to? */if (skb_tailroom(skb) > 0) {    /*skb_tailroom(skb)用于判断Socket Buffer的数据缓冲区中是否还有剩余空间,并返回剩余空间的大小。*//* We have some space in skb head. Superb! */if (copy > skb_tailroom(skb))copy = skb_tailroom(skb);if ((err = skb_add_data(skb, from, copy)) != 0)    /*skb_add_data 完成具体的数据复制操作*/goto do_fault;} else {   /*如果Socket Buffer的数据缓冲区已满,则查看紧接在Socket Buffer数据缓冲区后的struct skb_shared_info数据结构的frags页面数组中的最后一个页面是否还有空间,如果有就将数据合并到最后一个页面中*/int merge = 0;int i = skb_shinfo(skb)->nr_frags;struct page *page = TCP_PAGE(sk);int off = TCP_OFF(sk);if (skb_can_coalesce(skb, i, page, off) &&off != PAGE_SIZE) {/* We can extend the last page* fragment. */merge = 1;} else if (i == MAX_SKB_FRAGS || !sg) {/* Need to add new fragment and cannot* do this because interface is non-SG,* or because all the page slots are* busy. */tcp_mark_push(tp, skb);goto new_segment;} else if (page) {if (off == PAGE_SIZE) {put_page(page);TCP_PAGE(sk) = page = NULL;off = 0;}} elseoff = 0;if (copy > PAGE_SIZE - off)copy = PAGE_SIZE - off;if (!sk_wmem_schedule(sk, copy))goto wait_for_memory;if (!page) { /*如果struct skb_shared_info数据结构中frags页面数组的最后一个页面也已满,则分配一个新的页面,将数据复制到新页面中*//* Allocate new cache page. */if (!(page = sk_stream_alloc_page(sk)))goto wait_for_memory;}/* Time to copy data. We are close to* the end! 将数据复制到新页面中*/err = skb_copy_to_page(sk, from, skb, page,off, copy);if (err) {/* If this page was new, give it to the* socket so it does not get leaked.*/if (!TCP_PAGE(sk)) {TCP_PAGE(sk) = page;TCP_OFF(sk) = 0;}goto do_error;}/* Update the skb. 更新Socket Buffer的描述信息*/if (merge) {skb_shinfo(skb)->frags[i - 1].size +=copy;} else {skb_fill_page_desc(skb, i, page, off, copy);if (TCP_PAGE(sk)) {get_page(page);} else if (off + copy < PAGE_SIZE) {get_page(page);TCP_PAGE(sk) = page;}}TCP_OFF(sk) = off + copy;}if (!copied)TCP_SKB_CB(skb)->flags &= ~TCPCB_FLAG_PSH;/**************************3.  tcp_sendmsg函数的结束处理,准备发送*******************************/tp->write_seq += copy;TCP_SKB_CB(skb)->end_seq += copy;skb_shinfo(skb)->gso_segs = 0;from += copy;copied += copy;if ((seglen -= copy) == 0 && iovlen == 0)goto out;if (skb->len < max || (flags & MSG_OOB))continue;if (forced_push(tp)) { /*函数forced_push用于查看是否立即发送数据段,如果立即发送,则调用tcp_mark_push函数设置TCP协议头中的PSH标志。*/tcp_mark_push(tp, skb);__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);} else if (skb == tcp_send_head(sk))tcp_push_one(sk, mss_now);continue;/*缓存数据段:如果队列中还没有足够的缓冲区或页面,则等到有一定数据量的有效缓冲区后再发送。*/
wait_for_sndbuf:set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
wait_for_memory:if (copied)tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)goto do_error;mss_now = tcp_send_mss(sk, &size_goal, flags);}}out:if (copied)tcp_push(sk, flags, mss_now, tp->nonagle);TCP_CHECK_TIMER(sk);release_sock(sk);return copied;do_fault:if (!skb->len) {tcp_unlink_write_queue(skb, sk);/* It is the one place in all of TCP, except connection* reset, where we can be unlinking the send_head.*/tcp_check_send_head(sk, skb);sk_wmem_free_skb(sk, skb);}do_error:if (copied)goto out;
out_err:err = sk_stream_error(sk, flags, err);TCP_CHECK_TIMER(sk);release_sock(sk);return err;

2. TCP数据段输出

tcp_sendmsg复制完来自应用层的数据后,无论是立即发送存放了TCP数据段的Socket Buffer,还是缓冲发送,它们调用的__tcp_push_pending_frames/tcp_push_one/ tcp_push函数,最终是通过调用tcp_write_xmit函数,转而调用sk_transmit_skb函数来将数据段发送出去的。

而且从TCP协议实例向外传送的数据段,除了来自用户地址空间的数据包外,还有大量由TCP协议层本地产生的数据包,如:

  • 重传数据包tcp_retransmit_skb。
  • 探测路由最大传送单元数据包。
  • 发送复位连接数据包。
  • 发送连接请求数据包。
  • 发送回答数据包。
  • 0窗口探测数据包等。

所有这些数据包通过不同的函数在TCP层创建连同从用户地址空传来的数据包,最终都是通过tcp_transmit_skb函数向IP层传送的。

tcp_transmit_skb函数完成的主要功能包括创建TCP协议头、将数据段传给IP层向外发送。

构造向外发送的TCP数据段需要的重要信息有以下几种:

  • inet:指向inet选项结构,其中包含了AF_INET地址族SOCK_STREAM类套接字的所有信息。
  • tp:指向TCP选项结构,其中包含了TCP配置和连接的大部分信息。
  • tcb:指向TCP控制缓冲,包含了用于构造TCP协议头的各项标志。
  • th:指向TCP协议头数据结构,此后它将指向skb中存放TCP协议头的位置。
  • icsk:inet连接控制套接字。
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,gfp_t gfp_mask)
{/**************************1. tcp_transmit_skb函数初始化************************************/const struct inet_connection_sock *icsk = inet_csk(sk);struct inet_sock *inet; /*指向inet选项结构,其中包含了AF_INET地址族SOCK_STREAM类套接字的所有信息。*/struct tcp_sock *tp; /*指向TCP选项结构,其中包含了TCP配置和连接的大部分信息。*/struct tcp_skb_cb *tcb; /*指向TCP控制缓冲,包含了用于构造TCP协议头的各项标志。*/struct tcp_out_options opts;   unsigned tcp_options_size, tcp_header_size;struct tcp_md5sig_key *md5;struct tcphdr *th; /*指向TCP协议头数据结构,此后它将指向skb中存放TCP协议头的位置。*/int err;BUG_ON(!skb || !tcp_skb_pcount(skb));/**************************2. 确定TCP数据段协议头包含的内容************************************//* If congestion control is doing timestamping, we must* take such a timestamp before we potentially clone/copy.*/if (icsk->icsk_ca_ops->flags & TCP_CONG_RTT_STAMP)__net_timestamp(skb);/*如果输入参数clone_it指明当前Socket Buffer还有其他进程在使用,则需要首先克隆套接字缓冲区。*/if (likely(clone_it)) {if (unlikely(skb_cloned(skb)))skb = pskb_copy(skb, gfp_mask);elseskb = skb_clone(skb, gfp_mask);if (unlikely(!skb))return -ENOBUFS;}inet = inet_sk(sk);tp = tcp_sk(sk);tcb = TCP_SKB_CB(skb);memset(&opts, 0, sizeof(opts));/*根据当前数据包是否为发送SYN数据段的包确定TCP协议选项长度。*/if (unlikely(tcb->flags & TCPCB_FLAG_SYN))tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5);elsetcp_options_size = tcp_established_options(sk, skb, &opts,&md5);tcp_header_size = tcp_options_size + sizeof(struct tcphdr);/*网络拥塞控制管理*/if (tcp_packets_in_flight(tp) == 0)tcp_ca_event(sk, CA_EVENT_TX_START);skb_push(skb, tcp_header_size);skb_reset_transport_header(skb);skb_set_owner_w(skb, sk);/* Build TCP header and checksum it. 构造TCP协议头*/th = tcp_hdr(skb);th->source       = inet->inet_sport;th->dest      = inet->inet_dport;th->seq           = htonl(tcb->seq);th->ack_seq        = htonl(tp->rcv_nxt);*(((__be16 *)th) + 6) = htons(((tcp_header_size >> 2) << 12) |tcb->flags);if (unlikely(tcb->flags & TCPCB_FLAG_SYN)) {/* RFC1323: The window in SYN & SYN/ACK segments* is never scaled.*/th->window    = htons(min(tp->rcv_wnd, 65535U));} else {th->window = htons(tcp_select_window(sk));}th->check       = 0;th->urg_ptr     = 0;/* The urg_mode check is necessary during a below snd_una win probe */if (unlikely(tcp_urg_mode(tp) && before(tcb->seq, tp->snd_up))) {if (before(tp->snd_up, tcb->seq + 0x10000)) {th->urg_ptr = htons(tp->snd_up - tcb->seq);th->urg = 1;} else if (after(tcb->seq + 0xFFFF, tp->snd_nxt)) {th->urg_ptr = htons(0xFFFF);th->urg = 1;}}tcp_options_write((__be32 *)(th + 1), tp, &opts);/**************************3. 发送数据************************************/if (likely((tcb->flags & TCPCB_FLAG_SYN) == 0))TCP_ECN_send(sk, skb, tcp_header_size);#ifdef CONFIG_TCP_MD5SIG/* Calculate the MD5 hash, as we have all we need now */if (md5) {sk_nocaps_add(sk, NETIF_F_GSO_MASK);tp->af_specific->calc_md5_hash(opts.hash_location,md5, sk, NULL, skb);}
#endificsk->icsk_af_ops->send_check(sk, skb);if (likely(tcb->flags & TCPCB_FLAG_ACK))tcp_event_ack_sent(sk, tcp_skb_pcount(skb));if (skb->len != tcp_header_size)tcp_event_data_sent(tp, skb, sk);if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,tcp_skb_pcount(skb));/*在TCP协议中,queue_xmit函数指针指向的ip_queue_xmit函数,ip_queue_xmit函数是TCP层向IP层传送数据段时调用的函数,该函数由IP层实现,是IP层提供给TCP层的接口函数*/err = icsk->icsk_af_ops->queue_xmit(skb);if (likely(err <= 0))return err;tcp_enter_cwr(sk, 1);return net_xmit_eval(err);
}

3. 发送过程状态机

参考

《嵌入式Linux网络体系结构设计与TCP/IP协议栈–单立平》

linux内核中TCP发送的实现相关推荐

  1. linux内核中TCP接收的实现

    linux内核中TCP接收的实现入口函数是tcp_v4_rcv 1. 数据包检查处理 一开始做一些数据包详细检查处理,一旦出错,可能导致内核挂掉 int tcp_v4_rcv(struct sk_bu ...

  2. linux做预警机制,预警通告:Linux内核中TCP SACK机制远程DoS

    漏洞描述 2019年6月18日,RedHat官网发布报告:安全研究人员在Linux内核处理TCP SACK数据包模块中发现了三个漏洞,CVE编号为CVE-2019-11477.CVE-2019-114 ...

  3. Linux内核中TCP协议实现的关键数据结构

    1. TCP协议头tcphdr TCP协议头描述了TCP数据段发送的源地址.目标地址.数据段传送管理和连接管理的信息,是TCP协议实现的重要数据结构之一. struct tcphdr {__be16 ...

  4. linux程序获取透传参数,Linux内核中TCP SACK处理流程分析

    frankzfz2014-07-27 17:32 demo121:frankzfz您好: 我想请教一个问题,就是将写好的GenericApp项目(没有配置工具),我加入zigbee协议栈的配置工具后还 ...

  5. TCP三次握手在linux内核中的实现

    TCP三次握手在linux内核中的实现 以下基于linux内核2.4.0源码(转自www.yuanma.org/) 以前一直使用的网络通讯的函数都是工作在阻塞模式.在看connect实现源码时,突然想 ...

  6. linux下IPROTO_TCP,TCP/IP协议栈在Linux内核中的运行时序分析

    可选题目三:TCP/IP协议栈在Linux内核中的运行时序分析 在深入理解Linux内核任务调度(中断处理.softirg.tasklet.wq.内核线程等)机制的基础上,分析梳理send和recv过 ...

  7. linux内核协议栈 TCP数据发送之发送窗口

    目录 1 发送窗口概述 2 snd_una 和 snd_wnd 的更新 2.1 发送窗口初始化 2.1.1 客户端初始化 2.1.2 服务器端初始化 2.2 本地接收窗口 rcv_wnd 通告 2.2 ...

  8. linux内核协议栈 TCP层数据发送之TSO/GSO

    目录 1 基本概念 2 TCP延迟分段判定 2.1 客户端初始化 2.2 服务器端初始化 2.3 sk_setup_caps() 3 整体结构 4. TCP发送路径TSO处理 4.1 tcp_send ...

  9. TCP/IP协议栈在Linux内核中的运行时序分析

    本文主要是讲解TCP/IP协议栈在Linux内核中的运行时序,文章较长,里面有配套的视频讲解,建议收藏观看. 1 Linux概述 1.1 Linux操作系统架构简介 Linux操作系统总体上由Linu ...

最新文章

  1. 编程控制Word文档中Table的赋值
  2. C++中函数概念解析(3)
  3. CPU加了缓存后,有人急了~
  4. UE4在VS2013中各个编译配置代表意义
  5. (转)Spring简介
  6. ES6——变量的解构赋值
  7. 剑指offer面试题[37]-两个链表的第一个公共结点
  8. 生成发布包_制作R包指南
  9. 北京“曼联梦剧场”项目开业,迎接中国球迷与家庭
  10. javafx 菜单组件_基础6:新菜单组件
  11. js 彻底理解回调函数
  12. 如何理解虚拟DOM?
  13. java 第十一章 多线程技术
  14. 编译调试 chromium/v8
  15. python数据分析入门学习笔记
  16. LeetCode 1348. 推文计数
  17. 计算机四级office试题及答案,2014年计算机一级ms office试题及答案 47
  18. AndroidPlayPlane战机小游戏
  19. Camtasia2023最好用的电脑屏幕录制软件
  20. cocos2d-x 艺术字

热门文章

  1. ewsa 字典_汉语字典小程序
  2. 网页HTML5制作flex布局骰子,CSS3的Flexbox骰子布局的实现及分析
  3. linux下配置ndk路径,NDK调试arm-linux-androideabi-addr2line工具的使用
  4. 6-1图像分类网络模型框架解读(上)
  5. 3-36Pytorch与tensorboardX
  6. java 页面接收参数_详解SpringMVC——接收请求参数和页面传参
  7. 全局安装python_python pip 安装与使用
  8. 算法提高 质因数2(java)
  9. java为什么用工厂模式_为什么用简单工厂模式,而不是直接实例化对象
  10. java jsr305_java – 为什么我需要添加神器JSR305才能使用Guava 14?