01. 网络编程
一、网络编程
1. 模型
1.1 OSI七层模型
制定组织
ISO(国际标准化组织)
作用
使网络通信工程的工作流程标准化
内容
应用层:提供用户服务,具体功能由应用呈现;
表示层:数据的压缩、优化、加密;
会话层:建立用户级的连接,选择适当的传输服务(由软件开发商决定);
传输层:提供传输服务,进行流量监控;
网路层:路由选择,网络互联(路由的寻址);
链路层:进行数据交换,控制具体数据发送;
物理层:提供数据传输的物理保证、传输介质
优点
- 建立了统一的工作流程;
- 各部分功能清晰,各司其职;
- 降低耦合度,方便开发
1.2 四层模型
即(TCP/IP协议)
背景:在实际工作当中,七层模型太过细致,难以实践,逐渐演化成实际工作中应用的四层。
应用层:集中了原来的应用层、表示层、会话层的功能
传输层
网络层
物理链路层
1.2.1 TCP三次握手
1.2.2 TCP 四次挥手
1.2.3 UDP
UDP传输数据不建立连接,直接发送消息
1.3 套接字类型
流式套接字(SOCK_STREAM):以字节流的方式进行数据传输,实现TCP网络传输方案。
数据报套接字(SOCK_DGRAM):以数据报形式传输数据,实现UDP网络传输方案。
2. TCP套接字编程
2.1 服务端流程
socket() --> bind() --> listen() --> accept() --> recv()/send() --> close()
(1) 创建套接字
sockfd = socket.socket(socket_family=AF_INET,socket_type=SOCK_STREAM,proto=0)
参数:
socket_family : 网络地址类型(AF_INET ==> ipv4)
socket_type : 套接字类型(SOCK_STREAM> 流式套接字; SOCK_DGRAM> 数据报)
proto : 子协议类型,通常为0
返回值:
套接字对象
(2) 绑定地址
sockfd.bind(addr)
参数:
元组(ip,port) ==> ('127.0.0.1',8080)
(3) 设置监听端口
sockfd.listen(n)
功能:将套接字设置为监听套接字,创建监听队列
参数:监听队列大小(即)
(4) 等待处理客户端请求
connfd,addr = sockfd.accept()
功能:阻塞等待客户端处理请求
返回值:
connfd 客户端套接字
addr 连接的客户端地址
*阻塞函数:程序运行过程中遇到阻塞函数暂停执行,直到某种条件下才继续执行。
(5) 收发消息
data = connfd.recv(buffersize)
功能:接收客户端消息
参数: 每次接受消息最大的字符
返回值:收到的消息内容
n = connfd.send(data)
功能:发送消息
参数:要发送的消息(bytes格式)
返回值:发送了多少字节
字符串转字节串 str ==>bytes
str.encode()
字节串转字符串 bytes==>str
bytes.decode()
(5)关闭套接字
socket.close()
功能:关闭套接字
整体代码
import socket
# 创建套接字
sock_fd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定地址
sock_fd.bind(('0.0.0.0',8080))
# 设置监听端口
sock_fd.listen(5)
# 阻塞等待处理客户端的连接
print("Waiting ...")
conn,addr = sock_fd.accept()
print("Connect from ",addr[0]) # 客户地址
# 消息收发
data = conn.recv(1024)
print("Receive message:",data.decode())
n = conn.send(b"Hello World !")
print("Send %d bytes"%n)
# 关闭连接
conn.close()
sock_fd.close()
2.2 客户端流程
socket() --> connect() --> send/recv --> close()
(1) 创建套接字
sockfd = socket.socket()
只有相同类型套接字才可以通信。
(2) 请求连接
sockfd.connect(addr)
功能:连接服务端
参数:元组
(3) 消息收发
消息的收发须看自己的情况而定。
(4) 关闭套接字
sockfd.close()
整体代码
from socket import *
# 创建套接字
socket_fd = socket()
# 发起连接
server_addr = ('127.0.0.1',8080)
socket_fd.connect(server_addr)
# 收发消息
data = input(">>")
socket_fd.send(data.encode())
data = socket_fd.recv(1024)
print("From server : ",data.decode())
# 关闭套接字
socket_fd.close()
当接收数据大小受限时,多余的数据分批接收,这样的情况为粘包
2.3 TCP套接字传输特点
-
tcp连接中当一端退出,另一端如果阻塞在recv,则recv会立即返回一个空字符串;
-
tcp链接中如果另一端已经不存在,再试图使用send向其发送内容时会出现BrokenPipeError(管道破裂);
-
网络收发缓冲区
- 缓冲区有效的协调了消息的收发速度;
- send、recv实际是向缓冲区发送接收消息,当缓冲区不为空的时候recv就不会阻塞
实现循环收发消息
#server.py
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)
print("正在等待客户端连接...")
#waiting client connect ...
conn,addr = server.accept()
print('Connect from ',addr[0])
while True:
data = conn.recv(1024)
if not data:
break
print('收到%s的消息:%s'%(addr[0],data.decode()))
n = conn.send("已经收到您的消息".encode())
print('发送%s字节'%n)
conn.close()
server.close()
import socket
client = socket.socket() #创建套接字
server_addr = ('127.0.0.1',8080) #绑定IP及端口
client.connect(server_addr) # 连接服务端
while True:
data = input(">>>")
client.send(data.encode()) #发送消息
if not data:
break
data = client.recv(1024) #接收数据
print("来自服务器的消息",data.decode())
client.close()
实现循环连接客户端
#server.py
import socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
print("正在等待客户端连接...")
#waiting client connect ...
conn,addr = server.accept()
print('Connect from ',addr[0])
while True:
data = conn.recv(1024)
if not data:
break
print('收到%s的消息:%s'%(addr[0],data.decode()))
n = conn.send("已经收到您的消息".encode())
print('发送%s字节'%n)
conn.close()
#关闭套接字
server.close()
#client.py
import socket
client = socket.socket() #创建套接字
server_addr = ('127.0.0.1',8080) #绑定IP及端口
client.connect(server_addr) # 连接服务端
while True:
data = input(">>>")
client.send(data.encode()) #发送消息
if not data:
break
data = client.recv(1024) #接收数据
print("来自服务器的消息",data.decode())
client.close()
2.4 粘包(zhan bao)
原因
TCP以字节流方式传输数据,没有消息边界,多次发送的内容如果被一次性的接收就会形成粘包。
影响
如果每次发送的内容是需要独立解析的含义,此时沾包会对消息的解析产生影响。
处理
- 人为添加消息边界;
- 控制发送速度(发送少量消息适用)
3. UDP套接字编程
3.1 服务端流程
(1) 创建套接字
socketfd = AF_INET,SOCK_DGRAM
(2) 绑定地址
socketfd.bind(addr)
(3) 收发消息
data,addr = sockfd.recvfrom (buffersize)
功能:接收UDP消息
参数:每次接收多少字节流
返回值:data-->接收到的消息 addr-->消息发送方地址
n = sockfd.sendto(data,addr)
功能:发送UDP消息
参数:data-->发送的消息(bytes格式) addr-->目标地址
(4) 关闭套接字
socketfd.close()
作用
- 释放端口;
- 释放内存
整体代码
from socket import *
#创建数据报套接字,即创建UDP套接字
server = socket(AF_INET,SOCK_DGRAM)
#绑定地址
server.bind(('127.0.0.1',8080))
#收发消息
while True:
data,addr = server.recvfrom(1024)
print("收到消息来自%s的消息:%s"%(addr,data.decode()))
server.sendto('谢谢你的消息'.encode(),addr)
#关闭套接字
serve.close()
3.2 客户端流程
(1) 创建UDP套接字
client = socket(AF_INET,SOCK_DGRAM)
(2) 发送接收套接字
client.sendto(data.encode(),ADDR)
(3) 关闭套接字
client.close()
整体代码
from socket import *
#服务端地址
HOST = '127.0.0.1'
PORT = 8080
ADDR = (HOST,PORT)
#创建套接字
client = socket(AF_INET,SOCK_DGRAM)
# 收发消息
while True:
data = input(">>")
if data == 'bye':
break
client.sendto(data.encode(),ADDR)
msg,addr = client.recvfrom(2014)
print("来自服务器的消息:",msg.decode())
# 关闭套接字
client.close()
当接收内容大小受限的时候,多出的包直接丢失,并不会分批接收
常见问题
在收发消息的时候,会发现服务端收到的消息有时是前半部分,有时是后半部分,这是因为多个客户端发送消息的时候,如果正好同时发送,而其中一个包大,则服务端就会接收前边的部分。
2. TCP与UDP的区别
- 流式套接字以字节流方式传输数据,数据报套接字以数据报形式传输数据;
- TCP套接字会有粘包问题存在,UDP套接字因为有边界而无粘包问题;
- TCP套接字保证了消息的完整性,UDP无法保证,会丢包;
- TCP套接字依赖
listen accept
完成连接才能进行数据收发,UDP套接字不需要连接即可收发消息; - TCP 使用
send recv
收发消息,UDP使用sendto recvfrom
;
二、SOCKET模块
1. 部分socket模块方法
import socket
socket.gethostname() #本机获取主机名
socket.gethostbyname() #通过主机名获取IP地址
socket.getservbyname() #获取指定服务的端口
socket.getservbyport() #获取指定端口的服务
socket.inet_aton('192.168.1.1') #将IP转换为字节串,也就是转换为16进制
socket.inet_ntoa(b'\xc0\xa8\x01\x01') #将字节串转换为IP
socket.htons(5) #网络制式,不同计算机之间通信的一种方式
socket.ntohs(1280)
2. 套接字属性
带*
号的为需要掌握的内容
from socket import *
s = socket()
s.bind(('127.0.0.1',8888))
print(s.family) #地址类型
print(s.type) #套接字类型
print(s.getsockname()) #绑定地址
* print(s.getpeername()) #获取连接客户端的IP地址(需要在套接字连接之后使用)
* print(s.fileno()) #获取文件描述符
* s.setsockopt(level,option,value) # 设置套接字选项(参数:level设置选项类别,option具体选项内容,value具体的值)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,True) #设置端口立即重用
描述符
调用计算机的某个接口完成自己的实际需求,而这种操作是基于操作系统的,并非python,所有IO操作都会分配一个证书作为编号,该整数即为这个IO的文件描述符
特点
每个IO文件的描述符不会发生重复
三、广播
定义
一端接收,多端发送。
# server.py
from socket import *
client = socket(AF_INET,SOCK_DGRAM)
client.setsockopt(SOL_SOCKET,SO_BROADCAST,True)
#选择接收地址
client.bind(('0.0.0.0',8080))
while True:
msg,addr = client.recvfrom(1024)
print(msg.decode())
# client.py
from socket import *
from time import sleep
# 目标地址
dest = ('127.0.0.1',8080)
s = socket(AF_INET,SOCK_DGRAM)
# 设置可以发生和接收广播
s.setsockopt(SOL_SOCKET,SO_BROADCAST,True)
data = """
****************************************
****************清明********************
******清明时节雨纷纷,路上行人欲断魂******
******借问酒家何处有,牧童遥指杏花村******
****************************************
"""
while True:
sleep(2)
s.sendto(data.encode(),dest)
四、 HTTP协议
1. HTTP协议
即超文本传输协议
1.1 用途
网页的传输,数据传输
1.2 特点
-
应用层协议;
-
传输层使用TCP服务;
-
简单、灵活、无状态;
-
请求类型多样;
-
数据格式支持全面
1.3 请求格式
GET /sample.jsp HTTP/1.1
Accept : image/gif.image/jpeg,*/*
Accept-Language : zh-cn
Connection : Keep-Alive
Host : localhost
User-Agent : Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding : gzip,deflate
username = jinqiao&password=1234
(1) 请求行
具体的请求类别和请求内容
格式:GET / HTTP/1.1
分别为(请求类别、请求内容、协议版本)
请求类别:
GET : 获取网络资源;
POST : 提交一定的信息,得到反馈;
HEAD : 只获取网络资源响应头;
PUT :更新服务器资源;
DELETE:删除服务器资源;
CONNECT:预留协议;
TRACE:测试;
OPTIONS:获取服务器性能信息
(2) 请求头
对请求的进一步描述和解释。
格式:键 : 值
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
声明浏览器标识等......
(3) 空体
标准的规定,必须留空
(4) 请求体
请求参数或者提交内容
# 监听8080端口
from socket import *
s = socket()
s.bind(('0.0.0.0',8080))
s.listen(5)
c,addr = s.accept()
print("连接到".encode(),addr)
data = c.recv(4096)
print(data)
c.close()
s.close()
1.4 响应格式
响应行、响应头、空体、响应体
(1) 响应行
反馈基本的响应情况
HTTP/1.1 200 OK (协议版本、响应码、附加信息)
响应码(详细的响应码信息见跳转链接):
1xx 表示提示信息,请求被接受
2xx 表示响应成功
3xx 表示响应需要进一步处理
4xx 表示客户端错误
5xx 服务端错误
(2) 响应头
对响应内容的描述(以键值对作为描述)
Content-Type : text/html #声明网页类型
(3) 响应体
协议必须加,无内容
(4)响应体
响应的主体内容信息
1.5 应用
1.5.1 简单的网页
# 监听8080端口
from socket import *
s = socket()
s.bind(('0.0.0.0',8080))
s.listen(5)
c,addr = s.accept()
print("连接到",addr)
data = c.recv(4096)
print(data)
data = '''
HTTP/1.1 200 OK
Content-Type : text/html
Hello Chancey !
'''
c.send(data.encode())
print(data)
c.close()
s.close()
打开浏览器,打开URL127.0.0.1:8080
1.5.2 上传文件
# recv.py
from socket import *
s = socket()
s.bind(('0.0.0.0',8809))
s.listen(5)
c,addr = s.accept()
print("来自",addr[0],'的连接')
f = open('demo.png','wb')
while True:
data = c.recv(1024)
if not data:
break
f.write(data)
f.close()
c.close()
s.close()
# send.py
from socket import *
s = socket()
s.connect(('127.0.0.1',8809))
f = open('pic.png','rb')
while True:
data = f.read(1024)
if not data:
break
s.send(data)
f.close()
s.close()
1.5.3 前置网页文件
pass
2. HTTPS协议
pass
五、IO
即输入输出,在内存中发生数据交换的情况,程序不可缺少的一部分。
1. 定义
在内存中存在数据交换的操作都认为是IO操作
和终端交互:
input
、和磁盘交互:
read
、write
和网络交互:
recv
、send
分类
-
IO密集型:在程序中存在大量的IO,而CPU运算较少,消耗CPU资源少,耗时长,效率不高
-
计算密集:在程序中存在大量的计算操作,IO行为较少,CPU消耗多,执行速度快
2. IO模型
阻塞情况
- 因为某种条件没有达到而形成阻塞(accept、input、recv);
- 处理IO的时间太长产生的阻塞情况(网络传输、大文件的读写过程);
2.1 阻塞IO
默认形态
定义:在执行操作时,由于不足满某些条件形成的阻塞形态,阻塞IO时IO的默认形态。
效率:非常低
逻辑:简单
2.2 非阻塞IO
定义:通过修改IO的属性行为,使原本阻塞的IO变为非阻塞的状态。
2.2.1 设置方法
-
设置socket为非阻塞套接字
sockfd.setblocking(bool)
功能:设置套接字为非阻塞套接字
参数:True表示套接字IO阻塞,False表示非阻塞
-
设置超时检测
阻塞等待指定的时间,超时后不再阻塞。
sockfd.settimeout(sec)
功能:设置套接字超时时间
参数:超时时间
设置非阻塞和设置超时不会同时出现。要设置超时必须是阻塞。
2.2.1 示例
from socket import *
from time import sleep,ctime
#创建一个tcp套接字
sockfd = socket()
sockfd.bind(('127.0.0.1',8809))
sockfd.listen(5)
#设置非阻塞(True为阻塞、False为非阻塞)
sockfd.setblocking(False)
while True:
print("等待连接......")
conn,addr = sockfd.accept()
#运行结果
>>>等待连接......
Traceback (most recent call last):
File "D:/project/demo/网络编程/IO/block_io.py", line 12, in <module>
connfd,addr = sockfd.accept()
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib\socket.py", line 205, in accept
fd, addr = self._accept()
BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。
from socket import *
from time import sleep,ctime
#创建一个tcp套接字
sockfd = socket()
sockfd.bind(('127.0.0.1',8809))
sockfd.listen(5)
#设置超时时间
sockfd.settimeout(3)
while True:
print("等待连接......")
try: #捕获异常,设置异常
conn,addr = sockfd.accept()
except BlockingIOError:
sleep(2)
print(ctime(),"连接错误")
continue
except timeout:
print("超时连接")
else:
print("已连接至",addr[0])
data = conn.recv(1024)
#运行之后,在有连接的情况下将不会再等待连接
报错是IO模块错误,所以使用try语句,捕获异常
2.3 IO多路复用
定义
同时监听多个IO事件,当哪个IO事件准备就绪就执行哪个IO,以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成的其他IO无法执行,提高IO执行效率。
具体方案
-
select
(windows、linux、unix) -
poll
(linux、unix) -
epoll
(Linux专属)
2.3.1 SELECT
概念
ra,ws,xs = select(rlist, wlist, xlist[, timeout])
功能
监控多个IO时间,阻塞等待IO的发生
参数
rlist : 列表(存放关注的等待发生的事件)
wlist : 列表(存放要主动处理的IO事件)
xlist : 列表(存放发生异常要处理的事件)
timeout : 超时时间
返回值
rs : 列表 rlist 中准备就绪的IO
ws : 列表 wlist 中准备就绪的IO
xs : 列表 xlist 中准备就绪的IO
select(...)
select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)
Wait until one or more file descriptors are ready for some kind of I/O.
等待一个或多个文件描述符准备好进行某种I/O。
The first three arguments are sequences of file descriptors to be waited for:
前三个参数是等待的文件描述符序列:
rlist -- wait until ready for reading
rlist——等待阅读准备就绪
wlist -- wait until ready for writing
wlist——等到准备好写作
xlist -- wait for an ``exceptional condition''
xlist——等待“异常情况”
If only one kind of condition is required, pass [] for the other lists.
如果只需要一种条件,则为其他列表传递[]。
A file descriptor is either a socket or file object, or a small integer gotten from a fileno() method call on one of those.
文件描述符可以是套接字或文件对象,也可以是一个小整数。从其中一个的fileno()方法调用中获取。
The optional 4th argument specifies a timeout in seconds; it may be a floating point number to specify ractions of seconds. If it is absent or None, the call will never time out.
可选的第4个参数指定以秒为单位的超时;它可能是用于指定秒分数的浮点数。如果它不在或者没有,电话永远不会超时。
The return value is a tuple of three lists corresponding to the first three arguments; each contains the ubset of the corresponding file descriptors that are ready.
返回值是与前三个列表对应的三个列表的元组参数;每个包含相应文件描述符的子集准备好了。
*** IMPORTANT NOTICE ***
***重要通知***
On Windows, only sockets are supported; on Unix, all file descriptors can be used.
在Windows上,只支持套接字;在Unix上,所有文件可以使用描述符。
简单使用
from select import select
from socket import *
# 创建套接字作为关注的IO
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)
#添加到关注列表
rlist = [s]
wlist = []
xlist = []
while True:
# 监控IO
rs,ws,xs = select(rlist,wlist,xlist)
for r in rlist:
# s 就绪说明有客户端连接
if r is rs:
c,addr = r.accept()
print("连接来自",addr[0])
# 将客户端加入到关注列表里面
rlist.append(c)
# 如果是c就绪则表示对应的客户端发送消息
else:
data = r.recv(1024)
if not data:
rlist.remove(r)
r.close()
continue
print(data.decode())
# r.send(b'OK')
# 当r放进wlist中时希望主动去处理
wlist.append(r)
for w in wlist:
w.send(b'OK')
wlist.remove(w)
for x in xlist:
pass
2.3.2 POLL
步骤
(1) p = select.poll()
功能:创建poll对象,该函数返回一个实例对象
返回值:poll对象
(2) p.register(fd,event)
功能:注册关注的IO
返回值:
参数
fd : 关注的IO
event : 关注IO事件的类型(读事件、写事件、异常事件)
# 常用IO事件类型的划分及写法
POLLIN # 读IO(rlist)
POLLOUT # 写IO(wlist)
POLLERR # 异常IO(xlist)
POLLHUP # 断开连接
# 多个事件类型的写法
p.register = sockfd(POLLIN | POLLOUT)
(3) p.unregister(fd)
功能:取消对IO的关注
参数: IO对象或者IO对象的fileno(文件描述符)
(4) events = p.pull()
功能:阻塞等待监控IO事件的发生
返回值: 就绪的IO事件
events格式:[ ( files , event ) , ( ) , ( ) , ......]
需要通过fileno寻找对应的IO对象,以操作IO事件,建立字典作为查找地图。
{ fileno : io_obj }
实例 ①
from socket import *
from select import *
# 创建套接字作为监控的IO事件
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)
# 创建poll对象
p = poll()
# 建立地图(即创建字典)
fdmap = {s.fileno():s}
# 关注IO事件的维护,取关和关注
p.register(s,POLLIN | POLLERR) # 关注IO事件
# 循环监控IO
while True:
events = p.poll()
for fd,event in events: # 遍历events,处理IO
if fd == s.fileno():
c,addr = fdmap[fd].accept() # 通过键找到值,该值则为IO事件
print("来自%s的连接"%addr[0])
# 添加新的关注
p.register(c,POLLIN | POLLHUP)
fdmap[c.fileno()] = c
elif event & POLLHUP:
print("客户端退出")
p.unregister(fd)
fdmap[fd].close()
del fdmap[fd]
elif event & POLLIN:
data = fdmap[fd].recv(1024)
print(data.decode())
fdmap[fd].send(b'OK')
实例 ②
'''
需求说明:
创建一个服务端、一个客户端 ,在client连接server时记录日志,并且保留所有的连接记录和消息记录
'''
from select import select
from socket import *
import sys
from time import ctime
# 日志文件
f = open('log.txt','a')
s = socket()
s.bind(('',8888)) # 该处如果为空,则为0.0.0.0
s.listen(5)
rlist = [s,sys.stdin]
wlist = []
xlist = []
while True:
rs,ws,xs = select(rlist,wlist,xlist)
for r in rs:
if r is s:
c,addr = r.accept()
rlist.append(c)
elif r is sys.stdin:
name = 'Server'
time = ctime()
msg = r.readline()
f.write('%s %s %s \n'%(name,time,msg))
f.flush() # 清除缓存
else:
addr = r.getpeername()
time = ctime()
msg = r.recv(1024).decode()
f.write('%s %s %s \n' % (addr, time, msg))
f.flush()
f.close()
s.close()
2.3.3 EPOLL
使用方法:基本与poll相同
- 生成对象改为epoll()
- 将所有事件类型改为epoll事件
特点:
- 效率比poll高;
- 可以同时监控io的数量比poll多;
- 触发方式比poll更多;
- 只能 在Linux下使用
epoll触发方式
边缘触发:IO事件就绪之后不做处理,则会跳过该阻塞
水平触发:在某一个IO准备就绪之后,就会一直在该处阻塞,直到该IO处理完成
3. STRUTE
3.1 原理
将一组简单的数据进行打包,转换为bytes格式发送,或者将一组bytes转换为python数据类型,实现不同的语言之间的互动。
3.2 接口使用
(1) st = Struct(fmt)
功能:生成结构化数据
参数:定制的数据结构
要组织的数据:1 b'chancey' 1.75
fmt : 'i4sf'
解释:一个整型、四个字节型、一个浮点型
(2) st.pack(v1,...)
不定参,可以传多个参数
功能:将一组数据按照一定格式打包转换
返回值:bytes字节串
(3) st.unpack(bytes_data)
功能:将bytes按照格式解析
返回值:解析后的数据元组
In [2]: import struct
In [3]: st = struct.Struct('i4sf')
In [4]: data = st.pack(1,b'chancey',1.68)
In [5]: data
Out[5]: b'\x01\x00\x00\x00chan=\n\xd7?'
In [6]: st.unpack(data)
Out[6]: (1, b'chan', 1.6799999475479126)
In [7]:
(4) struct.pack( fmt , v1 , ... )
struct.unpack(fmt,bytes)
说明:使用
struct
模块直接调用pack
、unpack
,第一个参数直接传入fmt
In [1]: import struct
In [3]: data = struct.pack('7si',b'chancey',18) # 打包
In [5]: struct.unpack('7si',data) # 解包
Out[5]: (b'chancey', 18)
In [6]:
3.3 实例
需求:从客户端输入学生ID、姓名、年龄、成绩,打包发送给服务端,服务端将其存入一个数据表中
4. 本地套接字
1. 功能
本地两个程序之间的通信
2. 原理
对一个内存对象进行读写操作,完成两个程序之间的数据交互
3. 步骤
(1) 创建本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)
(2) 绑定套接字文件
sockfd.bind(file)
(3) 监听、连接、收发消息
listen
、accept
、recv/send
实例
# server.py
from socket import *
import os
# 本地套接字文件
sock_file = './sock'
# 判断文件是否存在,存在返回True,反之亦然
if os.path.exists(sock_file):
os.remove(sock_file) # 删除文件
# 创建本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)
# 绑定文件
sockfd.bind(sock_file)
sockfd.listen(5)
while True:
c,addr = sockfd.accept()
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.close()
sockfd.close()
# client.py
from socket import *
# 两边必须使用同一个套接字
sock_file = './sock'
sockfd = socket(AF_UNIX,SOCK_STREAM)
sockfd.connect(sock_file)
while True:
msg = input('>>>')
if not msg:
break
sockfd.send(msg.encode())
sockfd.close()