1. 客户端/服务器架构
服务器就是一系列硬件或软件,为一个或多个客户端(服务的用户)提供所需的 “服务” 。它存在唯一目的就是等待客户端的请求,并响应它们(提供服务),然后等待更多请求。
2. 套接字:通信端点
2.1 套接字
介绍: 套接字是计算机网络数据结构。在任何类型的通信开始之前,网络应用程序必须创建套接字。可以将它们比作电话插孔,没有它将无法进行通信。
套接字类型: 基于文件的和面向网络的。
UNIX套接字是基于文件的套接字,并且拥有一个 “家族名字” AF_UNIX,它代表地址家族(address family):UNIX。因为两个进程运行在同一台计算机上,所以这些套接字都是基于文件的,这意味着文件系统支持它们的底层基础结构。因为文件系统是一个运行在同一主机上的多个进程之间的共享常量。
基于网络的套接字的家族名字:AF_INET,或者地址家族:因特网。
Python2.5中引入了对特殊类型的Linux套接字的支持。套接字的AF_NETLINK家族允许使用标准的BSD套接字(伯克利套接字)接口进行用户级别和内核级别代码之间的IPC(Inter Process Communication;进程间通信)。
针对Linux的另一种特性(Python2.6中新增)就是支持透明的进程间通信(TIPC)协议。TIPC允许计算机集群之中的机器相互通信,而无需使用基于IP的寻址方式。Python对TIPC的支持以AF_TIPC家族的方式呈现。
总的来说,Python只支持 AF_UNIX、AF_NETLINK、AF_TIPC 和 AF_INET 家族。
此处只讨论网络编程,所以只使用AF_INET。
2.2 套接字地址:主机-端口对
如果一个套接字像一个电话插孔——允许通信的一些基础设施,那么主机名和端口号就像区号和电话号码的组合。一个网络地址由主机名和端口号对组成。
有效的端口号范围为 0~65535,小于1024的端口号预留给了系统。
2.3 面向连接的套接字
介绍: 面向连接意味着在进行通信之前必须先建立一个连接,这种类型的通信也称为虚拟电路或流套接字。
特点: 面响连接的通信提供序列化的、可靠的和不重复的数据交付,而没有记录边界。这基本上意味着每条消息可以拆分成多个片段,并且每一条消息片段都确保能够到达目的地,然后将它们按顺序组合在一起,最后将完整消息传递给正在等待的应用程序。
套接字类型: 实现这种连接类型的主要协议是传输控制协议(TCP)。为了创建 TCP 套接字,必须使用 SOCK_STREAM 作为套接字类型。TCP 套接字的名字 SOCK_STREAM 是基于流套接字的其中一种表示。
2.4 无连接的套接字
特点: 数据报类型的套接字,是一种无连接的套接字。这意味着,在通信开始之前并不需要建立连接。此时,在数据传输过程中并无法保证它的顺序性、可靠性或重复性。然而,数据报确实保存了记录边界,这就意味着消息是以整体发送的,而并非首先分成多个片段。
优点: 相较于面向连接的套接字,数据报不需要大量的维护开销,即它的成本更加 “低廉” 。因此,它们通常能提供更好的性能。
套接字类型: 实现这种连接类型的主要协议是用户数据报协议(UDP)。为了创建 UDP 套接字,必须使用 SOCK_DGRAM 作为套接字类型。DGRAM 来源于单词 datagram(数据报)。
2.5 socket() 模块函数
socket.socket() 函数的一般语法:
socket(socket_family, socket_type, protocol=0)
参数:
socket_family: AF_UNIX、AF_NETLINK、AF_TIPC 和 AF_INET
socket_type: SOCK_STREAM 和 SOCK_DGRAM
protocol: 通常省略,默认为0
TCP/IP 套接字:
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
UDP/IP 套接字:
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
简写:
from socket import *
tcpSock = socket(AF_INET, SOCK_STREAM)
udpSock = socket(AF_INET, SOCK_DGRAM)
2.6 套接字对象(内置)方法
2.6.1 服务器套接字方法:
名称 | 描述 |
---|---|
s.bind() | 将地址(主机号、端口号对)绑定到套接字上 |
s.listen() | 设置并启动 TCP 监听器 |
s.accept() | 被动接受 TCP 客户端连接,一直等待直到连接到达(阻塞) |
2.6.2 客户端套接字方法:
名称 | 描述 |
---|---|
s.connect() | 主动发起 TCP 服务器连接 |
s.connect_ex() | connect() 的扩展版本,此时会以错误码的形式返回问题,而不是抛出一个异常 |
2.6.3 普通的套接字方法
名称 | 描述 |
---|---|
s.recv() | 接收 TCP 消息 |
s.recv_into() | 接收 TCP 消息到指定的缓冲区 |
s.send() | 发送 TCP 消息 |
s.sendall() | 完整地发送 TCP 消息 |
s.recvfrom | 接收 UDP 消息 |
s.recvfrom_into() | 接收 UDP 消息到指定的缓冲区 |
s.sendto() | 发送 UDP 消息 |
s.getpeername() | 连接到套接字(TCP)的远程地址 |
s.getsockname() | 当前套接字的地址 |
s.getsockopt() | 返回给定套接字选项的值 |
s.setsockopt() | 设置给定套接字选项的值 |
s.shutdown() | 关闭连接 |
s.close() | 关闭套接字 |
s.detach() | 在未关闭文件描述符的情况下关闭套接字,返回文件描述符 |
s.ioctl() | 控制套接字的模式(仅支持 Windows) |
2.6.4 面响阻塞的套接字方法
名称 | 描述 |
---|---|
s.setblocking() | 设置套接字的阻塞或非阻塞模式 |
s.settimeout() | 设置阻塞套接字操作的超过时间 |
s.gettimeout() | 获取阻塞套接字操作的超过时间 |
2.6.5 面响文件的套接字方法
名称 | 描述 |
---|---|
s.fileno() | 套接字的文件描述符 |
s.makefile() | 创建与套接字关联的文件对象 |
2.6.6 数据属性
名称 | 描述 |
---|---|
s.family | 套接字家族 |
s.type | 套接字类型 |
s.proto | 套接字协议 |
3. TCP服务器和客户端
3.1 TCP时间戳服务器:
from socket import *
from time import ctime
HOST = '' # HOST变量是空白的,这是对 bind() 方法的标识,表示它可以使用任何可用的地址
PORT = 21567 # 随机的端口,该端口号需要没有被使用或被系统保留
BUFSIZE = 1024 # 缓冲区大小设置为 1KB
ADDR = (HOST, PORT) # 服务器地址 address
tcpSerSock = socket(AF_INET, SOCK_STREAM) # 分配 TCP 服务器套接字
tcpSerSock.bind(ADDR) # 将套接字绑定到服务器地址
tcpSerSock.listen(5) # 开启 TCP 监听器,在连接被转接或拒绝之前,传入连接请求的最大数为 5
while True: # 服务器无限循环,被动等待客户端连接
print('wating for connection...')
tcpCliSock, addr = tcpSerSock.accept() # 服务器与客户端建立连接,收到客户端的 sock(tcpCliSock) 和 地址(addr)
print('...connect from:', addr)
while True: # 服务器与客户端的对话循环
data = tcpCliSock.recv(BUFSIZE) # 获取客户端发送的消息(data)
if not data: # 如果消息为空,说明客户端已经退出,所以跳出对话循环
break
# 此处与书中稍微不同,书中是在 send() 方法里面只对 ctime() 编码,但我尝试后一直报编码错误,因而此处我将向客户端发送的字符串全部编码
s = '[%s] %s' % (ctime(), data) # 对客户端传来的消息加上时间戳
tcpCliSock.send(s.encode(encoding='utf-8')) # 编码字符串,并发送到客户端
tcpCliSock.close() # 关闭当前客户端的连接,等待下一个客户端的连接
tcpSerSock.close() # 关闭套接字,永远不会执行,只是用来提醒,如果写了一个处理程序应该考虑一个优雅的退出方式
3.2 TCP时间戳客户端
from socket import *
HOST = '127.0.0.1' # or 'localhost' 服务器的主机号
PORT = 21567 # 服务器的端口号 与服务器设置的相同
BUFSIZE = 1024 # 缓冲区大小设置为 1KB 与服务器的设置相同
ADDR = (HOST, PORT) # 服务器地址 address
tcpCliSock = socket(AF_INET, SOCK_STREAM) # TCP 客户端套接字
tcpCliSock.connect(ADDR) # 主动调用并连接到服务器
while True: # 客户端与服务器的对话循环(数据传输)
data = input('> ') # 输入数据
if not data: # 如果用户没有输入,跳出循环,对话结束
break
tcpCliSock.send(data.encode(encoding='utf-8')) # 将用户输入编码后发送到服务器
data = tcpCliSock.recv(BUFSIZE) # 接收服务器返回的数据(加上了时间戳的字符串)(此时数据还处于编码状态)
if not data: # 如果服务器没有返回数据,跳出循环,对话结束
break
print(data.decode('utf-8')) # 数据解码后输出在屏幕上
tcpCliSock.close() # 关闭套接字
3.3 执行TCP服务器和客户端
先在命令行中运行服务器代码:
再在pycharm中运行客户端代码:
在pycharm的命令行中输入字符,如:Hello
得到结果:[Tue Jul 12 21:29:38 2022] b'Hello'
说明服务器成功为客户端传送的数据加了时间戳并返回给客户端。
4. UDP服务器和客户端
4.1 UDP时间戳服务器
from socket import *
from time import ctime
HOST = '' # 主机号是空白的,这是对 bind() 方法的标识,表示它可以使用任何可用的地址
PORT = 21567 # 随机的端口,该端口号需要没有被使用或被系统保留
BUFSIZE = 1024 # 缓冲区大小设置为 1KB
ADDR = (HOST, PORT) # 服务器地址 address
udpSerSock = socket(AF_INET, SOCK_DGRAM) # 分配 UDP 服务器套接字
udpSerSock.bind(ADDR) # 将套接字绑定到服务器地址
while True: # 服务器无限循环,被动等待客户端发送消息(数据报)
print('waiting for message...')
data, addr = udpSerSock.recvfrom(BUFSIZE) # 获取客户端发送的消息(data)
s = '[%s] %s' % (ctime(), data) # 对客户端传来的消息加上时间戳
udpSerSock.sendto(s.encode(encoding='utf-8'), addr) # 编码字符串,并发送到客户端
print('... received from and returned to : ', addr)
udpSerSock.close() # 关闭套接字,永远不会执行,只是用来提醒,如果写了一个处理程序应该考虑一个优雅的退出方式
4.2 UDP时间戳客户端
from socket import *
HOST = '127.0.0.1' # or 'localhost' 服务器的主机号
PORT = 21567 # 服务器的端口号 与服务器设置的相同
BUFSIZE = 1024 # 缓冲区大小设置为 1KB 与服务器的设置相同
ADDR = (HOST, PORT) # 服务器地址 address
udpCliSock = socket(AF_INET, SOCK_DGRAM) # UDP 客户端套接字
while True: # 客户端与服务器的数据传输
data = input('> ') # 输入数据
if not data: # 如果用户没有输入,跳出循环,对话结束
break
udpCliSock.sendto(data.encode(encoding='utf-8'), ADDR) # 将用户输入编码后发送到服务器
data, ADDR = udpCliSock.recvfrom(BUFSIZE) # 接收服务器返回的数据(加上了时间戳的字符串)(此时数据还处于编码状态)
if not data:
break
print(data.decode(encoding='utf-8')) # 数据解码后输出在屏幕上
udpCliSock.close() # 关闭套接字
4.3 执行UDP服务器和客户端
先在命令行中运行服务器代码:
再在pycharm中运行客户端代码:
在pycharm的命令行中输入字符,如:Hello
得到结果:[Tue Jul 12 23:53:13 2022] b'Hello''
说明服务器成功为客户端传送的数据加了时间戳并返回给客户端。
References:
《Python核心编程》(第3版)