socket函数的domain、type、protocol解析
来源:互联网 发布:java 短链接 编辑:程序博客网 时间:2024/05/23 15:07
socket函数的domain、type、protocol解析
lxg@2015-04-09
内核中的socket概览
图一:socket概览
内核中套接字是一层一层进行抽象展示的,把共性的东西抽取出来,这样对外提供的接口可以尽量的统一。内核中把套接字的定义会抽象出来展示,如struct sock->struct inet_sock->struct tcp_sock从抽象到具体。还会把套接字的操作也会抽象,下面我们会提到怎么进行抽象展示的。
Socket函数中的三个参数其实就是把抽象的socket具体化的条件,domain参数决定了图中所示的第二层通信域,type决定了第三层的通信模式,protocol决定了第四层真正的通信协议。
Domain参数
Domain参数指定了通信的”域”(在后文中会用family替代domain),我们是在IPv4还是IPv6这个范围内通信,也就决定了我们通信的地址是IPv4格式还是IPv6格式。通常可选的定义如下:
名称
目的
AF_UNIX, AF_LOCAL
本地通信
AF_INET
IPv4网络通信
AF_INET6
IPv6网络通信
AF_PACKET
链路层通信
在Linux系统中AF_*和PF_*是等价的。
在内核源码中net目录下面有Af_开头的一系列文件(如:Af_inet.c、Af_inet6.c、Af_unix.c等),每一个文件分别代表了一种协议族。
在Net.h中定义了一个结构体:
//net_proto_family结构体定义了每一个协议族的新建socket句柄
struct net_proto_family {
int family;
int (*create)(struct net *net,struct socket *sock,
int protocol,int kern);
struct module *owner;
};
//Af_inet.c中的PF_INET domain的定义
staticconststruct net_proto_family inet_family_ops ={
.family= PF_INET,
.create= inet_create,
.owner = THIS_MODULE,
};
在协议栈初始化的通过sock_register(conststruct net_proto_family *ops)(socket.c)函数把协议栈支持的协议族family加入net_families数组中。
当我们通过socket系统调用创建套接字的时候流程走到__sock_create函数(SYSCALL_DEFINE3->sock_create->__sock_create)的时候根据family在net_families数组中取得对应协议族的create句柄,所以对于PF_INET协议族的套接字就是调用inet_create来新建socket。
通过上面分析可知family这个参数决定了调用哪个协议族create函数来新建socket,说得可能不准确点就是决定了你使用net/目录下面的哪个Af_*.c文件中的函数。
Type参数
Type就是socket的类型,对于AF_INET协议族而言有流套接字(SOCK_STREAM)、数据包套接字(SOCK_DGRAM)、原始套接字(SOCK_RAW)。
/**
* enum sock_type - Socket types
* @SOCK_STREAM: stream (connection) socket
* @SOCK_DGRAM: datagram (conn.less) socket
* @SOCK_RAW: raw socket
* @SOCK_RDM: reliably-delivered message
* @SOCK_SEQPACKET: sequential packet socket
* @SOCK_DCCP: Datagram Congestion Control Protocol socket
* @SOCK_PACKET: linux specific way of getting packets at the dev level.
* For writing rarp and other similar things on the user level.
*/
enum sock_type {
SOCK_STREAM =1,
SOCK_DGRAM =2,
SOCK_RAW =3,
SOCK_RDM =4,
SOCK_SEQPACKET =5,
SOCK_DCCP =6,
SOCK_PACKET =10,
};
在内核协议栈中有一个很重要的结构体,定义了每一个协议族中套接字在传输层的操作集合。PS:这里我理解传输层包括了套接字类型(STREAM,DGRAM)和具体的传输层协议(TCP,UDP)
/* This is used to register socket interfaces for IP protocols. */
struct inet_protosw {
struct list_head list;
/* These two fields form the lookup key. */
unsignedshort type; /* This is the 2nd argument to socket(2). */
unsignedshort protocol;/* This is the L4 protocol number. */
struct proto *prot;
conststruct proto_ops *ops;
unsignedchar flags; /* See INET_PROTOSW_* below. */
};
struct proto_ops结构体定义了每一种套接字类型(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW)的操作集合,在Af_inet.c中分别定义了inet_stream_ops、inet_dgram_ops、inet_sockraw_ops这三种类型的proto_ops:
conststruct proto_ops inet_stream_ops ={
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,
.bind = inet_bind,
.connect = inet_stream_connect,
.socketpair = sock_no_socketpair,
.accept = inet_accept,
.getname = inet_getname,
.poll = tcp_poll,
.ioctl = inet_ioctl,
.listen = inet_listen,
.shutdown = inet_shutdown,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = inet_sendmsg,
.recvmsg = inet_recvmsg,
.mmap = sock_no_mmap,
.sendpage = inet_sendpage,
.splice_read = tcp_splice_read,
#ifdef CONFIG_COMPAT
.compat_setsockopt= compat_sock_common_setsockopt,
.compat_getsockopt= compat_sock_common_getsockopt,
.compat_ioctl = inet_compat_ioctl,
#endif
};
我们新建socket的时候指定了type为SOCK_STREAM那么在后面的套接字操作中(如connect)会调用对应的inet_stream_ops中对应的函数(如inet_stream_connect)。
图2:inetsw、inet_protosw、proto_ops、proto的关系图
Protocol参数
上文中我们通过family和type已经基本确定了新建的socket具体是什么类型的套接字,最后一步通过protocol来确定socket到底支持的哪个协议(TCP?UDP?)。
在inet_protosw结构体中我们已经解释过proto_ops对应的是每一种套接字类型的操作集合,那么可知structproto对应的就是具体协议的操作集合。在Tcp_ipv4.c中定义TCP协议的struct proto tcp_prot:
struct proto tcp_prot ={
.name ="TCP",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v4_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy = tcp_v4_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.recvmsg = tcp_recvmsg,
.sendmsg = tcp_sendmsg,
.sendpage = tcp_sendpage,
.backlog_rcv = tcp_v4_do_rcv,
.release_cb = tcp_release_cb,
.hash = inet_hash,
.unhash = inet_unhash,
.get_port = inet_csk_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.stream_memory_free= tcp_stream_memory_free,
.sockets_allocated =&tcp_sockets_allocated,
.orphan_count =&tcp_orphan_count,
.memory_allocated =&tcp_memory_allocated,
.memory_pressure =&tcp_memory_pressure,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem = sysctl_tcp_wmem,
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER,
.obj_size =sizeof(struct tcp_sock),
.slab_flags = SLAB_DESTROY_BY_RCU,
.twsk_prot =&tcp_timewait_sock_ops,
.rsk_prot =&tcp_request_sock_ops,
.h.hashinfo =&tcp_hashinfo,
.no_autobind = true,
#ifdef CONFIG_COMPAT
.compat_setsockopt = compat_tcp_setsockopt,
.compat_getsockopt = compat_tcp_getsockopt,
#endif
#ifdef CONFIG_MEMCG_KMEM
.init_cgroup = tcp_init_cgroup,
.destroy_cgroup = tcp_destroy_cgroup,
.proto_cgroup = tcp_proto_cgroup,
#endif
};
所以对于TCP socket当执行connect连接的时候经过的流程大致是SYSCALL_DEFINE3 connect(系统调用)->inet_stream_connect(inet_stream_ops中定义)-> tcp_v4_connect(tcp_prot中定义)。
Family&type&protocol结合
上面分别说明了family、type、protocol这三个参数代表的意义,接下来我们把这三个参数结合起来一起看一下最后的效果。
structinet_protosw结构体在内核中是如何初始化的呢?
/* Upon startup we insert all the elements in inetsw_array[] into
* the linked list inetsw.
*/
staticstruct inet_protosw inetsw_array[]=
{
{
.type= SOCK_STREAM,
.protocol= IPPROTO_TCP,
.prot= &tcp_prot,
.ops= &inet_stream_ops,
.flags= INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
{
.type= SOCK_DGRAM,
.protocol= IPPROTO_UDP,
.prot= &udp_prot,
.ops= &inet_dgram_ops,
.flags= INET_PROTOSW_PERMANENT,
},
{
.type= SOCK_DGRAM,
.protocol= IPPROTO_ICMP,
.prot= &ping_prot,
.ops= &inet_dgram_ops,
.flags= INET_PROTOSW_REUSE,
},
{
.type= SOCK_RAW,
.protocol= IPPROTO_IP, /* wild card */
.prot= &raw_prot,
.ops= &inet_sockraw_ops,
.flags= INET_PROTOSW_REUSE,
}
};
在Af_inet.c中定义了PF_INET协议族的四个初始化的inet_protosw结构体,在内核协议栈初始化的时候通过inet_register_protosw函数将这些结构体按照类型(type)hash到全局的inetsw数组中。
图3:socket调用流程
上面是socket系统调用的一个主要的流程图,左半部分在前文中已经提到过了。当流程走到inet_create函数的时候根据type去inetsw数组中找到对应类型套接字的inet_protosw结构体,我们前面提到协议栈中已经定义了PF_INET协议族支持的inet_protosw结构体,总共有4个。
找到inet_protosw结构体以后还需要进一步判断protocol和inet_protosw中定义的protocol是否是一致的。内核中定义支持的protocol有一个特殊的值IPPROTO_IP(IPPROTO_IP为0),可以理解为一个通配符也可以理解为一个默认值,就是说我不指定protocol,由内核自己决定使用哪一个protocol。
那么内核根据什么来选择protocol呢?就是根据内核定义的全局inetsw中对应类型的inet_protosw中的protocol。
说起来可能比较拗口,直接看一下代码就很清楚了。
list_for_each_entry_rcu(answer,&inetsw[sock->type], list){
err =0;
/* Check the non-wild match. */
if(protocol== answer->protocol){
if(protocol!= IPPROTO_IP)
break;
}else{
/* Check for the two wild cases. */
if(IPPROTO_IP== protocol){
protocol = answer->protocol;
break;
}
if(IPPROTO_IP== answer->protocol)
break;
}
err =-EPROTONOSUPPORT;
}
所以如果我们在新建套接字的时候使用socket(PF_INET,SOCK_STREAM,0),那么内核就会默认给你把protocol修正为IPPROTO_TCP。
好吧,其实整篇文章我就是想搞清楚这最后的一句话。
- socket函数的domain、type、protocol解析
- int socket (int domain, int type, int protocol);
- int socket(int domain, int type, int protocol)参数详解
- socket编程总结(六) / int socket(int domain, int type, int protocol)
- socket的编程过程(Internet Domain, stream type)
- SOCKET socket(int af,int type,int protocol)解说
- Unix Domain Socket的例子
- Socket的accept函数解析
- socket的accept函数解析
- Socket的accept函数解析
- socket的accept函数解析
- socket的accept函数解析
- socket的accept函数解析
- int socket( int family, int type, int,protocol)
- socket(int family, int type, int protocol)各参数解释
- Domain Socket
- Domain Socket
- Unix domain socket 的一些小结
- 通过Outlook编辑和发送邮件的VBS脚本
- IOS 精度选择 UILabel
- ubuntu下管理网络的两套方案
- 玩转Sublime Text 3
- Gvim开发环境配置笔记--Windows篇
- socket函数的domain、type、protocol解析
- RDD的认识
- linux中的epoll机制
- Android开发——数据库的添加
- Activity的生命周期
- 螺旋式洗砂机适用于各种物料的洗选
- mysql 的二进制
- Leetcode5: Reverse Integer
- (4-1)HDFS笔记--命令