socket 通信过程及流程.docx

上传人:hw****26 文档编号:2989485 上传时间:2019-05-16 格式:DOCX 页数:9 大小:185.63KB
下载 相关 举报
socket 通信过程及流程.docx_第1页
第1页 / 共9页
socket 通信过程及流程.docx_第2页
第2页 / 共9页
socket 通信过程及流程.docx_第3页
第3页 / 共9页
socket 通信过程及流程.docx_第4页
第4页 / 共9页
socket 通信过程及流程.docx_第5页
第5页 / 共9页
点击查看更多>>
资源描述

1、socket 通信过程及流程下图是基于 TCP 协议的客户端/服务器程序的一般流程:服务器调用 socket()、bind()、listen()完成初始化后,调用 accept()阻塞等待,处于监听端口的状态,客户端调用 socket()初始化后,调用 connect()发出 SYN 段并阻塞等待服务器应答,服务器应答一个 SYN-ACK 段,客户端收到后从 connect()返回,同时应答一个 ACK 段,服务器收到后从 accept()返回。数据传输的过程:建立连接后,TCP 协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的

2、方式。因此,服务器从 accept()返回后立刻调用read(),读 socket 就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用 write()发送请求给服务器,服务器收到后从 read()返回,对客户端的请求进行处理,在此期间客户端调用 read()阻塞等待服务器的应答,服务器调用 write()将处理结果发回给客户端,再次调用 read()阻塞等待下一条请求,客户端收到后从 read()返回,发送下一条请求,如此循环下去。如果客户端没有更多的请求了,就调用 close()关闭连接,就像写端关闭的管道一样,服务器的read()返回 0,这样服务器就知道客户端关闭了连接,也调用

3、close()关闭连接。注意,任何一方调用 close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用 shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。在学习 socket API 时要注意应用程序和 TCP 协议层是如何交互的: *应用程序调用某个 socket 函数时 TCP 协议层完成什么动作,比如调用 connect()会发出 SYN 段 *应用程序如何知道 TCP 协议层的状态变化,比如从某个阻塞的 socket 函数返回就表明 TCP 协议收到了某些段,再比如 read()返回 0 就表明收到了 FIN 段看图所示的socket 通信过程图 12.

4、9 socket 的通信过程1.建立套接字在 sys/socket.h 中。int socket(int family, int type, int protocol);socket()打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描述符,应用程序可以像读写文件一样用 read/write 在网络上收发数据,如果 socket()调用出错则返回-1 。对于IPv4,family 参数指定为 AF_INET。对于 TCP 协议,type 参数指定为 SOCK_STREAM,表示面向流的传输协议。如果是 UDP 协议,则 type 参数指定为 SOCK_DGRAM,表示面向

