转自我的CSDN
1、引子
在C语言里,想要使用TCP/IP协议来实现一个客户端,必须要遵循以下步骤:
1)创建一个socket
1
| int socket(int family, int type, int protocol)
|
函数需要三个参数,分别是协议族的编号,socket的类型,和具体的协议类型。如果初始化时还有印象的话,我们的TCP/IP协议族属于PF_INET,而TCP socket则属于STREAM_SOCK,protocol这个参数一般是0,使用系统中该socket默认的协议类型。如STREAM_SOCK对应的默认协议就是TCP协议。这里只是内核里面的宏,具体到函数库中,可能会有别的表现形式。
函数执行完成,没有错误时,会返回一个int类型的变量,这个变量是socket的ID,与文件ID类似,后续的工作,都是通过对它的引用来开展的。
2)调用connect函数来连接到远程的服务器。
1 2 3 4 5 6 7
| int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen) ``` sockfd 就是1)中生成的socket ID。 addr 类型为struct sockaddr,用来描述一个socket的数据结构。这里addr表示要连接的远程服务器的socket描述,包括其服务器的IP地址和端口。 当connect调用完成,没有发生错误时,socket已经完成了TCP/IP协议中的三次握手与服务器端的socket建议了连接。 我感兴趣的是TCP/IP如何完成的这样的一个过程。有了前面的初始化的基础,梳理它的工作流程显得没有那么困难。 ### 2、创建 socket创建函数来自于一个glibc中,glibc是GNU版本的C函数库。C程序里的socket函数怎么到的系统调用,暂且不说。目前只需要最后进入内核的途径如下:
|
sys_socketcall(call, args) net/socket.c 2004
sys_socket(a0, a1, a[2]) net/socket.c 1202
sock_create(family, type, protocol, &sock) net/socket.c
sock_map_fd(sock) net/socket.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| 我们假定传入的参数就是(PF_INET, STREAM_SOCK, 0)。 通过对ICMP协议初始化时的分析,我们知道,最终是sock_create是创建的socket。而后面的sock_map_fd(sock)只是把sock和一个文件关系起来,记得socket函数返回的是一个类似文件的ID。而sock_create返回的是 创建是否成功的标识。关于sock_create的流程有必要再次梳理下,因为这次要创建的是TCP类型的socket。 sock_create定义在net/socket.c中,它只是一个包装,真正调用的是__sock_create。传入的参数在ICMP初始化时已经说过不再说明。同样根据参数family解引用,从net_families数组中得到struct net_proto_family类型的变量inet_family_ops。最后执行的是inet_create函数。此时不同的是传入的参数不再一样了。 inet_create(struct net *net, struct socket *sock, int protocol); 此时,由于已经进入到inet内部,family参数已经不再需要了。type参数被包含进入sock中,protocol仍然是0。 ``` 244 static int inet_create(struct net *net, struct socket *sock, int protocol) 245 { 246 struct sock *sk; 247 struct list_head *p; 248 struct inet_protosw *answer; 249 struct inet_sock *inet; 250 struct proto *answer_prot; 251 unsigned char answer_flags; 252 char answer_no_check; 253 int try_loading_module = 0; 254 int err; 256 if (net != &init_net) 257 return -EAFNOSUPPORT; 259 if (sock->type != SOCK_RAW && 260 sock->type != SOCK_DGRAM && 261 !inet_ehash_secret) 262 build_ehash_secret(); 264 sock->state = SS_UNCONNECTED; 267 answer = NULL; 268 lookup_protocol: 269 err = -ESOCKTNOSUPPORT; 270 rcu_read_lock(); 271 list_for_each_rcu(p, &inetsw[sock->type]) { 272 answer = list_entry(p, struct inet_protosw, list); 275 if (protocol == answer->protocol) { 276 if (protocol != IPPROTO_IP) 277 break; 278 } else { 280 if (IPPROTO_IP == protocol) { 281 protocol = answer->protocol; 282 break; 283 } 284 if (IPPROTO_IP == answer->protocol) 285 break; 286 } 287 err = -EPROTONOSUPPORT; 288 answer = NULL; 289 } 313 err = -EPERM; 314 if (answer->capability > 0 && !capable(answer->capability)) 315 goto out_rcu_unlock; 317 sock->ops = answer->ops; 318 answer_prot = answer->prot; 319 answer_no_check = answer->no_check; 320 answer_flags = answer->flags;
|
此时需要详细说下这个函数了。
256 同样是判断是不是属于init_net命名空间的socket,如是不是返回吧,不支持。
269-262 判断参数type的类型。如果既不是SOCK_RAW又不是SOCK_DGRAM,又没有建议过ehash的话,就悄悄地建立了一个散列。也就是说TCP socket也是需要建立这个散列的。也就是SOCK_RAW和SOCK_DGRAM这两种类型是不需要这种散列的。如果前面初始化有印象的话,这里建立的散列是为有连接状态的socket建立的。SOCK_RAW和SOCK_DGRAM都是无状态连接,所以不需要。
264 设置sock的状态为SS_UNCONNECTED,意为未连接。SS代表socket state。此时还没有创建完成,所以是未连接状态。
270-289 同样是一个协议的查找过程。此时sock->type为SOCK_STREAM,所以最后检索到的answer如下:
1 2 3 4 5 6 7 8 9 10
| { 913 .type = SOCK_STREAM, 914 .protocol = IPPROTO_TCP, 915 .prot = &tcp_prot, 916 .ops = &inet_stream_ops, 917 .capability = -1, 918 .no_check = 0, 919 .flags = INET_PROTOSW_PERMANENT | 920 INET_PROTOSW_ICSK, 921 }
|
314-320 同样用得到的answer来初始化sock的成员变量ops,同时记录其他成员到中间变量中。
inet_create上半部分又说完了。