1: socket 以及 小内容补充
1. socket : 又称为套件字,在python中使用需要导入这个模块(import socket)
2. 本机回环地址: (127.0.0.1) 只能自己识别自己,其他人无法访问
2: 基于TCP协议的socket使用
由于TCP协议都是基于链接的,所以必须要先启用服务端,再启动客户端
服务端:
import socket # 导入socket模块
sk = socket.socket() # 创建一个socket对象
sk.bind(('127.0.0.1',8080)) # 绑定ip和端口,注意: bind方法只接收一个参数,所以里面要将ip和port先到一个元组里面
sk.listen(5) # 开始监听 注意:listen方法后面跟的int类型,表示待连接的最大监听个数,不写默认最大
conn,addr = sk.accept() # 阻塞, accept接收一个元组,conn是一个链接,也就是建立起来一个TCP的全双工,addr是监听到客户端的地址
ret = conn.recv(1024) # 阻塞 ,接收客户端消息,接收1024个字节,可以改变,推荐为1024的倍数,不可超出当前可用内存
print(ret) # 打印客户端发来的信息
conn.send(b'hi') # 向客户端发送信息,注意发送的数据必须先转为二进制
conn.close() # 断开与客户端的链接
sk.close() # # 关闭服务器套接字, 可以不写,因为服务器通常都是24小时提供服务的
客户端:
import socket
sk = socket.socket() # 创建客户端套接字对象
sk.connect(('127.0.0.1',8080)) # 连接服务器,connect 绑定要访问的服务器的ip+port 元组形式
sk.send(b'hello') # 给服务器发送数据
ret = sk.recv(1024) # 接收服务器返回的数据
print(ret)
sk.close() # 关闭客户端的套接字
由于再操作时候由于刚才使用端口操作系统没有及时没有回收,再使用时后会报错:
Traceback (most recent call last):
File "D:/py/网络编程/blog/基于TCP协议的socket使用/server.py", line 4, in <module>
sk.bind(('127.0.0.1',8080)) # 绑定ip和端口,注意: bind方法只接收一个参数,所以里面要将ip和port先到一个元组里面
OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
我们可以这样处理:
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 加入这个配置,重用ip和端口
如
import socket # 导入socket模块
sk = socket.socket() # 创建一个socket对象
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 加入这个配置,重用ip和端口
sk.bind(('127.0.0.1',8080)) # 绑定ip和端口,注意: bind方法只接收一个参数,所以里面要将ip和port先到一个元组里面
sk.listen(5) # 开始监听 注意:listen方法后面跟的int类型,表示待连接的最大监听个数,不写默认最大
conn,addr = sk.accept() # 阻塞, accept接收一个元组,conn是一个链接,也就是建立起来一个TCP的全双工,addr是监听到客户端的地址
ret = conn.recv(1024) # 阻塞 ,接收客户端消息,接收1024个字节,可以改变,推荐为1024的倍数,不可超出当前可用内存
print(ret) # 打印客户端发来的信息
conn.send(b'hi') # 向客户端发送信息,注意发送的数据必须先转为二进制
conn.close() # 断开与客户端的链接
sk.close() # # 关闭服务器套接字, 可以不写,因为服务器通常都是24小时提供服务的
练习: client端:每隔10秒把当前时间的时间戳发送给客户端 server端:将client端发来的时间戳转化成格式化时间返回给client端
练习server端:
import socket
import time
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
while 1:
ret = conn.recv(1024).decode('utf-8')
print(ret)
res = float(ret)
ret = time.localtime(res)
my_time = time.strftime('%Y-%m-%d %H:%M:%S',ret)
conn.send(my_time.encode('utf-8'))
练习client端:
import socket
import time
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
while 1:
time.sleep(10)
sk.send(bytes(str(time.time()).encode('utf-8')))
ret = sk.recv(1024).decode('utf-8')
print(ret)
3: 黏包:主要是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
1: 基于TCP的传输当接收的数据小于对方发送的数据后,得到的数据是不完整的,再次发送请求时接收到的不是当前需要的数据而是上次未接收完的数据,如:
服务端:
import socket
import subprocess
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8080))
sk.listen()
while 1:
conn,addr = sk.accept()
while True:
try:
cmd = conn.recv(1024).decode('utf-8')
if cmd == 0:
break
obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
ret = obj.stdout.read() + obj.stderr.read()
conn.send(ret)
except ConnectionResetError:
break
conn.close()
客户端:
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
while 1:
cmd = input('>>>')
if len(cmd) == 0:
continue
sk.send(cmd.encode('utf-8'))
ret = sk.recv(1024).decode('gbk')
print(ret)
2:TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据.如:
服务端:
import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
res = conn.recv(1024)
print(res) # b'hello jayhello tomhello rose'
res1 = conn.recv(1024)
print(res1) # b''
res2 = conn.recv(1024)
print(res2) # b''
conn.close()
sk.close()
客户端:
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
sk.send(b'hello jay')
sk.send(b'hello tom')
sk.send(b'hello rose')
sk.close()
4: 解决黏包的办法:可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了,如:
服务端:
import socket
import json
import struct
import subprocess
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8080))
sk.listen()
while 1:
conn,addr = sk.accept()
while 1:
try:
cmd = conn.recv(1024).decode('utf-8')
if len(cmd) == 0: # 针对在linux,mac系统中客户端异常退出会一直接收到空
break
obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
ret = obj.stdout.read() + obj.stderr.read()
# 先制作一个发送给客户端的字典
d = {'name':'小明','size':len(ret)}
# 为了能将字典发送.将字典序列化
json_d = json.dumps(d)
# 制作字典的报头,导入struct模块将字典打包成固定长度
len_d = struct.pack('i',len(json_d))
# 发送报头
conn.send(len_d)
# 发送字典
conn.send(json_d.encode('utf-8'))
# 发送真实数据
conn.send(ret)
# 避免客户端强制中断连接报错
except ConnectionResetError:
break
conn.close()
客户端:
import socket
import json
import struct
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
while 1:
cmd = input('>>>').strip().encode('utf-8')
if len(cmd) == 0:
continue
sk.send(cmd)
# 接收字典的报头
len_d = sk.recv(4)
# 解析拿到字典数据的真实长度
len_d = struct.unpack('i',len_d)[0]
# 接收字典,并将其反序列化
json_d = sk.recv(len_d).decode('utf-8')
d = json.loads(json_d)
info_len = 0
with open('test','a',encoding='utf-8') as f:
# 获取字典中存放的真实数据的长度
while info_len < d.get('size'):
# 接收真实数据并循环写到文件中.避免文件过大内存溢出
f.write(sk.recv(1024).decode('gbk')) # window系统默认GBK编码
info_len += 1024
5: struct 模块的使用:该模块可以把一个类型,如数字,转成固定长度的bytes
1: struct.pack : 将一个类型打包成固定长的的bytes
import struct
d = {'name':'xiaoming','age':18}
print(len(d)) # 2
ret = struct.pack('i',len(d))
print(len(ret)) # 4
2: struct.unpack: 将打包的解析成原先的长度
import struct
d = {'name':'xiaoming','age':18}
print(len(d)) # 2
ret = struct.pack('i',len(d))
print(len(ret)) # 4
res = struct.unpack('i',ret)[0] # 注意返回一个元组,索引0就时他的长度
print(res) # 2
3.它是可以选择打包模式的.如'i'对应的就时打包成4个字节

