网络开发架构
1.C/S架构
即:Client与Server ,中文意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的。
这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。

2.B/S架构
B/S即:Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。
Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的资源(网页资源),客户端Browser浏览器就能进行增删改查。

SOCKET 概念
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
1.SOCKET 套接字的发展历史
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
基于网络类型的套接字家族
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
TCP协议和UDP协议
TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

半连接数
三次握手没有完成 称之为半连接
原因1 恶意客户端没有返回第三次握手信息
原因2 服务器没空及时处理你的请求
socket中 listen(半连接最大数量)
粘包问题
TCP流式协议, 数据之间没有分界, 就像水 一杯水和一杯牛奶倒在一起了!
UDP 用户数据报协议
粘包 仅发生在TCP协议中
- 发送端 发送的数据量小 并且间隔短 会粘
- 接收端 一次性读取了两次数据的内容 会粘
- 接收端 没有接收完整 剩余的内容 和下次发送的粘在一起
无论是那种情况,其根本原因在于 接收端不知道数据到底有多少
解决方案就是 提前告知接收方 数据的长度
粘包解决方案
先发长度给对方 再发真实数据
#发送端
1.使用struct 将真实数据的长度转为固定的字节数据
2.发送长度数据
3.发送真实数据
接收端
1.先收长度数据 字节数固定
2.再收真实数据 真实可能很长 需要循环接收
发送端和接收端必须都处理粘包 才算真正的解决了
自定义报头
当需要在传输数据
发送端
1.发送报头长度
- 发送报头数据
- 发送文件内容
接收端
接收报头长度
接收报头信息
接收文件内容
基于TCP的远程CMD服务器
服务器端
import socket
import subprocess
import struct
import smallTool
# 生成基于TCP的套接字
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 套接字对象绑定
server.bind(("127.0.0.1",8888))
# 设置套接字对象的监听器
server.listen(5)
# 双层循环
while True:
client,ip_port = server.accept()
print("客户端连接成功!!!")
while True:
try:
# 接收客户端传入的cmd命令
cmd = smallTool.recv_data(client)
if not cmd:
break
print("客户端发起命令>>> %s " %cmd)
# 将二进制的cmd数据转成字符串形式
p = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# 不要先读取管道内的错误信息,会卡住!!
cmd_data = p.stdout.read()
cmd_err = p.stderr.read()
# 服务器产生的信息
len_size = len(cmd_data) + len(cmd_err)
print("服务器产生并且即将打印 %s 字节的数据!!!"%len_size)
# 将即将发送的信息长度 提前告知 客户端,做 struct二进制转化
len_bytes = struct.pack('q',len_size)
client.send(len_bytes)
client.send(cmd_data + cmd_err)
except ConnectionAbortedError as e:
print("客户端断开了连接!!!",e)
break
client.close()
客户端
'''
客户端输入指令
服务器端接收指令 最后返回执行结果
'''
import socket
import struct
import smallTool
client = socket.socket()
try:
client.connect(('127.0.0.1',8888))
print('成功建立连接!!!!')
while True:
msg = input("请输入要执行的指令>>>>>>").strip()
# 输入q 则退出
if msg == 'q': break
# 空消息则重新输入
if not msg : continue
# 1. 发送指令的长度 struct将指令的长度(utf-8)转换成网络字节序,并发送
len_bytes = struct.pack('q',len(msg.encode("utf-8")))
client.send(len_bytes)
# 2. 发送指令
client.send(msg.encode("utf-8"))
# 调用工具模块的接收数据方法
data = smallTool.recv_data(client)
print(data.decode("GBK"))
client.close()
# 抛出异常
except ConnectionAbortedError as e:
print("服务器连接失败了!!!",e)
except ConnectionResetError as e:
print("服务器挂了!", e)
client.close()
except Exception as e:
raise e
模块
import struct
# 定义一个方法实现从socket对象拿到其所拥有的数据,
# 此数据经过struct模块处理,是一个元组信息,需要根据索引提取值
def recv_data(client):
# 建立一个表示接收对端全部原始数据长度的变量,8个字节 足矣
client_info_bytes = client.recv(8)
# 转换为整形
SERVSIZ = struct.unpack("q",client_info_bytes)[0]
print("服务器返回了 %s 长度的数据"% SERVSIZ)
# 以上操作完成之后再进行真实数据的接收
# 循环分片式接收
# 定义socket缓冲区大小
BUFSIZ = 1024
# 已接收的临时文件大小
RECVSIZ = 0
# 最终数据的初始状态
data = b''
# 循环接收数据
while True:
# 如果剩余没有接收到的数据长度 大于 缓存区的大小
# 则以缓存区大小接收尚未发送完成的数据(大段大段接收)
if SERVSIZ - RECVSIZ >= BUFSIZ:
temp_data = client.recv(BUFSIZ)
else:
# 剩余一小部分未接收的数据 长度大小小于缓冲区大小 全部接收
temp_data = client.recv(SERVSIZ - RECVSIZ)
# 已经接收的数据长度 一直在变化
RECVSIZ += len(temp_data)
# 最终数据也在一直变化
data += temp_data
# 跳出循环读取条件:已经接收的数据的长度与远端数据长度一致
if RECVSIZ == SERVSIZ:
break
# 返回最终接收到的完整的数据
return data
网络文件下载服务器
服务器端
import socket
import os
import struct
import json
# 创建server的socket对象
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8888))
server.listen(5)
while True:
client, ip_port = server.accept()
print("客户端连接服务器成功!!!")
# 文件对象的初始化
f = None
while True:
try:
# 提供待下载文件的绝对路径
file_path = r"G:\PythonWorkStation\文件传输\半链接数.mp4"
# 获取当前文件的大小
file_size = os.path.getsize(file_path)
# 获取当前文件的名字 并且文件的大小与其组合 生成一个信息字典
file_info_dic = {"file_name": "半链接数.mp4", "file_size": file_size}
# 文件信息字典转换成json格式用于将其作为包头信息发送
file_info_dic_json = json.dumps(file_info_dic).encode("utf-8")
# 发送报头长度,以告知客户端完整接收报头
client.send(struct.pack('q', len(file_info_dic_json)))
# 真正发送报头阶段
client.send(file_info_dic_json)
# 进入正题!!!发送文件(读取发送)
f = open(file_path, "rb")
# 进入循环
while True:
# 文件对象读取比特位
temp = f.read(2048)
if not temp:
break
# 读完立即发送出去!
client.send(temp)
print("文件发送完毕!!!")
except Exception as e:
print("发现错误: %s" %e)
finally:
if f:f.close()
# 关闭之后直接关闭套接字
client.close()
客户端
import socket
import struct
import json
client = socket.socket()
try:
client.connect(("127.0.0.1",8888))
print("连接服务器成功!!!")
# !!!接收文件内容阶段!!!
# 1.先接收表示文件信息字典长度的报头 8个字节可接收到很多信息
head_size = struct.unpack("q",client.recv(8))[0]
# 2.以接收到的8字节报头长度信息去接收报头数据
# 以接收到的长度报头读取,此时还是json格式的文件信息字典
head_str = client.recv(head_size).decode("utf-8")
# json数据转换为python数据类型
file_info_dic = json.loads(head_str)
# 测试打印报头数据
print("报头信息: ",file_info_dic)
# 文件大小
file_size = file_info_dic.get("file_size")
# 文件名
file_name = file_info_dic.get("file_name")
# !!!接收文件内容阶段!!!
# 接收到的临时文件的大小值(动态变化)
recv_size = 0
# 缓冲区大小
BUFSIZ = 2048
# 获取写文件对象f
f = open(file_name,"wb")
# 进入循环
while True:
if file_size - recv_size >= BUFSIZ:
temp = client.recv(BUFSIZ)
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 ConnectionAbortedError as e:
print("连接服务器失败!!!",e)
















