了解 TCP 系统调用序列.docx
- 文档编号:18315461
- 上传时间:2023-08-15
- 格式:DOCX
- 页数:9
- 大小:21.51KB
了解 TCP 系统调用序列.docx
《了解 TCP 系统调用序列.docx》由会员分享,可在线阅读,更多相关《了解 TCP 系统调用序列.docx(9页珍藏版)》请在冰点文库上搜索。
了解TCP系统调用序列
了解TCP系统调用序列
了解TCP系统调用序列2010-04-0111:
35TCP/IP编程接口提供各种系统调用,以帮助您有效地使用该协议。
TCP堆栈代码数量繁多,深入到内核级别的完整调用序列可以帮助您了解TCP堆栈。
在本文中,将回顾和学习关于TCP调用序列的详细信息,其中包括对FreeBSD的引用,以及在用户级进行系统调用后在TCP堆栈中发生的重要函数调用。
典型的TCP客户机和服务器应用程序通过发布TCP系统调用序列来获取某些函数。
这些系统调用包括socket()、bind()、listen()、accept()、send()和receive()。
本文介绍在应用程序发布TCP系统调用时在较低级别中发生的情况,如图1所示。
图2显示了TCP系统调用在物理链路上发出之前进行传播的各个层。
套接字层接收进行的任何TCP系统调用。
套接字层验证TCP应用程序传递的参数的正确性。
这是一个独立于协议的层,因为尚未将协议连接到调用中。
套接字层下面是协议层,该层包含协议的实际实现(本例中为TCP)。
当套接字层对协议层进行调用时,将确保对两个层之间共享的数据结构具有独占访问权限。
这样做是为了避免任何数据结构损坏。
各种网络设备驱动程序在接口层运行,该层从物理链路接收数据,并向物理链路传输数据。
每个套接字具有一个套接字队列,并且每个接口具有一个用于数据通信的接口队列。
不过,对于整个协议层,只有一个称为IP输入队列的协议队列。
接口层通过此IP输入队列将数据输入到协议层。
协议层使用相应的接口队列将数据输出到接口。
在本文中,将学习以下系统调用:
socket(structproc*p,structsocket_args*uap,intretval)structsock_args{intdomain,inttype,intprotocol;};
在socket系统调用中:
p是一个指针,指向进行socket调用的进程的proc结构。
uap是一个指向socket_args结构的指针,该结构包含传递到socket系统调用中的进程的参数。
retval是系统调用的返回值。
socket系统调用通过分配新的描述符创建新的套接字。
将新的描述符返回到调用进程。
任何后续的系统调用都使用创建的套接字标识。
socket系统调用还向创建的套接字描述符分配协议。
domain、type和protocol参数值指定系列、类型和协议,以分配给创建的套接字。
图3显示了调用序列。
从进程检索参数后,socket函数调用socreate函数。
socreate函数根据进程指定的参数发现指向协议切换protsw结构的指针。
socreate函数然后分配新的套接字结构。
然后进行协议特定的调用pr_usrreq,进而切换到与套接字描述符关联的相应协议特定的请求。
pr_usrreq函数的原型为:
intpr_usrreq(structsocket*so,intreq,structmbuf*m0,*m1,*m2);
在pr_usrreq函数中:
so是指向套接字结构的指针。
req的功能是标识请求。
本例中为PRU_ATTACH。
m0、m1和m2是指向mbuf结构的指针。
值因请求而异。
pr_usrreq函数为大约16个请求提供服务。
tcp_usrreq()函数调用tcp_attach(),以处理PRU_ATTACH请求。
要分配Internet协议控制块,可调用in_pcballoc()。
在in_pcballoc中,调用了内核的内存分配器函数,该函数将内存分配给Internet控制块。
完成所有必要的Internet控制块结构指针初始化之后,该控制返回到tcp_attach()。
分配新的TCP控制块,并在tcp_newtcpcb()中初始化。
它还初始化所有的TCP定时器变量,并且控制返回到tcp_attach()。
现在套接字状态初始化为CLOSED。
在返回到tcp_usrreq函数时,创建套接字描述符,以指向套接字的TCP控制块。
Internet控制块是双向链接的循环链表,其指针指向套接字结构,同时套接字结构的so_pcb部分指向Internet控制块结构。
Internet控制块还具有指向TCP控制块的指针。
有关Internet控制块和TCP控制块结构的更详细信息,请参见部分。
bind(structproc*p,structbind_args*uap,int*retval)structbind_args{ints;caddr_tname;intnamelen;};
在bind系统调用函数中:
s是套接字描述符。
name是指向包含网络传输地址的缓冲区的指针。
namelen是缓冲区的大小。
bind系统调用将本地网络传输地址与套接字关联。
对于客户端进程,发布bind调用不是强制的。
当客户端进程发布connect系统调用时,内核负责执行隐式绑定。
服务器进程接受连接或启动与客户端的通信之前,发布显式绑定请求通常是必需的。
bind调用将进程指定的本地地址复制到mbuf,并调用sobind,后者则根据请求使用PRU_BIND调用tcp_usrreq()。
tcp_usrreq()中的切换实例调用in_pcbbind(),后者将本地地址和端口号绑定到套接字。
in_pcbbind函数首先执行一些完整性检查,以确保不绑定套接字两次,并且至少一个接口分配了IP地址。
in_pcbbind负责隐式和显式绑定。
如果对in_pcbbind()(指向sockaddr_in结构的指针)的调用中的第二个参数为非空,则发生显式绑定。
其他情况下,则发生隐式绑定。
对于显式绑定,在绑定的IP地址上执行检查,并相应设置套接字选项。
如果指定的本地端口是一个非零值,则对超级用户特权进行检查,以确定绑定是否位于保留的端口(例如,根据Berkley约定,端口号1024)。
然后调用in_pcblookup(),以便查找具有提到的本地IP地址和本地端口号的控制块。
in_pcblookup()验证本地地址和端口对是否仍未使用。
如果in_pcbbind()中的第二个参数是NULL,或本地端口是零,则控制失败,并检查临时端口(例如,根据Berkley约定,1024端口号5000)。
然后调用in_pcblookup(),以验证发现的端口是否未使用。
listen(structproc*p,structlisten_args*uap,int*retval)structlisten_args{ints;intbacklog;};
在listen系统调用中:
s是套接字描述符。
backlog是套接字上的连接数的队列限制。
listen调用指示协议,服务器进程准备接受套接字上任何新传入的连接。
存在一个可以排列的连接数限制,在该连接数之后,忽略任何进一步的连接请求。
listen系统调用使用套接字描述符和listen调用中指定的backlog值调用solisten。
solisten仅使用PRU_LISTEN作为请求调用tcp_usrreq函数。
在tcp_usrreq()函数的切换语句中,PRU_LISTEN的实例检查套接字是否绑定到端口。
如果端口为零,则调用in_pcbbind(),将套接字绑定到一个端口(按照Bind部分中的描述)。
如果端口上已存在侦听的套接字,则将套接字的状态更改为LISTEN。
通常,所有的服务器进程都侦听众所周知的端口号。
很少调用in_pcbbind来执行服务器进程的隐式绑定。
图5显示了侦听的调用序列。
accept(structproc*p,structaccept_args*uap,int*retval);structaccept_args{ints;caddr_tname;int*anamelen;};
在accept系统调用中:
s是套接字描述符。
name是缓冲区(OUT参数),它包含外来主机的网络传输地址。
anamelen是name缓冲区的大小。
accept系统调用是等待传入连接的阻塞调用。
处理连接请求后,accept将返回新的套接字描述符。
将此新的套接字连接到客户端,使另外一个套接字s保持LISTEN状态,以接受进一步连接。
accept调用首先验证参数,并等待要到达的连接请求。
在此之前,函数在while循环中阻塞。
新的连接到达后,协议层唤醒服务器进程。
Accept然后检查函数阻塞时发生的任何套接字错误。
如果存在任何套接字错误,则函数返回,并继续从队列拾取新的连接并调用soaccept。
在soaccept()中调用tcp_usrreq()函数,并将请求作为PRU_ACCEPT。
tcp_usrreq函数中的切换调用in_setpeeraddr(),后者从协议控制块复制外来IP地址和外来端口号,并将其返回到服务器进程。
connect(structproc*p,structconnect_args*uap,int*retval);structconnect_args{ints;caddr_tname;intnamelen;};
在connect系统调用中:
s是套接字描述符。
name是指向具有外来IP/端口地址对的缓冲区的指针。
namelen是缓冲区的长度。
客户端进程通常调用connect系统调用,以连接到服务器进程。
如果在初始化连接之前,客户端进程没有显式发布bind系统调用,则堆栈负责本地套接字上的隐式绑定。
connect系统调用将外来地址(需要将连接请求发送到地址)从进程复制到内核,并调用soconnect()。
从soconnect()返回时,connect()函数进入睡眠状体,直到协议层将其唤醒,并指示连接是ESTABLISHED或套接字上存在错误。
soconnect()函数检查套接字的有效状态,并使用PRU_CONNECT作为请求调用pr_usrreq()。
tcp_usrreq()函数中的切换实例检查套接字与本地端口的绑定。
如果未绑定套接字,则调用执行隐式绑定的in_pcbbind()。
然后调用in_pcbconnect(),以获取到达目的地的路线,发现必须输出套接字的接口,并验证connect()指定的外来套接字对(IP地址和端口号)是否唯一。
然后使用外来IP地址和端口号更新其Internet控制块,并返回到PRU_CONNECT示例语句。
tcp_usrreq()现在调用soisconnecting(),它可以将客户端主机上的套接字的状态设置为SYN_SENT。
调用函数tcp_output,将SYN包输出到网络。
控制现在返回到connect()函数,该函数处于睡眠状态,直到协议层唤醒-指示连接现在是ESTABLISHED,或套接字上存在错误。
图8、图9和图10显示了客户端发布connect和服务器发布accept以指示和建立TCP连接时的调用序列。
当客户端发布connect时,在协议层调用tcp_output()函数,将SYN包输出到接口。
如图9所示,soconnect现在返回到connect()函数,并进入睡眠状态。
客户端上的套接字状态现在是SYN_SENT。
接口层调用if_output()(实际上是接口特定的输出函数),将包发送到n/w。
目的地(服务器)上的接口接收传入SYN包,将其放在ipintrq队列中,并引发软件中断。
包然后由调用tcp_input例程的ipintr()获取。
tcp_input()在s/w中断时执行,并从ipintrq拾取SYN包,对其进行处理,并将部分完成的套接字连接放入完成的套接字队列。
服务器端的套接字状态现在是SYN_RCVD。
每次处理后,tcp_input()例程都调用tcp_output()(如果需要将响应套接字发送到另一端)。
处理SYN后,服务器使用tcp_output()、ip_output()和if_output()序列发送SYNACK包。
客户端上的n/w接口接收此包,将其放在ipintrq中,并引发s/w中断。
同样,ipintr()从ipintrq获取该包,并将其传递到客户端TCP堆栈上的tcp_input()例程。
包现在是经过处理的,并调用了soisconnected(),它唤醒连接调用。
客户端上的套接字状态现在已建立。
客户端上的tcp_input()例程处理SYNACK包,并调用tcp_output()将ACK包发回到服务器。
服务器端上的tcp_input()处理此ACK包,并调用soisconnected()。
此函数从未完成的套接字队列移除套接字,并将其放入完成的套接字队列,然后调用Wakeup(),以唤醒accept调用。
服务器端的套接字现在已建立。
shutdown(structproc*p,structshutdown_args*uap,int*retval);Structshutdown_args{ints;inthow;}
在shutdown系统调用中:
s是套接字描述符。
how指定将关闭哪一部分连接。
how的值0、1和2分别指定关闭连接的读取部分、写入部分和同时关闭连接的读取及写入部分。
shutdown系统调用关闭连接的任意一端或两端。
如果需要关闭读取部分,则会丢弃接收缓冲区中存在的任何数据,并关闭该端的连接。
对写入部分,TCP发送任何剩余的数据,然后终止连接的写入端。
如果需要关闭连接的读取部分,则soshutdown()函数调用sorflush()。
sorflush()标记套接字以拒绝任何传入的包,并释放保存的任何系统资源。
如果需要关闭连接的写入部分,则调用tcp_usrreq(),并将PRU_SHUTDOWN作为请求。
PRU_SHUTDOWN的切换实例根据当前的状态调用tcp_usrclosed()函数,以更新套接字的状态。
TCP/IP状态图表可以帮助了解套接字在任何给定的时间存在的不同状态。
如果从tcp_usrclosed()返回时需要发送FIN,则调用tcp_output()将其发送到接口。
soo_close(structfile*fp,structproc*p);
在close系统调用中:
fp是指向文件结构的指针。
p是一个指向调用进程的proc结构的指针。
close系统调用可关闭或中止套接字上任何挂起的连接。
soo_close()仅调用so_close()函数,该函数首先检查要关闭的套接字是否为侦听套接字(正在接收传入连接的套接字)。
如果是,则遍历两个套接字队列,以检查任何挂起的连接。
对每个挂起的连接,将调用soabort()以发布tcp_usrreq(),并将PRU_ABORT用作请求。
此切换实例调用tcp_drop()以检查套接字的状态。
如果状态是SYN_RCVD,则通过将状态设置为CLOSED并调用tcp_output()发送RST段。
tcp_close()函数然后关闭套接字。
tcp_close函数更新路由度量结构的三个变量,然后释放套接字持有的资源。
如果套接字不是侦听套接字,则控制开始使用soclose(),以检查是否已存在附加到套接字的控制块。
如果不存在,则sofree()释放套接字。
如果存在,则调用具有PRU_DETACH的tcp_usrreq()将协议与套接字分离。
PRU_DETACH的切换实例调用tcp_disconnect(),以检查连接状态是否为ESTABLISHED。
如果不是,则tcp_disconnect()调用tcp_close(),以释放Internet和控制块。
否则,tcp_disconnect()检查延迟时间和延迟套接字选项。
如果设置了该选项,并且延迟时间为零,则调用tcp_drop()。
如果未设置,则调用tcp_usrclosed(),以设置套接字的状态,并调用tcp_output()(如果需要发送FIN段)。
图12显示了TCP应用程序发布close系统调用时发生的重要调用。
sendmsg(structproc*p,structsendmsg_args*uap,intretval);structsendmsg_args{ints;caddr_tmsg;intflags;};
在send系统调用中:
s是套接字描述符。
msg是指向msghdr结构的指针。
flags是控制信息。
n/w接口上有四个要发送数据的系统调用:
write、writev、sendto和sendmsg。
本文仅讨论sendmsg()系统调用。
所有的四个调用最终调用sosend()。
尽管send(进程调用的库函数)、sendto和sendmsg系统调用仅可以对套接字描述符操作,但write和writev系统调用则可以对任何类型的描述符操作。
sendmsg系统调用将从进程发送的消息复制到内核空间,并调用sendit()。
在sendit()中,将初始化一个结构,以便从进程将输出收集到内核中的内存缓冲区。
还可以将地址和控制信息从进程复制到内核,然后调用sosend(),以执行以下四项任务:
基于sendit()函数传递的值初始化各种参数。
验证套接字的条件和连接的状态,并确定传递消息和报告错误所需的空间。
分配内存并从进程复制数据。
使协议特定的调用将数据发送到网络。
然后调用tcp_usrreq(),并根据进程指定的标志,控制切换到PRU_SEND或PRU_SENDOOB(以发送带区外数据)。
对于PRU_SENDOOB,发送缓冲区大小可以超过512字节,将释放任何分配的内存并中断控制。
否则,sbappend()和tcp_output()函数由PRU_SEND和PRU_SENDOOB调用。
sbappend()在发送缓冲区的末尾添加数据,并且tcp_output()将该段发送到接口。
recvmsg(structproc*p,structrecvmsg_args*uap,int*retval);structrecvmsg_args{ints,structmsghdr*msg,intflags,};
在receive系统调用中:
s是套接字描述符。
msg是指向msghdr结构的指针。
flags指定控制信息。
有四个系统调用可以用于从连接接收数据:
read、readv、recvfrom和recvmsg。
尽管recv(进程使用的库函数)、recvfrom和recvmsg仅可以对套接字描述符操作,但read和readv可以对任何种类的描述符操作。
所有的read系统调用最终调用soreceive()。
图14显示了用于recvmsg系统调用的调用序列。
recvmsg()和recvit()函数初始化各种数组和结构,将接收的数据从内核发送到进程。
recvit()调用soreceive(),以便将接收的数据从套接字缓冲区传输到接收缓冲区进程。
soreceive()函数执行各种检查,如:
是否设置了MSG_OOB标志。
进程是否尝试接收数据。
是否应该阻塞,直到足够的数据到达。
将读取数据传输到进程。
检查数据是带区外数据还是常规数据,并进行相应的处理。
当数据接收完成后通知协议。
当设置MSG_OOB标志时或数据接收完成后,soreceive()函数进行与协议相关的请求。
在接收带区外数据的情况下,协议层检查不同的条件,以验证接收的数据是否为带区外数据,然后将其返回到套接字层。
在后一种情况中,协议层调用tcp_output(),将窗口更新段发送到网络。
它通知另一端任何空间都可用于接收数据。
在本文中,您学习了触发低级别调用以完成某些任务的最重要的TCP函数调用。
图中的调用序列显示了内核级TCP调用的简要概述。
本文是了解FreeBSDTCP/IP堆栈组织的很好起点。
学习
您可以参阅本文在developerWorks全球站点上的英文原文。
您可以通过访问"LINUXSocket编程"来了解更多的相关内容:
TCP/IPIllustrated,Volume2,TheImplementation,ISBN-10:
0-201-63354-X,作者:
GaryR.Wright和W.RichardStevens:
本书讨论在应用程序空间中使用的保护密钥。
有关inpcb和tcpcb结构的更详细信息,请参阅第22章。
受欢迎的内容:
查看您的同事所感兴趣的AIX和UNIX文章。
AIXandUNIX:
"AIXandUNIXdeveloperWorks"专区提供了大量与AIX系统管理的所有方面相关的信息,您可以利用它们来扩展自己的UNIX技能。
NewtoAIXandUNIX?
:
访问"NewtoAIXandUNIX"页面以学习更多关于AIX和UNIX的信息。
AIXWiki:
发现AIX相关技术信息的协作环境。
特别声明:
1:
资料来源于互联网,版权归属原作者
2:
资料内容属于网络意见,与本账号立场无关
3:
如有侵权,请告知,立即删除。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 了解 TCP 系统调用序列 系统 调用 序列
![提示](https://static.bingdoc.com/images/bang_tan.gif)