网络编程——TCP协议、UDP协议、socket套接字、粘包问题以及解决方法
TCP协议(流式协议)
当应用程序想通过TCP协议实现远程通信时,彼此之间必须先建立双向通信通道,基于该双向通道实现数据的远程交互,该双向通道直到任意一方主动断开才会失效
TCP协议的“三次握手” 和 “四次挥手”
三次握手 建连接
1、客户端向服务端发送建立连接请求
2、服务端返回收到请求信息,同时向客户端发送连接请求
3、客户端接收到服务端发来的请求,返回连接成功给服务端,完成双向连接
建立好连接后,会有一个反馈机制
客户端往服务器发送请求,服务端必须返回响应,告诉客户端收到请求了,并且将服务端的数据一并返回给客户端
C ----> S :一次请求,必须有一次响应
优点
数据安全
缺点
①:传输速度慢
②:洪水攻击
指的是通过伪造大量的请求,往对方服务器发送请求,导致对方服务器响应跟不上,以至于瘫痪
半连接池listen:限制用户在同一个时间段内的访问数量
四次挥手 断连接
1、客户端向服务端发送断开连接的请求
2、服务端返回接收到请求的信息给客户端
3、服务端确认所有数据接收完成以后,再发送同意断开连接的请求给客户端
4、客户端返回收到断开连接的请求,给服务端
UDP协议
UDP协议也被称之为数据包协议
UDP协议的特点:
①:不需要建立连接
②:不需要知道对方是否收到
③:数据不安全,会丢包
④:传输速度快
⑤:能支持并发
⑥:不会粘包
⑦:无需先启动服务端再启动客户端
UDP的优点:
①:传输速度快
②:能支持并发
③:不会粘包
UDP的缺点:(致命缺点)
数据不安全,容易丢失
UDP的应用场景:早期的QQ聊天室
socket套接字通信
什么是socket?
socket是一个模块,又称套接字,用来封装互联网协议(应用层以下的层)
为什么要有socket?
socket可以实现互联网协议应用层以下的层 的工作
提高开发效率
怎么使用socket?
# server.py文件
import socket
sever = socket.socket()
sever.bind(("127.0.0.1", 9527))
# 半连接池
sever.listen(5) # 最多接受5个客户端发送的请求,实际上是6个
# conn:指的是服务端网客户端的通道
coon, addr = sever.accept()
# 接收客户端发送过来的消息
ret = coon.recv(1024) # 一次可接受1024bytes的数据
print(ret)
# 关闭服务器
coon.close()
# client.py文件
"""
启动服务器后再启动客户端
"""
import socket
client = socket.socket()
client.connect(("127.0.0.1", 9527))
# 必须发送bytes类型的数据
# # 方式一
# client.send('hello'.encode('utf-8'))
# 方式二
client.send(b'hello')
粘包问题
TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
问题一:无法确认对方发送过来数据的大小
问题二:在发送数据时间隔短并且数据量小的情况下,会将所有数据一次性发送
解决方法: 确认对方的数据的大小
解决粘包问题(struct模块)
struct模块是什么?
是一个Python内置的模块,它可以将固定长度的数据,打包成固定格式的长度
常用的模式:i 模式
struct模块作用
可以将真实数据,做成一个固定长度的报头,客户端发送给服务器,服务器可以接收报头,然后对报头进行解包,获取真实数据的长度,进行接收即可
import struct
data = b'11111111111111'
print(len(data))
# 打包制作报头
header = struct.pack('i', len(data))
print(header)
print(len(header))
# 解包获取真实数据长度 ---> 得到一个元组,元组中第一个值是真实数据的长度
res = struct.unpack('i', header)[0]
print(res)
扩展例题
# srever.py文件
import socket
import struct
import subprocess
server = socket.socket()
server.bind(("127.0.0.1", 9527))
server.listen(5)
while True:
coon, addr = server.accept()
print(addr)
while True:
try:
# 获取客户端传来的报头
header = coon.recv(4)
# 解包获取真实数据长度
data_len = struct.unpack('i', header)[0]
# 准备接收真实数据
cmd = coon.recv(data_len)
if len(cmd) == 0:
continue
cmd = cmd.decode('utf-8')
if cmd == "q":
break
obj = subprocess.Popen(
cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
result = obj.stdout.read() + obj.stderr.read()
print("发送给客户端的真实数据长度为", len(result))
header = struct.pack('i', len(result))
print(len(header))
coon.send(header)
coon.send(result)
except Exception as e:
print(e)
break
coon.close()
# client.py文件
import struct
import socket
client = socket.socket()
client.connect(("127.0.0.1", 9527))
while True:
cmd = input("客户端输入的内容>>>:")
cmd_bytes = cmd.encode('utf-8')
# 做一个报头
header = struct.pack("i", len(cmd_bytes))
print(len(header))
client.send(header)
# 待服务器确认长度后,发送真实数据长度
client.send(cmd_bytes)
# 接收服务端返回的报头
header = client.recv(4)
# 解包,接收服务端返回的真实数据
data_len = struct.unpack('i', header)[0]
result = client.recv(data_len)
print("接收服务端返回的真实数据长度为:", len(result))
print(result.decode('gbk'))