1.socket
套接字,本质上是一个模块,里面封装了一些网络通讯协议
是处于传输层和应用层之间的一个抽象层,实际在OSI中并不存在
也就是没有socket也能能够通讯 ,但是这样一来 我们必须完全按照OSI规定的各种协议来编码
这是一个重复,复杂的过程,为了提高开发效率,就出现了socket模块,专门帮我们封装了传输层以下的一堆协议
有socket模块之后 当我们需要编写网络通讯的程序时,就不需要在关心具体的协议细节,直接使用socket提供的功能接口即可
门面设计模式,隐藏复杂的内部细节,仅提供最简单的使用接口 本质就是封装
2.TCP通讯
网络通讯一定分为两端,服务器和客户端
服务器:
1.创建socket对象 server = socket.socket()
2.绑定一个固定的ip和端口 server.bind ip必须是本机ip 端口1024-65535 不要使用常见的端口 web:80 / 8080 mysql 3306 ssh:22 ftp:21
3.开始监听客户端的到来 server.listen
4.接收客户端的链接请求 server.accept # 阻塞直到客户链接到来 没有新连接则不可能执行该函数
5.收发数据 需要循环
recv 收 收多少字节数
send 发 只能发二进制数据
客户端:
1.创建socket对象 client = socket.socket()
2.链接服务器 client.connect((ip,port))
3.收发数据 通常需要循环
send 发 只能发二进制数据
recv 收 收多少字节数
4.断开链接 client.close()
3.常见的异常
1.一方异常下线,另一方还要收发数据 ,ConnectionResetError
在send recv的地方 加上try 客户端也需要异常处理
2.端口占用异常
重复运行服务器
之前的进程没有正确关闭
关闭和打开端口 都是操作系统负责 在一些极端情况下 可能应用程序已经正确接收并且通知了操作系统要关闭端口
但是操作系统 没及时处理
2.1 更换端口
2.2 查后台进程 杀掉它
2.3 重启服务器电脑
4.循环通讯
一.半连接数:
三次握手没有完成 称之为半连接
原因1 恶意客户端没有返回第三次握手信息
原因2 服务器没空及时处理你的请求
socket中 listen(半连接最大数量)
二.粘包问题
TCP流式协议, 数据之间没有分界, 就像水 一杯水和一杯牛奶倒在一起了!
UDP 用户数据报协议
粘包 仅发生在TCP协议中
-
发送端 发送的数据量小 并且间隔短 会粘
-
接收端 一次性读取了两次数据的内容 会粘
-
接收端 没有接收完整 剩余的内容 和下次发送的粘在一起
无论是那种情况,其根本原因在于 接收端不知道数据到底有多少
解决方案就是 提前告知接收方 数据的长度
解决方案
先发长度给对方 再发真实数据
#发送端
1.使用struct 将真实数据的长度转为固定的字节数据
2.发送长度数据
3.发送真实数据
接收端
1.先收长度数据 字节数固定
2.再收真实数据 真实可能很长 需要循环接收
发送端和接收端必须都处理粘包 才算真正的解决了
案例: 远程CMD程序
# ==================================================客户端
import socket
from 二_CMD程序 import smallTool
import struct
client = socket.socket()
try:
client.connect(("127.0.0.1",1688))
print("链接成功!")
while True:
msg = input("请输入要执行指令:").strip()
if msg == "q": break
if not msg: continue
# 发送指令
# 先发长度
len_bytes = struct.pack("q",len(msg.encode("utf-8")))
client.send(len_bytes)
# 在发指令
client.send(msg.encode("utf-8"))
data = smallTool.recv_data(client)
print(data.decode("GBK"))
client.close()
except ConnectionRefusedError as e:
print("链接服务器失败了!",e)
except ConnectionResetError as e:
print("服务器挂了!", e)
client.close()
服务器
import socket
import subprocess
import struct
from 二_CMD程序 import smallTool
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(("127.0.0.1",1688))
server.listen()
# back
while True:
# socket,addr一个元组 客户端的ip和port
client,addr = server.accept()
print("客户端链接成功!")
# 循环收发数据
while True:
try:
cmd = smallTool.recv_data(client)
if not cmd:
break
print(cmd)
p = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# 不要先读err错误信息 它会卡主 原因不详 linux不会有问题 tasklist netstat - ano啥的
data = p.stdout.read()
err_data = p.stderr.read()
len_size = len(data) + len(err_data)
print("服务器返回了: %s " % len_size)
len_bytes = struct.pack("q",len_size)
# 在发送真实数据前先发送 长度
client.send(len_bytes)
# 返回的结果刚好就是二进制
# 发送真实数据
client.send(data + err_data)
except ConnectionResetError as e:
print("客户端了挂了!",e)
break
client.close()
#server.close()
自定义报头
当需要在传输数据时 传呼一些额外参数时就需要自定义报头
报头本质是一个json 数据
具体过程如下:
发送端
1 发送报头长度
2 发送报头数据 其中包含了文件长度 和其他任意的额外信息
3 发送文件内容
接收端
1.接收报头长度
2.接收报头信息
3.接收文件内容
案例:
服务器
import socket
import os
import struct
import json
"""
客户端接链成功我就给你发个文件过去
固定的文件下载
"""
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(("127.0.0.1",1688))
server.listen()
# back
while True:
# socket,addr一个元组 客户端的ip和port
client,addr = server.accept()
print("客户端链接成功!")
f = None
try:
path = r"F:\2.半链接数.mp4"
file_size = os.path.getsize(path)
# 我想把文件名发过去
file_info = {"file_name":"半链接数.mp4","file_size":file_size,"md5":"xxxxxxxxx"}
json_str = json.dumps(file_info).encode("utf-8")
# 发送报头长度
client.send(struct.pack("q",len(json_str)))
# 发报头
client.send(json_str)
# 发文件了
# 发送文件数据
f = open(path,"rb")
# 循环发送文件内容 每次发2048
while True:
temp = f.read(2048)
if not temp:
break
client.send(temp)
print("文件发送完毕!")
except Exception as e:
print("出问题了",e)
finally:
if f:f.close()
client.close()
# 无论是否抛出异常 文件都要关闭
#server.close()
# 用户可以指定要下载什么文件 FTP
客户端
"""
客户端输入指令
服务器接收指令并执行 最后返回执行结果
"""
import socket
import struct
import json
client = socket.socket()
try:
client.connect(("127.0.0.1",1688))
print("链接成功!")
# 1.先收报头长度
head_size = struct.unpack("q",client.recv(8))[0]
# 2.收报头数据
head_str = client.recv(head_size).decode("utf-8")
file_info = json.loads(head_str)
print("报头数据:",file_info)
file_size = file_info.get("file_size")
file_name = file_info.get("file_name")
# 3.再收文件内容
# 已接收大小
recv_size = 0
buffer_size = 2048
f = open(file_name,"wb")
while True:
if file_size - recv_size >= buffer_size:
temp = client.recv(buffer_size)
else:
temp = client.recv(file_size - recv_size)
f.write(temp)
recv_size += len(temp)
print("已下载:%s%%" % (recv_size / file_size * 100))
if recv_size == file_size:
break
f.close()
except ConnectionRefusedError as e:
print("链接服务器失败了!",e)