Skip to content

Python 网络编程

Python 提供了两个级别访问的网络服务。:

  • 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统 Socket 接口的全部方法。
  • 高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。

互联网上的服务,最底层都是基于 tcp/ip 协议封装,根据是要场景主要分为两种类型:C/S 架构B/S 架构

C/S 架构:c/s 架构 主要是指一个客户端(client)、一个服务器(server),像手机 app、电脑程序都是这种。需要服务器提供服务的应用,都是这种架构。

如果使用 python 进行网络编程,一般需要写两个 py 文件,一个服务器、一个客户端,才能实现数据通信。

B/S 架构:

b/s 架构 主要是指一个浏览器(browser)、一个服务器(server),使用浏览器打开的应用程序都是这种。

提示

c/s 架构: 客户端(手机应用、电脑应用、需要服务器提供服务的应用)/ 服务器

b/s 架构: 浏览器 / 服务器。(这种架构的优点是不需要开发自己的客户端)

服务器有时候指代很多东西:服务器(提供服务)、web服务器(专门返回网页)、腾讯云服务器(部署写好的服务程序 物理设备)

Socket 模块

python 网络编程的主要使用模块为 socket 模块。使用socket(family, type[,protocal]) 使用给定的套接族,套接字类型,协议编号(默认为0)就可以创建套接字

  • family:协议版本
  • protocal:通信协议

 

socket 类型描述
socket.AF_UNIX用于同一台机器上的进程通信(既本机通信)
socket.AF_INET用于服务器与服务器之间的网络通信
socket.AF_INET6基于 IPV6 方式的服务器与服务器之间的网络通信
socket.SOCK_STREAM基于TCP的流式socket通信
socket.SOCK_DGRAM基于 UDP 的数据报式 socket 通信
socket.SOCK_RAW原始套接字,普通的套接字无法处理 ICMPIGMP 等网络报文,而SOCK_RAW可以;其次SOCK_RAW也可以处理特殊的 IPV4 报文;此外,利用原始套接字,可以通过 IP_HDRINCL 套接字选项由用户构造 IP
socket.SOCK_SEQPACKET可靠的连续数据包服务

想要进行通信,就需要创建 socket 对象。

创建 TCP Socket

python
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

创建 UDP Socket

python
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

注意

  • TCP 发送数据时,已建立好 TCP 链接,所以不需要指定地址,而 UDP 是面向无连接的,每次发送都需要指定发送给谁。
  • 服务器与客户端不能直接发送列表,元素,字典等带有数据类型的格式,发送的内容必须是字符串数据。

创建 socket 对象之后,可以使用 socket 的函数进一步将其设置为客户端或者是服务器。

服务器端 Socket 函数

用于创建服务器对象的函数

Socket 函数描述
s.bind(address)将套接字绑定到地址,在 AF_INET 下,以 tuple(host, port) 的方式传入,如s.bind((host, port))
s.listen(backlog)开始监听 TCP 传入连接,backlog 指定在拒绝链接前,操作系统可以挂起的最大连接数,该值最少为 1 ,大部分应用程序设为5就够用了
s.accept()接受 TCP 链接并返回(conn, address),其中 conn 是新的套接字对象,可以用来接收和发送数据,address 是链接客户端的地址。

客户端 Socket 函数

用于创建客户端的服务器

Socket 函数描述
s.connect(address)链接到 address 处的套接字,一般 address 的格式为 tuple(host, port),如果链接出错,则返回 socket.error 错误
s.connect_ex(address)功能与 s.connect(address) 相同,但成功返回0,失败返回 errno 的值

公共 Socket 函数

客户端、服务器都可以使用的公共函数,一般是用于获取通信对象的属性、收发数据。