5、数据报的传输协议。protocol 参数的介绍从略,指定为 0 即可。Linux 在利用 socket()系统调用建立新的套接字时,需要传递套接字的地址族标识符、套接字类型以及协议,其函数定义于 net/socket.c 中:asmlinkagelong sys_socket(int family, int type, int protocol)int retval;struct socket *sock;retval = sock_create(family, type, protocol,if (retval 0)goto out;retval = sock_map_fd(sock);if

6、 (retval 0)goto out_release;out:/* It may be already another descriptor 8) Not kernel problem. */return retval;out_release:sock_release(sock);return retval;实际上,套接字对于用户程序而言就是特殊的已打开的文件。内核中为套接字定义了一种特殊的文件类型,形成一种特殊的文件系统 sockfs,其定义于 net/socket.c:static struct vfsmount *sock_mnt;static DECLARE_FSTYPE(sock_

7、fs_type, “sockfs“,sockfs_read_super, FS_NOMOUNT);在系统初始化时,要通过 kern_mount()安装这个文件系统。安装时有个作为连接件的 vfsmount数据结构,这个结构的地址就保存在一个全局的指针 sock_mnt 中。所谓创建一个套接字,就是在sockfs 文件系统中创建一个特殊文件,或者说一个节点,并建立起为实现套接字功能所需的一整套数据结构。所以,函数 sock_create()首先是建立一个 socket 数据结构,然后将其“映射”到一个已打开的文件中,进行 socket 结构和 sock 结构的分配和初始化。新创建的 BSD so

8、cket 数据结构包含有指向地址族专有的套接字例程的指针,这一指针实际就是proto_ops 数据结构的地址。BSD 套接字的套接字类型设置为所请求的 SOCK_STREAM 或 SOCK_DGRAM 等。然后,内核利用 proto_ops 数据结构中的信息调用地址族专有的创建例程。之后,内核从当前进程的 fd 向量中分配空闲的文件描述符,该描述符指向的 file 数据结构被初始化。初始化过程包括将文件操作集指针指向由 BSD 套接字接口支持的 BSD 文件操作集。所有随后的套接字(文件)操作都将定向到该套接字接口,而套接字接口则会进一步调用地址族的操作例程,从而将操作传递到底层地址族,如图

9、12.10 所示。实际上,socket 结构与 sock 结构是同一事物的两个方面。如果说 socket 结构是面向进程和系统调用界面的,那么 sock 结构就是面向底层驱动程序的。可是,为什么不把这两个数据结构合并成一个呢?我们说套接字是一种特殊的文件系统,因此,inode 结构内部的 union 的一个成分就用作 socket 结构,其定义如下:struct inode union struct socket socket_i;由于套接字操作的特殊性,这个结构中需要大量的结构成分。可是,如果把这些结构成分全都放在socket 结构中,则 inode 结构中的这个 union 就会变得很大,

10、从而 inode 结构也会变得很大,而对于其他文件系统,这个 union 成分并不需要那么庞大。因此,就把套接字所需的这些结构成分拆成两部分,把与文件系统关系比较密切的那一部分放在 socket 结构中,把与通信关系比较密切的那一部分则单独组成一个数据结构,即 sock 结构。由于这两部分数据在逻辑上本来就是一体的,所以要通过指针互相指向对方,形成一对一的关系。2在 INET BSD 套接字上绑定(bind)地址为了监听传入的 Internet 连接请求,每个服务器都需要建立一个 INET BSD 套接字,并且将自己的地址绑定到该套接字。绑定操作主要在 INET 套接字层中进行,还需要底层 T

11、CP 层和 IP 层的某些支持。将地址绑定到某个套接字上之后,该套接字就不能用来进行任何其他的通讯,因此,该 socket 数据结构的状态必须为 TCP_CLOSE。传递到绑定操作的 sockaddr 数据结构中包含要绑定的 IP 地址,以及一个可选的端口地址。通常而言,要绑定的地址应该是赋予某个网络设备的 IP 地址,而该网络设备应该支持 INET 地址族,并且该设备是可用的。利用 ifconfig 命令可查看当前活动的网络接口。被绑定的 IP 地址保存在 sock 数据结构的 rcv_saddr 和 saddr 域中,这两个域分别用于哈希查找和发送用的 IP 地址。端口地址是可选的,如果没

12、有指定,底层的支持网络会选择一个空闲的端口。int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用 bind 绑定一个固定的网络地址和端口号。bind()成功返回 0,失败返回-1。bind()的作用是将参数 sockfd 和 myaddr 绑定在一起,使 sockfd 这个用于网络通讯的文件描述符监听 myaddr 所描述的地址和端口号。前面讲过,struct sockad

13、dr *是一个通用指针类型,myaddr 参数实际上可以接受多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数addrlen 指定结构体的长度。我们的程序中对 myaddr 参数是这样初始化的:bzero(servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);首先将整个结构体清零,然后设置地址类型为 AF_INET,网络地址为 INADDR_ANY,这个宏表示本地的任意 IP 地址,因为服务器可能有

14、多个网卡,每个网卡也可能绑定多个 IP 地址,这样设置可以在所有的 IP 地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址,端口号为 SERV_PORT,我们定义为 8000。当底层网络设备接受到数据包时,它必须将数据包传递到正确的 INET 和 BSD 套接字以便进行处理,因此,TCP 维护多个哈希表,用来查找传入 IP 消息的地址,并将它们定向到正确的socket/sock 对。TCP 并不在绑定过程中将绑定的 sock 数据结构添加到哈希表中,在这一过程中,它仅仅判断所请求的端口号当前是否正在使用。在监听操作中,该 sock 结构才被添加到 TCP 的哈希表中。3

15、在 INET BSD 套接字上建立连接(connect)创建一个套接字之后,该套接字不仅可以用于监听入站的连接请求,也可以用于建立出站的连接请求。不论怎样都涉及到一个重要的过程:建立两个应用程序之间的虚拟电路。出站连接只能建立在处于正确状态的 INET BSD 套接字上,因此,不能建立于已建立连接的套接字,也不能建立于用于监听入站连接的套接字。也就是说,该 BSD socket 数据结构的状态必须为 SS_UNCONNECTED。在建立连接过程中,双方 TCP 要进行三次“ 握手”,具体过程在 本章第二节网络协议一文中有详细介绍。如果 TCP sock 正在等待传入消息,则该 sock 结构添

16、加到 tcp_listening_hash 表中,这样,传入的 TCP 消息就可以定向到该 sock 数据结构。由于客户端不需要固定的端口号,因此不必调用 bind(),客户端的端口号由内核自动分配。注意,客户端不是不允许调用 bind(),只是没有必要调用 bind()固定一个端口号,服务器也不是必须调用bind(),但如果服务器不调用 bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen

17、);客户端需要调用 connect()连接服务器,connect 和 bind 的参数形式一致,区别在于 bind 的参数是自己的地址,而 connect 的参数是对方的地址。 connect()成功返回 0,出错返回-1。4监听(listen) INET BSD 套接字int listen(int sockfd, int backlog);典型的服务器程序可以同时服务于多个客户端,当有客户端发起连接时,服务器调用的 accept()返回并接受这个连接,如果有大量的客户端发起连接而服务器来不及处理,尚未 accept 的客户端就处于连接等待状态,listen()声明 sockfd 处于监听状态

