目录
一、socket套接字
1.基于文件类型的套接字家族
套接字家族名字:AF_UNIX
2.基于网络类型的套接字家族
套接字家族名字:AF_INET
3.socket模块之服务端
import socket
# 1.创建一个socket对象
server = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字
# 2.绑定一个固定的地址(ip\port)
server.bind(('127.0.0.1', 8080)) # 127.0.0.1本地回环地址(只允许自己的机器访问)
# 3.半连接池(暂且忽略)
server.listen(5)
# 4.开业 等待接客
sock, address = server.accept()
print(sock, address) # sock是双向通道 address是客户端地址
# 5.数据交互
sock.send(b'hello big baby~') # 朝客户端发送数据
data = sock.recv(1024) # 接收客户端发送的数据 1024bytes
print(data)
# 6.断开连接
sock.close() # 断链接
server.close() # 关机
4.socket模块之客户端
import socket
# 1.产生一个socket对象
client = socket.socket()
# 2.连接服务端(拼接服务端的ip和port)
client.connect(('127.0.0.1', 8080))
# 3.数据交互
data = client.recv(1024) # 接收服务端发送的数据
print(data)
client.send(b'hello sweet server') # 朝服务端发送数据
# 4.关闭
client.close()
二、代码优化
1.send与recv
客户端与服务端不能同时执行同一个,不能同时收或发
- 有一个收,另外一个就是发
- 有一个发,另外一个就是收
2.消息自定义
input获取用户数据(主要是编码解吗)
3.循环通信
给数据交互环节添加循环
4.服务端能够持续提供服务
需求:不会因为客户端断开连接而报错
方法:异常捕获
- 一旦客户端断开连接,服务端结束通信循环调到连接处等待
5.消息不能为空
判断是否为空,是则重新输入(主要针对客户端)
6.服务端频繁重启可能会报端口被占用的错(主要针对mac电脑)
from socket import SOL_SOCKET, SO_REUSEADDR
server.aetsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 加在bind前面
7.客户端异常退出会发送空消息(针对mac、linux)
针对接收的消息加判断处理即可
三、半连接池
主要为了做缓冲,避免太多的无效等待
sever.listen(5)
四、黏包问题
# 服务端
import socket
# 1.创建一个socket对象
server = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字
# 2.绑定一个固定的地址(ip\port)
server.bind(('127.0.0.1', 8080)) # 127.0.0.1本地回环地址(只允许自己的机器访问)
server.listen(5)
sock, address = server.accept()
print(sock.recv(5))
print(sock.recv(5))
print(sock.recv(5))
# 客户端
import socket
# 1.产生一个socket对象
client = socket.socket()
# 2.连接服务端(拼接服务端的ip和port)
client.connect(('127.0.0.1', 8080))
client.send(b'jason')
client.send(b'kevin')
client.send(b'jerry')
1.TCP特性
流式协议:所有的数据类似于水流连接在一起
2.recv
不知道即将要接收的数据量多大,如果知道的话不会产生黏包
3.struct模块
struct模块无论数据长度是多少,都可以打包成固定长度,然后基于该固定长度可以反向解析出真实长度
import struct
info = 'something to send'
print(len(info)) # 17 数据原本的长度
res = struct.pack('i', len(info)) # 将数据原本的长度打包
print(len(res)) # 打包之后的长度是4
ret = struct.unpack('i', res) # 将打包之后固定长度为4的数据拆包
print(ret[0]) # 13 又得到了原本数据的长度
思路:
发送:
- 先将真实数据的长度制作成固定长度 4
- 先发送固定长度的报头
- 再发送真实数据
接收:
- 先接收固定长度的报头 4
- 再根据报头解压出真实长度
- 根据真实长度接收即可
4.解决黏包问题的终极方案:
4.1.服务端:
- 先构造一个数据的详细字典
- 对字典数据进行打包处理,得到一个固定长度的数据 4
- 将上述打包之后的数据发送给客户端
- 将字典数据发送给客户端
- 将真实数据发送给客户端
import socket
import os
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
sock, address = server.accept()
while True:
# 1.先构造数据文件的字典
file_dict = {
'file_name': 'Jason合集.txt',
'file_size': os.path.getsize(r'../XXX视频合集.txt'),
'file_desc': '内容很精彩 一定不要错过',
'file_root': 'jason'
}
# 2.将字典打包成固定长度的数据
dict_json = json.dumps(file_dict)
file_bytes_dict = len(dict_json.encode('utf8'))
dict_len = struct.pack('i', file_bytes_dict)
# 3.发送固定长度的字典报头
sock.send(dict_len)
# 4.发送真实字典数据
sock.send(dict_json.encode('utf8'))
# 5.发送真实数据
with open(r'../XXX视频合集.txt', 'rb') as f:
for line in f:
sock.send(line)
break
4.2.客户端:
- 先接收固定长度的数据 4
- 根据固定长度解析出即将要接收的字典真实长度
- 接收字典数据
- 根据字典数据获取真实数据长度
- 接收真实数据长度
import socket
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
# 1.先接收长度为4的报头数据
header_len = client.recv(4)
# 2.根据报头解包出字典的长度
dict_len = struct.unpack('i', header_len)[0]
# 3.直接接收字典数据
dict_data = client.recv(dict_len) # b'{"file_name":123123123}'
# 4.解码并反序列化出字典
real_dict = json.loads(dict_data)
print(real_dict)
# 5.从数据字典中获取真实数据的各项信息
total_size = real_dict.get('file_size') # 32423423423
file_size = 0
with open(r'%s' % real_dict.get('file_name'), 'wb') as f:
while file_size < total_size:
data = client.recv(1024)
f.write(data)
file_size += len(data)
print('文件接收完毕')
break