Socket 函数描述
s.recv(bufsize[, flag])接受 TCP 套接字的数据,数据以字符串形式返回,buffsize 指定要接受的最大数据量,flag 提供有关消息的其他信息,通常可以忽略
s.send(string[, flag])发送 TCP 数据,将字符串中的数据发送到链接的套接字,返回值是要发送的字节数量,该数量可能小于 string 的字节大小
s.sendall(string[, flag])完整发送 TCP 数据,将字符串中的数据发送到链接的套接字,但在返回之前尝试发送所有数据。成功返回 None,失败则抛出异常
s.recvfrom(bufsize[, flag])接受 UDP 套接字的数据,与 recv() 类似,但返回值是 tuple(data, address)。其中 data 是包含接受数据的字符串,address 是发送数据的套接字地址
s.sendto(string[, flag], address)发送 UDP 数据,将数据发送到套接字,address 形式为 tuple(ipaddr, port) ,指定远程地址发送,返回值是发送的字节数
s.close()关闭套接字
s.getpeername()返回套接字的远程地址,返回值通常是一个 tuple(ipaddr, port)
s.getsockname()返回套接字自己的地址,返回值通常是一个 tuple(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()创建一个与该套接字相关的文件

Socket 编程思想

socket 通信流程

socket 是 "打开—读/写—关闭" 模式的实现,以使用 TCP 协议通讯的 socket 为例,其交互流程大概是这样子的

assets

  1. 服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
  2. 服务器为socket绑定ip地址和端口号
  3. 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
  4. 客户端创建socket
  5. 客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
  6. 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞 状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求
  7. 客户端连接成功,向服务器发送连接状态信息
  8. 服务器accept方法返回,连接成功
  9. 客户端向socket写入信息
  10. 服务器读取信息
  11. 客户端关闭
  12. 服务器端关闭

注意

生活中的电话机

如果想让别人能更够打通咱们的电话获取相应服务的话,需要做以下几件事情:

  1. 买个手机
  2. 插上手机卡
  3. 设计手机为正常接听状态(即能够响铃)
  4. 静静的等着别人拨打

如同上面的电话机过程一样,在程序中,如果想要完成一个tcp服务器的功能,需要的流程如下:

  1. socket 创建一个套接字
  2. bind 绑定 ipport
  3. listen 使套接字变为可以被动链接
  4. accept 等待客户端的链接
  5. recv/send 接收发送数据

TCP 服务器

1、创建套接字,绑定套接字到本地 ip 与端口

python
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 7788))

函数 socket.socket 创建一个 socket,该函数带有两个参数:

  • Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX (用于同一台机器进程间通信),实际工作中常用 AF_INET
  • Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)

2、开始监听链接

python
s.listen()

3、进入循环,不断接受客户端的链接请求

python
while True:
    conn, addr = s.accept()

4、接收客户端传来的数据,并且发送给对方发送数据

python
data = s.recv()
s.send('hello world !'.encode())

5、传输完毕后,关闭套接字

python
s.close()

TCP 服务器完整代码

py
import socket

# 创建socket
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 本地信息
address = ('127.0.0.1', 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('utf-8'))

# 发送一些数据到客户端
client_socket.send("thank you !".encode('utf-8'))

# 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接
client_socket.close()

TCP 客户端

所谓的服务器端:就是提供服务的一方,而客户端,就是需要被服务的一方

tcp 客户端构建流程

1、创建套接字并链接至远端地址

python
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 7788))

2、链接后发送数据和接收数据

python
s.send('hello world !')
data = s.recv()

3、传输完毕后,关闭套接字

python
s.close()

TCP客户端完整代码

py
import socket

# 创建socket
tcp_client_socket = socket.socket(socket.AF_INET, socket.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("utf-8"))

# 接收对方发送过来的数据,最大接收1024个字节
recvData = tcp_client_socket.recv(1024)
print('接收到的数据为:', recvData.decode('utf-8'))

# 关闭套接字
tcp_client_socket.close()

运行之后

python
请输入服务器ip: 127.0.0.1
请输入服务器port: 7788
请输入要发送的数据:你好啊
接收到的数据为: 我很好,你呢

循环收发数据

服务接循环收数据

py
# 服务器循环接收数据
import socket

HOST = '127.0.0.1'
PORT = 7788

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(5)

print('服务器已经开启在: %s:%s' % (HOST, PORT))
print('等待客户端连接...')

conn, addr = s.accept()
print('客户端已经连接: ', addr)

