Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1910723
  • 博文数量: 376
  • 博客积分: 2147
  • 博客等级: 大尉
  • 技术积分: 3642
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-06 10:47
文章分类

全部博文(376)

文章存档

2019年(3)

2017年(28)

2016年(15)

2015年(17)

2014年(182)

2013年(16)

2012年(115)

我的朋友

分类: LINUX

2012-02-14 09:46:51

一、套接字与套接字接口

套接字是应用程序访问系统网络服务的接口。端到端的通信通过一对套接字来实现,一个套接字对应一个通信端点。

从实现来看,套接字是端端通信的抽象描述。在应用程序里,套接字对应一个整数值(套接字描述符);在内核里,套接字对应一个管理通信过程的对象(struct socket结构)。该结构与前面所说的整数值一一对应。

在 Linux系统内核中,struct socket结构对象不仅封装了管理通信过程的数据信息,还封装了负责网络通信的功能函数(其具体的实现方式有些类似MFC中的回调函数)。为了便于访问 这些功能函数,Linux提供了一系列编程接口函数,即套接字接口。通过这些接口,应用程序触发系统调用来调用上述功能函数,从而访问网络服务。

根据底层网络机制的差异,套接字可以定义为不同协议族的套接字。比如INET协议族套接字,UNIX域套接字等。在内核源文件include/linux/socket.h中,每个协议族标识符被定义为一个整数:

1: #define PF_UNSPEC AF_UNSPEC
 2: #define PF_UNIX AF_UNIX
3: #define PF_LOCAL AF_LOCAL
4: #define PF_INET AF_INET
 5: #define PF_AX25 AF_AX25
 6: ...

创建套接字时,可以通过参数选择协议族。如果指定为PF_INET协议族,则称套接字为INET套接字。INET套接字的接口函数提供了TCP/IP网络服务功能。

套接字接口会实现一系列功能以便应用程序使用,包括:套接字创建、地址绑定、连接请求、端口监听、连接请求允许、数据包发送和接收等。

二、套接字创建流程

应用程序采用socket函数创建套接字。socket会触发内核调用sys_socket函数,随后sys_socket又调用 sock_create函数。根据socket函数在参数中指定的协议族类型sock_create函数有选择地调用不同的套接字创建函数,例如,当指定 PF_INET协议族时,sock_create函数调用inet_create创建INET套接字。这些套接字函数首先创建套接字的内核表示结构,再返 回一个套接字描述符来标识生成的套接字对象。socket函数返回时,应用程序获得这个套接字描述符。

三、一些重要的套接字数据结构

Linux内核提供了一系列管理套接字的数据类型,此处只介绍几个比较重要的。其中,struct net_proto_family是协议族管理类型,负责不同协议族套接字的创建;struct socket是套接字结构类型,每个套接字在内核中都对应唯一的struct sockt结构;struct proto_ops是协议族套接字的操作集,统一管理套接字操作函数;struct sock是套接字在传输层的表示类型,为套接字指定传输层协议后,其struct socket的sk指针将指向一个与传输协议关联的struct sock结构;struct proto是传输层协议操作集,统一管理传输层协议的操作函数。

1、struct net_proto_family

