Python网络编程

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_UNIXAF_NETLINKAF_TIPCAF_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版)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值