socket 模块
我们知道基于网络编写程序时,需要使用 OSI七层协议里的七层,每一层都要写。很复杂很繁琐。
socket 模块相当于集成了这一块,封装了复杂的接口提空了简单快捷的接口。
socket套接字简介
基于文件类型的套接字家族(单机)
AF_UNIX
基于网络类型的套接字家族(联网)
AF_INET
代码实操
cs架构的软件都应该考虑到服务端,因为只有有了服务场所,才能提供服务
# 1.产生一个socket对象并指定采用的通信版本和协议(TCP)
server = socket.socket() # 括号内不写参数 默认就是TCP协议 family=AF_INET基于网络的套接字 type=SOCK_STREAM流式协议即TCP
# 2.绑定一个固定的地址(服务端必备的条件)
server.bind(('127.0.0.1', 8080)) # 127.0.0.1为本地回环地址 只有自己的电脑可以访问
# 3.设立半连接池(暂且忽略)
server.listen(5)
# 4.等待接客
sock, addr = server.accept() # return sock, addr 三次握手 建立链接
print(sock, addr) # sock就是双向通道 addr就是客户端地址
# 5.服务客人
data = sock.recv(1024) # 接收客户端发送过来的消息 1024字节
print(data.decode('utf8'))
sock.send('滚开我不开心'.encode('utf8')) # 给客户端发送消息 注意消息必须是bytes类型
# 6.关闭双向通道
sock.close() # 四次挥手 关闭链接
# 7.关闭服务端
server.close()
# 客户端
import socket
# 1.生成socket对象指定类型和协议
client = socket.socket()
# 2.通过服务端的地址链接服务端
client.connect(('127.0.0.1', 8080))
# 3.直接给服务端发送消息
client.send('今天你吃饭了吗'.encode('utf8'))
# 4.接收服务端发送过来的消息
date = client.recv(1024)
print(date.decode('utf8'))
# 5.断开与服务端的链接
client.close()
数据交互循环
# 1.解决信息固定的问题
利用input获取用户输入
# 2.解决通信循环的问题
将双方用于数据交互的代码循环起来
# 服务端
while True:
data = sock.recv(1024) # 获取别人说的什么
print(data.decode('utf8')) # 解码别人说的啥
msg = input('请回复>>>:').strip()
sock.send(msg.encode('utf8')) # 回复别人的话,因为基于网络传输,使用二进制编码
# 客户端
while True:
msg = input('请输入你要发送的信息>>>:').strip()
client.send(msg.encode('utf8')) # 给服务端发送信息
data = client.recv(1024) # 接收服务端回复消息
print(data.decode('utf8'))
代码优化
1、发送信息不能为空
统计长度然后进行判断(len)
2、苹果电脑 反复重启服务端可能会发生报错: address in use
解决方法:
# 在最上面放一个
from socket import SOL_SOCKET,SO_REUSEADDR
# 在bind上面放
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
当客户端异常断开的情况下 如何让服务端继续服务其他客人
"""
如果在windows客户端异常处理退出之后服务端就会直接报错,直接加上异常处理 break结束退出内循环 跳到外循环 接待另一个客人
处理方式: 异常处理 """
while True:
sock, addr = server.accept()
try:
data = sock.recv(1024) # 获取别人说的什么
print(data.decode('utf8')) # 解码别人说的啥
msg = input('请回复>>>:').strip()
sock.send(msg.encode('utf8')) # 回复别人的话,因为基于网络传输,使用二进制编码
except Exception:
break
"""
如果是mac或linux服务端,就会接收到一个空的信息
处理方式: len判断 """
while True:
sock, addr = server.accept()
try:
data = sock.recv(1024) # 获取别人说的什么
if data(len) == 0:
break
print(data.decode('utf8')) # 解码别人说的啥
msg = input('请回复>>>:').strip()
sock.send(msg.encode('utf8')) # 回复别人的话,因为基于网络传输,使用二进制编码
except Exception:
break
半连接池
listen(5)
py文件默认只能同时只能运行一次,如果想要单独分开运行多次:
Edit Configuration选择all
# 半连接池
设置最大等待人数 >>>: 节省资源,提升效率
黏包现象
我们在使用socket 模块进行数据交互时,服务端连续执行了3次recv, 客户端连续执行了 3次send
此时如果我们的recv 填写的1024字节 如果send 传输的字节不超过1024,那么我们只第一次recv就会得到3条信息合并成一条。
如果我们填写的字节大写跟我们传输的一样,那么就会一次性连续得到3条消息
此现象就称为黏包现象。
"黏包现象产生的原因"
1.不知道每次的数据到底多大
2.TCP也称为流式协议:数据像水流一样绵绵不绝没有间隔(TCP会针对数据量较小且发送间隔较短的多条数据一次性合并打包发送)
"解决方法:"
明确的得知即将获取的数据具体有多大。使用 "struct模块"
socket基本使用
bind() 绑定地址
listen() 半连接池
accept() 等待客户端链接
send() 发送消息
recv() 接收消息
connect() 链接服务端
黏包终极解决思路
# 发送方
1.先构造一个字典,里面放真实数据的信息
eg: 数据大小、名称、简介、、、
2、对字典做打包处理pack
3、发送固定长度的报头 (字典的长度)
4、发送真实的字典数据
5、发送真实的真正数据
# 接收方
1、先接收打包好固定长度的字典
2、解析出字典的真实长度unpack
3、接收字典数据
4、从字典数据中解析出各种信息
5、接收真实的数据
代码实操
服务端:
import socket
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 14334))
server.listen(5)
sock, addr = server.accept()
# 接收固定长度的报头
date_dict_head = sock.recv(4)
# 根据报头解析出字典数据的长度
data_dict_len = struct.unpack('i',date_dict_head)[0]
# 接收字典数据
data_dict_bytes = sock.recv(data_dict_len)
# 利用jason 模块 解码并反序列化
data_dict = json.loads(data_dict_bytes)
# 获取真实字典数据
print(data_dict)
total_size = data_dict.get('file_size')
recv_size = 0
with open(data_dict.get('file_name'),'wb') as f:
while recv_size < total_size:
data = sock.recv(1024)
f.write(data)
recv_size += len(data)
print(recv_size)
第二种获取真实数据的方法:
total_size = data_dict.get('file_size')
with open(data_dict.get('file_name'), 'wb') as f:
f.write(sock.recv(total_size))
客户端:
import socket
import os
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1',14334))
# 获取真实数据大小
file_size = os.path.getsize(r'D:\pythonProject\11.16\1.txt')
# 制作真实数据的字典数据
data_dict = {
'file_name': '我爱你.txt',
'file_size': file_size,
'file_desc': '很带劲',
'file_info': '我的心里话'
}
# 制作字典报头
data_dict_bytes = json.dumps(data_dict).encode('utf8')
# 获取字典报头长度 打包
data_dict_len = struct.pack('i',len(data_dict_bytes))
# 发送字典报头 (i模式报头固定长度是4)
client.send(data_dict_len)
# 发送字典
client.send(data_dict_bytes)
# 发送真实数据
with open(r'D:\pythonProject\11.16\1.txt','rb') as f:
for line in f:
client.send(line)
socket UDP协议
客户端
import socket
# 指定使用UDP协议, 不指定的话,默认为TCP协议
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8888))
msg, addr = server.recvfrom(1024) # recvfrom 是UDP协议的接收
print(msg.decode('utf8')) # 解析发送来的内容
print(addr) # 服务端的位置
server.sendto(b'wuhu, i am you father',addr)
# 接收到('127.0.0.1', 52376)发来的hello i am superman
服务端:
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
server_addr = ('127.0.0.1', 8888)
client.sendto(b'hello i am superman', server_addr) # sendto就是UDP协议的发送
msg, addr = client.recvfrom(1024)
print(msg.decode('utf8'))
print(addr)
# 接收到('127.0.0.1', 8888)发来的wuhu, i am you father
基于UDP实现简易版聊天室
# 服务端
import socket
server = socket.socket(type= socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8888))
while True:
msg, addr = server.recvfrom(1024)
print(addr)
print(msg.decode('utf8'))
back_msg = input('请回复信息>>>:').strip()
server.sendto(back_msg.encode('utf8'),addr)
# 客户端
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
server_addr = ('127.0.0.1', 8888)
while True:
msg = input('请输入要输入的内容>>>:').strip()
msg = '这是来自客户端1号发来的信息: %s'%msg
client.sendto(msg.encode('utf8'), server_addr)
msg, addr = client.recvfrom(1024)
print(msg.decode('utf8'), addr)
# 增加新的客户端时,代码不需要改变,只需要msg修改一些这是客户n发来的信息