网络协议栈2:socket函数
首先,这里会用到一个结构体,先把这个结构体整理出来
struct socket {
{
short type;
/*套接字所用的流类型,可取值SOCK_RAW,SOCK_DGRAM,SOCK_STREAM,SOCK_SEQPACKET,SOCK_PACKET,其中SOCK_STREAM 就是通常所说的TCP协议所用*/
socket_state state;
/*套接字状态,可取值SS_FREE ,SS_UNCONNECTED ,SS_CONNECTING ,SS_CONNECTED ,SS_DISCONNECTING */
long flags;/*一些标志信息*/
struct proto_ops *ops;
/*操作函数集指针,对应初始化pops [ ]数组时的各个协议指针*/
void *data;
/*私有数据,对于INET域,其指向sock数据结构(跟socket数据结构不同)*/
struct socket *conn; /* server socket connected to */
struct socket *iconn; /* incomplete client conn.s */
struct socket *next;
/*对于INET域,conn icon 没有用,next是把所有socket组成链表 */
struct wait_queue **wait;
/*当进程对套接字的操作无法得到满足时,就在这里等待*/
struct inode *inode;
/*套接字关联的I节点*/
struct fasync_struct *fasync_list;
/*该结构用于同步文件的读写*/
}
在client.c中,我们是通过
if ((clifd = socket(AF_INET,SOCK_STREAM,0)) < 0)
语句来来创建套接字,socket函数就是创建套接字的函数,我们结合实例跟内核函数来看看套接字时如何被创建的。
在内核中,socket()函数被定义如下
1 static int sock_socket(int family, int type, int protocol)
2 {
3 int i, fd;
4 struct socket *sock;
5 struct proto_ops *ops;
6
7 for (i = 0; i < NPROTO; ++i)
8 {
9 if (pops[i] == NULL) continue;
10 if (pops[i]->family == family)
11 break;
12 }
13
14 if (i == NPROTO)
15 {
16 return -EINVAL;
17 }
18
19 ops = pops[i];
20
21 if ((type != SOCK_STREAM && type != SOCK_DGRAM &&
22 type != SOCK_SEQPACKET && type != SOCK_RAW &&
23 type != SOCK_PACKET) || protocol < 0)
24 return(-EINVAL);
25
26 if (!(sock = sock_alloc()))
27 {
28 printk("NET: sock_socket: no more sockets\n");
29 return(-ENOSR);
30 }
31
32 sock->type = type;
33 sock->ops = ops;
34 if ((i = sock->ops->create(sock, protocol)) < 0)
35 {
36 sock_release(sock);
37 return(i);
38 }
39
40 if ((fd = get_fd(SOCK_INODE(sock))) < 0)
41 {
42 sock_release(sock);
43 return(-EINVAL);
44 }
45
46 return(fd);
47 }
其中,第8行就是扫描pops [ ]全局数据,找寻有没有匹配我们应用程序调用socket( )函数时指定的协议,通过上节的socket初始化,我们知道pops [ ] 数组中的某项被
static struct proto_ops inet_proto_ops = {
AF_INET,
inet_create,
。。。。。。
}
的地址所填充的,即有pops [ ]->family= AF_INET,结合应用程序的socket调用socket(AF_INET,SOCK_STREAM,0)所指定的family为AF_INET,第10行是得到满足,因此跳出循环。
第26行调用sock = sock_alloc()在内存的I节点表(不是磁盘I节点)上找到一个空闲的I节点,并把这个I节点设置成网络I节点,填充I节点的相关属性后,把I节点中的socket地址返回给sock变量,即sock套接字跟I节点在此时关联起来了,能找到sock,就能找到sock对应的I节点,而知道I节点也能找到对应的套接字。
32,33行设置了套接字的类型,和它所属的协议。34行就是利用套接字所属的协议的create函数来创建套接字的。
40行则是通过fd = get_fd(SOCK_INODE(sock)调用get_fd()来找到当前进程中一个空闲的文件句柄,并把这个文件句柄跟sock中的I节点关联起来,之后把文件句柄返回给调用函数。因为I节点跟sock是关联起来,而I节点是跟文件句柄关联的,因此sock跟文件句柄也是关联在一起的,只要找到文件句柄,就能找到I节点,也就能找到sock了。
34行中的create是如何被赋值并被调用的呢。由19,33行我们看到,ops实质就是初始化socket时pops [ ]数组所被指向的数组的地址,也就是数组
static struct proto_ops inet_proto_ops = {
AF_INET,
inet_create,
。。。。。。
}
的地址,因此,34行的create函数,就是inet_create函数,即
static int inet_create(struct socket *sock, int protocol)
由于函数比较长,就不贴出来了,其大致的功能是
创建套接字对应的sock结构体(注意,是sock,而不是socket,这两个结构体是有区别的,而且核心的结构体是sock),并初始化sock结构体,并把sock结构体跟socket结构体关联起来,在结构体socket中有一个
void *data;
成员,就是用来记录现在创建的sock结构体的首地址的,而sock结构体中也将会有一个成员,用于记录sock结构体对应的socket套接字,这样,sock结构体和socket套接字又被关联起来了。
到此,socket套接字的建立就算完成了,总结起来,所谓的建立套接字,实际上就是调用socket系统调用,创建了socket结构体,sock结构体,同时两个结构体相互关联,并把对应的协议的操作集关联到socket结构体上,再把socket结构体跟I节点关联,I节点又跟文件句柄关联,这样的层层关联,应该层就可以通过常用的文件句柄来得到一个套接字了。