第十九章 网络编程
第十九章:网络编程
Python是很强大的网络编程工具。Python有很多针对常见网络协议的库,这些库可以使我们集中精力在程序的逻辑处理上,而不是停留在网络实现的细节中。使用Python很容易写出处理各种协议格式的代码,Python在处理字节流的各种模式方面很擅长。
初识网络编程
自从互联网诞生以来,基本上所有程序都是网络程序,很少有单机版程序了。
计算机网络把各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程在程序中实现了两台计算机的通信。
举个例子,当你使用浏览器访问淘宝网时,你的计算机和淘宝的某台服务器通过互联网连接起来了,淘宝的服务器就会把网页内容作为数据通过互联网传输到你的计算机上。
由于你的计算机上可能不止有浏览器,还有微信、办公软件、邮件客户端等,不同程序连接的计算机也会不同,因此网络通信是两台计算机的两个进程之间的通信。比如,浏览器进程是和淘宝服务器上某个Web服务进程通信,而微信进程是和腾讯服务器上某个进程通信。
网络编程对所有开发语言都是一样的,Python也不例外。用Python进行网络编程就是在Python程序的进程内连接别的服务器进程的通信端口进行通信。
TCP/IP简介
大家对互联网应该很熟悉,计算机网络的出现比互联网要早很多。
为了联网,计算机必须规定通信协议。早期的计算机网络都是由各厂商自己规定一套协议,如IBM和Microsoft都有各自的网络协议,互不兼容。这就好比一群人有的说英语,有的说中文,有的说德语,但都只懂一种语言,因此只有说同一种语言的人可以交流,说不同语言的人就不行了。
为了把全世界所有不同类型的计算机都连接起来,必须规定一套全球通用协议。为了实现互联网这个目标,大家共同制定了互联网协议簇(Internet Protocol Suite)作为通用协议标准。Internet是由inter和net两个单词组合起来的,原意是连接“网络”的网络,有了Internet,只要支持这个协议,任何私有网络都可以连入互联网。
互联网协议包含上百种协议标准,由于最重要的两个协议是TCP和IP协议,因此大家把互联网协议简称为TCP/IP协议。
通信时双方必须知道对方的标识,好比发邮件必须知道对方的邮件地址。互联网上计算机的唯一标识就是IP地址,如192.168.12.27。如果一台计算机同时接入两个或更多网络(如路由器),就会有两个或多个IP地址,所以IP地址对应的实际是计算机的网络接口,通常是网卡。
IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此路由器负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途经多个路由,但不保证能到达,也不保证按顺序到达。IP地址实际上是一个32位整数(IPv4),以字符串表示的IP地址实际上是把32位整数按8位分组后的数字表示(如192.168.0.1),目的是便于阅读。
IPv6地址实际上是128位整数,是目前使用的IPv4地址的升级版,以字符串表示类似于2001:0db8:85a3:0042:1000:8a2e:0370:7334。
TCP协议建立在IP协议之上。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后对每个IP包编号,确保对方按顺序收到,如果包丢掉了就自动重发。
许多常用的更高级的协议都是建立在TCP协议基础上的,如用于浏览器的HTTP协议、发送邮件的SMTP协议等。
一个IP包除了包含要传输的数据外,还包含源IP地址和目标IP地址、源端口和目标端口。
端口有什么作用?两台计算机通信时,只发IP地址是不够的,因为同一台计算机运行着多个网络程序。一个IP包来了之后交给浏览器还是微信,需要用端口号进行区分。每个网络程序都向操作系统申请唯一的端口号,这样两个进程在两台计算机之间建立网络连接就需要各自的IP地址和端口号。
一个进程也可能同时与多台计算机建立连接,因此它会申请很多端口。
网络设计模块
前面我们了解了TCP/IP协议、IP地址和端口的基本概念,下面我们开始了解网络编程。
标准库中有很多网络设计模块,除了明确处理网络事务的模块外,还有很多模块是与网络相关的。接下来我们讨论其中几个模块。
Socket 简介
网络编程中有一个基本组件——套接字(Socket)。
套接字为特定网络协议(如TCP/IP、ICMP/IP、UDP/IP等)套件对上的网络应用程序提供者提供当前可移植标准的对象。套接字允许程序接收数据并进行连接,如发送和接收数据。为了建立通信通道,网络通信的每个端点拥有一个套接字对象极为重要。
套接字为BSD UNIX系统核心的一部分,而且被许多类似UNIX的操作系统(包括Linux)所采纳。许多非BSD UNIX系统(如MS-DOS、Windows、OS/2,Mac OS及大部分主机环境)都以库形式提供对套接字的支持。
套接字主要是两个程序之间的“信息通道”。程序(通过网络连接)可能分布在不同的计算机上,通过套接字相互发送信息。在Python中,大多数网络都隐藏了socket模块的基本细节,并且不直接和套接字交互。
socket 模块
套接字模块是一个非常简单的基于对象的接口,提供对低层BSD套接字样式网络的访问。使用该模块可以实现客户机和服务器套接字。要在Python中建立具有TCP和流套接字的简单服务器需要使用socket模块。利用该模块包含的函数和类定义可生成通过网络进行通信的程序。
客户端
客户端:就是需要被服务的一方
所谓的服务器端:就是提供服务的一方
tcp客户端构建流程:
- tcp的客户端要比服务器端简单很多,如果说服务器端是需要自己买手机、查手机卡、设置铃声、等待别人打电话流程的话。那么客户端就只需要找一个电话亭,拿起电话拨打即可,流程要少很多。
实例代码:
from socket import *
# 创建socket
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
# 目的信息
server_ip = input("请输入服务器ip:")
server_port = int(input("请输入服务器port:"))
# 链接服务器
tcp_client_socket.connect((server_ip, server_port))
# 提示用户输入数据
send_data = input("请输入要发送的数据:")
tcp_client_socket.send(send_data.encode("gbk"))
# 接收对方发送过来的数据,最大接收1024个字节
recvData = tcp_client_socket.recv(1024)
print('接收到的数据为:', recvData.decode('utf-8'))
# 关闭套接字
tcp_client_socket.close()
服务器
如果想让别人能更够打通咱们的电话获取相应服务的话,需要做以下几件事情:
- 买个手机
- 插上手机卡
- 设计手机为正常接听状态(即能够响铃)
- 静静的等着别人拨打
如同上面的电话机过程一样,在程序中,如果想要完成一个tcp服务器的功能,需要的流程如下:
- socket创建一个套接字
- bind绑定ip和port
- listen使套接字变为可以被动链接
- accept等待客户端的链接
- recv/send接收发送数据
一个很简单的tcp服务器如下:
from socket import *
# 创建socket
tcp_server_socket = socket(AF_INET, SOCK_STREAM)
# 本地信息
address = ('', 7788)
# 绑定
tcp_server_socket.bind(address)
# 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的链接了
tcp_server_socket.listen(128)
# 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务
# client_socket用来为这个客户端服务
# tcp_server_socket就可以省下来专门等待其他新客户端的链接
client_socket, clientAddr = tcp_server_socket.accept()
# 接收对方发送过来的数据
recv_data = client_socket.recv(1024) # 接收1024个字节
print('接收到的数据为:', recv_data.decode('gbk'))
# 发送一些数据到客户端
client_socket.send("thank you !".encode('gbk'))
# 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接
client_socket.close()
文件下载器
客户端代码:
import socket
def main():
# 1. 创建套接字
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 获取服务器的ip port
dest_ip = input("请输入下载服务器的ip:")
dest_port = int(input("请输入下载服务器的port:"))
# 3. 链接服务器
tcp_socket.connect((dest_ip, dest_port))
# 4. 获取下载的文件名字
download_file_name = input("请输入要下载的文件名字:")
# 5. 将文件名字发送到服务器
tcp_socket.send(download_file_name.encode("utf-8"))
# 6. 接收文件中的数据
recv_data = tcp_socket.recv(1024) # 1024--->1K 1024*1024--->1k*1024=1M 1024*1024*124--->1G
if recv_data:
# 7. 保存接收到的数据到一个文件中
with open("[新]" + download_file_name, "wb") as f:
f.write(recv_data)
# 8. 关闭套接字
tcp_socket.close()
if __name__ == "__main__":
main()
服务端代码:
import socket
def send_file_to_client(new_client_socket, client_addr):
# 1. 接收客户端 需要下载的文件名
# 接收客户端发送过来的 要下载的文件名
file_name = new_client_socket.recv(1024).decode("utf-8")
print("客户端(%s)需要下载文件是:%s" % (str(client_addr), file_name))
file_content = None
# 2. 打开这个文件,读取数据
try:
f = open(file_name, "rb")
file_content = f.read()
f.close()
except Exception as ret:
print("没有要下载的文件(%s)" % file_name)
# 3. 发送文件的数据给客户端
if file_content:
# new_client_socket.send("hahahghai-----ok-----".encode("utf-8"))
new_client_socket.send(file_content)
def main():
# 1. 买个手机(创建套接字 socket)
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 插入手机卡(绑定本地信息 bind)
tcp_server_socket.bind(("", 7890))
# 3. 将手机设置为正常的 响铃模式(让默认的套接字由主动变为被动 listen)
tcp_server_socket.listen(128)
while True:
# 4. 等待别人的电话到来(等待客户端的链接 accept)
new_client_socket, client_addr = tcp_server_socket.accept()
# 5. 调用发送文件函数,完成为客户端服务
send_file_to_client(new_client_socket, client_addr)
# 6. 关闭套接字
new_client_socket.close()
tcp_server_socket.close()
if __name__ == "__main__":
main()
补充内容
网络编程中的 UDP 协议
在网络开发中,除了上述所讲的TCP协议之外,还有一种协议为UDP协议。
UDP协议相对简单,我们接下来就大致了解一下。
UDP 协议与 TCP 协议的区别
UDP 协议实现相对于 TCP 协议要简单许多。
在上一小节中讲到了 TCP 的实现方式,大家会发现在 TCP 协议中是严格区分客户端与服务端的。但是在 UDP 协议中并没有客户端与服务端的概念。
UDP 协议除了代码构造与 TCP 有一定区别外,在性能与可靠性上也有区别:
- UDP 协议相对于 TCP 协议在性能上要优于 TCP 协议。
- TCP 协议一般都是单点发送信息,无法群发信息。UDP 协议支持广播,可以群发消息。
- UDP 协议在信息安全上要弱于 TCP。发送的消息可能存在丢失的风险。
UDP 协议代码实现方式
发送数据:
from socket import *
# 1. 创建udp套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)
# 2. 准备接收方的地址
# '192.168.1.103'表示目的ip地址
# 8080表示目的端口
dest_addr = ('192.168.1.103', 8080) # 注意 是元组,ip是字符串,端口是数字
# 3. 从键盘获取数据
send_data = input("请输入要发送的数据:")
# 4. 发送数据到指定的电脑上的指定程序中
udp_socket.sendto(send_data.encode('utf-8'), dest_addr)
# 5. 关闭套接字
udp_socket.close()
接收数据:
#coding=utf-8
from socket import *
# 1. 创建udp套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)
# 2. 准备接收方的地址
dest_addr = ('192.168.236.129', 8080)
# 3. 从键盘获取数据
send_data = input("请输入要发送的数据:")
# 4. 发送数据到指定的电脑上
udp_socket.sendto(send_data.encode('utf-8'), dest_addr)
# 5. 等待接收对方发送的数据
recv_data = udp_socket.recvfrom(1024) # 1024表示本次接收的最大字节数
# 6. 显示对方发送的数据
# 接收到的数据recv_data是一个元组
# 第1个元素是对方发送的数据
# 第2个元素是对方的ip和端口
print(recv_data[0].decode('utf-8'))
print(recv_data[1])
# 7. 关闭套接字
udp_socket.close()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?