网络并发通信
1.常见网络通信模型
1.循环服务器模型 :循环接收客户端请求,处理请求。同一时刻只能处理一个请求,处理完毕后再处理下一个。
优点:实现简单,占用资源少
缺点:无法同时处理多个客户端请求
适用情况:处理的任务可以很快完成,客户端无需长期占用服务端程序。udp比tcp更适合循环。
2.多进程/线程网络并发模型:每当一个客户端连接服务器,就创建一个新的进程/线程为该客户端服务,客户端退出时再销毁该进程/线程。
优点:能同时满足多个客户端长期占有服务端需求,可以处理各种请求。
缺点: 资源消耗较大
适用情况:客户端同时连接量较少,需要处理行为较复杂情况。
3**.IO并发模型:利用IO多路复用,异步IO等技术,同时处理多个客户端IO请求**。
优点 : 资源消耗少,能同时高效处理多个IO行为
缺点 : 只能处理并发产生的IO事件,无法处理cpu计算
适用情况:HTTP请求,网络传输等都是IO行为。
2.基于fork的多进程网络并发模型
"""
基于fork的多进程并发
步骤:
创建监听套接字
等待接收客户端请求
客户端连接创建新的进程处理客户端请求
原进程继续等待其他客户端连接
如果客户端退出,则销毁对应的进程
"""
import os
from socket import *
import signal
# 全局变量
HOST = "192.168.1.6"
POST = 8844
ADDR = (HOST, POST)
# 处理客户端请求函数
def handle(c, addr):
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.send(b"OK")
c.close()
# 创建监听套接字
s = socket()
# 端口立即重用 (在bind之前)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(ADDR)
s.listen(3)
print("Listen the port 8844")
# 处理僵尸进程
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
# 循环等待客户端链接
while True:
c, addr = s.accept()
print("Connect from", addr)
# 创建一个新的进程处理客户端请求
pid = os.fork()
if pid == 0:
# 处理客户端请求
# 处理客户端请求函数
handle(c, addr)
# 子进程处理完客户端请求则退出
os._exit(0)
else:
# 出错或者父进程都继续等待接受客户端链接
continue
# 服务端退出
s.close()
"""
tcp_client.py
tcp客户端演示: 重点代码
"""
from socket import *
# 服务器地址
server_addr = ("192.168.1.6", 8844)
# 创建tcp套接字
sockfd = socket() # 默认值就是tcp
# 连接服务器
sockfd.connect(server_addr)
# 发送接收消息
while True:
data = input(">>")
if not data:
break
sockfd.send(data.encode())
# 输入##表示退出
# if data == '##':
# break
data = sockfd.recv(1024)
print("From server:", data.decode())
sockfd.close()
2.基于threading的多线程网络并发
"""
创建监听套接字
循环接收客户端连接请求
当有新的客户端连接创建线程处理客户端请求
主线程继续等待其他客户端连接
当客户端退出,则对应分支线程退出
"""
from socket import *
from threading import Thread, Lock
HOST = "192.168.1.6"
POST = 8844
ADDR = (HOST, POST)
# 处理客户端请求函数
def use(c):
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.send(b"OK")
c.close()
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(ADDR)
s.listen(4)
print("Listen the port 8844")
while True:
c, addr = s.accept()
print("Connect from", addr)
t = Thread(target=use, args=(c,))
t.setDaemon(True)
t.start()
# t.join()
s.close()
"""
tcp_client.py
tcp客户端演示: 重点代码
"""
from socket import *
# 服务器地址
server_addr = ("192.168.1.6", 8844)
# 创建tcp套接字
sockfd = socket() # 默认值就是tcp
# 连接服务器
sockfd.connect(server_addr)
# 发送接收消息
while True:
data = input(">>")
if not data:
break
sockfd.send(data.encode())
# 输入##表示退出
# if data == '##':
# break
data = sockfd.recv(1024)
print("From server:", data.decode())
sockfd.close()
3.ftp 文件服务器
"""
FTP 文件处理
多线程并发和套接字练习
"""
from socket import *
from threading import Thread
import os
from time import sleep
# 全局变量
HOST = '0.0.0.0'
PORT = 8845
ADDR = (HOST, PORT)
# 文件库
FTP = "FTP/"
# 处理客户端请求 (自定义线程类)
class FTPServer(Thread):
def __init__(self, connfd):
self.connfd = connfd
super().__init__()
self.dict_func = {"E": self.f4, "L": self.f1, "P": self.f2, "G": self.f3}
# 循环接收请求,分发任务
def run(self):
while True:
data = self.connfd.recv(1024).decode() # 接收请求
print(data)
if not data or data == 'E':
# run函数结束对应线程结束
break
elif data == 'L':
self.list()
elif data[0] == 'G':
self.get_file(data.split(" ", 1)[-1])
elif data[0] == 'P':
self.put(data.split(" ", 1)[-1])
# 查看文件
def list(self):
# 判断文件库是否为空
file_list = os.listdir(FTP)
if not file_list:
self.connfd.send('文件库为空'.encode())
return
else:
self.connfd.send(b'OK')
sleep(0.1)
# 发送文件列表
# 讲文件列表以‘\n’拼接,进行添加消息边界以处理沾包
data = '\n'.join(file_list)
self.connfd.send(data.encode())
# 下载文件
def get_file(self, file_name):
file_path = FTP + file_name
try:
f = open(file_path, "rb")
except:
self.connfd.send("文件不存在".encode())
return
else:
self.connfd.send(b"OK")
sleep(0.1)
while True:
data = f.read(1024)
if not data:
sleep(0.1)
self.connfd.send(b"##")
break
self.connfd.send(data)
f.close()
# 处理上传
def put(self, filename):
if os.path.exists(FTP + filename):
self.connfd.send("文件已存在".encode())
return
else:
self.connfd.send(b'OK')
# 接收文件
# 循环接收文件写入本地
f = open(FTP + filename, 'wb')
while True:
data = self.connfd.recv(1024)
# 文件接收完毕的标志
if data == b'##':
break
f.write(data)
f.close()
# 框架结构,启动函数
def main():
# 创建监听套接字
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(ADDR)
s.listen(3)
print("Listen the port 8845")
# 循环等待客户端链接
while True:
c, addr = s.accept()
print("Connect from", addr)
# 创建线程处理客户端请求
t = FTPServer(c) # 通过自定义线程类创建线程
t.setDaemon(True) # 分支线程随主线程退出
t.start()
s.close()
if __name__ == '__main__':
main()
"""
ftp_client
【1】 分为服务端和客户端,要求可以有多个客户端同时操作。
【2】 客户端可以查看服务器文件库中有什么文件。
【3】 客户端可以从文件库中下载文件到本地。
【4】 客户端可以上传一个本地文件到文件库。
【5】 使用print在客户端打印命令输入提示,引导操作
"""
from socket import socket
import os, sys
from time import sleep
# 全局变量
ADDR = ("127.0.0.1", 8845)
FTP = "Use/"
class FTPClient:
def __init__(self, sockfd):
self.sockfd = sockfd
def quit(self):
self.sockfd.send(b'E')
self.sockfd.close()
sys.exit("谢谢使用!")
def list(self):
self.sockfd.send(b'L')
# 等待回复
data = self.sockfd.recv(128).decode()
if data == 'OK':
# 接收文件
data = self.sockfd.recv(4096)
print(data.decode())
else:
# 打印原因
print(data)
def get_file(self, file_name):
data = "G " + file_name
# 下载文件 G filename
self.sockfd.send(data.encode())
data = self.sockfd.recv(128).decode()
if data == "OK":
# 接收文件
f = open(FTP + file_name, "wb")
while True:
data = self.sockfd.recv(1024)
# print(data)
# 文件接收完毕的标志
if data == b"##":
print("OK")
break
f.write(data)
f.close()
# 上传文件
def put(self, filename):
try:
f = open(FTP + filename, 'rb')
except:
print("文件不存在")
return
data = "P " + filename
self.sockfd.send(data.encode()) # 发送请求
# 等待回复
data = self.sockfd.recv(128).decode()
if data == 'OK':
while True:
data = f.read(1024)
if not data:
sleep(0.1)
self.sockfd.send(b'##') # 结束标志
break
self.sockfd.send(data)
f.close()
else:
print(data)
# 启动函数
def main():
# 链接服务端
s = socket()
s.connect(ADDR)
# 实例化对象,用于调用类中的方法,实现与服务端的交互
ftp = FTPClient(s)
# 循环发送请求
while True:
print("===============命令选项===============")
print("*** list ***")
print("*** get file ***")
print("*** put file ***")
print("*** quit ***")
print("=====================================")
cmd = input("输入命令:")
# s.send(cmd.encode())
if cmd == 'quit':
ftp.quit()
elif cmd == 'list':
ftp.list()
elif cmd[:3] == 'get':
file_name = cmd.split(" ", 1)[-1]
ftp.get_file(file_name)
elif cmd[:3] == 'put':
file_name = cmd.split(" ", 1)[-1]
ftp.put(file_name)
else:
pass
if __name__ == '__main__':
main()
项目技术分析
FTP文件服务
-
技术点分析
- 并发模型 : 多线程并发
- 网络传输 : tcp网络
客户端请求流程:
1.客户端发起请求
2.服务端判断能否满足客户端请求,给出回复
3.根据回复,确定下一步操作 -
功能模块划分 (结构设计)
- 框架搭建
- 查看文件
- 下载文件
- 上传文件
封装: 核心功能封装为类
-
通信协议设定
请求类型 参量
查看文件 L
上传文件 P filename
下载文件 G filename
退出 E
-
分模块设计功能逻辑流程
-
框架搭建
-
查看文件
客户端: 发送请求
等待接收反馈结果
Y 接收文件列表
N 结束服务端: 接收请求 判断能否满足请求,将结果回复 (看文件库是否为空) Y 发送文件列表 N 结束
-
下载文件
客户端 : 发送请求 (请求包含文件名字)
等待接收反馈结果
Y 接收文件
N 结束服务端 : 接收请求,提取出文件名 判断能否满足请求,将结果回复 (看文件库中是否有这个文件) Y 发送文件 N 结束
-
上传文件
-
退出
-