并发网络通信模型
常见网络模型
- 循环服务器模型:循环接受客户端请求,处理请求.同一时刻只能处理一个请求,处理完毕后在处理下一个
优点:实现简单,占用资源少
缺点:无法同时处理多个客户端请求
适用情况:处理的任务可以很快完成,客户端无需长期占用服务端程序.UDP比TCP更适合循环
- 多进程/线程网络并发模型:每当一个客户端连接服务器,就创建一个新的进程/线程为该客户端服务,客户端退出时在销毁该进程/线程
优点:能同时满足多个客户端长期占有服务端需求,可以处理各种请求
缺点:资源消耗较大
适用情况:客户端同时连接量较少,需要处理行为较复杂情况
- IO并发模型:利用IO多路复用,异步IO等技术,同时处理多个客户端IO请求
优点:资源消耗少,能同时高效处理多个IO行为
缺点:只能处理并发产生的IO事件,无法处理CPU计算
适用情况:HTTP请求,网络传输等都是IO行为
基于fork的多进程网络并发模型
实现步骤
- 创建监听套接字
- 等待接受客户端请求
- 客户端连接创建新的进程处理客户端请求
- 原进程继续等待其他客户端连接
- 如果客户端退出,则销毁对应的进程
fork代码示例:

1 from socket import *
2 import os
3 import signal
4
5 ADDR = ('127.0.0.1',8080)
6
7 def han(c):
8 while True:
9 data = c.recv(1024).decode()
10 if not data:
11 break
12 print(data)
13 c.send(b'OK')
14 #创建套接字监听
15 s = socket()
16 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
17 s.bind(ADDR)
18 s.listen(5)
19
20 signal.signal(signal.SIGCHLD,signal.SIG_IGN)
21 print("端口连接 8080...")
22
23 while True:
24 #循环等待客户端连接
25 try:
26 c,addr = s.accept()
27 print("连接中...",addr)
28 except KeyboardInterrupt:
29 os._exit(0)
30 except Exception as e:
31 print(e)
32 continue
33
34 #创建新进程
35 pid = os.fork()
36 if pid == 0:
37 #子进程处理具体的客户端
38 s.close()
39 han(c) #具体处理请求
40 os._exit(0) #子进程处理请求后销毁
41
42 else:
43 pass
基于threading的多线程网络并发
实现步骤
- 创建监听套接字
- 等待接收客户端请求
- 客户端连接创建新的线程处理客户端请求
- 主线程继续等待其他客户端连接
- 如果客户端退出,则对应分支线程退出
threading代码示例:

1 from socket import *
2 from threading import Thread
3 import os
4
5 ADDR = ('0.0.0.0',8888)
6
7 # 客户端处理函数,循环收发消息
8 def handle(c):
9 while True:
10 data = c.recv(1024).decode()
11 if not data:
12 break
13 print(data)
14 c.send(b'OK')
15
16 # 创建监听套接字
17 s = socket()
18 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
19 s.bind(ADDR)
20 s.listen(5)
21
22 print("Listen the port 8888....")
23
24 while True:
25 # 循环等待客户端连接
26 try:
27 c,addr = s.accept()
28 print("Connect from",addr)
29 except KeyboardInterrupt:
30 os._exit(0)
31 except Exception as e:
32 print(e)
33 continue
34
35 # 创建新的线程处理请求
36 client = Thread(target=handle,args=(c,))
37 client.setDaemon(True)
38 client.start()
ftp文件服务器
小练习
功能
- 分为服务端和客户端,要求可以有多个客户端同时操作
- 客户端可以查看服务器文件库中有什么文件
- 客户端可以从文件库中下载文件到本地
- 客户端可以上传一个本地文件到文件库
- 使用print在客户端打印命令输入提示,引导操作
ftp服务端代码示例:

1 from socket import *
2 from threading import Thread
3 import os,sys
4 import time
5
6 # 全局变量
7 HOST = '0.0.0.0'
8 PORT = 8080
9 ADDR = (HOST,PORT)
10 FTP = "/home/tarena/FTP/" # 文件库路径
11
12 # 功能类 (线程类)
13 # 查文档, 下载,上传
14 class FTPServer(Thread):
15 def __init__(self,connfd):
16 super().__init__()
17 self.connfd = connfd
18
19 # 处理文件列表
20 def do_list(self):
21 # 获取文件列表
22 files = os.listdir(FTP)
23 if not files:
24 self.connfd.send("文件库为空".encode())
25 return
26 else:
27 self.connfd.send(b'OK')
28 time.sleep(0.1)
29 # 拼接文件
30 filelist = ''
31 for file in files:
32 filelist += file + '\n'
33 self.connfd.send(filelist.encode())
34
35 def do_get(self,filename):
36 try:
37 f = open(FTP+filename,'rb')
38 except Exception:
39 # 文件不存在
40 self.connfd.send('文件不存在'.encode())
41 return
42 else:
43 self.connfd.send(b'OK')
44 time.sleep(0.1)
45
46 # 发送文件
47 while True:
48 data = f.read(1024)
49 if not data:
50 time.sleep(0.1)
51 self.connfd.send(b'##')
52 break
53 self.connfd.send(data)
54
55 def do_put(self,filename):
56 if os.path.exists(FTP+filename):
57 self.connfd.send("文件已存在".encode())
58 return
59 else:
60 self.connfd.send(b'OK')
61 # 接收文件
62 f = open(FTP + filename,'wb')
63 while True:
64 data = self.connfd.recv(1024)
65 if data == b'##':
66 break
67 f.write(data)
68 f.close()
69
70 # 循环接受来自客户端的请求
71 def run(self):
72 while True:
73 request=self.connfd.recv(1024).decode()
74 if not request or request == 'Q':
75 return # 线程退出
76 elif request == 'L':
77 self.do_list()
78 elif request[0] == 'G':
79 filename = request.split(' ')[-1]
80 self.do_get(filename)
81 elif request[0] == 'P':
82 filename = request.split(' ')[-1]
83 self.do_put(filename)
84
85 # 启动函数
86 def main():
87 # 创建监听套接字
88 s = socket()
89 s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
90 s.bind(ADDR)
91 s.listen(5)
92
93 print("Listen the port 8080....")
94
95 while True:
96 # 循环等待客户端连接
97 try:
98 c, addr = s.accept()
99 print("Connect from", addr)
100 except KeyboardInterrupt:
101 os._exit(0)
102 except Exception as e:
103 print(e)
104 continue
105
106 # 创建新的线程处理请求
107 client = FTPServer(c)
108 client.setDaemon(True)
109 client.start()
110
111 main()
ftp客户端代码示例:

1 import time
2 from socket import *
3 import sys
4
5 # 服务器地址
6 ADDR = ('127.0.0.1',8080)
7
8 # 文件处理类
9 class FTPClient:
10 # 所有函数都使用sockfd,所以把它变为属性变量
11 def __init__(self,sockfd):
12 self.sockfd = sockfd
13
14 def do_list(self):
15 self.sockfd.send(b'L') # 发送请求
16 # 等待回复 (服务端能否满足请求)
17 data = self.sockfd.recv(128).decode()
18 if data == 'OK':
19 # 一次性接收所有文件
20 data = self.sockfd.recv(4096)
21 print(data.decode())
22 else:
23 print(data)
24
25 def do_quit(self):
26 self.sockfd.send(b'Q') # 退出请求
27 self.sockfd.close()
28 sys.exit("谢谢使用")
29
30 def do_get(self,filename):
31 # 发送请求
32 self.sockfd.send(('G '+filename).encode())
33 # 等待回复
34 data = self.sockfd.recv(128).decode()
35 if data == 'OK':
36 f = open(filename,'wb')
37 # 循环接收内容,写入文件
38 while True:
39 data = self.sockfd.recv(1024)
40 if data == b'##': # 发送完成
41 break
42 f.write(data)
43 f.close()
44 else:
45 print(data)
46
47 def do_put(self,filename):
48 try:
49 f = open(filename,'rb')
50 except Exception as e:
51 print("该文件不存在")
52 return
53 # 发送请求
54 filename = filename.split('/')[-1]
55 self.sockfd.send(('P '+filename).encode())
56 # 等待反馈
57 data = self.sockfd.recv(128).decode()
58 if data == 'OK':
59 while True:
60 data = f.read(1024)
61 if not data:
62 time.sleep(0.1)
63 self.sockfd.send(b'##')
64 break
65 self.sockfd.send(data)
66 f.close()
67 else:
68 print(data)
69
70 # 启动函数
71 def main():
72 sockfd = socket()
73 try:
74 sockfd.connect(ADDR)
75 except Exception as e:
76 print(e)
77 return
78
79 ftp = FTPClient(sockfd) # 实例化对象,用于调用功能
80 # 循环发送请求给服务器
81 while True:
82 print("""\n
83 =========Command============
84 **** list ****
85 **** get file ****
86 **** put file ****
87 **** quit ****
88 ============================
89 """)
90 cmd = input("输入命令:")
91 if cmd.strip() == 'list':
92 ftp.do_list()
93 elif cmd.strip() == 'quit':
94 ftp.do_quit()
95 elif cmd[:3] == 'get':
96 filename = cmd.split(' ')[-1]
97 ftp.do_get(filename)
98 elif cmd[:3] == 'put':
99 filename = cmd.split(' ')[-1]
100 ftp.do_put(filename)
101 else:
102 print("请输入正确命令")