1: struct net_proto_family 2: { 3: int family; //协议族标志 4: int (*create)(strcut socket *sock, int protocol); //套接字创建方法 5: short authentication; //认证管理字段 6: short encryption; //加密管理字段 7: short encrypt_net; 8: struct module * owner; 9: };

struct net_proto_family管理不同协议族套接字的创建方法,其中create指针指向具体协议族套接字的创建函数。在include/linux /socket.h文件中,内核用整数定义这些协议族。在初始化时,Linux系统支持的协议族被注册到数组static struct net_proto_family *net_families中。以下为一些常见协议族

1: #define PF_UNIX 1 //UNIX域协议族
2: #define PF_INET 2 //TCP/IP协议族
3: #define PF_IPX 4 //Novell网的IPX协议族
4:
#define PF_APPLETALK 5
5: #define PF_ATMPVC 8
6: #define PF_X25 9
7: #define PF_INET6 10
8: #define PF_NETLINK 16

Linux通过net_families表来管理协议族,该表是struct net_proto_family类型的指针数组,定义为:

1: static struct net_proto_family * net_families[NPROTO];

协议族初始化时,套接字创建方法被sock_register函数(位于net\socket.c中)注册到net_families中:

1: int sock_register(const struct net_proto_family *ops) 2: { 3: ... 4: if (rcu_dereference_protected(net_families[ops->family], 5: lockdep_is_held(&net_family_lock))) 6: err = -EEXIST; 7: else { 8: rcu_assign_pointer(net_families[ops->family], ops); 9: err = 0; 10: } 11: ... 12: }

比如INET协议族初始化时,函数inet_init调用sock_register来注册INET套接字的创建方法(struct net_proto_family类型变量inet_family_ops管理INET套接字的创建方法)。

sock_register函数定义如下:

1: int sock_register(const struct net_proto_family *ops) 2: { 3: int err; 4:  5: if (ops->family >= NPROTO) { 6: printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n", ops->family, 7: NPROTO); 8: return -ENOBUFS; 9: } 10:  11: spin_lock(&net_family_lock); 12: if (rcu_dereference_protected(net_families[ops->family], 13: lockdep_is_held(&net_family_lock))) 14: err = -EEXIST; 15: else { 16: rcu_assign_pointer(net_families[ops->family], ops); 17: err = 0; 18: } 19: spin_unlock(&net_family_lock); 20:  21: printk(KERN_INFO "NET: Registered protocol family %d\n", ops->family); 22: return err; 23: }

例如,inet_family_ops变量定义为:

1: struct net_proto_family inet_family_ops = 2: { 3: .family = PF_INET, 4: .create = inet_create, 5: .owner = THIS_MODULE 6: };

变量inet_family_ops管理INET协议族套接字的创建方法,其create指针指向inet_create函数。

上文提到的inet_init函数代码如下:

1: static int __init inet_init(void) 2: { 3: (void)sock_register(&inet_family_ops); 4: }

struct net_proto_family的定义代码位于文件include/linux/net.h

2、struct proto_ops



1: struct proto_ops 2: { 3: int family;//协议族 4: struct module *owner;//所属模块 5: //以下均为以函数指针形式指定的套接字操作 6: int (*release)(struct socket *sock); 7: int (*bind)(struct socket *sock, struct sockaddr *myaddr, 8: int sockaddr_len); 9: ... 10: };

struct proto_ops类型是协议族操作集。不同协议族套接字的操作函数可能不同,但Linux通过struct proto_ops中那些函数指针统一了接口,用这些函数指针来操作套接字。对于INET协议族的TCP和UDP协议,Linux分别提供了 inet_stream_ops和inet_dgram_ops两个struct proto_ops类型的变量,以inet_stream_ops为例:

1: struct proto_ops inet_stream_ops = 2: { 3: .family = PF_INET, 4: .owner = THIS_MODULE, 5: .release = inet_release, 6: .bind = inet_bind, 7: ... 8: }

struct proto_ops的声明位于include/linux/net.h

3、struct socket类型

1: struct socket 2: { 3: socket_state state;//状态值 4: unsigned long flags;//标识 5: struct proto_ops *ops;//指向一个struct proto_ops结构,为套接字提供协议族操作集 6: struct fasync_struct *fasync_list;//异步唤醒链表 7: struct file *file;//文件指针 8: struct sock *sk;//指向struct sock结构体,该结构体是套接字在传输层的表示结构 9: wait_queue_head_t wait;//等待队列 10: short type;//传输层数据类型 11: unsigned char passcred;//授权描述 12: };

struct socket类型统一表示不同协议族的套接字,它与应用程序引用的套接字描述符一一对应。成员ops指向struct proto_ops结构体,代表具体协议族套接字的操作集。比如若INET协议族采用TCP传输协议,那么ops指向的结构体为 inet_stream_ops;若INET协议族套接字采用UDP传输协议,那么ops指向的结构体等同于inet_dgram_ops。sk指向的 struct sock结构体代表了传输层的套接字结构,包含了与具体传输层协议相关的信息,如其中的sk_prot指针提供了传输层的操作集。

struct socket的定义代码位于文件include/linux/net.h

4、struct proto类型

1: struct proto 2: { 3: void (*close)(struct sock *sk, long timeout);//关闭套接字 4: ...//一系列函数指针 5: atomic_t *memory_allocated;//分配的内存数 6: atomic_t *sockets_allocated;//分配的套接字数 7: int *memory_pressure;//与memory_allocated有关的控制 8: int *sysctl_mem;//内存访问限制指针 9: int *sysctl_wmem; 10: int *sysctl_rmem; 11: int max_header;//最大头部 12: char name[32];//名字描述 13: struct 14: { 15: int inuse; 16: u8 __pad[SMP_CACHE_BYTES - sizeof(int)]; 17: }stats[NR_CPUS];//填充字段 18: };

该结构体封装了传输协议操作集。

5、struct sock类型

该结构体是套接字在传输层的表示结构。所有的套接字最后通过该结构来使用网络协议栈的服务。这个结构体定义于include/net/sock.h。

6、struct net_protocol类型

该结构体定义于include/net/protocol.h。

该结构管理传输层接收数据包的方法(也是通过函数指针)。例如TCP初始化时,内核在前文提到过的inet_init函数中注册了接收TCP数据包的方法用(struct net_protocol类型变量tcp_protocol表示),代码如下

1: if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0) 2: printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");

7、struct inet_protosw类型

该结构把INET套接字的协议族操作集与传输层协议操作集关联起来。

1: struct inet_protosw { 2: struct list_head list; 3:  4: /* These two fields form the lookup key. */ 5: unsigned short type; /* This is the 2nd argument to socket(2). */ 6: unsigned short protocol; /* This is the L4 protocol number. */ 7:  8: struct proto *prot;//传输层协议操作集 9: const struct proto_ops *ops;//协议族套接字操作集 10: 11: char no_check; /* checksum on rcv/xmit/none? */ 12: unsigned char flags; /* See INET_PROTOSW_* below. */ 13: };

在/net/ipv4/Af_inet.c中分别针对INET套接字中TCP、UDP和RAW三种协议,以数组方式定义了三个inet_protosw类型变量:

1: static struct inet_protosw inetsw_array[] = 2: { 3: { 4: .type = SOCK_STREAM, 5: .protocol = IPPROTO_TCP, 6: .prot = &tcp_prot, 7: .ops = &inet_stream_ops, 8: .no_check = 0, 9: .flags = INET_PROTOSW_PERMANENT | 10: INET_PROTOSW_ICSK, 11: }, 12:  13: { 14: .type = SOCK_DGRAM, 15: .protocol = IPPROTO_UDP, 16: .prot = &udp_prot, 17: .ops = &inet_dgram_ops, 18: .no_check = UDP_CSUM_DEFAULT, 19: .flags = INET_PROTOSW_PERMANENT, 20: }, 21:  22:  23: { 24: .type = SOCK_RAW, 25: .protocol = IPPROTO_IP, /* wild card */ 26: .prot = &raw_prot, 27: .ops = &inet_sockraw_ops, 28: .no_check = UDP_CSUM_DEFAULT, 29: .flags = INET_PROTOSW_REUSE, 30: } 31: };

这三个变量实现了INET协议族中各协议族操作集与对应的传输层操作的关联。在inet_init函数初始化INET协议族时,通过 inet_register_protosw把数组inetsw_array记录的信息注册到inetsw数组中,在系统实际使用时,以协议族为索引访问 inetsw。

struct inet_protosw位于include/net/protocol.h

8、Msghdr类型

该结构负责应用程序与内核交互网络数据,准备发送的数据信息封装在这里。该结构体位于include/linux/socoket.h

四、套接字创建流程及数据发送流程

以INET协议族为例:

image

套接字创建流程:

应用程序创建套接字时,调用socket函数,该函数触发内核的sys_socket函数。sys_socket函数引用 sock_create,sock_create又调用__sock_create来创建套接字结构,具体实现包括:struct socket结构对象,访问net_families变量得到协议族套接字的创建方法。对于INET套接字得到对应的套接字创建方法 inet_create。inet_create函数创建一个INET套接字(用socket结构体表示),并根据套接字类型来决定传输层所采用的协议。 socket结构体最重要的信息保存于其内部的sock结构体中(由sk指针指向该结构),inet_create为此结构分配内存,并根据套接字类型做 出不同的初始化。

阅读(1609) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~