Python--第二阶段--03.网络编程基础
计算机网络功能主要包括实现资源共享,实现数据信息的快速传递。
OSI七层模型
制定组织:ISO(国际标准化组织)
作用:使网络通信工作流程标准化
我们主要实践的:应用层、表示层、会话层
通信的研究:网络层、链路层、物理层
应用层:提供用户服务,具体功能有应用程序实现
表示层:数据的压缩优化加密
会话层:建立用户级的链接,选择适当的传输服务
传输层:提供传输服务
网络层:路由选择,网络互联----ip地址层
链路层:进行数据交互,控制具体数据的发送----信息转换成二进制信息
物理层:提供数据传输的硬件保证,网卡接口,传输介质----MAC地址层
优点:
- 建立了统一的工作流程;
- 分布清晰,各司其职,每个步骤分工明确;
四层模型(TCP/IP模型)
数据传输过程
- 发送端由应用程序发送消息,逐层添加首部信息,最终在物理层发送消息包;
- 发送的消息经过多个节点(交换机、路由器)传输,最终达到目标主机;
- 目标主机由物理层逐层解析首部消息包,最终到应用程序呈现消息。
网络协议
在网络数据传输中,都遵循的规定,包括建立什么样的数据结构,什么样的特殊标志等。
网络基础概念
- IP地址(确定哪一台主机)
功能:确定一台主机的网络路由位置
查看本机网络地址命令:ifconfig
结构:- IPv4点分十进制表示 172.40.91.185 每部分取值范围是0-255
- IPv6 128位 扩大了地址范围
- 域名
定义:给网络服务器地址起名字
作用:方便记忆,表达一定含义
ping [IP]:测试是否可以正常连接 - 端口号(port)(确定哪一个应用)
作用:端口是网络地址的一部分,用于区分主机上不同的网络应用程序。
特点:一个系统中的应用监听端口不能重复
取值范围:1-65535- 1-1023系统应用或者大众程序监听端口
- 1024-65535自用端口
传输层服务
面向连接的传输服务(基于TCP协议的数据传输)
- 传输特征:提供了可靠的数据传输,靠靠性指数据传输过程中无丢失,无失序,无差错,无重复。
- 实现手段:在通信前需要建立数据连接,通信结束要正常断开连接。
三次握手(建立连接)
- 客户端向服务器发送消息报文请求连接
- 服务端收到请求后,回复报文确定可以连接
- 客户端收到回复,发送最终报文连接建立
- 适用情况:对数据传输准确性有明确要求,传输文件较大,需要确保可靠性的情况。比如:网页获取,文件下载,邮件收发。
四次挥手(断开连接)
面向无连接的传输服务(基于UDP协议的数据传输)
- 传输特点:不保证传输的可靠性,传输过程没有连接和断开,数据收发自由随意。
- 适用情况:网络交叉,对传输可靠性要求不高。比如:网络视频,群聊,广播
面试要求
- OSI七层模型介绍一下,TCP/IP模型是什么?
- tcp服务和udp服务有什么区别?
- 三次握手和四次挥手指什么,过程是怎样的?
socket套接字编程
套接字介绍
- 套接字:实现网络编程进行数据传输的一种技术分段
- Python实现套接字编程:import socket
- 套接字分类:
- 流式套接字(SOCK_STREAM):以字节流方式传输数据,实现tcp网络传输方案。(面向连接–tcp协议–可靠的–流式套接字)
- 数据报套接字(SOCK_DGRAM):以数据报形式传输数据,实现UDP网络传输方案。(无连接–UDP协议–不可靠–数据报套接字)
tcp套接字编程
服务端流程
socket(创建电话)–>bind(绑定地址)–>listen(监听,可以被连接)–>accept(开机,等待连接)–>send/recv–>close(销毁电话)
- 创建套接字(socket.socket())
- 函数:
- sockfd = socket.socket()
- 功能:创建套接字对象
- 参数:
- socket_family 网络地址类型 AF_INET表示ipv4(默认)
- socket_type 套接字类型SOCK_STREAM(流式套接字TCP) SOCK_DGRAM(数据报UDP)
- proto 通常为0 选择子协议,默认值为0
- 返回值:套接字对象
- 绑定地址(套接字对象.bind(addr))
- 可填写地址:
- 本地地址:‘localhost’,‘127.0.0.1’别的计算机客户端无法访问,只能在本机的客户端才可以访问
- 网络地址:‘172.40.91.185’ ifconfig出来的网络地址,都可以访问,都必须访问该ip来访问
- 自动获取地址:‘0.0.0.0’,或者‘’自动获取IP
- 函数方法:
- sockfd.bind(addr)
- 功能:绑定本机网络地址
- 参数:addr是一个二元元祖(ip,port)(‘0.0.0.0’,8888)
- 设置监听(套接字对象.listen)
- 函数方法
- sockfd.listen(n)
- 功能:将套接字设置为监听套接字,确定监听队列大小
- 参数:n监听队列大小
- 等待处理客户端连接请求
- 函数方法:
- connfd.addr = sockfd.accept()–>阻塞操作
- 功能:阻塞等待处理客户端请求
- 返回值:
- connfd客户端连接套接字
- addr 连接的客户端地址
- 消息收发
- 收消息:
- 函数方法:
- data = connfd.recv(buffersize)–>阻塞操作
- 功能:接收客户端消息
- 参数:每次最多接收消息的大小
- 返回值:接收到的内容
- 函数方法:
- 发消息:
- 函数方法:n = connfd.send(data)
- 参数要求发送的内容均为bytes格式
- 返回值:发送的字节数
- 关闭套接字
sockfd.close()
关闭套接字
TCP的socket服务端代码实现:
功能性代码,注重流程和函数的使用
'''
功能性代码,注重流程和函数的使用
'''
import socket
# 创建TCP套接字
sockfd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定地址
sockfd.bind(('0.0.0.0',8888))
# 设置监听
sockfd.listen()
# 阻塞等待连接处理
print('waiting for connect...')
connfd,addr = sockfd.accept()
print('connect from ',addr) # 打印连接的客户端地址
# 收发消息
data = connfd.recv(1024).decode()
print('收到:',data)
n = connfd.send(b'Thanks') # 发送字节串
print(n)
# 关闭套接字
connfd.close()
sockfd.close()
作业:
1.将文件操作总结
2.总结面试要求题目的回答
3.将服务端流程函数熟练
客户端流程
socket–>bind[可选] -->connect–>send/recv–>close
- socket
- 函数:
- sockfd = socket.socket()
-
connect(和服务端accept相对应)
套接字对象.connect(server_addr)
功能:连接服务器
参数:元祖 服务器地址 -
send/recv
-
close
- 注意:
- 连接端阻塞在recv,对方断开的话,收到空
- tcp连接中如果一段已经不存在,仍然视图通过send发送则会产生broken pipe错误
- 一个服务端可以连接多个客户端。
TCP客户端实现代码:
import socket
# 创建tcp套接字
sockfd = socket.socket() # 使用默认草书-->tcp套接字
# 和服务端连接
sockfd.connect(('192.168.1.102',8888))
# 发送消息
sockfd.send('你好'.encode()) # 转换为字节串再发送
# 接收消息
data = sockfd.recv(1024)
print(data)
# 断开连接
sockfd.close()
TCP服务端可循环代码:
import socket
#创建TCP套接字
sockfd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sockfd.bind(('0.0.0.0',8888))
# 设置监听
sockfd.listen()
while True:
# 阻塞等待连接处理
print('waiting for connect...')
connfd,addr = sockfd.accept()
print('connect from ',addr) # 打印连接的客户端地址
while True:
# 收发消息
data = connfd.recv(1024).decode()
print('收到:',data)
if not data:
break
n = connfd.send(b'Thanks') # 发送字节串
print(n)
# 关闭套接字
connfd.close()
sockfd.close()
TCP客户端可循环代码:
import socket
# 创建套接字
sockfd = socket.socket()
# 和服务端连接
sockfd.connect(('192.168.1.102',8888))
# 发送消息
while True:
data = input('msg>>')
if data == 'quit':
break
sockfd.send(data.encode()) # 转换为字节串再发送
# 接收消息
data = sockfd.recv(1024)
print(data)
# 断开连接
sockfd.close()
TCP粘包现象
- 缓冲区:
- 作用:协调收发速度
- 传输速度过快,多次的接收的内容一次性的被缓冲区接收,但是所有发过来的数据都会接收,不会丢失。
- send–>发送端缓冲区–>接收端缓冲区–>recv
- 粘包现象
- 原因:tcp以字节流方式传输,没有消息便捷。多次发送的消息被一次接收,此时就会形成粘包。
- 影响:如果每次发送内容是一个独立的含义,需要接收端独立解析,此时粘包会有影响。
- 处理方法
- 人为的添加消息边界;
- 控制发送速度(发送端sleep())。
练习:
文件的传输:将一个文件从客户端发送到服务端保存
要求:文件可能是文本类型,也可能是二进制类型文件。
文件传输服务端代码:
from socket import *
sk = socket()
sk.bind(('0.0.0.0',8888))
sk.listen()
conn,addr = sk.accept()
print('连接',addr)
'''
思路:
1.以二进制写的模式打开文件wb
2.将接收到的内容写入文件
'''
# 打开文件
f = open('test1.txt','wb')
# 循环接收写入文件:
while True:
data = conn.recv(1024)
if not data:
break
f.write(data)
f.close()
conn.close()
sk.close()
文件传输客户端代码:
from socket import *
s = socket()
s.connect(('192.168.1.102',8888))
# 读取目标文件,循环发送
f = open('test.txt','rb')
while True:
data = f.read(1024)
if not data:
break
s.send(data)
f.close()
s.close()
UDP套接字编程
服务端流程
socket–>bind–>recvfrom–>sendto–>close
- 创建UDP套接字连接
sockfd = socket(AF_INET,SOCK_DGRAM) - 绑定地址
sockfd.bind(addr) - 消息收发
- 消息接收:
- 函数:data,addr = sockfd.recvfrom(buffersize)
- 功能:接收UDP消息
- 参数:每次最多接收多少字节
- 返回值:
- data 接收到的内容
- addr消息发送方地址
- 消息发送
- 函数:n = sockfd.sendto(data,addr)
- 功能:发送UDP消息
- 参数:
- data发送的内容,bytes格式
- addr目标地址
- 返回值:发送的字节数
- 消息接收:
- 关闭套接字
"""
UDP_server.py
重点代码
"""
from socket import *
# 创建套接字
sk = socket(AF_INET,SOCK_DGRAM)
# 绑定地址
server_addr = ('127.0.0.1',8888)
sk.bind(server_addr)
# 循环收发消息
while True:
data,addr = sk.recvfrom(1024)
print(data)
sk.sendto(b'Thanks',addr)
# 关闭套接字
sk.close()
客户端流程
socket–>sendto–>recvfrom–>close
代码实现:
"""
UDP_client.py
重点代码
"""
from socket import *
# 创建套接字
sk = socket(AF_INET,SOCK_DGRAM)
# 服务端地址
server_addr = ('127.0.0.1', 8888)
# 循环收发消息
while True:
msg = input('msg')
if not msg:
break
sk.sendto(msg.encode(), server_addr)
data,addr = sk.recvfrom(1024)
print(data)
# 关闭套接字
sk.close()
练习:使用udp客户端查询单词,得到单词的解释,如果没有该单词则得到“没有单词”。客户端可以循环输入单词,知道输入空退出
作业代码(dict文件参考上一篇文章最后的任务文件):
server端:
from socket import *
# 创建套接字
sk = socket(AF_INET, SOCK_DGRAM)
# 绑定地址
server_addr = ('127.0.0.1', 8888)
sk.bind(server_addr)
def find_word(data):
f = open('dict.txt', 'r')
for line in f:
w = line.split(' ')[0]
if w > data:
f.close()
return '没有找到该单词'
elif w == data:
f.close()
return line
# 循环收发消息
while True:
data, addr = sk.recvfrom(1024)
data = data.decode()
# 查单词
return_date = find_word(data)
sk.sendto(return_date.encode(), addr)
# 关闭套接字
sk.close()
client端:
from socket import *
# 创建套接字
sk = socket(AF_INET,SOCK_DGRAM)
# 服务端地址
server_addr = ('127.0.0.1', 8888)
# 循环收发消息
while True:
msg = input('word>>')
if not msg:
break
sk.sendto(msg.encode(), server_addr) # 发送单词
data,addr = sk.recvfrom(1024) # 返回单词的解释
print(data.decode())
# 关闭套接字
sk.close()
总结:
tcp套接字和udp套接字编程区别
- 流式套接字是以字节流方式传输数据,数据报套接字以数据报形式传输;
- tcp套接字会有粘包,udp套接字有消息边界,不会粘包;
- tcp套接字保证消息的完整性,udp套接字则不能;
- tcp套接字依赖listen、accept建立连接才能收发消息,udp套接字则不需要;
- tcp套接字使用send、recv收发消息,udp套接字使用sendto、recvfrom。
套接字属性
from socket import *
s = socket()
s.bind(('127.0.0.1', 8889))
print('地址类型:', s.family)
print('套接字类型:', s.type)
print('获取套接字绑定地址:', s.getsockname())
print('文件描述符:', s.fileno())
print('连接端地址:', s.getpeername())
# setsockopt设置套接字选项,需要在绑定端口之前使用
# 参数:level选项类别(SOL_SOCKET) option 具体选项内容 value选项值
# setsockopt(SOL_SOCKET,SO_REUSRADDR,1)
# getsockopt(SOL_SOCKET,SO_REUSRADDR)
UDP套接字的应用
广播
广播定义:一端发送多点接收
广播地址:每个网络的最大地址为发送广播的地址,向该地址发送,则网段内所有主机都能接收。
recv.py
"""
广播接收
1.创建UDP套接字
2.设置套接字可以发送接收广播(setsockopt)
3.选择接收的端口
4.等待接收
"""
from socket import *
sk = socket(AF_INET,SOCK_DGRAM)
# 设置套接字接收广播
sk.setsockopt(SOL_SOCKET,SO_BROADCAST,1)
sk.bind(('0.0.0.0',9999))
while True:
msg,addr = sk.recvfrom(1024)
print(msg.decode())
server.py
"""
广播发送端
1.创建UDP套接字;
2.设置可以发送广播;
3.循环向广播地址发送。
"""
import time
from socket import *
# 广播地址192.168.1.255
dest = ('192.168.1.255',9999)
s = socket(AF_INET,SOCK_DGRAM)
s.setsockopt(SOL_SOCKET,SO_BROADCAST,1)
data = '''
没意思,拜拜了
你想玩啥去玩吧
'''
while True:
time.sleep(2)
s.sendto(data.encode(),dest)
TCP套接字之HTTP传输
HTTP协议(超文本传输协议)
- 用途:网络获取,数据的传输
- 特点:
- 应用层协议,传输层使用tcp传输
- 简单,灵活,很多语言都有HTTP专门接口
- 无状态,协议不记录传输内容
- HTTP1.1支持持久连接,丰富了请求类型
- 网页请求过程
- 客户端(浏览器)通过tcp传输,发送http请求给服务端
- 服务端接收到http请求后进行解析
- 服务端处理请求内容,组织响应内容
- 服务端将响应内容以http响应格式发送给浏览器
- 浏览器接收到响应内容,解析展示
HTTP请求(request)
- 请求行:具体的请求类别和请求内容
GET | / | HTTP/1.1 |
---|---|---|
请求类别 | 请求内容 | 协议版本 |
-
请求类别:每个请求类别表示要做不同的事情
- GET:获取网络资源
- POST:提交一定的信息,得到反馈
- HEAD:只获取网络资源的响应头
- PUT:更新服务器资源
- DELETE:删除服务器资源
- CONNECT
- TRACE:测试
- OPTIONS:获取服务器性能信息
-
请求内容:/后面的网址,就是请求内容
-
协议版本:
- 请求头:对请求的进一步解释和描述.
键值对表述 - 空行
- 请求体
作业:
1.熟悉http协议请求的格式结构
2.熟练四个程序(tcp、udp)
HTTP响应(response)
响应格式:
响应行:
HTTP/1.1 200 OK
版本信息 响应码 附加信息
响应码:
1xx 提示信息,表示请求被接收
2xx 响应成功
3xx 响应需要进一步操作,重定向
4xx 客户端错误
5xx 服务器错误
响应头:对响应内容的描述
空行:
响应体:响应的主题内容信息
HTTP协议服务器端代码:
import socket
#创建TCP套接字
sockfd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sockfd.bind(('0.0.0.0',8888))
# 设置监听
sockfd.listen(3)
while True:
# 阻塞等待连接处理
print('waiting for connect...')
connfd,addr = sockfd.accept()
print('connect from ',addr) # 打印连接的客户端地址
while True:
# 收发消息
data = connfd.recv(1024).decode()
print('收到:',data)
if not data:
break
response = '''HTTP/1.1 200 OK\r\nContent-Type:text/html\r\n\r\n<h1>Hello world</h1>
'''
n = connfd.send(response.encode()) # 发送字节串
print(n)
print(response)
# 关闭套接字
connfd.close()
sockfd.close()
struct模块的使用
可以按照自己设定的格式,将数字、字符、字节串等转化为可以作为传输的bytes格式。
发送的内容转化为网络字节序,在接收端在用网络字节序转化为可读。
原理:将一组简单数据进行打包,转化为bytes格式发送,或将一组bytes格式数据进行解析。(将Python的数据格式转换成类C的数据)
接口使用:
Struct(fmt)
功能:生成结构化对象
参数:fmt 定制的数据结构 查表(i表示整形,s表示字节串,f表示浮点型)
st.pack(v1,v2,v3…)
功能:将一组数据按照指定格式打包转换为bytes
参数:要打包的数据
返回值:bytes字节串
st.unpack(bytes_data)
功能:将bytes数据转换为元组数据
参数:bytes字节串
返回值:一个元组
也可以直接用模块名来操作,
struct.pack(fmt,v1,v2,v3)
struct.unpack(fmt,bytes_data)
格式符 | C语言类型 | Python类型 | Standard size |
---|---|---|---|
x | pad | byte(填充字节) | no value |
c | char | string of length 1 | 1 |
b | signed char | integer | 1 |
B | unsigned char | integer | 1 |
? | _Bool | bool | 1 |
h | short | integer | 2 |
H | unsigned short | integer | 2 |
i | int | integer | 4 |
I(大写的i) | unsigned int | integer | 4 |
l(小写的L) | long | integer | 4 |
L | unsigned long | long | 4 |
q | long long | long | 8 |
Q | unsigned long long | long | 8 |
f | float | float | 4 |
d | double | float | 8 |
s | char[] | string | |
p | char[] | string | |
P | void * | long |
练习:
1.使用UDP,在客户端不断录入学生信息,将其打包发送到服务端,在服务端将学生信息写入到一个文件中,每个学生信息占一行;
2.信息格式:id name age score
注意:id (int) name(str) age(int)score(float)
server端:
from socket import *
import struct
# 创建套接字
sk = socket(AF_INET,SOCK_DGRAM)
# 绑定地址
server_addr = ('127.0.0.1',8888)
sk.bind(server_addr)
st = struct.Struct('i32sif')
f = open('student.txt','a')
# 循环收发消息
while True:
data,addr = sk.recvfrom(1024)
print(data)
data = st.unpack(data)
info = '%d %-10s %d %.1f\n'%data
f.write(info)
f.flush()
# 关闭套接字
sk.close()
client端
from socket import *
import struct
st = struct.Struct('i32sif')
# 创建套接字
sk = socket(AF_INET,SOCK_DGRAM)
# 服务端地址
server_addr = ('127.0.0.1', 8888)
# 循环收发消息
while True:
id = int(input('ID:'))
name = input('Name:').encode()
age = int(input('age:'))
score = float(input('Score:'))
data = st.pack(id,name,age,score)
sk.sendto(data, server_addr)
# 关闭套接字
sk.close()