TCP/IP详解(卷2实现)学习笔记(一)udp的socket通信过程底层实现概述(1)

来源:互联网 发布:dvr9000监控软件下载 编辑:程序博客网 时间:2024/06/04 19:39

本文主要根据一个基本的BSD网络udp通信程序过程来阐述其在内核实现的基本情况。菜鸟水平有限,请诸大神谅解并批评指正。

(一)一个基本的BSD网络udp通信程序过程:

1.创建udp socket;(socket)

2.把服务器地址放到socketaddr_in中;

3.发送报文到服务器;sendto

4读取返回报文(recvfrom)

下来就这几个系统调用的底层机理来说明包处理的大致流程。

(二)从socket调用说起

一般我们把IP+port称之为socket(请与系统调用socket区分),一个socket通常可以表示一组通信。

sockfd = socket(PF_INET,SOCK_DGRAM,0).

系统调用socket,需要为其定义socket类型。PF_INET(Internet协议族)+SOCK_DGRAM(数据报)定义了一个udp协议的socket。

调用返回值是一个描述符。和一般的文件描述符fd一样,用户一般用它来进行操作,而避免了直接操作其相关数据结构(其实似乎也无权操作)。

其调用后的内核数据结构基本关系如下:


即一个socket的描述符其实和通常的文件描述符类似,用来在一个进程的进程表proc{}中索引对应的file{}结构,不过其类型不一样而已。

其实调用之后更加详细的内核数据结构图如下:


简单解释为一个描述符可以在进程的进程表中索引到一个file结构,这个file结构的f_data指向的内容由f_type决定。

f_type为DTYPE_SOCKET的file结构,这个结构的f_data指向的是一个socket结构。(socket对应的结构)

f_type为DTYPE_VNODE的file结构,这个结构的f_data指向的是一个VNODE结构。(一般文件对应的结构)

我们自然关注点在前者。在socket结构中暂时关注两个成员:

so_type :socket的类型如SOCK_DGRAM;

so_pcb:一个指向inpcb结构(Internet协议控制块)的指针。同时inpcb结构中的成员inp_socket指向对应的socket结构。

        inpcb结构中含有inp_faddr(远地IP地址)和inp_laddr(本地IP地址)以及两个port.(inp_fport,inp_lport).

       所有的UDP  PCB被组织成双链表。其表头为一个全局inpcb结构,标识为udb。

下面简单理解其作用:

当进程执行系统调用如sendto,内核从其进程表中的fd索引到对应file结构,从中找到对应的socket结构,其中的so_pcb 指向对应的inpcb结构;

当一个udp的数据报文到达一个网络接口时,内核搜索所有的udp协议控制块。根据ip+port来定位对应的inp_pcb结构,然后通过inp_socket指针找到对应socket结构。

(三)包输出处理过程

在讲述这个过程之前,必须先介绍一个基本数据结构mbuf.这个结构的主要作用是保存在进程和网络接口之间互相传递的用户数据。也用来保存其他各种数据,如源地址与目标地址,socket选项等。

1.      socket层的处理:(这里的socket层可以理解为一个特殊的层,并不能等价于用户层)

sendto函数的一个调用实例:

sendto(sockfd,buff,BUFFIZE,0,(structsockaddr*)&serv,sizeof(serv))

第一个参数是前文提到的描述符,用来在进程的进程表中索引到对应的socket结构。

复制目标socket地址到mbuf:

sendto的第五个参数指向一个Internet的socket地址结构。第六个参数表明其大小。

Socket层会先验证这些参数的合法性,并将socket地址结构复制到一个mbuf结构中。如下所示:


mbuf结构总长度为128字节,前20字节是其首部,包含关于此mbuf的信息.其中m_next 和m_nextpkt是用来链接mbuf结构的(参见后文),这里只用了一个结构,所以都为NULL。m_data和m_len分别用来指示数据的位置和大小。m_type指示数据的类型,这里是MT_SONAME(socket名称)。m_flag在此为0 ,其作用后文阐述。

复制数据到mbuf:

Sendto 的第二三个参数表明数据的缓冲区地址和大小。socket层将此数据缓冲区内容复制到一个或多个mbuf中,在本例中150字节缓冲区数据的存储情况如下:


由于mbuf的大小总共只有128字节,除去首部20字节,108字节,不够存储150字节的数据,所以数据被复制到多个mbuf之中。这些mbuf用成员m_next链在一块,称为mbuf链表。

变化是mbuf链表的首个mbuf的首部有些变化。m_flags 被赋值为M_PKTHDR,表明这是一个链表的开头mbuf结构(一个分组首部),m_pkthdr.len表明整个mbuf链的数据区长度,这样在需要总长度时无需遍历整个链表去做加法;m_pkthdr.rcvif是一个接受分组的接口结构的指针,这里是发送数据,所以为NULL.只有链首的mbuf结构会赋值这些域。

其他字段都类似,m_data,m_len定位了数据的地址和大小。m_type 为MT_DATA,表明是数据mbuf.可以看到链表的第一个mbuf数据缓冲区是100字节,其余都是108字节。

对于要发送缓冲区较大的数据,这种链表的结构可能会很长,因此便有了后面的“簇”技术。

udp输出例程处理:

在socket层把目标socket地址结构和发送数据复制到mbuf中后,与此socket描述符(udp描述符)对应的协议层被调用(udp输出例程被调用)。指向mbuf的指针作为该例程的一个参数。这个例程的主要工作就是在150字节的数据前添加一个IP首部和UDP首部,然后把处理过的mbuf再传递给IP输出例程。

         添加IP首部和UDP首部的方法是额外分配一个mbuf,放在之前数据mbuf链的链首,分组首部从原来的mbuf链的分组首部复制过来,并且修改长度。同时清除原来的mbuf链首的mbuf结构中m_pkthdr.len和m_pkthdr.rcvif域。图示如下:


注意,IP和TCP首部被放置在新的mbuf的最后面,这是因为要允许其他更底层协议在IP首部之前直接添加自己的首部,而不再复制,只需要修改m_data和m_len就行。

UDP输出例程填写UDP首部(包括UDP校验和)以及IP首部中所能填写的部分(如目标IP,但比如IP校验和要在IP输出例程中才能完成)。

然后此例程调用IP输出例程,把处理过的mbuf链表指针当做参数传递给IP输出例程。

IP输出例程处理:

主要任务填写IP首部剩余字段,包括IP校验和;确定数据包应教导哪个输出接口,必要时还得对IP报文分片。然后调用接口输出函数并把处理完的mbuf链表指针作为参数传递给它。(后文假定输出接口是一个以太网接口)

以太网输出例程处理:

这例程的第一个功能就是把IP地址转换成相应的mac地址。(在使用ARP时会用此功能)。然后以太网输出例程把以太网首部(目标mac地址、源mac地址、以太网帧类型等)添加到mbuf链表的第一个mbuf中。

之后这个链表会被添加到此接口的输出队列队尾。若接口不忙,则其“startoutput”例程被调用。接口处理其输出队列的mbuf链表时,会将其中的数据部分复制到自己的传输缓存中(这便是通常完整的数据包),并且输出;复制完后,mbuf链表便被释放。

至此,一个UDP数据包的发送过程基本阐述完毕。

(关于数据包的输入过程待后续---TCP/IP详解(卷2实现)学习笔记(一)udp的socket通信过程底层实现概述(2))


0 0