本地的进程间通信可以有很多种,比如队列、同步(互斥锁)等;
那么网络中进程之间如何通信??
首先要解决的问题是,如何准确标识一个网络中的进程;
我们知道,网络中的 ip 地址可以准确的标识一个主机,而 "协议+端口" 可以准确的标识一个主机中的应用程序(进程);
这样,利用 ip地址、协议、端口 就可以标识网络的进程了,网络中的进程可以利用这个标志与其他进程进行通信;
socket,也称为套接字,是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机直接的进程间通信,即网络通信;
在 python 中,使用 socket 模块中的 socket 函数就可以创建一个 socket,语法如下:
sock = socket.socket(AddressFamily, Type)
上面方法返回 socket 套接字对象,用于进程间通信;
参数 AddressFamily:表示地址协议族,可以选择 AF_INET,用于 internet 进程间通信;也可以选择 AF_UNIX,用于同一台机器进程间通信;一般常用 AF_INET;
参数 Type:表示套接字类型,可以是 SOCK_STREAM,表示流式套接字,主要用于 TCP 通信;也可以是 SOCK_DGRAM,表示数据报套接字,主要用于 UDP 通信;
UDP 是用户数据报协议,是一个无连接的简单的面向数据报的运输层协议。UDP 不提供可靠性,它只是把应用程序传给 IP 层的数据报发送出去,但是并不能保证它们能到达目的地。
由于 UDP 在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
UDP 是一种面向无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
UDP 数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送。
UDP 传输数据时有大小限制,每个被传输的数据报必须限定在 64KB 之内。
UDP 是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方。
UDP 操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中 client/server 应用程序。例如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用 UDP 会更合理一些。
创建一个 UDP 通信客户端的步骤:
1、创建客户端套接字
2、指定服务端 IP 地址和端口
3、发送/接收数据
4、关闭套接字
# 导入 socket 模块
import socket
# 通过 socket 模块中的 socket 方法创建一个 socket 对象并返回;
# 参数1 表示地址协议族,AF_INET 表示用于 internet 进程间通信;
# 参数2 表示套接字类型,SOCK_DGRAM 表示数据报套接字,用于 UDP 通信;
# SOCK_STREAM 表示流式套接字,用于 TCP 通信;
udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 指定服务器 ip 地址和端口号
sendAddr = ("127.0.0.1", 8888)
# 发送数据到服务器:
# 使用 sendto 方法,参数1 表示要发送的数据,必须是 bytes 类型;
# 参数2 表示要发送到的服务器的 ip 地址和端口号;
udpSocket.sendto("how are you!".encode("UTF-8"), sendAddr)
# 接收服务器返回的数据:1024 表示最大接收字节数;
# 接收到的数据是一个元祖,包含两个元素,第一个元素
# 是服务端真正发送的数据,以 bytes 类型发送的;
# 第二个元素是服务端的 ip 地址和端口号;
recvData = udpSocket.recvfrom(1024)
print("服务端发送的数据:", recvData[0].decode("UTF-8"))
print("服务端地址:", recvData[1])
# 关闭套接字
udpSocket.close()
创建一个 UDP 通信服务端的步骤:
1、创建一个服务端套接字
2、指定本地服务端的 ip 地址和端口
3、绑定本地的 ip 地址和端口
4、接收/发送数据
5、关闭套接字
# 导入 socket 模块
import socket
# 创建 socket
udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 指定本地服务端 ip 地址和端口(元祖形式)
# 如果不写 ip 地址,表示本地任意一个 ip 地址
servAddr = ("", 8888)
# 绑定本地 ip 地址和端口(在作为服务器的时候需要绑定)
udpSocket.bind(servAddr)
# 接收客户端发送的数据,1024 表示接收的最大字节数;
# 接收到的数据是一个元祖,包含两个元素,第一个元素
# 是客户端真正发送的数据,以 bytes 类型发送的;
# 第二个元素是客户端的 ip 地址和端口号;
recvData = udpSocket.recvfrom(1024)
print("客户端发送的数据:", recvData[0].decode("UTF-8"))
print("客户端地址:", recvData[1])
# 发送数据到客户端:参数1 表示要发送的数据,必须是 bytes 类型;
# 参数2 表示客户端的地址,客户端的地址是通过 recvfrom 方法获取到的;
udpSocket.sendto("I am fine, thank you!".encode("UTF-8"), recvData[1])
# 关闭套接字
udpSocket.close()
一个 udp 网络程序,可以不绑定 ip 地址和端口,此时操作系统会随机进行分配一个端口,如果重新运行程序,端口可能会发生改变;
一个 udp 网络程序,也可以绑定 ip 地址和端口,如果绑定成功,那么操作系统用这个端口号,来进行区分收到的网络数据是否是此进程的;
一般情况下,服务器端需要绑定端口,目的是为了让其他的客户端能够正确发送到此进程;
客户端一般不需要绑定,而是让操作系统随机分配,这样就不会出现因为需要绑定的端口被占用而导致程序无法运行的情况。
广播就是在一个程序中,可以将数据发送到本网络中的所有电脑上;
# 导入 socket 模块
import socket
# 创建 socket
udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 指定广播地址:
# 对于 192.168.1 这个网段来说,192.168.1.255 就是广播地址;
# 如果是 192.168.2 这个网段,就会发送失败;
# addr = ("192.168.1.255", 8888)
# 广播地址还可以写成下面这样:表示自动识别当前网段的 ip 地址;
# 推荐下面这种写法;
addr = ("", 8888)
# 设置套接字,允许发送广播数据
udpSocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# 发送广播数据,将数据发送到本网络中的所有电脑中
udpSocket.sendto("hello".encode("UTF-8"), addr)
# 关闭套接字
udpSocket.close()