网络开发架构
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)