前言
测试过程中,你一定遇到不少网络通信的情况,最常见的如网络语音通信、文本传输、微信QQ聊天、浏览器访问等等,这些都涉及到网络进程间通信,然而这些进程间是如何通信的呢?你一定听开发同学提过很多次socket这个东西,没错,一切都是靠Socket实现的。它为何如此神奇?下面小编做个科普,帮你揭开Socket的神秘面纱~
- 网络进程间通信
要了解Socket首先要熟悉网络进程通信的原理,一个完整的网络应用程序包括客户端和服务器两个部分。网络间通信需要由两个进程组成,并且只能用同一种协议,也就是说,不能在通信的一端使用TCP协议,而另一端则用UDP协议。
在TCP/IP协议中,两个因特网主机通过两个路由器和对应的层连接。各主机上的应用通过一些数据通道相互执行读取操作,如下图所示:
实现网络间通信,要解决一个首要问题是-如何唯一标识一个进程,在网络上,通常利用ip地址+协议+端口号唯一标示网络中的一个进程。IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样它们就可以利用Socket进行通信了。
-
Socket套接字
网络中的进程是通过Socket来通信的,那么Socket是什么呢?Socket是一个进程间通信终结点,它是Socket应用程序用来在网络上发送或接收数据包的对象,是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
如下图所示,在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。Socket的使用类型主要有两种:
(1)流套接字(streamsocket):基于 TCP协议,采用流的方式提供可靠的字节流服务;
(2)数据报套接字(datagramsocket):基于 UDP协议,采用数据报文提供数据打包发送的服务。 - Socket通信流程
Socket起源于UNIX,在Unix一切皆文件哲学的思想下,Socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。以使用TCP协议通讯的Socket为例,其交互流程大概是这样子的:
(1)服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket;
(2)服务器为socket绑定ip地址和端口号;
(3)服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开;
(4)客户端创建socket;
(5)客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket;
(6)服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求;
(7)客户端连接成功,向服务器发送连接状态信息;
(8)服务器accept方法返回,连接成功;
(9)客户端向socket写入信息;
(10)服务器读取信息;
(11)客户端关闭;
(12)服务器端关闭。
以上,一次socket通信流程的交互结束。 - 代码实现
(1)创建一个socket:socket()
int socket(int domain, int type, int protocol);- domain:与socket通信的domain。
- type:socket类型,SOCK_STREAM(TCP),SOCK_DGRAM(UDP)。
- protocol:通常指定为0,在RAW_SOCKET中为IPPROTO_RAW。
return: 新创建socket的文件描述符。
(2)将socket绑定到地址:bind()
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen); - sockfd:在socket()调用取得的文件描述符。
- addr:要socket绑定到的地址。
- addrlen:制定了地址结构的大小。
return:-1为绑定失败。
(3)监听接入连接:listen()
int listen(int sockfd, int backlog); - sockfd:socket文件描述符。
- backlog:限制未决连接的数量(在调用accept()前收到connect()的连接)。
return: -1为监听失败。
(4)接受连接:accept()
int accept(int sockfd, struct sockaddr addr, socklen_t addrlen); - sockfd:socket文件描述符。
- addr:对端socket的地址结构。
- addrlen:对端socket地址结构的长度。
return:和对端连接的文件描述符。
当调用accept()时,会创建一个新的socket,并且由这个新创建的socket来与执行connect()的对等socket进行连接。
(5)连接到对等socket:connect()
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); - sockfd:socket文件描述符。
- addr:要连接到socket的地址。
- addrlen:地址结构的长度。
(6)连接终止:close()
终止一个流socket的连接,如果多个文件描述符引用了一个socket,那么所有描述符被关闭后才会终止(若调用shutdown()则可以强制关闭socket上的信道)。
- Socket中TCP的三次握手建立连接详解
TCP建立连接要进行“三次握手”,即交换三个分组。大致流程如下:
1)客户端向服务器发送一个SYN J;
2)服务器向客户端响应一个SYN K,并对SYN J进行确认ACKJ+1;
3)客户端再想服务器发一个确认ACK K+1。
当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
- Socket中TCP的四次握手释放连接
Socket中四次握手释放连接的过程,请看下图:
大致流程为:
1)某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
2)另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
3)一段时间之后,接收到文件结束符的应用进程调用close关闭它的Socket。这导致它的TCP也发送一个FIN N;
4)接收到这个FIN的源发送端TCP对它进行确认。
结束语
基于Socket的知识科普就先进行到这里,希望你能够对它的工作原理有个概念性的掌握,不会在开发沟通过程中一脸懵~因为Socket在网络中的应用非常广,无论是语音、文字、图片甚至文件的网络传输,都是其用武之地,待小编深入学习之后,将针对涉及Socket方面的测试做一次专门的分享,敬请期待~