揭开网络编程常见API的面纱【上】

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套接字时,就是要完成 opsfilesk等这些成员的初始化。
 
       1). 创建套接字: sock_create()
       根据 family参数值在全局数组 struct net_proto_family net_families[]里找到我们所指定的地址簇。不同类型的地址簇都有一个 struct net_proto_family{}类型的对象,例如我们常见的 IPv4inet_family_opsIPv6inet6_family_opsX25协议的 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成员函数。
       本篇主要进一步分析了网络编程中常见的几个 API函数内部的调用流程,一方面可以使大家对这些 API的有了更深的认识,不是仅仅停留在形而上的层面;另一方面为后面分析协议栈的实现原理,奠定坚实的基础。
       未完,待续

你可能感兴趣的:(linux,网络编程)