6: 使用socket 客户端上传文件到服务端实例
服务端:
import socket
import os
import json
import struct
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
# 将上传文件目录的路径存到变量中
BASE_DIR = 'D:\学习资料\day28\视频'
# 获取当前目录下的所有文件
dir_list = os.listdir(BASE_DIR)
while 1:
# 循环打印选择要上传的文件
for index,name in enumerate(dir_list,1):
print(index,name)
choice = input('请输入上传文件的序号').strip()
if not choice.isdigit() or not (int(choice)-1 in range(len(dir_list))):
continue
name = dir_list[int(choice)-1]
# 拼接路径
path = os.path.join(BASE_DIR,name)
# 获取文件的大小
size = os.path.getsize(path)
# 定义存储文件信息的字典
d = {'name':name,'size':size}
# 序列化字典
json_d = json.dumps(d)
# 制作字典的报头
len_d = struct.pack('i',len(json_d))
# 发送报头
sk.send(len_d)
# 发送字典
sk.send(json_d.encode('utf-8'))
# 循环发送真实数据.避免内存溢出
with open(path,'rb') as f:
for line in f:
sk.send(line)
客户端:
import socket
import struct
import json
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8080))
sk.listen(5)
while 1:
conn,addr = sk.accept()
while 1:
try:
len_d = conn.recv(4)
if len_d == 0:
break
len_d = struct.unpack('i',len_d)[0]
d = json.loads(conn.recv(len_d).decode('utf-8'))
recv_len = 0
with open(d.get('name'),'wb') as f:
while recv_len < d.get('size'):
info = conn.recv(1024)
f.write(info)
recv_len += len(info)
except ConnectionResetError:
break
7: 基于UDP协议的socket使用
8: 模拟QQ交互
9: socketsever模块