12.网络通信
Posted traditional
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了12.网络通信相关的知识,希望对你有一定的参考价值。
‘‘‘ 网络通信用于获取一个算法在本地运行所需的数据,还可以共享信息实现分布式处理,另外可以用来管理云服务。 python的标准库提供了一些模块来创建网络服务以及访问现有服务 ipaddress模块提供了一些类来验证、比较和处理IPV4/IPV6网络地址 底层socket库允许直接访问原生C套接字库,可以用于与任何网络服务通信。 selectors提供了一个高层接口,可以同时监视多个套接字,这对于支持网络服务器同时与多个客户通信很有用。select提供了selectors使用的底层API socketserver中的框架抽象了创建一个新的网络服务器所需要的大量重复性工作。 可以结合这些类创建服务器来建立或使用线程以及支持TCP或UDP。应用只需要完成实际的消息处理 ‘‘‘
(一)ipaddress:Internet地址
‘‘‘ ipaddress模块提供了处理IPV4和IPV6网络地址的类。这些类支持验证,查找网络上的地址和主机,以及其他常见操作 ‘‘‘
1.地址
import binascii import ipaddress ‘‘‘ 最基本的对象表示网络地址本身。可以像ip_address函数传入一个字符串,整数或者字节序列来构造一个地址。 返回值是一个IPv4Address或IPv6Address实例,这取决于使用什么类型的地址 ‘‘‘ address = "61.135.169.125" addr = ipaddress.ip_address(address) print(addr) print(f"ip version: {addr.version}") print(f"is private: {addr.is_private}") print(f"packed form: {binascii.hexlify(addr.packed)}") print(f"integer: {int(addr)}") ‘‘‘ 61.135.169.125 ip version: 4 is private: False packed form: b‘3d87a97d‘ integer: 1032300925 ‘‘‘
(二)socket:网络通信
sock模块提供了一个底层的C API,可以使用BSD套接字接口实现网络通信。
它包括socket类,用于处理具体的数据通道,还包括用来完成网络相关任务的函数,如将一个服务器名转换为一个地址以及格式化数据以便在网络上发送
1.寻址、协议簇和套接字类型
套接字(socket)是程序在本地或者通过互联网来回传递数据时所用的通信通道的一个端点。套接字有两个主要属性用于控制如何发送数据:地址簇(address family)控制所用的OSI网络协议;套接字类型(socket type)控制传输层协议。
python通常支持三个地址簇。最常用的是AF_INET,用于IPV4寻址。IPV4地址长度为4个字节,通常表示为4个数的序列,每个字节对应一个数,用点号分隔(如127.0.0.1)。这些值通常被称为IP地址,目前几乎所有的互联网网络通信都是用IPV4.
AF_INET6用于IPV6寻址。IPV6是下一代Internet协议,它支持128位地址和通信流调整,还支持IPV4不支持的一些路由特性。采用IPV6的应用在不断增多,特别是伴随着云计算的大量普及以及物联网项目而为网络增加很多额外的设备,都促使IPV6得到更广泛的应用
AF_UNIX是unix域套接字(unix domain socket,uds)的地址簇,这是一种POSIX兼容系统上的进程间通信协议。uds的实现通常允许操作系统直接从进程向进程传递数据,而不用通过网络栈。这比使用AF_INET更加高效,但是由于要使用文件系统作为寻址的命名空间,所以uds的优势仅限于同一个系统上的进程。相比其他的ipc机制(如命名管道或共享内存),使用uds的优势在于它与ip网络应用的编程接口是一致的。这说明,应用在单个主机上运行时可以利用高效的通信,在网络上发送数据时仍然可以使用相同的代码。
套接字类型往往是SOCK_DGRAM或SOCK_STREAM,其中SOCK_DGRAM对应面向消息的数据报传输,而SOCK_STREAM对应面向流的传输。数据报套接字通常与UDP关联,即用户数据报协议(user datagram protocol)。这些套接字能提供不可靠的消息传送。面向流的套接字与TCP相关,即传输控制协议(transmission control protocol)。它们可以在客户和服务器之间提供字节流,通过超时管理、重传和其他特性确保提供消息传送或失败通知。
大多数传送数据的应用协议(如HTTP)都建立在TCP基础上,因为这样可以更容易地创建自动处理消息排序和传送的复杂应用。UDP通常用于顺序不太重要的协议(因为消息是自包含的,而且通常很小,如通过DNS的名字查找),或者用于组播(向多个主机发送相同的数据)。UDP和TCP都可以用于IPV4或IPV6寻址
举个栗子:UDP就好比发短信,发完了就不管了,信息是可能丢失的,但是不管,只要发了就ok,对方是否接收到,我不管。TCP就好比打电话,我拨号,必须要确保对方接电话,才可以。所以说TCP相比UDP操作更复杂,但更安全
i)在网络上查找主机
import socket ‘‘‘ socket包含一些与网络上的域名服务交互的函数,这使得程序可以将服务器的主机名转换为数字网路地址。 应用使用地址链接服务器之前并不需要显式地转换地址,不过报告错误时除了报告所用的名字之外,如果还能包含这个数字地址,那么便会很有用 ‘‘‘ # 要查找当前主机的正式名,可以使用gethostname函数 print(socket.gethostname()) # WINDOWS-JTP7CGR # 还可以使用gethostbyname函数根据主机名获取ip地址 print(socket.gethostbyname(socket.gethostname())) # 192.168.0.110 # 不仅如此,还可以根据url,找出网站的ip。 # 总所周知,我们访问百度,可以通过www.baidu.com,但是我们是通过这个域名来访问百度的ip地址,只是ip比较难记罢了 # 我们也可以根据url找到对应网站的ip print(socket.gethostbyname("www.baidu.com")) # 61.135.169.125 # 如果想访问更多的信息,可以使用这个函数 name, aliases, addressed = socket.gethostbyname_ex("www.baidu.com") print(name) # www.a.shifen.com print(aliases) # [‘www.baidu.com‘] print(addressed) # [‘61.135.169.121‘, ‘61.135.169.125‘]
ii)查找服务信息
import socket ‘‘‘ 除了ip地址之外,每个套接字地址好包括一个整数端口号。 很多应用可以在同一个主机上运行并监听一个ip地址,不过只有一个套接字可以使用该地址的端口。 通过结合ip地址、协议和端口号,可以唯一地标识一个通信通道,并确保通过一个套接字发送的消息能到达正确的目标 有些端口号已经预先分配给特定的协议。例如,使用SMTP的email服务器使用TCP在端口25完成通信,web客户和服务器使用端口80完成HTTP通信。 ‘‘‘ # 可以使用getservbyname方法查找网络服务的端口号和标准名 print(socket.getservbyname("http")) # 80 print(socket.getservbyname("https")) # 443 print(socket.getservbyname("ftp")) # 21 print(socket.getservbyname("smtp")) # 25 # 也可以使用getservbyport根据端口号查找协议 print(socket.getservbyport(80)) # http print(socket.getservbyport(666)) # doom
iii)查找服务器地址
import socket ‘‘‘ getaddrinfo函数将一个服务的基本地址转换为一个元组列表,其中包含建立一个连接所需的全部信息。每个元组可能包含不同的网络簇或协议 ‘‘‘ res = socket.getaddrinfo("www.baidu.com", "https") print(res) # [(<AddressFamily.AF_INET: 2>, 0, 0, ‘‘, (‘61.135.169.125‘, 443)), (<AddressFamily.AF_INET: 2>, 0, 0, ‘‘, (‘61.135.169.121‘, 443))]
2.TCP/IP客户和服务器
服务端:
import socket # 创建tcp/ip套接字,这里作为服务端 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定ip和端口,客户端连接这个ip和端口就可以向我们这里的服务端发送消息 # 这里要传入元组 server.bind(("localhost", 8888)) # 同时监听多少个到来的链接,也就是一次最多可以让多少个客户连接 # 虽然可以监听多个链接,但是一次性只能处理一个。 # 比如监听5,表示接收5个链接,当来了5个链接,并不是同时处理5个,而是一次性处理一个,其他的4个要排队。而再来第6个链接连队也不让排了,直接拒绝接收了 server.listen(5) # 创建循环,表示一个链接关闭了,继续循环处理下一个链接 while True: # accept:等待客户端的链接,一旦到来,就会与之交互 # 会有两个返回值。conn:客户端与服务端之间建立的一个链接。addr:客户的地址 # 这个过程是会阻塞的 conn, addr = server.accept() # 创建循环,因为消息不止发送一次,会多次来回发送,你一句我一句,直到没话了,结束循环 # 从而执行外层循环,等待下一个链接,然后继续循环你一句我一句 while True: # 发送消息和接收消息都是conn这个链接实例去执行的 # recv表示接收数据,1024表示最多一次性最多接收1024个字节 data = conn.recv(1024) # 如果对方断开链接了,那么发送的数据就为空了 # 所以要进行判断,如果得到的数据为空,那么就break,也就是断开当前的链接 if not data: print(f"客户端{addr}:断开链接") break # 有数据的话,那么打印出来 print(str(data, encoding="utf-8")) # 同时也给客户端发送数据 conn.sendall(data + bytes(" 我收到了", encoding="utf-8")) # 清理链接,进行资源、端口的释放等等 conn.close()
客户端:
import socket # 我们是客户端,所以要创建一个绑定到指定ip和端口的链接 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 这里不再是bind了,而是connect,因为我们不是创建服务器等待链接,而是客户端,要绑定指定的链接去连接别人 client.connect(("localhost", 8888)) while True: # 发送数据,到时候服务端也会回数据 client.sendall(bytes(input("你要发送的数据:"), encoding="utf-8")) data = client.recv(1024) print(str(data, encoding="utf-8"))
可以看到,这里还有一个不完美的地方,那就是当客户端断开链接时,服务端报错了,这要咋办呢?可以在第二层循环来一个异常捕获就行了
python3标准库这本书太厚了,而且还有一些功能只能在Linux上面用,实在没有时间全部写完。所以一些模块的不常用的功能这里就不写了
(三)selectors:I/O多路复用抽象
selectors模块在select中平台特定的I/O监视函数之上提供了一个平台独立的抽象层。
1.操作模型
以上是关于12.网络通信的主要内容,如果未能解决你的问题,请参考以下文章
VSCode自定义代码片段14——Vue的axios网络请求封装
VSCode自定义代码片段12——JavaScript的Promise对象