Socket网络通信套接字
socket库提供了一个底层C API,可以使用BSD套接字接口实现网络通信。它包括socket类,用于处理具体的数据通道,还包括用来完成网络相关任务的函数,如将一个服务器名转换为一个地址以及格式化数据以便在网络上发送。
什么是套接字?
套接字是程序在本地或者通过互联网来回传递数据时所用通信通道的一个端点。
套接字有2个主要属性用于控制如何发送数据:地址簇(address family)控制所用的OSI网络层协议;套接字类型(socket type)控制传输层协议。(参考《计算机网络》7层协议)
地址簇
Python支持3个地址簇:
AF_INET:用于IPv4寻址。IPv4长度为4个字节,通常表示为4个数的序列,每个字节对应一个数,用点号分割(如121.63.0.243)。这些值通常被称为IP地址。目前IPv4依旧还是主流。
AF_INET6:用于IPv6寻址。目前IPv6已经小范围应用,它支持128位地址和通信流调整,还支持IPv4不支持的一些路由特性。
AF_UNIX:用于UNIX域套接字(UDS)的地址簇,这是一种POSIX兼容系统上的进程间通信协议。UDS的实现通常允许操作系统直接从进程向进程间传递数据,而不用通过网络栈。这比使用AF_INET更高效,但是由于要用到文件系统作为寻址的命令空间,所以UDS仅限于同一个系统上的进程。
套接字类型
套接字类型有两种:
SOCK_DGRAM:面向消息的数据报传输,数据报套接字通常与UDP关联,即用户数据报协议。这些套接字能提供不可靠的消息传送。
SOCK_STREAM:面向流的传输,与TCP相关,即传输控制协议。它们可以在客户和服务器之间提供字节流,通过超时管理,重传和其他特性确保提供消息传送或失败通知。
大多数应用协议(如HTTP)都建立在TCP基础上,因为这样更容易创建自动处理消息排序和传送的复杂应用。
UDP通常用于顺序不太重要的协议(如DNS交换)。UDP与TCP都支持IPv4与IPv6。
套接字的简单应用
gethostbyname_ex()与gethostbyname()
socket库包含一些与网络上的域名服务交互的函数,比如解析域名为IP地址可以用到gethostbyname_ex(),示例如下:
import socket host_str = [ 'www.baidu.com', 'cloud.tencent.com', 'www.csdn.net' ] for host in host_str: try: name, aliases, addresses = socket.gethostbyname_ex(host) print(host) print("主机名:", name) print("所有别名:", aliases) print("所有可用IP地址:", addresses) except socket.error as msg: print(host, msg)
运行之后,效果如下:
gethostbyname_ex:该函数返回3个参数,主机名,别名,以及解析能跳转到当前主机的IP地址。
gethostbyname:类似的函数,只返回当前主机的IP地址。
getservbyname()与getservbyport()
socket库提供getservbyname()函数用于查找网络服务的端口号和标准名,示例如下所示:
import socket from urllib.parse import urlparse url_str = [ 'https://www.baidu.com', 'https://www.csdn.net', 'smtp://smtp.qq.com', ] for url in url_str: try: parsed_url = urlparse(url) port = socket.getservbyname(parsed_url.scheme) print(url) print("端口号:", port) except socket.error as msg: print(url, msg)
运行之后,效果如下:
当然,其实最有用的并不是给定一个链接去查询端口号,而是逆向操作。(因为标准化服务端口号一般都是固定的)
socket库提供getservbyport()函数用于完成逆向的服务端口查找,示例代码如下所示:
import socket url = '{}://smtp.qq.com'.format(socket.getservbyport(25)) print(url)
运行之后,效果如下:
getprotobyname()
socket库还可以使用getprotobyname()函数获取分配给一个传输协议的端口号,示例如下:
import socket #获取匹配开头字符串的所有属性值 def getConstants(prefix): return { getattr(socket, n): n for n in dir(socket) if n.startswith(prefix) } ipproto_str = getConstants("IPPROTO_") for agree in ['tcp', 'udp']: num = socket.getprotobyname(agree) name = ipproto_str[num] print(name, num)
运行之后,效果如下:
对于协议码,在程序定义中一般都是标准化常量,这就是意味着,它们的常量名都有一定的规律,而socket协议码前缀是IPPROTO_。