socket数据发送过程zz.docx

上传人:b****2 文档编号:1619422 上传时间:2022-10-23 格式:DOCX 页数:9 大小:24.01KB
下载 相关 举报
socket数据发送过程zz.docx_第1页
第1页 / 共9页
socket数据发送过程zz.docx_第2页
第2页 / 共9页
socket数据发送过程zz.docx_第3页
第3页 / 共9页
socket数据发送过程zz.docx_第4页
第4页 / 共9页
socket数据发送过程zz.docx_第5页
第5页 / 共9页
点击查看更多>>
下载资源
资源描述

socket数据发送过程zz.docx

《socket数据发送过程zz.docx》由会员分享,可在线阅读,更多相关《socket数据发送过程zz.docx(9页珍藏版)》请在冰豆网上搜索。

socket数据发送过程zz.docx

socket数据发送过程zz

socket数据发送过程zz

本文在基于以下三个条件所写的:

1)OSI七层网络通信模型。

2)所阐述的函数是基于Linux2.6.1内核。

3)在面向连接的通信协议TCP/IPV4的基础上。

由于七层模型应用层,表示层,会话层,传输层,网络层,数据链路层,物理层可以简化为以下五层结构应用层(ApplicationLayer),传输层(TransportLayer),网络层(NetworkLayer),数据链路层(DataLinkLayer),物理层(PhysicalLayer).其中七层模型中的前三层都归结为五层结构中的应用层。

为了简化讨论,本文主要从这五层结构来探讨。

Layer5:

应用层ApplicationLayer

在TCP协议上,当通过三方握手建立了连接之后,就进入数据包的实质发送阶段,在本文中以send命令来阐述。

当通过send将数据包发送之后,glibc函数库会启用另外一个其定义的别用名函数__libc_sendto(),该函数最后会间接执行到sendto系统调用:

inline_syscall##nr(name,args);//##nr说明是该系统调用带有nr个args参数sendto系统调用的参数值是6,而name就是sendto

从上面的分析可以看出glibc将要执行的下面一条语句是

inline_syscall6(name,arg1,arg2,arg3,arg4,arg5,arg6)

在该函数中一段主要功能实现代码如下:

__asm____volatile__\

("callsys#%0%1=%2%3%4%5%6%7%8"\

inline_syscall_r0_out_constraint(_sc_0),\

"=r"(_sc_19),"=r"(_sc_16),"=r"(_sc_17),\

"=r"(_sc_18),"=r"(_sc_20),"=r"(_sc_21)\

"0"(_sc_0),"2"(_sc_16),"3"(_sc_17),"4"(_sc_18),\

"1"(_sc_19),"5"(_sc_20),"6"(_sc_21)\

inline_syscall_clobbers);\

_sc_ret=_sc_0,_sc_err=_sc_19;

该代码采用了嵌入汇编详细介绍查阅嵌入汇编相关书籍,其中:

_sc_0=sendto;

_sc_19--_sc_21分别是arg1-arg6;

inline_syscall_r0_out_constraint:

功能相当于"=r",选用一个寄存器来存储输出变量。

"0"--"6"分别是%0--%6,代表_sc_0--_sc_21

接下来函数最终通过Linux中顶顶有名的INT0X80陷入系统核心。

具体的过程可以参考内核相关书籍。

下面是一个兄弟对INT0X80的简要介绍:

在陷入系统内核以后,最终会调用系统所提供的系统调用函数sys_sendto(),该函数直接调用了__sock_sendmsg(),该函数对进程做一个简单的权限检查之后就触发套接字(socket)中定义的虚拟sendmsg的函数,进而进入到下一层传输层处理。

Layer4:

传输层(TransportLayer)

由上层的讨论可知,系统触发了sendmsg虚拟接口函数,其实就是传输层中的tcp_sendmsg或是udp_sendmsg,看你所使用的协议而定。

本文介绍tcp_sendmsg().

该函数需要做如下工作:

1)为sk_buff(后面简称skb)分配空间,该函数首先尝试在套接字缓冲队列中寻找空闲空间,如果找不到就使用tcp_alloc_pskb()为其重新分配空间。

2)下面这步就会tcp_sendmsg函数的主要部分了,将数据拷贝到缓冲区。

它分为如下两种情况:

2.1)如果skb还有剩余空间的话,就使用skb_add_data()来向skb尾部添加数据包。

代码如下:

if(skb_tailroom(skb)0){

/*Wehavesomespaceinskbhead.Superb!

*/

if(copyskb_tailroom(skb))

copy=skb_tailroom(skb);

if((err=skb_add_data(skb,from,copy))!

=0gotodo_fault;

}

2.2)如果skb没有了可用空间,内核会使用TCP_PAGE宏来为发送的数据包分配一个高速缓存页空间,当该页被正确地分配后就调用Copy_from_user(to(page地址),from(usr空间),n)将用户空间数据包复制到page所在的地址空间。

但是我们都知道数据包在协议层之间的传输是通过skb的,难道将数据包复制到这个新分配的page中,内核就可以去睡大觉了吗?

当然不是!

接下来内核就要来处理这个问题了,那么怎样来处理呢?

此时就需要使用到skb中的另外一个数据区structskb_shared_info,但是该数据区在创建skb时是没有为其分配空间的,也就是说它开始纯粹就是个指针,而没有具体的告诉它要指向什么地方。

这时大家应该知道它可以指向什么地方了,对,就是page!

在内核中对这种情况的具体是通过fill_page_desc(structsk_buff*skb,intI,structpage*page,intoff,intsize)来实现的,代码如下:

