Python网络编程入门


纸上得来终觉浅,绝知此事要躬行。

Python网络编程入门


自从互联网诞生以来,现在基本上所有的程序都是网络程序,很少有单机版的程序了。计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。用 Python 进行网络编程,就是在 Python 程序本身这个进程内,连接别的服务器进程的通信端口进行通信。

Python网络编程入门


1. TCP/IP 协议

要编写一个计算机之间的网络通讯的程序,首先需要确定双方通信所使用的协议,之后才能够使用这种约定的协议进行数据的传输。

TCP/IP 协议的作用

  • TCP/IP是用于因特网(Internet)的通信协议。它是对计算机必须遵守的规则的描述,只有遵守这些规则,计算机之间才能进行通信。

Python网络编程入门

TCP/IP 协议的分类

  • IP - 网际协议
  • TCP - 传输控制协议
  • UDP - 用户数据报协议

Python网络编程入门

网络编程重要的模块

协议 功能用处 端口号 对应模块模块
HTTP 网页访问 80 httpliburllibxmlrpclib
NNTP 阅读和张贴新闻文章 119 nntplib
FTP 文件传输 20 ftpliburllib
SMTP 发送邮件 25 smtplib
POP3 接收邮件 110 poplib
IMAP4 获取邮件 143 imaplib
Telnet 命令行 23 telnetlib
Gopher 信息查找 70 gopherliburllib

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

Python网络编程入门

TCP 的三次握手

Python网络编程入门

TCP 的四次挥手

Python网络编程入门


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
    • 用户数据报协议,无连接的协议

Python网络编程入门


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) 如果flag0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值);非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常
s.makefile() 创建一个与该套接字相关连的文件

4. TCP 编程

TCP协议是面向连接的,会事先建立好连接,所以不需要指定地址

Python网络编程入门

  • 服务端示例代码
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是面向无连接的,所以每次发送都需要指定发送给谁

Python网络编程入门

  • 服务端示例代码
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

文章作者: Escape
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Escape !
  目录