了解socket
socket:
Socket是应用层与TCP/IP协议族通信的中间抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
套接字的分类:
AF_UNIX:用在局域网内
AF_INET:用在互联网中
客户端和服务端是如何启动的?
我们应该先启动服务端,服务端启动以后,等待客户端进行连接,然后接收客户端的信息,进行通信
套接字的工作流程
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
基于TCP的套接字
#############################服务端##############################
import socket
"""
SOCK_STREAM:使用的是TCP协议
SOCK_DGRAM:使用的是UDP协议
"""
# 通过实例化得到了一个socket的对象sever,相当于买了一部手机
sever = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 服务端绑定信息,相当于买了一张电话卡
sever.bind(('127.0.0.1', 8080))
# 监听消息 ,相当于手机处于待机状态
sever.listen(4) # 4代表的是半连接池:可以等待的客户端的数量
# 接收消息 , 相当于接电话
sock, addr = sever.accept() # 代码走到这里会停住,等待接收客户端发来的消息
"""
sock:代表的是当前客户端的连接对象
addr:代表的是客户端的信息(ip+port)
"""
# 听消息
date = sock.recv(1024) # 获取到客户端发来的消息
print(f'这是客户端发来的消息{date}') # 这是客户端发来的消息b'hello'
# 发消息
# 数据类型必须是字节类型
sock.send(b'BayBay') # 服务端给客户端回话
sever.close() # 挂电话
sock.close() # 手机关机
#############################客户端##############################
import socket
# 相当于买了一个手机
client = socket.socket() # 实例化出来一个对象,
# 给服务端打电话
client.connect(('127.0.0.1', 8080)) # 跟服务端进行创建连接
# 给服务端说消息
client.send(b'hello') # 发送的必须是字节类型
# 接收服务端返回过来的消息
date = client.recv(1024) # 一次性接受的最大量,也是字节类型
print(f'这是服务端发来的消息>>>>{date}') # 这是服务端发来的消息>>>>b'BayBay'
# 挂掉电话
client.close() # 断掉与服务器的链接
加上链接循环与通信循环
#############################服务端##############################
import socket
sever = socket.socket()
sever.bind(("127.0.0.1", 8000))
sever.listen(3)
while True:
sock, addr = sever.accept()
while True:
date = sock.recv(1024)
print(date)
sock.send(b'bay bay')
sock.close()
sever.close()
#############################客户端##############################
import socket
client = socket.socket()
client.connect(("127.0.0.1", 8000))
while True:
msg = input('请输入你的信息>>>').strip()
if len(msg) == 0:
continue
client.send(msg.encode('utf8'))
date = client.recv(1024)
print(date)
client.close()
基于UDP协议的套接字编程
#############################服务端##############################
import socket
sever = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sever.bind(("127.0.0.1", 8000))
data, server_addr = sever.recvfrom(1024)
print(data, server_addr)
sever.sendto(data, server_addr)
sever.close()
#############################客户端##############################
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议-》UDP
client.sendto(b'hello', ("127.0.0.1", 8000))
data, server_addr = client.recvfrom(1024)
print(data)
client.close()
加上链接循环与通信循环
#############################服务端##############################
import socket
sever = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议-》UDP
sever.bind(("127.0.0.1", 8000))
while True:
# client_addr:客户端的地址:ip+port
data, client_addr = sever.recvfrom(1024) # 接收客户端发来的消息,1024是字节数
print(f'接收到来自客户端的{data},{server_addr}'吗)
sever.sendto(data, server_addr) # 给客户端发消息
sever.close()
#############################客户端##############################
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 数据报协议-》UDP
while True:
msg = input('请输入你发送的信息>>>>>').strip()
if len(msg) == 0:
continue
client.sendto(msg.encode('utf8'), ("127.0.0.1", 8000))
data, server_addr = client.recvfrom(1024)
print(data)
client.close()
黏包
#############################服务端##############################
import socket
import subprocess
sever = socket.socket()
sever.bind(("127.0.0.1", 8080))
sever.listen(3)
sock, addr = sever.accept()
while True:
try:
date = sock.recv(1024)
obj = subprocess.Popen(date.decode('utf8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
user_stdout = obj.stdout.read()
user_stderr = obj.stderr.read()
print(len(user_stdout) + len(user_stderr))
sock.send(user_stdout)
sock.send(user_stderr)
except Exception:
break
sock.close()
#############################客户端##############################
import socket
client = socket.socket()
client.connect(("127.0.0.1", 8080))
while True:
cmd = input('请输入指令>>>>>').strip()
client.send(cmd.encode('gbk'))
date = client.recv(1024)
print(date.decode('gbk'))
"""上述程序是基于tcp的socket,在运行时会发生粘包"""
什么是黏包
须知:只有TCP有粘包现象,UDP永远不会粘包
因为TCP在传输数据之前需要先建立一个双向通道,然而在传输数据的时候又会设定一个最大传输的字节,一旦传输的数据大于这个字节(假如我们把最大字节设置为1024)以后,多余出来的数据就会滞留在通道内,下次再调用又会吐出来1024个字节的数据,就这样一直循环下去,直到所有的数据都传输完毕
"这样的一个问题就是我们传输的数据会乱掉"
UDP协议的话是不用建立双向通道的,所以也就不会存在数据滞留的问题,然后也就不会出现黏包的现象
解决黏包现象:struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
如果说一个数据的大小是2000000,那么我们就可以通过使用struct模块将他变为一个固定大小的字节,详情请参照下图
import struct
res = struct.pack('i', 1000000)
print(len(res)) # 4
res1 = struct.unpack('i', res)
print(res1) # (1000000,)
print(res1[0]) # 1000000
# 但是被打包的数据是有限制的
"""
struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
"""
如果超过这个范围又该如何解决呢?
#############################服务端##############################
import socket
import json
import struct
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
sock, address = server.accept()
while True:
# 1.先接收固定长度为4的字典报头数据
recv_first = sock.recv(4)
# 2.解析字典报头
dict_length = struct.unpack('i', recv_first)[0]
# 3.接收字典数据
real_data = sock.recv(dict_length)
# 4.解析字典(json格式的bytes数据 loads方法会自动先解码 后反序列化)
real_dict = json.loads(real_data)
# 5.获取字典中的各项数据
data_length = real_dict.get('size')
file_name = real_dict.get("file_name")
recv_size = 0
with open(file_name,'wb') as f:
while recv_size < data_length:
data = sock.recv(1024)
recv_size += len(data)
f.write(data)
# data = sock.recv(1024) # 接收cmd命令
# command_cmd = data.decode('utf8')
# sub = subprocess.Popen(command_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# res = sub.stdout.read() + sub.stderr.read() # 结果可能很大
# # 1.定义一个字典数据
# data_dict = {
# 'desc': '这是非常重要的数据',
# 'size': len(res),
# 'info': '下午挺困的 ...提神醒脑'
# }
# data_json = json.dumps(data_dict)
# # 2.制作字典报头
# data_first = struct.pack('i', len(data_json))
# # 3.发送字典报头
# sock.send(data_first)
# # 4.发送字典
# sock.send(data_json.encode('utf8'))
# # 5.发送真实数据
# sock.send(res)
#############################客户端##############################
import json
import socket
import struct
import os
client = socket.socket() # 买手机
client.connect(('127.0.0.1', 8080)) # 拨号
while True:
data_path = os.path.dirname(os.path.abspath(__file__))
# print(os.listdir(data_path)) # [文件名称1 文件名称2 ]
movie_name_list = os.listdir(data_path)
for i, j in enumerate(movie_name_list, 1):
print(i, j)
choice = input('请选择您想要上传的电影编号>>>:').strip()
if choice.isdigit():
choice = int(choice)
if choice in range(1, len(movie_name_list) + 1):
# 获取文件名称
movie_name = movie_name_list[choice - 1]
# 拼接文件绝对路径
movie_path = os.path.join(data_path, movie_name)
# 1.定义一个字典数据
data_dict = {
'file_name': 'XXX老师合集.mp4',
'desc': '这是非常重要的数据',
'size': os.path.getsize(movie_path),
'info': '下午挺困的,可以提神醒脑'
}
data_json = json.dumps(data_dict)
# 2.制作字典报头
data_first = struct.pack('i', len(data_json))
# 3.发送字典报头
client.send(data_first)
# 4.发送字典
client.send(data_json.encode('utf8'))
# 5.发送真实数据
with open(movie_path,'rb') as f:
for line in f:
client.send(line)
# msg = input('请输入cmd命令>>>:').strip()
# if len(msg) == 0:
# continue
# client.send(msg.encode('utf8'))
# # 1.先接收固定长度为4的字典报头数据
# recv_first = client.recv(4)
# # 2.解析字典报头
# dict_length = struct.unpack('i',recv_first)[0]
# # 3.接收字典数据
# real_data = client.recv(dict_length)
# # 4.解析字典(json格式的bytes数据 loads方法会自动先解码 后反序列化)
# real_dict = json.loads(real_data)
# print(real_dict)
# # 5.获取字典中的各项数据
# data_length = real_dict.get('size')
# data_bytes = client.recv(data_length)
# print(data_bytes.decode('gbk'))