作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处
网络基础
计算机为了联网,就必须规定通信协议,所谓通信协议就是计算机在网络中进行数据交换而建立的规则、标准或约定的集合。早期的计算机网络都是由各厂商自己规定一套协议,IBM、Apple 和 Microsoft 都有各自的网络协议,互不兼容。这就好比一群人,有的说中文,有的说英文,有的说德文,说同一种语言的人可以交流,不同语言之间就无法交流。
为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议,为了实现互联网这个目的,互联网协议(Internet Protocol Suite)诞生了。由于全世界的计算机众多,网络节点纷繁复杂,要想将数以亿计的网络设备都连通在一起,就需要进行分层。这也是将复杂的问题进行分解的一种思想。
因此互联网的实现,是按分层构建的。每一层都有自己独特的功能,而且上层的实现需要依赖于下层的功能。这就像建筑物一样,每一层都靠下一层支持。一般互联网的协议有 OSI 七层模型,和 TCP/IP 协议,如下图所示:
协议和套接字
-
IP 协议
在通信时,通信双方必须知道对方的标识,好比发送快递必须知道对方的地址。互联网上每个计算机的唯一标识就是 IP 地址。IP 地址实际上是一个 32 位整数(称为 IPv4),以字符串表示的 IP 地址如 172.16.254.1 实际上是把 32 位整数按 8 位分组后的数字表示,目的是便于阅读。
IP 协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,类似于将一个大包裹拆分成几个小包裹,然后通过 IP 包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个 IP 包转发出去。IP 包的特点是按块发送,途径多个路由,但不保证都能到达,也不保证顺序到达。 -
TCP 协议
TCP 协议是建立在 IP 协议之上的。TCP 协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP 协议会通过 3 次握手建立可靠连接,然后对每个 IP 包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。
许多常用的更高级的协议都是建立在 TCP 协议基础上的,比如用于浏览器的 HTTP 协议、发送邮件的 SMTP 协议等。一个 TCP 报文除了包含要传输的数据外,还包含源 IP 地址和目标 IP 地址,源端口和目标端口。 -
UDP 协议
相对于 TCP 协议,UDP 协议则是面向无连接的协议。使用 UDP 协议时,不需要建立连接,只需要知道对方的 IP 地址和端口号,就可以直接发送数据包。但是,数据无法保证一定到达。虽然用 UDP 传输数据不可靠,但它的优点是比 TCP 协议速度快。对于不要求可靠到达的数据,就可以使用 UDP 协议。
-
套接字 Socket
为了让两个程序通过网络进行通信,二者均必须使用 Socket 套接字。Socket 的英文原意是 “孔” 或 “插座”,通常也称作 “套接字”,用于描述 IP 地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
Socket 正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供 220 伏交流电,有的提供 110 伏交流电,有的则提供有线电视节目。客户软件将插头插到不同编号的插座,就可以得到不同的服务。
在 Python 中使用 socket 模块的函数 socket() 来创建套接字,语法如下:
s = socket.socket(AddressFamily, Type)
- Address Family:可以选择 AF_INET(用于 Internet 进程间通信)或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用 AF_INET;
- Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议);
Socket 对象的内置方法如下表所示:
函数 | 描述 |
---|---|
s.bind() | 绑定地址(host, port)到套接字,在 AF_INET 下以元组 (host, port) 的形式表示地址 |
s.listen() | 开始 TCP 监听。backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为 1,大部分应用程序设为 5 就可以了 |
s.accept() | 被动接收 TCP 客户端连接,并且以阻塞方式等待连接的到来 |
s.connect() | 主动初始化 TCP 服务器连接,一般 address 的格式为元组 (host, port),如果连接出错,则返回 socket.error 错误 |
s.recv() | 接收 TCP 数据,数据以字符串形式返回,bufsize 指定要接受的最大数据量。flag 提供有关消息的其他信息,通常可以忽略 |
s.send() | 发送 TCP 数据,将 string 中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于 string 的字节大小 |
s.sendall() | 完整发送 TCP 数据。将 string 中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回 None,失败则抛出异常 |
s.recvfrom() | 接收 UDP 数据,与 recv() 类似,但返回值是 (data, address)。其中 data 是包含接收数据的字符串,address 是发送数据的套接字地址 |
s.sendto() | 发送 UDP 数据,将数据发送到套接字,address 是形式为 (ipaddr, port) 的元组,指定远程地址。返回值是发送的字节数 |
s.close() | 关闭套接字 |
TCP 编程
由于 TCP 连接具有安全可靠的特性,所以 TCP 应用更为广泛。创建 TCP 连接时,主动发起连接的叫客户端,被动响应连接的叫服务器。TCP 客户端和服务器通信模型如下图所示:
示例:
# server.py
import socket
host = socket.gethostname() # 获取主机地址
port = 12345 # 设置端口号
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建 TCP/IP 套接字
s.bind((host, port)) # 绑定地址 (host, port) 到套接字
s.listen(1) # 设置最多连接数量
print("开始监听,等待客户端连接...")
sock, addr = s.accept() # 被动接收 TCP 客户端连接
print("连接已建立")
info = sock.recv(1024).decode() # 接收客户端数据
while info != 'byebye':
if info:
print("从客户端接收到的内容是: " + info)
send_data = input("输入要发送的内容: ") # 发送消息
sock.send(send_data.encode()) # 发送 TCP 数据
if send_data == 'byebye': # 如果发送 byebye,则退出
break;
info = sock.recv(1024).decode() # 接收客户端数据
sock.close() # 关闭客户端套接字
s.close() # 关闭服务器套接字
# client.py
import socket
s = socket.socket()
host = socket.gethostname()
port = 12345
s.connect((host, port))
print("已连接服务器")
info = ''
while info != 'byebye':
send_data = input("请输入要发送的内容: ")
s.send(send_data.encode())
if send_data == 'byebye':
break;
info = s.recv(1024).decode()
print("接收到的内容是: " + info)
s.close()
上述例子的运行结果为:
# server.py
开始监听,等待客户端连接...
连接已建立
从客户端接收到的内容是: 123456
输入要发送的内容: 987555
# client.py
已连接服务器
请输入要发送的内容: 123456
接收到的内容是: 987555
请输入要发送的内容: byebye
UDP 编程
UDP 是面向消息的协议,通信时不需要建立连接,数据的传输自然是不可靠的,UDP 一般用于多点通信和实时的数据业务,例如:
- 语音广播
- 视频
- 聊天软件
- TFTP(简单文件传送)
- SNMP(简单网络管理协议)
- RIP(路由信息协议,如报告股票市场、航空信息)
- DNS(域名解释)
和 TCP 类似,使用 UDP 的通信双方也分为客户端和服务器,UDP 通信模型如下图所示:
示例:
# server.py
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建 UDP 套接字
s.bind(("127.0.0.1", 8888)) # 绑定地址 (host, port) 到套接字
print("绑定 UDP 到 8888 端口")
data, addr = s.recvfrom(1024) # 接收数据,返回值是 (data, address)。其中 data 是包含接收数据的字符串,address 是发送数据的套接字地址
data = float(data) * 1.8 + 32 # 转化公式
send_data = "转换后的温度(单位:华氏温度): " + str(data)
print("Received from %s: %s" % addr)
s.sendto(send_data.encode(), addr) # 发送给客户端
s.close() # 关闭服务器套接字
# client.py
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建 UDP 套接字
data = input("请输入要转换的温度(单位:摄氏度): ") # 输入要转换的温度
s.sendto(data.encode(), ("127.0.0.1", 8888)) # 发送数据
print(s.recv(1024).decode()) # 打印接收数据
s.close() # 关闭套接字
上述例子的运行结果为:
# server.py
绑定 UDP 到 8888 端口
Received from 127.0.0.1: 59470
# client.py
请输入要转换的温度(单位:摄氏度): 25
转换后的温度(单位:华氏温度): 77.0
更多请参考
- Python 进阶之路