Linux网络编程API函数初步分析

明天咱们来剖析一下前几篇博文中提到的网络编程中几个外围的API,探索一下当咱们调用每个API时,内核中具体做了哪些筹备和初始化工作。

1、socket(family,type,protocol)

当咱们在开发网络应用程序时,应用该零碎调用来创立一个套接字。该API所做的工作如下所示:

该零碎调用次要实现两个工作:“创立套接字”和“为套接字绑定文件句柄”。

socket{}<include/linux/net.h>构造定义如下:

struct socket {    socket_state             state;  //socket状态    unsigned long           flags; //标识,如SOCK_ASYNC_NOSAPCE    const struct proto_ops  *ops; //协定特定的socket操作集    struct fasync_struct         *fasync_list; //异步唤醒队列    struct file                   *file; //指向文件的指针    struct sock                *sk; //指向下一层中的sock构造    wait_queue_head_t         wait; //期待在这个socket上的工作列表    short                           type; //数据包的类型};

在创立socket套接字时,就是要实现ops、file和sk等这些成员的初始化。

1). 创立套接字:sock_create()

依据family参数值在全局数组struct net_proto_family net_families[]里找到咱们所指定的地址簇。不同类型的地址簇都有一个struct net_proto_family{}类型的对象,例如咱们常见的IPv4的inet_family_ops,IPv6的inet6_family_ops,X25协定的ax25_family_ops等。在内核是初始化时,这些模块会在本人的初始化函数外部调用sock_register()接口将各自的地址簇对象注册到net_families[]数组里。

咱们剖析的焦点集中在IPv4协定簇,即inet_family_ops对象上。重点是inet_create函数,该函数的次要工作就是创立一个socket套接字,并对其中相干构造体成员进行必要的初始化。至于它创立套接字时的根据和原理等到咱们讲协定栈时大家就明确了,这里次要是让大家对其流程执行流程有个理性的把握。

sock_alloc()函数中咱们创立一个struct socket{}类型的对象,如果叫做A,将socket()零碎调用的第二参数type字段赋值给A->type。

在inet_create()函数中,咱们依据type的值,在全局数组 struct inet_protosw inetsw[]里找到咱们对应的协定转换开关。而inetsw[]数组是在inet_init()函数里被初始化的:

其中inetsw_array[]是一个比拟重要的数据结构,定义在af_inet.c文件中:

依据type的值,就能够确定struct socket{}->ops,到底是inet_stream_ops、inet_dgram_ops或者inet_sockraw_ops。而后,对应地,就以tcp_prot、udp_prot或raw_prot为输出参数,实例化一个struct sock{}对象sk=sk_alloc()。紧接着建设socket{}和sock{}的关联,最初将socket()零碎调用的第三个参数protocol付给sock{}对象中的属性sk_protocol。

看不懂别着急,我说过,这里只是给大家梳理整体流程,等到咱们讲了协定栈章节,而后再回头看本篇,就感觉这些货色就太小儿科了。

2). 为套接字绑定文件句柄:sock_map_fd()

咱们都晓得网络套接字也是一种零碎IO,所以不可避免的要与文件系统打交道。每个套接字都对应一个已关上的文件标识符,所以在套接字初始化实现后,就要将其和本地一个惟一的文件标识符关联起来,即建设socket{}和file{}之间的关联关系。

2、bind (sockfd, sockaddr, addrlen)

该零碎调用在内核中的执行过程如下:

重点是socket->ops->bind()回调接口。咱们当初曾经晓得了,针对IPv4而言,这里的ops无非就是inet_stream_opsinet_dgram_opsinet_sockraw_ops对象。碰巧的是,这三个对象中的bind函数指针均指向inet_bind()函数。只有原始套接字的状况,这里会去调用raw_prot对象的bind回调函数,即raw_bind()。

3、listen(sockfd, backlog)

这里咱们能够看到面向无连贯的套接字和原始套接字是不必listen的,只有流式套接字才无效。

4、connect(sockfd, sockaddr, addrlen)

从这幅图中咱们的确看到,connect()零碎调用岂但能够面向连贯的套接字,也可用于无连贯及原始套接字。

5、accept(sockfd, sockaddr, addrlen)

同样地,咱们看到只有面向连贯的流式套接字调用accept()才有意义。最终调用的是tcp_prot对象的accept成员函数。

须要C/C++ Linux高级服务器架构师学习材料加群812855908(包含C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)

Linux网络编程数据收发的API流程剖析

只有把数据在协定栈中的流动线路和脉络弄清楚了,对于协定栈的实现局部,了解起来就轻松多了。在网络编程章节的数据接管过程中,咱们次要介绍过read()、recv()、recvfrom()还有一个recvmsg()没介绍到,明天咱们就来看一下这几个API函数到底有什么差异。

数据接管

在接收数据的过程,次要分两个阶段:BOTTOM-HALFTOP-HALFBOTTOM-HALF

当从网卡驱动收到数据包后即进入BOTTOM-HALF阶段,在这里要依据以太帧头部中的类型字段来确定下层承载的具体协定类型,如IP,或ARP、RARP等。IP报文的处理函数通常交付给ip_recv()函数来解决,而后数据进入网络层,具体流程: 如果该数据包是发给本机的个别调用ip_local_deliver()函数,如果是须要本机转发给进来的,并且本机也开启了转发性能,那么就会调用ip_forward()函数。 在这里咱们看到了Netfilter的身影,好久没看到它了,还是有些亲切。大家能够联合这幅图回头再了解一下Netfilter和协定栈的关系。 BOTTOM-HALF最初将收到的skb填充到socket套接字的接管队列里,参见下图。

TOP-HALF

紧承BOTTOM-HALF阶段,该阶段的次要工作就是从接管队列里拿出一个skb而后将其传递到用户空间去,如下:

能够看出,这几个函数的外部最终都对立到了一起:__sock_recvmsg()。
数据发送

同样的,数据发送也分两个阶段,对照接管的状况,发送数据时必定也存在一个发送队列,这样想就对了。后面对于发送数据包时咱们介绍过的API有write()、send()、sendto()还有一个sendmsg()没介绍到。 TOP-HALF如下:

BOTTOM-HALF如下所示:

通过这么一份摸索,咱们对这几个数据收发的API至多了解的要比他人粗浅些了吧。