Python网络编程入门
一、网络编程基础
当今的世界是一个互联的世界,绝大多数的计算机和人都在通过网络和他人传递信息、沟通互联。怎么做到实现网络的通信?需要了解通信机制。
网络通信是通过多方协作共同完成的,各个参与方需要遵守同一规则约定,才能保证通信有序进行。这样的规则约定,就是所谓的 网络协议 。
为降低复杂度并提高灵活性,大型软件系统一般采用分层的思路进行设计。每个层只专注于本层的处理逻辑,复杂性大大降低;各个层互相配合,共同完成复杂的业务处理。
国际标准组织推出的OSI(Open System Interconnection Reference Model,开放式系统互联通信参考模型)的网络模型(也成为体系结构),共有七层,实则分得太细,比较复杂,过于理想化,不太实用,被用作学校进行网络基础教育的示范和典型。在现实中发展壮大起来的TCP/IP网络模型是四层模型,从根本上和OSI七层网络模型是一样的,只是合并了几层,两者的对应关系如下图:
特别提示:TCP/IP是 Transmission Control Protocol/Internet Protocol的简写,从字面意义上讲,有人可能会认为 TCP/IP 是指 TCP 和 IP 两种协议。实际生活当中有时也确实就是指这两种协议。然而在很多情况下,它只是利用 IP 进行通信时所必须用到的协议群的统称。具体来说,IP 或 ICMP、TCP 或 UDP、TELNET 或 FTP、以及 HTTP 等都属于 TCP/IP 协议。
在TCP/IP协议中,其各层之间的通信机制,大致如下图所示:
TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。由于是基于双方连接的情况下传输的,因此它的连接以及数据传输是非常稳定可靠的,可以使一台计算机发出的字节流完好无损的发生给另一台计算机。对要求可靠性非常高的应用程序会选择此种通信方式。
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去。UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。传输速率快,但不太稳定,会出现发生失败的情况,更极端的就是发生掉包的情况。
IP地址和端口
在网络中每台计算机都必须有一个的IP地址;目前,IP地址广泛使用的版本是IPv4 32位,4个字节,用点分十进制的格式表示,例如:192.168.1.100 。
127.0.0.1 是固定ip地址,代表当前计算机,相当于面向对象里的 "this"。
一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来 实现。那么,主机是怎样区分不同的网络服务呢?通过“IP地址+端口号”来区 分不同的服务的。
程序员实现网络通信,原则上讲,需要将程序代码和TCP/IP网络协议连接起来——直接去和TCP/IP协议打交道,这需要他具备大量、扎实和高水平的网络相关知识,编写大量的网络协议实现相关代码,这些工作对于大多数人来说,不但是困难的,也是耗时费力,还不一定做得好的。这也不符合现代编程理念,因为网络通信是一个非常通用,应用非常广泛的场景,底层内容大家都是一样的,完全可以封装成一个通用的模块,提供调用接口,方便大家使用,而不用重复造轮子。幸运的是,对于普通的程序员有socket帮助,简单地说,socket是对TCP/IP协议的封装和应用(程序员层面上),是一个针对TCP或者UDP编程的接口,是进程之间用来对话的中间层。
有了套接字(socket)其各层之间的通信机制,大致如下图所示:
进行网络编程,通常需要编写两个程序文件,一个作为服务端,一个作为客户端。
下面的示意图展示 Socket和 TCP 的数据流:
其中,客户(Client)端: 发起访问的一方; 服务器(Server)端: 接受访问的一方。
Server端的编写流程
1.建立Socket负责具体通信。这个socket其实只负责接受对方的请求
2.绑定端口和地址
3.监听接入的访问Socket
4.接受访问的Socket,可以理解接受访问即建立了一个通讯的链路通路
5.接受对方的发送内容,利用接收到的Socket接受内容
6.如果有必要,给对方发送反馈信息
7.关闭链路通路
Client端流程
1.建立通信socket
2.链接对方,请求与对方建立通路
3.发送内容到对方服务器
4.接受对方的反馈
5.关闭链接通路
下面的示意图展示 Socket和UDP的数据流:
Server端流程
1.建立socket
2.绑定,为创建的socket指派固定的端口和ip地址
3.接受对方发送内容
4.给对方发送反馈,此步骤为非必须步骤
Client端流程
1.建立通信的socket
2.发送内容到指定服务器
3.接受服务器给定的反馈内容
二、Python 网络编程
用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。Python 网络编程和其它语言类似,通常通过"Socket(套接字)"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。
Python 提供了两个级别访问的网络服务:
低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统 Socket 接口的全部方法。socket --- 底层网络接口 — Python 3.10.0 文档
高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。socketserver --- 用于网络服务器的框架 — Python 3.10.0 文档
下面先介绍Socket(套接字)编程,然后介绍网络服务模块 SocketServer编程。
Python 中的 socket()函数/方法来创建套接字,语法格式如下:
socket.socket([family[, type[, proto]]])
参数
family: 套接字家族可以使 AF_UNIX 或者 AF_INET。
type: 套接字类型可以根据是面向连接的还是非连接分为 SOCK_STREAM 或 SOCK_DGRAM。
protocol: 一般不填默认为 0。
套接字(socket)提供的方法功能
套接字服务器端方法 | |
s.bind() | 绑定地址(host,port)到套接字, 在 AF_INET下,以元组(host,port)的形式表示地址。 |
s.listen() | 开始 TCP 监听。backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为 1,大部分应用程序设为 5 就可以了。 |
s.accept() | 被动接受TCP客户端连接,(阻塞式)等待连接的到来 |
套接字客户端方法 | |
s.connect() | 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 |
s.connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 |
套接字公共用途方法 | |
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() | 关闭套接字 |
s.getpeername() | 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。 |
s.getsockname() | 返回套接字自己的地址。通常是一个元组(ipaddr,port) |
s.setsockopt(level,optname,value) | 设置给定套接字选项的值。 |
s.getsockopt(level,optname[.buflen]) | 返回套接字选项的值。 |
s.settimeout(timeout) | 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect()) |
s.gettimeout() | 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。 |
s.fileno() | 返回套接字的文件描述符。 |
s.setblocking(flag) | 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。 |
s.makefile() | 创建一个与该套接字相关连的文件 |
简单TCP通信功能示例
进行网络编程,通常需要编写两个py文件,一个服务端,一个客户端。
用Socket进行TCP协议编程,对于服务器,要提供IP地址和端口号,监听连接请求,对每一个新的连接,创建一个线程或进程来处理。对于客户端,需要要主动连接服务器的IP和指定端口。
1、server端源码(server端.py)如下:
import socket
ip_port = ('127.0.0.1', 9999)
sk = socket.socket() # 创建套接字
sk.bind(ip_port) # 绑定服务地址
sk.listen(5) # 监听连接请求
print('启动socket服务,等待客户端连接...')
conn, address = sk.accept() # 等待连接,此处自动阻塞
while True: # 一个死循环,直到客户端发送‘exit’的信号,才关闭连接
client_data = conn.recv(1024).decode() # 接收信息
if client_data == "exit": # 判断是否退出连接
exit("通信结束")
print("来自%s的客户端向你发来信息:%s" % (address, client_data))
conn.sendall('服务器已经收到你的信息'.encode()) # 回馈信息给客户端
conn.close() # 关闭连接
2、client端(client端.py)源码如下:
import socket
ip_port = ('127.0.0.1', 9999)
s = socket.socket() # 创建套接字
s.connect(ip_port) # 连接服务器
while True: # 通过一个死循环不断接收用户输入,并发送给服务器
inp = input("请输入要发送的信息: ").strip()
if not inp: # 防止输入空信息,导致异常退出
continue
s.sendall(inp.encode())
if inp == "exit": # 如果输入的是‘exit’,表示断开连接
print("结束通信!")
break
server_reply = s.recv(1024).decode()
print(server_reply)
s.close() # 关闭连接
特别说明,我们的例子中为了方便实验使用了特殊的IP地址127.0.0.1,这个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。
打开两个cmd(命令行)窗口,一个先运行服务器端程序,另一个运行客户端程序,就可以看到效果了。
我这里,为了方便管理与测试将这两个文件放在这个文件夹中:D:\Python网络编程实践
先启动server端,打开cmd输入
Python D:\Python网络编程实践\server端.py
再启动\client,打开cmd输
Python D:\Python网络编程实践\client端.py
运行效果参见下图:
简单UDP通信功能示例
相对TCP编程,UDP编程就简单多了,当然可靠性和安全性也差很多。由于UDP没有握手和挥手的过程,因此accept()和connect()方法都不需要。
server端源码(UDPserver端.py)如下:
import socket
ip_port = ('127.0.0.1', 9999)
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) # 创建套接字
sk.bind(ip_port) # 绑定服务地址
print('服务端启动,等待客户端连接...')
while True:
data = sk.recv(1024).strip().decode() # 接收信息
print(data)
if data == "exit":
print("客户端主动断开连接!")
break
sk.close() # 关闭连接
2、client端(UDPclient端.py)源码如下:
import socket
ip_port = ('127.0.0.1', 9999)
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) # 创建套接字
while True:
inp = input('发送的消息:').strip()
sk.sendto(inp.encode(), ip_port) # 发送信息
if inp == 'exit':
break
sk.close()
打开两个cmd(命令行)窗口,一个先运行服务器端程序,另一个运行客户端程序,就可以看到效果了。
我这里,为了方便管理与测试将这两个文件放在这个文件夹中:D:\Python网络编程实践
先启动server端,打开cmd输入
Python D:\Python网络编程实践\UDPserver端.py
再启动\client,打开cmd输
Python D:\Python网络编程实践\UDPclient端.py
运行效果参见下图:
socketserver模块编程
socketserver 模块简化了编写网络服务器的任务。
socketserver --- 用于网络服务器的框架 — Python 3.10.0 文档
虽说用socket(套接字)编写简单的网络程序很方便,但复杂一点的网络程序还是用现成的框架比较 好。这样就可以专心事务逻辑,而不是套接字的各种细节。SocketServer模块简化了编写网络服务程序的任务。同时SocketServer模块也 是Python标准库中很多服务器框架的基础。
socketserver 包含4个类型:TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer。前两个用于windows,后两个用于Unix。
ThreadingTCPServer()方法/函数可实现多线程。多线程即同时与多个用户交互且互不影响。
ForkingTCPServer()是多进程方法/函数。多进程同多线程道理一样。 但是此方法/函数适用于Unix。
创建一个SocketServer分3步:(1)创建一个请求处理类,并继承BaseRequestHandler,且在类中重写BaseRequestHandler的handle(). (2)实例化一个TCPServer,且传递server ip 和第一步所创建的请求处理类给实例化对象。(3)处理多个请求 server.serve_forver()。
使用socketserver编程的例子
客户端的代码很好理解,可以和前面一样,关键是服务器端。
1、server端源码(socketServer端.py)如下:
import socketserver
class MyServer(socketserver.BaseRequestHandler):
"""
必须继承socketserver.BaseRequestHandler类
"""
def handle(self):
"""
必须实现这个方法!
:return:
"""
conn = self.request # request里封装了所有请求的数据
conn.sendall('欢迎访问socketserver服务器!'.encode())
while True:
data = conn.recv(1024).decode()
if data == "exit":
print("断开与%s的连接!" % (self.client_address,))
break
print("来自%s的客户端向你发来信息:%s" % (self.client_address, data))
conn.sendall(('已收到你的消息<%s>' % data).encode())
# 创建一个多线程TCP服务器
server = socketserver.ThreadingTCPServer(('127.0.0.1', 9999), MyServer)
print("启动socketserver服务器!")
# 启动服务器,服务器将一直保持运行状态
server.serve_forever()
2、client端(clientMy端2.py)源码如下:
import socket
ip_port = ('127.0.0.1', 9999)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)
data = sk.recv(1024).decode()
print('服务器:', data)
while True:
inp = input('请输入要发送的信息:').strip()
if not inp:
continue
sk.sendall(inp.encode())
if inp == 'exit':
print("谢谢使用,再见!")
break
data = sk.recv(1024).decode()
print('服务器:', data)
sk.close()
打开两个cmd(命令行)窗口,一个先运行服务器端程序,另一个运行客户端程序,就可以看到效果了。
我这里,为了方便管理与测试将这两个文件放在这个文件夹中:D:\Python网络编程实践
先启动server端,打开cmd输入
Python D:\Python网络编程实践\socketServer端.py
再启动\client,打开cmd输
Python D:\Python网络编程实践\client端2.py
运行效果参见下图:
附录