天天看点

《Python核心编程(第3版)》——2.3 套接字:通信端点

本节书摘来自异步社区《python核心编程(第3版)》一书中的第2章,第2.3节,作者[美] wesley chun(卫斯理 春),孙波翔 李斌 李晗 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。

本节将介绍套接字(socket),给出有关其起源的一些背景知识,并讨论各种类型的套接字。最后,将讲述如何利用它们使运行在不同(或相同)计算机上的进程相互通信。

套接字是计算机网络数据结构,它体现了上节中所描述的“通信端点”的概念。在任何类型的通信开始之前,网络应用程序必须创建套接字。可以将它们比作电话插孔,没有它将无法进行通信。

套接字的起源可以追溯到20世纪70年代,它是加利福尼亚大学的伯克利版本unix(称为bsd unix)的一部分。因此,有时你可能会听过将套接字称为伯克利套接字或bsd套接字。套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个进程)与另一个运行的程序进行通信。这就是所谓的进程间通信(inter process communication,ipc)。有两种类型的套接字:基于文件的和面向网络的。

unix套接字是我们所讲的套接字的第一个家族,并且拥有一个“家族名字”af_unix(又名af_local,在posix1.g标准中指定),它代表地址家族(address family):unix。包括python在内的大多数受欢迎的平台都使用术语地址家族及其缩写af;其他比较旧的系统可能会将地址家族表示成域(domain)或协议家族(protocol family),并使用其缩写pf而非af。类似地,af_local(在2000~2001年标准化)将代替af_unix。然而,考虑到后向兼容性,很多系统都同时使用二者,只是对同一个常数使用不同的别名。python本身仍然在使用af_unix。

因为两个进程运行在同一台计算机上,所以这些套接字都是基于文件的,这意味着文件系统支持它们的底层基础结构。这是能够说得通的,因为文件系统是一个运行在同一主机上的多个进程之间的共享常量。

第二种类型的套接字是基于网络的,它也有自己的家族名字af_inet,或者地址家族:因特网。另一个地址家族af_inet6用于第6版因特网协议(ipv6)寻址。此外,还有其他的地址家族,这些要么是专业的、过时的、很少使用的,要么是仍未实现的。在所有的地址家族之中,目前af_inet是使用得最广泛的。

python 2.5中引入了对特殊类型的linux套接字的支持。套接字的af_netlink家族(无连接[见2.3.3节])允许使用标准的bsd套接字接口进行用户级别和内核级别代码之间的ipc。之前那种解决方案比较麻烦,而这个解决方案可以看作一种比前一种更加优雅且风险更低的解决方案,例如,添加新系统调用、/proc支持,或者对一个操作系统的“ioctl”。

针对linux的另一种特性(python 2.6中新增)就是支持透明的进程间通信(tipc)协议。tipc允许计算机集群之中的机器相互通信,而无须使用基于ip的寻址方式。python对tipc的支持以af_tipc家族的方式呈现。

总的来说,python只支持af_unix、af_netlink、af_tipc和af_inet家族。因为本章重点讨论网络编程,所以在本章剩余的大部分内容中,我们将使用af_inet。

如果一个套接字像一个电话插孔——允许通信的一些基础设施,那么主机名和端口号就像区号和电话号码的组合。然而,拥有硬件和通信的能力本身并没有任何好处,除非你知道电话打给谁以及如何拨打电话。一个网络地址由主机名和端口号对组成,而这是网络通信所需要的。此外,并未事先说明必须有其他人在另一端接听;否则,你将听到这个熟悉的声音“对不起,您所拨打的电话是空号,请核对后再拨”。你可能已经在浏览网页的过程中见过一个网络类比,例如“无法连接服务器,服务器没有响应或者服务器不可达。”

1.面向连接的套接字

不管你采用的是哪种地址家族,都有两种不同风格的套接字连接。第一种是面向连接的,这意味着在进行通信之前必须先建立一个连接,例如,使用电话系统给一个朋友打电话。这种类型的通信也称为虚拟电路或流套接字。

面向连接的通信提供序列化的、可靠的和不重复的数据交付,而没有记录边界。这基本上意味着每条消息可以拆分成多个片段,并且每一条消息片段都确保能够到达目的地,然后将它们按顺序组合在一起,最后将完整消息传递给正在等待的应用程序。

实现这种连接类型的主要协议是传输控制协议(更为人熟知的是它的缩写tcp)。为了创建tcp套接字,必须使用sock_stream作为套接字类型。tcp套接字的名字sock_stream基于流套接字的其中一种表示。因为这些套接字(af_inet)的网络版本使用因特网协议(ip)来搜寻网络中的主机,所以整个系统通常结合这两种协议(tcp和ip)来进行(当然,也可以使用tcp和本地[非网络的af_local/af_unix]套接字,但是很明显此时并没有使用ip)。

2.无连接的套接字

与虚拟电路形成鲜明对比的是数据报类型的套接字,它是一种无连接的套接字。这意味着,在通信开始之前并不需要建立连接。此时,在数据传输过程中并无法保证它的顺序性、可靠性或重复性。然而,数据报确实保存了记录边界,这就意味着消息是以整体发送的,而并非首先分成多个片段,例如,使用面向连接的协议。

使用数据报的消息传输可以比作邮政服务。信件和包裹或许并不能以发送顺序到达。事实上,它们可能不会到达。为了将其添加到并发通信中,在网络中甚至有可能存在重复的消息。

既然有这么多副作用,为什么还使用数据报呢(使用流套接字肯定有一些优势)?由于面向连接的套接字所提供的保证,因此它们的设置以及对虚拟电路连接的维护需要大量的开销。然而,数据报不需要这些开销,即它的成本更加“低廉”。因此,它们通常能提供更好的性能,并且可能适合一些类型的应用程序。

实现这种连接类型的主要协议是用户数据报协议(更为人熟知的是其缩写udp)。为了创建udp套接字,必须使用sock_dgram作为套接字类型。你可能知道,udp套接字的sock_dgram名字来自于单词“datagram”(数据报)。因为这些套接字也使用因特网协议来寻找网络中的主机,所以这个系统也有一个更加普通的名字,即这两种协议(udp和ip)的组合名字,或udp/ip。