tcp紧急数据处理源码浅析

2024-03-27 21:48

本文主要是介绍tcp紧急数据处理源码浅析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

tcp紧急数据用于一端有紧急通知需要告之对端的时候,他传输的其实是一种命令或者说信号,而不算是数据,因为他只有一个字节。对端收到紧急数据后会给对应的进程发送一个信号,通知该进程有紧急的命令需要处理(前提是设置了进程或者进程组来处理紧急数据)。下面看一下紧急数据的发送。入口函数是tcp_write。关键代码如下。

  1. 之前缓存了小量数据,还没发送。则先发送出去。
if ((skb = tcp_dequeue_partial(sk)) != NULL) {int hdrlen;/* IP header + TCP header */// 所有协议头的长度hdrlen = ((unsigned long)skb->h.th - (unsigned long)skb->data)+ sizeof(struct tcphdr);...// 数据部分大于等于mss或者是紧急数据或者还没有发出去一个数据包则直接发送if ((skb->len - hdrlen) >= sk->mss ||(flags & MSG_OOB) || !sk->packets_out)tcp_send_skb(sk, skb);else// 继续缓存,满足条件后一起发送tcp_enqueue_partial(skb, sk);continue;}

2 然后则构造一个新的skb发送。

// 可发送的序列化最大值 - 下一个可写的序列化值等于可以发送的字节数,如果当前可以发送的数据量太大,这里会导致紧急数据不在当前的tcp报文中,需要等下一个报文才会发送真正的紧急数据,但是该tcp报文还是会设置紧急指针和紧急标记位copy = sk->window_seq - sk->write_seq;if (copy <= 0 || copy < (sk->max_window >> 1) || copy > sk->mss)copy = sk->mss;// 能发送的比需要发送的大,则取需要发送的if (copy > len)copy = len;...
// 构建ip头和mac头,返回ip头+mac头的长度的大小,查找路由项的时候会给dev赋值tmp = prot->build_header(skb, sk->saddr, sk->daddr, &dev,IPPROTO_TCP, sk->opt, skb->mem_len,sk->ip_tos,sk->ip_ttl);if (tmp < 0 ) {prot->wfree(sk, skb->mem_addr, skb->mem_len);release_sock(sk);if (copied) return(copied);return(tmp);}// 更新data中的数据长度skb->len += tmp;skb->dev = dev;// 指向可写地址,准备写入tcp头buff += tmp;// skb的tcp头指向data字段的tcp头skb->h.th =(struct tcphdr *) buff;// 构建tcp头,len-copy表示是否已经传输完len字节的数据,用于设置push标记tmp = tcp_build_header((struct tcphdr *)buff, sk, len-copy);if (tmp < 0) {prot->wfree(sk, skb->mem_addr, skb->mem_len);release_sock(sk);if (copied) return(copied);return(tmp);}// 带外数据if (flags & MSG_OOB) {	// 设置urg标记位,设置紧急指针指向紧急数据的后面一个字节,并且只有这个字节算紧急数据((struct tcphdr *)buff)->urg = 1;((struct tcphdr *)buff)->urg_ptr = ntohs(copy);}// 更新skb->data中的数据长度skb->len += tmp;// 复制copy个字节到tcp头后面成为tcp报文的负载memcpy_fromfs(buff+tmp, from, copy);// 更新需要复制的数据地址from += copy;// 复制字节数累加copied += copy;// 还有多少个字节需要复制len -= copy;// 更新skb->data的数据长度skb->len += copy;skb->free = 0;// 更新下一个tcp报文的序列化sk->write_seq += copy;// 数据量太少并且不是紧急数据,并且有待确认的包(nagle算法规则),则先缓存if (send_tmp != NULL && sk->packets_out) {tcp_enqueue_partial(send_tmp, sk);continue;}// 否则直接发送tcp_send_skb(sk, skb);

由上代码知道,构造完新的数据包后,直接调用tcp_send_skb函数,下面我们看一下该函数的代码。实现里如果tcp因为某些原因导致不能发送数据包的时候,会先缓存该skb,但是紧急数据是不受拥塞控制影响的,还是应该直接发送。这里待研究。

/**	This is the main buffer sending routine. We queue the buffer*	having checked it is sane seeming.*/
// 发送数据包 
static void tcp_send_skb(struct sock *sk, struct sk_buff *skb)
{int size;// 指向skb->data字段了的tcp头地址struct tcphdr * th = skb->h.th;/**	length of packet (not counting length of pre-tcp headers) */// tcp头+数据的长度size = skb->len - ((unsigned char *) th - skb->data);/**	Sanity check it.. */if (size < sizeof(struct tcphdr) || size > skb->len) {printk("tcp_send_skb: bad skb (skb = %p, data = %p, th = %p, len = %lu)\n",skb, skb->data, th, skb->len);kfree_skb(skb, FREE_WRITE);return;}/**	If we have queued a header size packet.. (these crash a few*	tcp stacks if ack is not set)*/// 相等说明待发送的数据长度0if (size == sizeof(struct tcphdr)) {/* If it's got a syn or fin it's notionally included in the size..*/// 不是syn或fin包则报错,只有这两种包的负载可以为0if(!th->syn && !th->fin) {printk("tcp_send_skb: attempt to queue a bogon.\n");kfree_skb(skb,FREE_WRITE);return;}}/**	Actual processing.*/tcp_statistics.TcpOutSegs++; // size - 4*th->doff为数据负载的大小 skb->h.seq = ntohl(th->seq) + size - 4*th->doff;/**	We must queue if**	a) The right edge of this frame exceeds the window*	b) We are retransmitting (Nagle's rule)*	c) We have too many packets 'in flight'*/// 包的序列号大于可以发送的最大序列号,正在进行超时重传(nagle算法规定只能有一个未收到确认的包,发出的包大于拥塞窗口了	 if (after(skb->h.seq, sk->window_seq) ||(sk->retransmits && sk->ip_xmit_timeout == TIME_WRITE) ||sk->packets_out >= sk->cong_window) {/* checksum will be supplied by tcp_write_xmit.  So* we shouldn't need to set it at all.  I'm being paranoid */th->check = 0;if (skb->next != NULL) {printk("tcp_send_partial: next != NULL\n");skb_unlink(skb);}// 插入待发送队列skb_queue_tail(&sk->write_queue, skb);/**	If we don't fit we have to start the zero window*	probes. This is broken - we really need to do a partial*	send _first_ (This is what causes the Cisco and PC/TCP*	grief).*/// 可发送的最大序列号小于包的序列号,并且没有等待确认的包,则需要发送窗口探测包看能不能继续发送数据 if (before(sk->window_seq, sk->write_queue.next->h.seq) &&sk->send_head == NULL && sk->ack_backlog == 0)reset_xmit_timer(sk, TIME_PROBE0, sk->rto);} else {/**	This is going straight out*/// 希望对方传输的数据的序列化,即小于ack_seq的都收到了th->ack_seq = ntohl(sk->acked_seq);th->window = ntohs(tcp_select_window(sk));tcp_send_check(th, sk->saddr, sk->daddr, size, sk);// 将要发送的数据包第一个字节的序号 sk->sent_seq = sk->write_seq;/**	This is mad. The tcp retransmit queue is put together*	by the ip layer. This causes half the problems with*	unroutable FIN's and other things.*/// 使用ip_queue_xmit发送sk->prot->queue_xmit(sk, skb->dev, skb, 0);/**	Set for next retransmit based on expected ACK time.*	FIXME: We set this every time which means our *	retransmits are really about a window behind.*/// 设置定时器用于超时重传reset_xmit_timer(sk, TIME_WRITE, sk->rto);}
}

至此,一个带有紧急数据的tcp报文就发送到对端了。下面看一下对端的接收实现代码。入口函数是tcp_rcv,但是真正处理的代码是tcp_urg。下面是该函数代码。

 
extern __inline__ int tcp_urg(struct sock *sk, struct tcphdr *th,unsigned long saddr, unsigned long len)
{unsigned long ptr;/**	Check if we get a new urgent pointer - normally not */// 报文设置了紧急标记位,说明有紧急数据需要处理 if (th->urg)tcp_check_urg(sk,th);/**	Do we wait for any urgent data? - normally not*/// 在tcp_check_urg里设置,说明紧急数据有效,紧急数据可被当做普通数据处理。if (sk->urg_data != URG_NOTYET)return 0;/**	Is the urgent pointer pointing into this packet? */// 指向紧急数据相对偏移ptr = sk->urg_seq - th->seq + th->doff*4;if (ptr >= len)return 0;/**	Ok, got the correct packet, update info */// urg_data是两个字节,一个保存标记,一个保存一个字节的紧急数据sk->urg_data = URG_VALID | *(ptr + (unsigned char *) th);if (!sk->dead)sk->data_ready(sk,0);return 0;
}static void tcp_check_urg(struct sock * sk, struct tcphdr * th)
{	// 指向紧急数据最后一个字节的下一个字节unsigned long ptr = ntohs(th->urg_ptr);// ptr指向有效数据的最后一个字节,if (ptr)ptr--;// 数据的第一个字节的序列号+偏移,ptr指向紧急数据偏移ptr += th->seq;/* ignore urgent data that we've already seen and read */// ptr小于可以已读取的字节的序列号,说明ptr指向的数据被读取过了if (after(sk->copied_seq, ptr))return;/* do we already have a newer (or duplicate) urgent pointer? */// 之前已经收到过紧急数据,并且之前收到的紧急数据序列号比现在收到的大if (sk->urg_data && !after(ptr, sk->urg_seq))return;/* tell the world about our new urgent pointer */// 通知进程或组收到紧急数据if (sk->proc != 0) {if (sk->proc > 0) {// 给进程发送一个SIGURG信号kill_proc(sk->proc, SIGURG, 1);} else {// 给进程组发送一个SIGURG信号kill_pg(-sk->proc, SIGURG, 1);}}// 标记紧急数据有效sk->urg_data = URG_NOTYET;// 设置紧急数据的序列号sk->urg_seq = ptr;
}

从上面的代码中看到,tcp处理紧急数据的时候,最后把紧急数据的有效标记和数据存储在sk->urg_data字段里,所以紧急数据的大小只有一个字节,并且会发生覆盖。至此,处理收到的紧急数据已经完成。还有最后一步就是,收到紧急数据的时候会给进程或进程组发送一个信号,那进程在信号的处理函数里会调用tcp_read来读取紧急数据。实际处理逻辑在tcp_read_urg函数,下面我们看实现的代码。

tcp_read:if (flags & MSG_OOB)return tcp_read_urg(sk, nonblock, to, len, flags);tcp_read_urg:static int tcp_read_urg(struct sock * sk, int nonblock,unsigned char *to, int len, unsigned flags)
{/**	No URG data to read*/// 没有紧急数据或者紧急数据被读取了又或者紧急数据当作普通数据处理了if (sk->urginline || !sk->urg_data || sk->urg_data == URG_READ)return -EINVAL;	/* Yes this is right ! */...sk->inuse = 1;// urg_data是两个字节,一个字节保存紧急数据有效标记,一个保存一个字节的紧急数据if (sk->urg_data & URG_VALID) {char c = sk->urg_data;// 如果不是预读,则读完后设置已读,下次读的时候就直接返回错误if (!(flags & MSG_PEEK))sk->urg_data = URG_READ;// 只读一个字节put_fs_byte(c, to);release_sock(sk);return 1;}release_sock(sk);/** Fixed the recv(..., MSG_OOB) behaviour.  BSD docs and* the available implementations agree in this case:* this call should never block, independent of the* blocking state of the socket.* Mike <pall@rz.uni-karlsruhe.de>*/return -EAGAIN;
}

这篇关于tcp紧急数据处理源码浅析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/853489

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除

【Go】go连接clickhouse使用TCP协议

离开你是傻是对是错 是看破是软弱 这结果是爱是恨或者是什么 如果是种解脱 怎么会还有眷恋在我心窝 那么爱你为什么                      🎵 黄品源/莫文蔚《那么爱你为什么》 package mainimport ("context""fmt""log""time""github.com/ClickHouse/clickhouse-go/v2")func main(

2024.9.8 TCP/IP协议学习笔记

1.所谓的层就是数据交换的深度,电脑点对点就是单层,物理层,加上集线器还是物理层,加上交换机就变成链路层了,有地址表,路由器就到了第三层网络层,每个端口都有一个mac地址 2.A 给 C 发数据包,怎么知道是否要通过路由器转发呢?答案:子网 3.将源 IP 与目的 IP 分别同这个子网掩码进行与运算****,相等则是在一个子网,不相等就是在不同子网 4.A 如何知道,哪个设备是路由器?答案:在 A