任务

下载 任务具体内容.pdf (271kB)

分析

异常

为了让服务端可以正常的接收任何奇怪的请求并不会直接崩溃,我们来介绍 python 中的异常体系。

什么是异常

在 python 无法正常处理程序时就会发生一个异常。如果没有捕获处理它,程序会终止执行。

异常处理

在 python 中,异常处理由 try / except 语句组成。

最简单的一个示例如下:

1
2
3
4
5
6
try:
print(1/0)
except:
print("program failed!")
else:
print("program success! :)")

try 语句后缩进,在这部分执行自己的代码。如果出错了,会执行 except 语句的部分,否则会执行 else 的部分。

except 语句后可以接一个异常名,这样只会捕获特定异常。常见的异常名字如下表。

异常名称 描述
Exception 所有异常都是 Exception,即 except Exception: 可以捕获所有异常。
ZeroDivisionError 除(或取模)零 (所有数据类型)
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
NameError 未声明/初始化对象 (没有属性)
1
2
3
4
try:
print(1/0)
except IndexError:
print("index not found!")

这样写没有捕获 ZeroDivisionError 的异常,python 就报错了。

服务端流程

先分析任务,尝试画出一个流程图。

服务端代码

由此,可以写出代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import socket
import re
import os
import logging # 有关 logging 模块的使用,请自行百度

logging.basicConfig(format="[%(asctime)s]%(levelname)s:%(message)s", datefmt="%H:%M:%S", level=logging.DEBUG)

logging.info("Server start running. PID: {}".format(os.getpid()))

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_addr = ('127.0.0.1',11451)
server.bind(server_addr)
server.listen(5)
logging.info("listening on {}".format(server_addr))
logging.info("wait for client to connnect... (Ctrl+C is now useless)")
cli, addr = server.accept()
logging.info("get connection from {}".format(addr))

while True: # 进入消息循环
data = cli.recv(32*1024*1024) # 32M
logging.info("get data: {}".format(data[:200]))

if data[:3].decode()=='GET': # GET占三个字节
try: # NEW! try...except 语句
req_str = data.decode()
file_path = re.search(r"GET (.+)\n",req_str,re.I).group(1) # 忽略大小写
file_data = open(file_path,"rb").read()
# 组装响应
logging.info("GET OK")
response = "200\nContent-Length: {}\n\n".format(len(file_data))
cli.send(response.encode()+file_data)
except Exception as e:
logging.exception("Catch Exception")
response = "300"
cli.send(response.encode())
elif data[:3].decode()=='PUT': # PUT占三个字节
try:
# 先找到请求头的位置 O(n)
for pos in range(len(data)):
# 标头以 \n\n 结束
if chr(data[pos])==chr(data[pos+1])=='\n':
break
header = data[:pos].decode()
pattern = r"PUT (.+)\nContent-Length:\s*?([\d]+)"
search_obj = re.search(pattern,header,re.I) # 忽略大小写
upload_path = search_obj.group(1)
content_length = int(search_obj.group(2))
file_buffer = data[pos+2:] # 这里开始是消息体开始的地方
logging.debug("content-length = {}".format(content_length))
while len(file_buffer) < content_length:
file_blob = cli.recv(32*1024*1024) # 32M
file_buffer += file_blob
logging.debug("now recv: {}bytes ({:.1f}%)"
.format(len(file_buffer),len(file_buffer)/content_length*100))
open(upload_path,"wb").write(file_buffer)
response = "200"
logging.info("PUT OK")
cli.send(response.encode())

except Exception as e:
logging.exception("Catch Exception")
response = "300"
cli.send(response.encode())
else:
response = "300"
cli.send(response.encode())

运行效果

$ python server.py
[18:38:25]INFO:Server start running. PID: 58184
[18:38:25]INFO:listening on ('127.0.0.1', 11451)
[18:38:25]INFO:wait for client to connnect... (Ctrl+C is now useless)
[18:38:31]INFO:get connection from ('127.0.0.1', 9203)
[18:38:33]INFO:get data: b'GET C:\\llx\\game\\t\\2\\patch.xp3\n'
[18:38:33]ERROR:Catch Exception
Traceback (most recent call last):
  File "server.py", line 27, in 
    file_data = open(file_path,"rb").read()
FileNotFoundError: [Errno 2] No such file or directory: 'C:\\llx\\game\\t\\2\\patch.xp3'
[18:38:43]INFO:get data: b'PUT C:\\llx\\test.rmvb\nContent-Length: 139016208\n\n.RMF\x00\x00\x00\x12\x00\x01\x00\x00\x00\x00\x00\x00\x00\x07PROP\x00\x00\x002\x00\x00\x00\x18j\x00\x00\tb\x04\x00\x00>\x83\x00\x00\x02[\x00\x03p\xad\x00\x1b\x01a\x00\x00\x05t\x08E\xd5\xe8\x00\x00\x03\xe4\x00\x03\x00\tCONT\x00\x00\x00@\x00\x00\x00\x13japan.E01.720p-RMDX\x00\x0eYYets_\xc8\xcb\xc8\xcb\xd3\xb0\xca\xd3\x00\n\xd4\xad\xd3\xb0\xca\xd3\xb9\xab\xcb\xbe\x00\x03\x00\x00\x00MDPR\x00\x00\x00\x9f\x00\x00\x00\x00\x00\x01w\x00\x00\x01w\x00'
[18:38:43]DEBUG:content-length = 139016208
[18:38:43]DEBUG:now recv: 67108816bytes (48.3%)
[18:38:43]DEBUG:now recv: 100663248bytes (72.4%)
[18:38:43]DEBUG:now recv: 134217680bytes (96.5%)
[18:38:43]DEBUG:now recv: 139016208bytes (100.0%)
[18:38:44]INFO:PUT OK
[18:39:21]INFO:get data: b'GET C:\\llx\\mail.txt\n'
[18:39:21]INFO:GET OK

客户端流程

请自行实现客户端的代码。