staticinlinevoidfill_page_descstructsk_buff*skb,inti,structpage*page,intoff,intsize){skb_frag_t*frag=&skb_shinfo(skb)-frags[i];frag-page=page;frag-page_offset=off;frag-size=size;skb_shinfo(skb)-nr_frags=i+1;}这里需要注意的是structskb_shared_info只能通过skb_shinfo来获取,在该结构体中skb_flag_t类型的flags[i]就是具体指向page的数组。

2.3)至此skb数据包的装载工作算是结束了,接下来就需要做一些后续工作,包括是否要分片,以及后来的TCP协议头的添加。

先看在tcp_sendmsg()中的最后一个重要函数tcp_push,它的调用格式如下:

staticinlinevoidtcp_pushstructsock*sk,structtcp_opt*tp,intflags,intmss_now,intnonagle)细心的朋友会发现,在该函数中传输的竟然不是skb,而是一个名为sock的结构体,那这又是什么东东呢?

个人理解是它在顶层协议层之间例如:

应用层和传输层之间的传输起着非常重要的作用,相当于沟通两层之间的纽带。

再深入查找下该结构体的构成,我们很容易发现这样一个结构体变量:

structsk_buff_head,有名称我们可以知道它是用来描述skb头部信息的一个结构体,它指向了buffer的数据区。

这下我们也明白了点,这个结构体其实还充当了一个队列作用,是用来存储skb的数据区。

协议层之间传输完之后,具体到该层处理时内核就会从sk_buff_head逐个中取出skb数据区来处理,例如添加协议头等。

好了,tcp_sendmsg到此结束了它的使命了,下面将要需要的一个函数就是在tcp_push()中直接用到的一个函数:

__tcp_push_pending_frames(),该函数又直接调用tcp_write_xmit()函数来进一步对数据包处理,它包括一下两步:

1)检查是否需要对数据包进行分片,条件是只要skb中全部数据长度大于当前路由负荷量就需要分片。

2)采用skb_clone(skb,GFP_ATOMIC)为TCP_HEAD分配一个sk_buff空间,这里需要注意的是skb_clone分配空间的特点,它首先是依照参数skb来来复制出一个新的sk_buff,新的skb和旧的skb共享数据变量缓存区,但是结构体缓冲区不是共享的,这似乎和copyonwrite机制有些相似。

3)在分配了一个新的skb之后,内核就会执行tcp_transmit_skb().其实内核中是将2,3步合在一起的,如下:

tcp_transmit_skb(sk,skb_clone(skb,GFP_ATOMIC))

接下来就是tcp_transmit_skb函数的实现过程了。

1)通过skb_push()在skb前面加入tcp协议头信息。

这包括序列号,源地址,目的地址,校验和等。

2)通过tcp_opt结构体它是在该函数的开始部分从sock结构体中获得的tcp_func结构体中的.queue_xmit虚拟功能函数,在IPV4中是调用了ip_queue_xmit(),这样就进入了下一层--网络层。

Layer3网络层(NetworkLayer)

在ip_queue_xmit()函数中需要做的事情有一下几件:

1)是否需要将数据包进行路由,如果需要的话就跳到包路由子程序段。

判断是否需要路由是由如下语句执行的:

rt=(structrtable*)skb-dst;if(rt!

=NULL)gotopacket_routed;在skb的dst变量中指明发送目标地址。

它存放了路由路径中的下台主机地址。

如果是需要对数据包进行路由,那么其执行分如下步骤

1.1)使用skb_push()在skb前面插入一段ip_headsize大小的空间。

1.2)填写ip协议头,包括ttl,protocol等

1.3)写入校验和,最后调用NF_HOOK宏,关于NF_HOOK后面介绍。

调用的NF_HOOK宏语句如下:

NF_HOOK(PF_INET,NF_IP_LOCAL_OUT,skb,NULL,rt-u.dst.dev,dst_output);2)如果没有路由地址,内核会尝试从外部可选项中来获取该地址,此时传输层发现没有路由地址会不断地发出重发机制,直到路由地址获取到。

当获取到路由地址之后,内核会通过以下语句重新将地址赋给skb-dst.之后就会进入到1)所述的路由子程序段执行。

skb-dst=dst_clone(&rt-u.dst);

所以这样看来正常情况下内核都会进入1.3)所阐述的NF_HOOK宏的执行。

关于NF_HOOK宏,我也不怎么了解,但是查了下内核后可以大体的知道,当二维数组nf_hooks[pf][hook](其下标分别是调用宏中的第一个和第二个参数中定义了需要的钩子函数时,就会调用nf_hook_slow函数来处理,如果没有定义钩子函数就直接调用NF_HOOK中的最后一个参数所指向的函数,在这里是:

dst_output(skb)。

在网上搜了下,发现一篇讲解NF_HOOK的帖子,很详细,链接如下:

上面已经谈到,当存在钩子函数时,内核转向nf_hook_slow函数来处理。

下面阐述下这个函数:

1)检查hook函数是否真的已经设置,如果没有设置就将hook对应位通过移位来设置;当确认已经设置后就取出该钩子函数,如下:

elem=&nf_hooks[pf][hook];2)执行nf_iterate()函数,该函数采用list_for_each_continue_rcu()HOOK链表中的每个nf_hook_ops钩子结构体,通过其内部变量priority来判断它的优先级是否大于系统所定义的INT_MIN,如果小于就继续搜索,否则就执行该结构体单元中所指向的钩子函数。

if(hook_threshelem-priority)continue;/*Optimization:

wedon'tneedtoholdmo

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > IT计算机 > 互联网

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1