您现在的位置是:首页 >其他 >pt13网络编程网站首页其他
pt13网络编程
网络编程
-
OSI 7层模型
-
建立了统一的通信标准
-
降低开发难度,每层功能明确,各司其职
-
七层模型实际规定了每一层的任务,该完成什么事情
-
-
TCP/IP模型
- 七层模型过于理想,结构细节太复杂
- 在工程中应用实践难度大
- 实际工作中以TCP/IP模型为工作标准流程
-
网络协议
-
什么是网络协议:在网络数据传输中,都遵循的执行规则。
-
网络协议实际上规定了每一层在完成自己的任务时应该遵循什么规范。
-
-
需要应用工程师做的工作 : 编写应用工功能,明确对方地址,选择传输服务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cuMqe1Y4-1682691580328)(picture2317130415.jpg)]
UDP 传输方法
UDP套接字编程
套接字(Socket) : 实现网络编程进行数据传输的一种技术手段,网络上各种各样的网络服务大多都是基于 Socket 来完成通信的。
创建套接字
import socket
sock=socket.socket(family,type)
功能:创建套接字
参数:family 网络地址类型 AF_INET表示ipv4,默认值
type 套接字类型 SOCK_DGRAM 表示udp套接字 默认是tcp(也叫数据报套接字)
返回值: 套接字对象
绑定地址
sock.bind(addr)
功能: 绑定本机网络地址
参数: 二元元组 (ip,port) ('0.0.0.0',8888) 自动获取地址0.0.0.0
"""
套接字选择
"""
import socket
# 创建UDP套接字
udp_socket = socket.socket(socket.AF_INET,
socket.SOCK_DGRAM)
# 绑定一个IP地址和端口
udp_socket.bind(("0.0.0.0", 8888))
# 创建TCP套接字
# tcp_socket=socket.socket(socket.AF_INET,
# socket.SOCK_STREAM)
消息收发
data,addr = sock.recvfrom(buffersize)
功能: 接收UDP消息
参数: 每次最多接收多少字节
返回值: data 接收到的内容
addr 消息发送方地址
n = sock.sendto(data,addr)
功能: 发送UDP消息
参数: data 发送的内容 bytes格式
addr 目标地址
返回值:发送的字节数
关闭套接字
sock.close()
功能:关闭套接字
练习
"""
udp服务端实例代码 重点代码 !!!
"""
from socket import *
# 创建UDP套接字
udp_socket = socket(AF_INET,SOCK_DGRAM)
# 绑定地址
udp_socket.bind(("0.0.0.0",8888))
while True:
# 接收发送消息 data--> bytes
data,addr = udp_socket.recvfrom(5)
# if data == b"##":
# break
print("从",addr,"收到:",data.decode())
# 发送给刚才收到的地址
udp_socket.sendto(b"Thanks",addr)
# 关闭套接字
udp_socket.close()
#######################################################
"""
udp 客户端示例 重点代码!!
"""
from socket import *
# 服务器地址
ADDR = ("127.0.0.1",8888)
# 与服务端相同套接字
udp_socket = socket(AF_INET,SOCK_DGRAM)
# 发送消息
while True:
msg = input(">>")
if not msg:
break
udp_socket.sendto(msg.encode(),ADDR)
# 结束发送
# if msg == "##":
# break
data,addr = udp_socket.recvfrom(128)
print("从服务端收到:",data.decode())
udp_socket.close()
随堂练习:
使用udp完成网络单词查询,从客户端输入单词,发送给服务端,得到单词的解释,打印出来
利用 dict 数据库下的 words表来完成
########################## 服务端 ###############################
from socket import *
import pymysql
# 数据处理类
class Dict:
def __init__(self):
self.kwargs = {
"host": "localhost",
"port": 3306,
"user": "root",
"password": "123456",
"database": "dict",
"charset": "utf8"
}
self.connect()
# 完成数据库连接
def connect(self):
self.db = pymysql.connect(**self.kwargs)
self.cur = self.db.cursor()
# 关闭
def close(self):
self.cur.close()
self.db.close()
def get_mean(self, word):
sql = "select mean from words where word=%s;"
self.cur.execute(sql, [word])
mean = self.cur.fetchone() # (mean,) None
if mean:
return mean[0]
else:
return "Not Found"
# 逻辑处理 网络搭建
class QueryWord:
def __init__(self, host="0.0.0.0", port=8888):
self.host = host
self.port = port
self.dict = Dict()
self.sock = self.create_socket()
def create_socket(self):
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind((self.host, self.port))
return sock
def close(self):
self.sock.close()
# 查找单词方法
def query_word(self):
while True:
word, addr = self.sock.recvfrom(128)
# 查询单词
mean = self.dict.get_mean(word.decode())
self.sock.sendto(mean.encode(), addr)
if __name__ == '__main__':
query = QueryWord()
query.query_word()
############################ 客户端代码#########################
from socket import *
# 服务器地址
ADDR = ("127.0.0.1",8888)
class QueryWord:
def __init__(self):
self.sock = socket(type=SOCK_DGRAM)
def close(self):
self.sock.close()
# 网络传输
def recv_mean(self,word):
self.sock.sendto(word.encode(),ADDR)
mean,addr = self.sock.recvfrom(1024)
return mean.decode()
# 输入输出
def query_word(self):
while True:
word = input("Word:")
if not word:
break
mean = self.recv_mean(word)
print("%s : %s"%(word,mean))
if __name__ == '__main__':
query = QueryWord()
query.query_word() # 查单词
query.close()
UDP套接字特点
可能会出现数据丢失的情况、 传输过程简单,实现容易
数据以数据包形式表达传输、 数据传输效率较高
TCP 传输方法
TCP传输特点
面向连接的传输服务,数据传输过程中无丢失,无失序,无差错,无重复。三次握手四次挥手。
TCP服务端
创建套接字
sock=socket.socket(family,type)
功能:创建套接字
参数:family 网络地址类型 AF_INET表示ipv4
type 套接字类型 SOCK_STREAM 表示tcp套接字 (也叫流式套接字)
返回值: 套接字对象
绑定地址
sock.bind(addr)
功能: 绑定本机网络地址
参数: 二元元组 (ip,port) ('0.0.0.0',8888) 自动获取地址0.0.0.0
设置监听
sock.listen(n)
功能 : 将套接字设置为监听套接字,确定监听队列大小
参数 : 监听队列大小
处理客户端连接请求
conn,addr = sock.accept()
功能: 阻塞等待处理客户端请求
返回值: conn 客户端连接套接字
addr 连接的客户端地址
消息收发
data = conn.recv(buffersize)
功能 : 接受客户端消息
参数 :每次最多接收消息的大小
返回值: 接收到的内容
n = conn.send(data)
功能 : 发送消息
参数 :要发送的内容 bytes格式
返回值: 发送的字节数
#不再像UDP一样,在这里返回地址了
关闭套接字
tcp_socket.close()
请求连接
sock.connect(server_addr)
功能:连接服务器
参数:元组 服务器地址
"""
tcp 服务端流程 重点代码 !! 长连接形态
"""
from socket import *
# 创建tcp套接字
tcp_socket = socket(AF_INET, SOCK_STREAM)
# 绑定地址
tcp_socket.bind(("0.0.0.0", 8888))
# 设置监听
tcp_socket.listen(5)
# 循环处理客户端连接
while True:
print("等待连接....")
conn, addr = tcp_socket.accept()
print("连接:", addr)
# 先收后发
while True:
data = conn.recv(1024) #客户端退出,返回空
# 两种结束情况 客户端退出返回空 客户端指令##
if not data or data == b"##":
break
print("收到:", data.decode())
conn.send(b"Thanks")
conn.close()
# 关闭套接字
tcp_socket.close()
"""
tcp 客户端 重点代码 !!
"""
from socket import *
# 服务器地址
ADDR = ("127.0.0.1", 8888)
# 创建相同类型套接字
tcp_socket = socket()
# 建立连接
tcp_socket.connect(ADDR)
# 循环 发送 接收 注意: 防止两端都阻塞,recv send要配合
while True:
msg = input(">>")
tcp_socket.send(msg.encode())
if msg == '##':
break # 结束循环
data = tcp_socket.recv(1024)
print(data.decode())
tcp_socket.close()
短连接形态了解
"""
tcp 服务端流程 短连接形态
"""
from socket import *
# 创建tcp套接字
tcp_socket = socket(AF_INET, SOCK_STREAM)
# 绑定地址
tcp_socket.bind(("0.0.0.0", 8888))
# 设置监听
tcp_socket.listen(5)
# 循环处理客户端连接
while True:
conn, addr = tcp_socket.accept()
data = conn.recv(1024)
print("收到:", data.decode())
conn.send(b"Thanks")
conn.close()
# 关闭套接字
tcp_socket.close()
############################################################
"""
tcp 客户端
"""
from socket import *
# 服务器地址
ADDR = ("127.0.0.1", 8888)
# 循环输入发送
while True:
msg = input(">>")
if not msg:
break
tcp_socket = socket() # 每次发消息都要重新 创建套接字
tcp_socket.connect(ADDR)
tcp_socket.send(msg.encode())
data = tcp_socket.recv(1024)
print(data.decode())
tcp_socket.close()
练习
在客户端将一张图片上传到服务端,图片自选,上传到服务端后命名为 recv.jpg
思路: 客户端 获取文件内容--》发送出去
服务端 接收文件内容--》写入磁盘
########################## 服务端部分 #####################
from socket import *
# 接收内容,写入文件
def recv_file(connfd):
file = open("recv.jpg",'wb')
# 边收边写
while True:
data = connfd.recv(1024)
if data == b"##":
break
file.write(data)
file.close()
connfd.send("上传完毕".encode())
#搭建网络模型
def main():
# 创建tcp套接字
tcp_socket = socket()
tcp_socket.bind(("0.0.0.0",8888))
tcp_socket.listen(5)
while True:
connfd,addr = tcp_socket.accept()
print("Connect from",addr)
# 接收文件
recv_file(connfd)
connfd.close()
if __name__ == '__main__':
main()
############################ 客户端部分 ###################################
from socket import *
def send_file(tcp_socket,filename):
file = open(filename,"rb")
# 边读边发送
while True:
data = file.read(1024)
if not data:
break
tcp_socket.send(data)
file.close()
tcp_socket.send(b"##") # 告知服务端结束
msg = tcp_socket.recv(128)
print(msg.decode())
def main(filename):
tcp_socket = socket()
tcp_socket.connect(("127.0.0.1",8888))
# 发送文件
send_file(tcp_socket,filename)
tcp_socket.close()
if __name__ == '__main__':
main("./zly.jfif")
TCP服务端示例02:
from socket import *
# 创建tcp套接字
tcp_socket = socket(AF_INET,SOCK_STREAM)
# 绑定地址
tcp_socket.bind(("0.0.0.0",8880))
# 设置为监听套接字
tcp_socket.listen(5)
# 循环连接 收发 断开 -》 每次发送消息都需要连接
while True:
connfd,addr = tcp_socket.accept()
data = connfd.recv(1024)
print("收到:",data.decode())
connfd.send(b"Thanks")
connfd.close()
# 关闭套接字
tcp_socket.close()
TCP客户端示例02:
from socket import *
# 服务端地址
ADDR = ("127.0.0.1",8880)
# 循环发送接收消息
while True:
msg = input(">>")
if not msg:
break
tcp_socket = socket()
tcp_socket.connect(ADDR)
tcp_socket.send(msg.encode())
data = tcp_socket.recv(1024)
print("From server:",data.decode())
tcp_socket.close()
TCP套接字细节
-
tcp连接中当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串。
-
tcp连接中如果一端已经不存在,仍然试图通过send向其发送数据则会产生BrokenPipeError
-
一个服务端可以同时连接多个客户端,也能够重复被连接
-
tcp粘包问题
-
产生原因
- 为了解决数据再传输过程中可能产生的速度不协调问题,操作系统设置了缓冲区
- 实际网络工作过程比较复杂,导致消息收发速度不一致
- tcp以字节流方式进行数据传输,在接收时不区分消息边界
-
带来的影响
- 如果每次发送内容是一个独立的含义,需要接收端独立解析此时粘包会有影响。
-
处理方法
- 消息格式化处理,如人为的添加消息边界,用作消息之间的分割
-
控制发送的速度
-
随堂练习:
在客户端有一些数据
data = [
"张三 18 177",
"李四 19 180",
"王五 120 183"
]
从客户端向服务端发送这些数据,在服务端将这些数据分别写入到一个文件中,每个数据占一行
客户端发送完成是 向服务端发送 '#' 表示发送完毕
######################### 服务端 ######################
from socket import *
# 接收数据 (对应客户端方法1)
# def recv_data(connfd):
# file = open("student.txt",'w')
# while True:
# data = connfd.recv(1024).decode()
# if data == '##':
# break
# file.write(data + '
')
# file.close()
# 接收数据 (对应客户端方法2)
def recv_data(connfd):
file = open("student.txt", 'wb')
# data--> xxxx
xxxx
xxxx
data = connfd.recv(1024 * 1024)
file.write(data)
file.close()
def main():
sock = socket()
sock.bind(("0.0.0.0",8888))
sock.listen(3)
connfd,addr = sock.accept()
print("连接:",addr)
recv_data(connfd) # 接收数据
if __name__ == '__main__':
main()
###################### 客户端 ################################
from socket import *
from time import sleep
data = [
"张三 18 177",
"李四 19 180",
"王五 120 183"
]
# 发送数据 (处理粘包方法1)
# def send_data(sock):
# for item in data:
# sock.send(item.encode())
# sleep(0.1) # 延迟发送
# sock.send(b"##") # 表示发送完成
# 发送数据 (处理粘包方法2)
def send_data(sock):
info = '
'.join(data)
sock.send(info.encode()) # 一次性发送
def main():
sock = socket()
sock.connect(("127.0.0.1",8888))
send_data(sock) # 发送数据
sock.close()
if __name__ == '__main__':
main()
TCP与UDP对比
-
传输特征
- TCP提供可靠的数据传输,但是UDP则不保证传输的可靠性
- TCP传输数据处理为字节流,而UDP处理为数据包形式
- TCP传输需要建立连接才能进行数据传,效率相对较低,UDP比较自由,无需连接,效率较高
-
套接字编程区别
- 创建的套接字类型不同
- tcp套接字会有粘包,udp套接字有消息边界不会粘包
- tcp套接字依赖listen accept建立连接才能收发消息,udp套接字则不需要
- tcp套接字使用send,recv收发消息,udp套接字使用sendto,recvfrom
-
使用场景
- tcp更适合对准确性要求高,传输数据较大的场景
- 文件传输:如下载电影,访问网页,上传照片
- 邮件收发
- 点对点数据传输:如点对点聊天,登录请求,远程访问,发红包
- udp更适合对可靠性要求没有那么高,传输方式比较自由的场景
- 视频流的传输: 如部分直播,视频聊天等
- 广播:如网络广播,群发消息
- 实时传输:如游戏画面
- 在一个大型的项目中,可能既涉及到TCP网络又有UDP网络
- tcp更适合对准确性要求高,传输数据较大的场景
"""
完成一个对话小程序,客户端可以发送问题给服务端,
服务端接收到问题将对应答案给客户端,客户端打印出来
要求可以同时多个客户端提问,如果问题没有指定答案,
则回答 “人家还小,不知道。”
注意: 不需要使用数据库文件存储应答内容,
在服务端用字典表示关键字和答案之间的对应关系即可
{"key":"value"}
key: 几岁
value : 我2岁啦
"""
from socket import *
# 问答小字典
answer = {
"你好": "你好啊",
"几岁": "我两岁啦",
"叫什么": "我叫小美",
"漂亮": "我当然漂亮啦",
"男生女生": "我是机器人"
}
def chat(conn):
q = conn.recv(1024).decode()
# 查找关键词是否在问题中
for key, val in answer.items():
if key in q:
conn.send(val.encode())
break
else:
conn.send("人家还小,不知道啦".encode())
def main():
tcp_socket = socket(AF_INET, SOCK_STREAM)
tcp_socket.bind(("0.0.0.0", 8888))
tcp_socket.listen(5)
# 循环处理问题
while True:
conn, addr = tcp_socket.accept()
chat(conn) # 处理问题回复
conn.close()
if __name__ == '__main__':
main()
#########################################################################
"""
tcp 客户端
"""
from socket import *
# 服务器地址
ADDR = ("127.0.0.1", 8888)
def chat(msg):
tcp_socket = socket() # 创建套接字
tcp_socket.connect(ADDR)
tcp_socket.send(msg.encode())
data = tcp_socket.recv(1024)
print("小美:", data.decode())
tcp_socket.close()
def main():
while True:
msg = input("我:")
if not msg:
break
chat(msg)
if __name__ == '__main__':
main()
tcp粘包问题
tcp按字节数接收消息,超出时,分多次接收,不足时一次性接收,产生粘包,一次性接收多个消息。解决:增加消息边界换行 或 加大发送时间间隔。
"""
实验:tcp
客户端有一个列表,列表中是一组学生信息
stu=["Tom:18:89","Lily:17:88","Jame:18:90"]
将这写信息发送到服务端,服务端在终端打印出来,
每个信息项打印一行
"""
#########################server
from socket import *
# 监听套接字
sock = socket()
sock.bind(("0.0.0.0", 8888))
sock.listen(5)
# 连接客户端
conn, addr = sock.accept()
print("Connect from", addr)
# 循环收 收一个打印一个
while True:
data = conn.recv(1024) #字节数较小时分多次接收,不一定与发的次数保持一致
if data == b"##":
break
print(data.decode())
# while True:
# data = conn.recv(5)
# if not data:
# break
# print(data.decode())
conn.close()
sock.close()
##########################client
from socket import *
from time import sleep
stu = [
"Tom:18:89",
"Lily:17:88",
"Jame:18:90"
]
sock = socket()
sock.connect(("127.0.0.1", 8888))
# 循环发送内容 最后 ## 表示发送完毕
for row in stu:
sock.send((row + "
").encode()) # 增加消息边界 不加
产生tcp粘包问题
#tcp按conn.recv(1024)的字节数接收,超出时,多次接收,不足时一次性接收,产生粘包
#udp按条接收不存在
sleep(0.1) # 发送延迟 防止粘包
sock.send(b"##")
# sock.send(b"Hello world")
sock.close()
加大发送时间间隔防止粘包
"""
假设在客户端有一张照片,请使用tcp
上传到服务端,在服务端以 20210909.jpeg 保存,带有上传成功回复
plus : 假设文件比较大,不宜一次行读取全部内容
思路: 读取数据 发送
接收数据 写入
"""
from socket import *
# 做事 : 收一个文件
def handle(conn):
fw = open("../day11/20210909.jpeg", 'wb')
while True:
data = conn.recv(1024)
if data == b'##':
break # 对方发送完成直接退出得到空字串
fw.write(data)
fw.close()
conn.send("您已上传成功".encode())
# 搭建网络模型
def main():
# 创建监听套接字
sock = socket()
sock.bind(("0.0.0.0", 8888))
sock.listen(5)
# 循环接收连接
while True:
conn, addr = sock.accept()
print("Connect from", addr)
handle(conn) # 具体的文件处理
conn.close()
if __name__ == '__main__':
main()
#################################client
from socket import *
from time import sleep
ADDR = ("127.0.0.1", 8888)
def handle(sock):
fr = open("/home/下载/reba.jpeg", 'rb')
while True:
data = fr.read(1024)
if not data:
break # 文件结尾结束
sock.send(data)
fr.close()
sleep(0.1) #防止粘包操作
sock.send(b'##') # 表示告知已经发送完成
data = sock.recv(1024) # 接收最后的“上传成功提示”
print(data.decode())
def main():
sock = socket()
sock.connect(ADDR)
handle(sock) # 发送文件
sock.close()
if __name__ == '__main__':
main()
数据传输过程
传输流程
- 发送端由应用程序发送消息,逐层添加首部信息,最终在物理层发送消息包。
- 发送的消息经过多个节点(交换机,路由器)传输,最终到达目标主机。
- 目标主机由物理层逐层解析首部消息包,最终到应用程序呈现消息。
TCP协议首部信息
-
源端口和目的端口 各占2个字节,分别写入源端口和目的端口。
-
序号seq 占4字节。TCP是面向字节流的。在一个TCP连接中传送的字节流中的每一个字节都按顺序编号。例如,一报文段的序号是301,而接待的数据共有100字节。这就表明本报文段的数据的第一个字节的序号是301,最后一个字节的序号是400。
-
确认号ack 占4字节,是期望收到对方下一个报文段的第一个数据字节的序号。例如,B正确收到了A发送过来的一个报文段,其序号字段值是501,而数据长度是200字节(序号501~700),这表明B正确收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701。
-
flags: ACK SYN FIN
确认ACK(ACKnowledgment) 仅当ACK = 1时确认号字段才有效,当ACK = 0时确认号无效。TCP规定,在连接建立后所有的传送的报文段都必须把ACK置为1。
同步SYN(SYNchronization) 在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使SYN=1和ACK=1,因此SYN置为1就表示这是一个连接请求或连接接受报文。
终止FIN(FINis,意思是“完”“终”) 用来释放一个连接。当FIN=1时,表明此报文段的发送发的数据已发送完毕,并要求释放运输连接。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MP81XITR-1682691580330)(picture cp_head.png)]
三次握手四次挥手
三次握手(建立连接)
客户端向服务器发送消息报文请求连接 SYN=1 seq=x
服务器收到请求后,回复报文确定可以连接 SYN=1 ACK=1 ack=x+1 seq=y
客户端收到回复,发送最终报文连接建立 ACK=1 ack=y+1
四次挥手(断开连接,都可以客气,客服端发起较多,有可能不能完成)
主动方发送报文请求断开连接 FYN=1 seq=x
被动方收到请求后,立即应答,表示准备断开 ACK=1 ack=x+1 seq=y
被动方准备就绪,再次发送报文表示可以断开 FYN=1 ACK=1 ack=x+1 seq=Z
主动方收到确定,发送最终报文完成断开 ACK=1 ack=z+1 seq=h