while True:
    data = conn.recv(1024)
    print(data)

    if data == '拜拜':
        conn.send('over')
        conn.close()
        break

    conn.send("服务器已经收到你的信息")

conn.close()

客户端循环发送数据

py
# 客户端循环发送数据
import socket

HOST = '127.0.0.1'
PORT = 7788

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

while True:
    cmd = input("请输入需要发送的信息:")
    s.send(cmd)
    data = s.recv(1024)
    print(data)
    if data.decode('utf-8') == 'over':
        break

s.close()

案例:零食销售系统

商品数据

python
goods = {
    '瓜子': 4.5,
    '西瓜': 2,
    '矿泉水': 2.5,
}

请编写客户端与服务器,实现下面的逻辑。

>>> 【服务器】目前商店还有:瓜子(2.5),西瓜(2),矿泉水。请问您需要什么?
>>> 【客户端】红牛
>>> 【服务器】抱歉,您购买的商品目前没有,请选购其他的。
>>> 【客户端】西瓜
>>> 【服务器】购买成功,余额 -2
>>> 【客户端】拜拜
>>> 【客户端】欢迎下次光临
参考答案
py
import socket

goods = {
    '瓜子': 4.5,
    '西瓜': 2,
    '矿泉水': 2.5,
}

tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server.bind(('0.0.0.0', 9999))
tcp_server.listen(124)
tcp_client, tcp_client_addr = tcp_server.accept()

tcp_client.send('目前商店还有:瓜子(2.5),西瓜(2),矿泉水(2.5)。请问您需要什么?'.encode('gbk'))

while True:
    recv_data = tcp_client.recv(1024)  # 一次性最大接收 1024 字节的字符串
    good = recv_data.decode('gbk')

    if good == '拜拜':
        tcp_client.send('欢迎下次光临'.encode('gbk'))
        break
    if good in goods.keys():
        price = goods.get(good)
        tcp_client.send(f'购买成功,余额 -{price}'.encode('gbk'))
    else:
        tcp_client.send('抱歉,您购买的商品目前没有,请选购其他的。'.encode('gbk'))

tcp_client.close()
tcp_server.close()

请问如果同时有多个顾客上门,现在的服务器是否能够支持呢?

TCP 总结

TCP 协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由 IETF 的 RFC 793 定义。

TCP 通信需要经过 创建连接、数据传送、终止连接 三个步骤。

TCP 通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,"打电话"

TCP 注意点

  1. tcp 服务器一般情况下都需要绑定,否则客户端找不到这个服务器
  2. tcp 客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机
  3. tcp 服务器中通过 listen 可以将 socket 创建出来的主动套接字变为被动的,这是做 tcp 服务器时必须要做的
  4. 当客户端需要链接服务器时,就需要使用 connect 进行链接,udp 是不需要链接的而是直接发送,但是 tcp 必须先链接,只有链接成功才能通信
  5. 当一个 tcp 客户端连接服务器时,服务器端会有 1 个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务
  6. listen 后的套接字是被动套接字,用来接收新的客户端的链接请求的,而 accept 返回的新套接字是标记这个新客户端的
  7. 关闭 listen 后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信。
  8. 关闭 accept 返回的套接字意味着这个客户端已经服务完毕
  9. 当客户端的套接字调用 close 后,服务器端会 recv 解堵塞,并且返回的长度为 0 ,因此服务器可以通过返回数据的长度来区别客户端是否已经下线

TCP 特点

面向连接

  • 通信双方必须先建立连接
  • 双方间的数据传输都可以通过一个连接进行
  • 完成数据交换后,双方必须断开此连接,以释放系统资源。

这种连接是一对一的,因此 TCP 不适用于广播的应用程序,基于广播的应用程序请使用 UDP 协议。

附录:网络调试助手

socket 协议只要符合规范,就能够与任意的编程语言进行通信。接下来我们来演示 python 如何与网络调试助手进行通信。

提示

网络调试助手下载地址: 点击下载

mac 版网络调试工具: 点击下载

网络调试助手 windows 系统下默认编码为 gbk