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

相关文章

浅析如何保证MySQL与Redis数据一致性

《浅析如何保证MySQL与Redis数据一致性》在互联网应用中,MySQL作为持久化存储引擎,Redis作为高性能缓存层,两者的组合能有效提升系统性能,下面我们来看看如何保证两者的数据一致性吧... 目录一、数据不一致性的根源1.1 典型不一致场景1.2 关键矛盾点二、一致性保障策略2.1 基础策略:更新数

从基础到进阶详解Pandas时间数据处理指南

《从基础到进阶详解Pandas时间数据处理指南》Pandas构建了完整的时间数据处理生态,核心由四个基础类构成,Timestamp,DatetimeIndex,Period和Timedelta,下面我... 目录1. 时间数据类型与基础操作1.1 核心时间对象体系1.2 时间数据生成技巧2. 时间索引与数据

8种快速易用的Python Matplotlib数据可视化方法汇总(附源码)

《8种快速易用的PythonMatplotlib数据可视化方法汇总(附源码)》你是否曾经面对一堆复杂的数据,却不知道如何让它们变得直观易懂?别慌,Python的Matplotlib库是你数据可视化的... 目录引言1. 折线图(Line Plot)——趋势分析2. 柱状图(Bar Chart)——对比分析3

浅析Java如何保护敏感数据

《浅析Java如何保护敏感数据》在当今数字化时代,数据安全成为了软件开发中至关重要的课题,本文将深入探讨Java安全领域,聚焦于敏感数据保护的策略与实践,感兴趣的小伙伴可以了解下... 目录一、Java 安全的重要性二、敏感数据加密技术(一)对称加密(二)非对称加密三、敏感数据的访问控制(一)基于角色的访问

SpringBoot快速搭建TCP服务端和客户端全过程

《SpringBoot快速搭建TCP服务端和客户端全过程》:本文主要介绍SpringBoot快速搭建TCP服务端和客户端全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录TCPServerTCPClient总结由于工作需要,研究了SpringBoot搭建TCP通信的过程

Android实现一键录屏功能(附源码)

《Android实现一键录屏功能(附源码)》在Android5.0及以上版本,系统提供了MediaProjectionAPI,允许应用在用户授权下录制屏幕内容并输出到视频文件,所以本文将基于此实现一个... 目录一、项目介绍二、相关技术与原理三、系统权限与用户授权四、项目架构与流程五、环境配置与依赖六、完整

浅析如何使用xstream实现javaBean与xml互转

《浅析如何使用xstream实现javaBean与xml互转》XStream是一个用于将Java对象与XML之间进行转换的库,它非常简单易用,下面将详细介绍如何使用XStream实现JavaBean与... 目录1. 引入依赖2. 定义 JavaBean3. JavaBean 转 XML4. XML 转 J

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

MySQL重复数据处理的七种高效方法

《MySQL重复数据处理的七种高效方法》你是不是也曾遇到过这样的烦恼:明明系统测试时一切正常,上线后却频频出现重复数据,大批量导数据时,总有那么几条不听话的记录导致整个事务莫名回滚,今天,我就跟大家分... 目录1. 重复数据插入问题分析1.1 问题本质1.2 常见场景图2. 基础解决方案:使用异常捕获3.

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思