18、,并且最多允许有 backlog 个客户端处于连接待状态,如果接收到更多的连接请求就忽略。listen()成功返回 0,失败返回-1。当某个套接字被绑定了地址之后,该套接字就可以用来监听专属于该绑定地址的传入连接。网络应用程序也可以在未绑定地址之前监听套接字,这时,INET 套接字层将利用空闲的端口编号并自动绑定到该套接字。套接字的监听函数将 socket 的状态改变为 TCP_LISTEN。当接收到某个传入的 TCP 连接请求时,TCP 建立一个新的 sock 数据结构来描述该连接。当该连接最终被接受时,新的 sock 数据结构将变成该 TCP 连接的内核 bottom_half 部分,这时

19、,它要克隆包含连接请求的传入 sk_buff 中的信息,并在监听 sock 数据结构的 receive_queue 队列中将克隆的信息排队。克隆的 sk_buff 中包含有指向新 sock 数据结构的指针。5接受连接请求 (accept )接受操作在监听套接字上进行,从监听 socket 中克隆一个新的 socket 数据结构。其过程如下:接受操作首先传递到支持协议层,即 INET 中,以便接受任何传入的连接请求。相反,接受操作进一步传递到实际的协议,例如 TCP 上。接受操作可以是阻塞的,也可以是非阻塞的。接受操作为非阻塞的情况下,如果没有可接受的传入连接,则接受操作将失败,而新建立的 so

20、cket 数据结构被抛弃。接受操作为阻塞的情况下,执行阻塞操作的网络应用程序将添加到等待队列中,并保持挂起直到接收到一个 TCP 连接请求为至。当连接请求到达之后,包含连接请求的 sk_buff 被丢弃,而由 TCP 建立的新 sock 数据结构返回到 INET 套接字层,在这里,sock 数据结构和先前建立的新 socket 数据结构建立链接。而新 socket 的文件描述符(fd)被返回到网络应用程序,此后,应用程序就可以利用该文件描述符在新建立的 INETBSD 套接字上进行套接字操作。int accept(int sockfd, struct sockaddr *cliaddr, so

21、cklen_t *addrlen);三方握手完成后,服务器调用 accept()接受连接,如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。cliaddr 是一个传出参数,accept()返回时传出客户端的地址和端口号。addrlen 参数是一个传入传出参数( value-result argument),传入的是调用者提供的缓冲区 cliaddr 的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给 cliaddr 参数传 NULL,表示不关心客户端的地址。注意:服务器接收到传入的请求后,如果能够接受该请求,服务器必须创建一个新的套接字来接受该请求并建立通讯连接(用于监听的套接字不能用来建立通讯连接),这时,服务器和客户就可以利用建立好的通讯连接传输数据。转载地址:http:/

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育教学资料库 > 精品笔记

Copyright © 2018-2021 Wenke99.com All rights reserved

工信部备案号浙ICP备20026746号-2  

公安局备案号:浙公网安备33038302330469号

本站为C2C交文档易平台,即用户上传的文档直接卖给下载用户,本站只是网络服务中间平台,所有原创文档下载所得归上传人所有,若您发现上传作品侵犯了您的权利,请立刻联系网站客服并提供证据,平台将在3个工作日内予以改正。