Python网络编程入门
纸上得来终觉浅,绝知此事要躬行。
自从互联网诞生以来,现在基本上所有的程序都是网络程序,很少有单机版的程序了。计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。用 Python 进行网络编程,就是在 Python 程序本身这个进程内,连接别的服务器进程的通信端口进行通信。
1. TCP/IP 协议
要编写一个计算机之间的网络通讯的程序,首先需要确定双方通信所使用的协议,之后才能够使用这种约定的协议进行数据的传输。
TCP/IP 协议的作用
- TCP/IP是用于因特网(Internet)的通信协议。它是对计算机必须遵守的规则的描述,只有遵守这些规则,计算机之间才能进行通信。
TCP/IP 协议的分类
- IP - 网际协议
- TCP - 传输控制协议
- UDP - 用户数据报协议
网络编程重要的模块
协议 | 功能用处 | 端口号 | 对应模块模块 |
---|---|---|---|
HTTP | 网页访问 | 80 | httplib、urllib、xmlrpclib |
NNTP | 阅读和张贴新闻文章 | 119 | nntplib |
FTP | 文件传输 | 20 | ftplib、urllib |
SMTP | 发送邮件 | 25 | smtplib |
POP3 | 接收邮件 | 110 | poplib |
IMAP4 | 获取邮件 | 143 | imaplib |
Telnet | 命令行 | 23 | telnetlib |
Gopher | 信息查找 | 70 | gopherlib、urllib |
TCP 和 UDP 的区别
- TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。
- 通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送。而UDP则不为 IP 提供可靠性、流控或差错恢复功能。一般来说,TCP 对应的是可靠性要求高的应用,而UDP对应的则是可靠性要求低、传输经济的应用。
IPv4 地址的分类
- 公有地址
- A 类地址:1-127
- B 类地址:128-191
- C 类地址:192-223
- D 类地址:224-239 组播
- E 类地址:240-254
- 私有地址
- A 类地址:10.0.0.0/8
- B 类地址:172.16.0.0/16 - 172.31.0.0/16
- C 类地址:192.168.0.0/24 - 192.168.255.0/24
TCP 的三次握手
TCP 的四次挥手
2. 套接字
Socket是进程间通信(IPC)的一种实现,允许位于不同主机甚至同一主机上不同进程之间进行通信。socket本质上是对TCP/IP的封装,包含进行网络通信必需的五种信息:连接使用的协议,本地主机的 IP 地址,本地进程的协议端口,对方主机的 IP 地址,对方进程的协议端口。
- 套接字可以简单理解为:套接字 = IP 地址 + 端口号
- 进程间通信的实现不只有socket,还有信号量、共享内存、消息队列等方式
套接字的分类 - 根据传输使用的协议类型
- 流式套接字(SOCK_STREAM)
- TCP通信协议,流式传输
- 建立虚链路、双向通信、可靠地传递、面向连接、无边界
- 数据报套接字(SOCK_DGRAM)
- UDP通信协议,数据报传输
- 不可靠地传递、有边界、无连接
- 裸套接字(SOCK_RAW)
- 不使用传输层协议,直接和底层进行数据传输,如IP
- 在编程工作中,基本不会涉及,后续的三种套接字自行了解
套接字的分类 - 根据套接字地址家族(Address Family)
- IPv4 协议簇(AF_INET)
- 不同主机之间进行通信时使用
- IPv6 协议簇(AF_INET6)
- 不同主机之间进行通信时使用
- UNIX 协议簇(AF_UNIX)
- 同一主机上不同进程之间通信时使用
- 不需要将数据向下传递,不占用TCP/UDP协议栈,提升传输效率
套接字的端口号
- TCP 的端口号
- 0-65535
- 传输控制协议,面向连接的协议
- 通信前需要建立虚拟链路,结束后拆除链路
- UDP 的端口号
- 0-65535
- 用户数据报协议,无连接的协议
3. 函数模块
Socket又称套接字,应用程序通常通过套接字网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。这里,我们将介绍Socket套接字函数的使用方法。
创建套接字的语法
- 在Python中,网络编程主要用的是socket模块
# socket_family: 主要包含AF_INET、AF_INET6、AF_UNIX # socket_type: 主要包含SOCK_STREAM、SOCK_DGRAM、SOCK_RAW # protocol: 用于指定创建套接字的协议,通常默认省略值为0 socket(socket_family, socket_type, protocol=0)
# 创建TCP套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Socket 模块的方法
- 服务器端套接字
套接字方法 | 解释说明 |
---|---|
s.bind(address) | 将地址(host, port)绑定到套接字;在AF_INET下以元组的形式表示地址 |
s.listen(backlog) | 开始监听TCP传入的连接;backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量;该值至少为1,大部分应用程序设为5就可以了 |
s.accept() | 被动接受TCP客户端的连接并返回一个元组(conn, address);其中conn是新的套接字对象,可以用来接收和发送数据;address是连接客户端的地址 |
- 客户端套接字
套接字方法 | 解释说明 |
---|---|
s.connect(address) | 连接到address服务器的套接字;一般address的格式为元组(host, port);如果连接出错,则返回socket.error错误 |
s.connect_ex(address) | 功能和connect()函数一样;但成功的时候返回数字码0,而出错时返回出错码,而不是抛出异常 |
- 公共用途的套接字函数
套接字方法 | 解释说明 |
---|---|
s.recv(bufsize[, flag]) | 接收TCP套接字的数据,数据以字符串形式返回;bufsize指定要接收的最大数据量;flag提供有关消息的其他信息,通常可以忽略 |
s.send(string[, flag]) | 发送TCP套接字的数据,将string中的数据发送到连接的套接字;返回值是要发送的字节数量,该数量可能小于string的字节大小 |
s.sendall(string[, flag]) | 完整发送TCP套接字的数据,将string中的数据发送到连接的套接字;但在返回之前会尝试发送所有数据,成功返回None,失败则抛出异常 |
s.recvfrom(bufsize[, flag]) | 接收UDP套接字的数据,与recv()类似,但返回值是元组(data, address);其中data是包含接收数据的字符串;address是发送数据的套接字地址 |
s.sendto(string[, flag], address) | 发送UDP套接字的数据,将数据发送到指定的address套接字;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表示永不过期;一般超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作,如s.connect() |
s.gettimeout() | 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None |
s.fileno() | 返回套接字的文件描述符 |
s.setblocking(flag) | 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值);非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常 |
s.makefile() | 创建一个与该套接字相关连的文件 |
4. TCP 编程
TCP协议是面向连接的,会事先建立好连接,所以不需要指定地址
- 服务端示例代码
import socket HOST = '127.0.0.1' PORT = 8001 # 创建、绑定和监听套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(5) print(f'Server start at: {HOST}:{PORT}') while True: # 接收客户端的连接 conn, addr = s.accept() print(f'Connected by {addr}.') # 通信循环 # 因为一次发送的内容可能很大,所以需要多次接收 while True: data = conn.recv(1024) print(f'[Receive client]: {data}') # 服务端给客户端发送数据 # 使用send方法发送byte类型的数据,所有utf8格式需要手动转换一下 conn.send(bytes(f'Server received {data}.', 'utf-8')) # 关闭客户端套接字 conn.close() # 关闭服务端套接字 s.close()
$ python server.py Server start at: 127.0.0.1:8001 Connected by ('127.0.0.1', 64138). [Receive client]: b'hello' [Receive client]: b'my name is escape.' [Receive client]: b'' [Receive client]: b'' Traceback (most recent call last): File "server.py", line 24, in <module> conn.send(bytes(f'Server received {data}.', 'utf-8')) BrokenPipeError: [Errno 32] Broken pipe
- 客户端示例代码
import socket HOST = '127.0.0.1' PORT = 8001 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) while True: msg = input('Please input message: ') s.send(bytes(msg, 'utf-8')) data = s.recv(1024) print(f'[Received Server]: {data}')
$ python client.py Please input message: hello [Received Server]: b"Server received b'hello'." Please input message: my name is escape. [Received Server]: b"Server received b'my name is escape.'." Please input message: ^CTraceback (most recent call last): File "client.py", line 10, in <module> msg = input('Please input message: ') KeyboardInterrupt
5. UDP 编程
而UDP是面向无连接的,所以每次发送都需要指定发送给谁
- 服务端示例代码
import socket HOST = '127.0.0.1' PORT = 8001 # 创建和绑定套接字,不需要监听连接 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind((HOST, PORT)) print(f'Server start at: {HOST}:{PORT}') while True: # 不需要接收连接,直接接收数据 data, addr = s.recvfrom(1024) print(f'Connected by {addr}.') print(f'[Receive client]: {data}') # 服务端给客户端发送数据 s.sendto(bytes(f'{data}', 'utf-8'), addr) # 关闭服务器套接字 s.close()
$ python runsocket.py Server start at: 127.0.0.1:8001 Connected by ('127.0.0.1', 54007). [Receive client]: b'test' Connected by ('127.0.0.1', 54007). [Receive client]: b'exit' ^CTraceback (most recent call last): File "server.py", line 13, in <module> data, addr = s.recvfrom(1024) KeyboardInterrupt
- 客户端示例代码
import socket HOST = '127.0.0.1' PORT = 8001 # 不需要使用connect方法连接到服务器 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: msg = input('Please input message: ') s.sendto(bytes(msg, 'utf-8'), (HOST, PORT)) data = s.recvfrom(1024) print(f'[Received Server]: {data}')
$ python runsocket.py Please input message: test [Received Server]: (b"b'test'", ('127.0.0.1', 8001)) Please input message: exit [Received Server]: (b"b'exit'", ('127.0.0.1', 8001)) Please input message: ^CTraceback (most recent call last): File "client.py", line 10, in <module> msg = input('Please input message: ') KeyboardInterrupt