一、
两层循环:一个是接收连接,一个是接收请求
逻辑:
- 实例化、绑定服务器和端口、监听
- 无限循环,保持接收状态
- 接收到请求后,解析请求,拿到路径
- 根据路径找到对应的视图函数,视图函数会返回响应
- 发送响应,断开连接
import socket
s = socket.socket()
host = '0.0.0.0'
port = 3000
s.bind((host, port))
s.listen()
while True:
log('before accept')
connection, address = s.accept()
print(connection)
print('address', address)
print('after accept')
request = b''
buffer_size = 1024
while True
r = connection().rev(buffer_size)
request += r
if len(r) < buffer_size:
break
print('ip and request, {}\n{}'.format(address, request.decode()))
response = b'HTTP/1.1 233 VERY OK\r\nMJ:mamengli\r\n\r\n<h1>Hello World</h1>'
connection().sendall(response)
connection.close()
- 实例化
socket
- 绑定
ip
和端口,并进行监听
0.0.0.0
表示任意ip
都可以访问此服务器,访问的ip
必须是3000
。 - 这是一个无限循环,表示一直接收客户端发过来的连接请求。
accept()
是一个阻断函数,当有客户端请求的时候,它会判断连接状态,然后往下走。如果没有请求连接,那么它会一直停留在这里。
这就是socket的第一步:建立连接 图片.png - 接收请求数据
这又是一个无限循环,表示一直接收客户端发过来的数据(每次接收1024),直到接收完。
这里request = b' '
,如果把b
去掉,那么: 图片.png 表明客户端通过网络发送过来的数据是bytes类型,通过b‘ ’
将其转换为str
。
request
与request.decode()
的区别:
- 发送响应
利用sendall()
函数发送请求,这里的放松给网络传播的数据必须是bytes
类型。发送完数据后,关闭连接。
小知识点:
- 字符串的
format()
方法,实现字符串的格式化,使用方式:
'name:{}, age:{}'.format('majun', 27)
替代了%s %
。 -
break
:表示跳出当前循环(最底层的),还可以继续执 行外层循环,如果没有,那么循环结束。
continue
:跳出循环,继续执行循环。 - 数据类型的转换
在客户端,数据在发送之前需要encode()
,也就是将字符串转换为bytes
类型。
在服务器端,接收数据的格式也必须是bytes
类型,不然格式不对称,所以有个b
。
服务器端响应的时候,加上了一个b
,表示发送的是字节类型。
由此可知,encode()
放和b
的作用一样。
由上图(类型转换.png)可以看到,request
经过decode()
后,变成了字符串类型。注意,第一个request
前面有个b
。
可见:
encode()
将字符串转换为bytes
decode()
将bytes
转换为str
;
二、
import socket
def log(*args, **kwargs):
print('log', *args, **kwargs)
def route_index():
header = 'HTTP/1.1 233 OK\r\nContent-Type: text/html\r\n'
body = '<h1>Hello World</h1><img src="doge.gif"/>'
r = '{}\r\n{}'.format(header, body)
return r.encode()
def html_content(path):
with open(path, encoding='utf-8') as f:
return f.read()
def route_message():
# 主页的处理函数, 返回主页的响应
header = 'HTTP/1.1 233 OK\r\nContent-Type: text/html\r\n'
body = html_content('html_basic.html')
r = '{}\r\n{}'.format(header, body)
return r.encode()
def route_image():
# 图片的处理函数, 读取图片并生成响应返回,二进制方式读取
with open('doge.gif', 'rb') as f:
header = b'HTTP/1.1 200 OK\r\nContent-Type: image/gif\r\n\r\n'
// 拼接的时候,数据类型要一直
image = header + f.read()
return image
def error(code=404):
# 根据 code 返回不同的错误响应,目前只有 404
e = {
404: b'HTTP/1.1 404 NOT FOUND\r\nContent-Type: text/html\r\n\r\n<h1>NOT FOUND</h1>',
}
return e.get(code, b'')
// 表驱动法
def response_for_path(path):
# 根据 path 调用相应的处理函数,没有处理的 path 会返回 404
# 函数当做参数传递,不需要括号
r = {
'/': route_index,
'/message': route_message,
'/doge.gif': route_image,
}
response = r.get(path, error)
# 调用函数的时候,需要加()
return response()
def run(host, port):
# with 可以保证程序中断的时候正确关闭 socket 释放占用的端口
with socket.socket() as s:
s.bind((host, port))
s.listen()
# server 用 connection 来接收请求和发送响应
while True:
# accept 为阻断函数
connection, address = s.accept()
# 这里只读取了 1024 字节的内容, 应该用一个循环全部读取
request = connection.recv(1000)
request = request.decode()
# 因为 chrome 会发送空请求导致 split 得到空 list
# 所以这里先判断一下 split 得到的数据的长度
parts = request.split()
log('parts', parts)
if len(parts) > 0:
path = parts[1]
# 用 response_for_path 函数来得到 path 对应的响应内容
response = response_for_path(path)
# 把响应发送给客户端
connection.sendall(response)
else:
log("接收到了一个空请求")
connection.close()
if __name__ == '__main__':
config = dict(
host='0.0.0.0',
port=2000,
)
# 等价于:run(host = '0.0.0.0',port = 3000)
run(**config)
函数当做参数传递的时候不需要写括号和参数,有括号的时候表示 return
的结果
-
GET / POST
端口直接和host
在一起;
POST方法提交的数据储存在body中;
GRT提交的数据在请求头;
POST / HTTP/1.1
Host: localhost:3000
Connection: keep-alive
author=gua&message=hello
GET /?message=&author=gua HTTP/1.1
Host: localhost:3000
Connection: keep-alive
2. with open
读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。
3. 读文件
// 先打开文件,with自动调用close()
方法,出错时,自动关闭
// mode
为读文件的模式,r
表示读
with open('/path/filename', 'mode') as f:
// 接下来就是读文件,`read()`方法一次性读取所有文件
// `python` 把文件读到内存,用一个`str`对象(字符串)表示
f.read()
如果文件很小,一次性读取很方便,如果文件很多,而且是配置文件的时候,就要用到readlines()
方法了:
for line in f.readlines()
// `strip()` 方法可以去掉空格和行末的 `\r\n`
print(line.strip())
4. 写文件
写文件和上面一样,只不过 r
改为 w
,f.read()
改为 f.write()
rb
或 wb
表示以二进制方法读写;
open
函数的其他参数:
要读取非UTF-8
编码的文件时,比如:gbk
,传入参数 encoding = 'gbk'
,如果文件中有一些非法编码的字符:errors = 'ignore'
可以解决;
5. 异常处理:try except slse:
try的工作原理是,当开始一个try语句后,python就在当前程序的上下文中作标记,这样当异常出现时就可以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常。
try语句将可能发生错误的代码包住,except用来捕获被包住代码可能发生的异常,如果异常匹配,则执行except里面的代码,如果没有异常,执行完try里面后的代码后,再执行else里面的代码;
client 是用 s 来操作:s = socket.socket()
s.connect((host, port)) s.send(request) s.recv(response)
server 是用 connection 来操作
connection, address = s.accept()
connection.recv(request) connection.sendall(response)
connection.close()
网友评论