一、网络编程入门
网络将具有独立功能的多台计算机通过通信线路和通信设备连接起来,在网络管理软件及通信协议下实现资源共享和信息传递。
1.IP地址
IP地址是分配给网络设备上网使用使用的数字标签,能够唯一标识网络中的某台设备并和其实现通信。
1)IPV4地址
32位二进制数,以4位的点分十进制表示的。
点分十进制(Dotted Decimal Notation)全称为点分(点式)十进制表示法,是IPv4的IP地址标识方法。IPv4中用四个字节表示一个IP地址,每个字节按照十进制表示为0255。点分十进制就是用4组从0255的数字,来表示一个IP地址。如192.168.1.1
2)IPV6地址
IPv6的地址长度为128位,是IPv4地址长度的4倍。于是IPv4点分十进制格式不再适用,采用十六进制表示。IPv6有3种表示方法。
(1)冒分十六进制表示法
格式为X:X:X:X:X:X:X:X,其中每个X表示地址中的16b,以十六进制表示,例如:
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
这种表示法中,每个X的前导0是可以省略的,例如:
2001:0DB8:0000:0023:0008:0800:200C:417A→ 2001:DB8:0:23:8:800:200C:417A
(2)0位压缩表示法
在某些情况下,一个IPv6地址中间可能包含很长的一段0,可以把连续的一段0压缩为“::”。但为保证地址解析的唯一性,地址中”::”只能出现一次,例如:
FF01:0:0:0:0:0:0:1101 → FF01::1101
0:0:0:0:0:0:0:1 → ::1
0:0:0:0:0:0:0:0 → ::
(3)内嵌IPv4地址表示法
为了实现IPv4-IPv6互通,IPv4地址会嵌入IPv6地址中,此时地址常表示为:X:X:X:X:X:X:d.d.d.d,前96b采用冒分十六进制表示,而最后32b地址则使用IPv4的点分十进制表示,例如::192.168.0.1与::FFFF:192.168.0.1就是两个典型的例子,注意在前96b中,压缩0位的方法依旧适用[9]。
3)内网ip
ip地址里面预留的位置,常用地址如下:
192.168.0.0 ~ 192.168.255.255
10.0.0.0 ~ 10.255.255.255
172.16.0.0 ~ 172.16.255.255
4)公网ip
一个ip地址你想要让所有人都能访问到,这种就是要花钱的,常说的租用服务器。
5)本地环回地址
127.0.0.1 只要本机上运行的网络程序和本机通信就可以使用这个地址
2.端口
1)端口
电脑上每运行一个程序都会有对应的端口,想要发送数据,找到对应的端口就可以了,
端口:数据传输的通道,好比学校的教室门,是数据传输的必经之路。
2)端口号
端口号:操作系统为了方便管理端口,对端口进行了编号,这就是端口号,实际上端口号就是一个数字,端口号共有 65536个。
目前最终通信流程就是通过ip地址找到对应的设备,通过端口号找到对应的端口,然后通过端口找到对应的程序然后发送数据。
- 知名端口号:
- 知名端口号就是指众所周知的端口号,范围 0-1023,这些端口号是固定分配固定的服务,比如说21端口号是ftp,文件传输协议,80端口号分配给http等等。
- 动态端口号:
- 一般程序员开发应用程序之后就会有一个默认的端口号,当这个程序退出的时候,所占用的这个端口号就会被释放。
3.socket
使用socket套接字来完成网络编程
4.tcp
tcp简称传输控制协议,它是面向连接,可靠的,基于字节流的传输层的通信协议。
- 面向连接:通信双方必须先建立连接才能传输数据,并且双方都会为此次连接准备必不可少的资源用来记录连接时的状态和信息,当数据传输完成之后,双方必须断开连接,用来释放系统资源。
- 可靠传输
- tcp采用发送应答机制
- 超时重传
- 错误校验
- 流量控制和阻塞管理
二、网络开发的框架
1.常见软件架构
- CS:(Clinet & Server) 服务端和客户端的架构,这个架构是从用户层面进行划分的,通过这个架构开发出来的东西对用户的系统环境依赖比较大。
- (微信 , 抖音 , QQ , pycharm……)
- BS:(Browser & Server) 浏览器和服务端架构 , 这个模式下用户只需要通过浏览器发送http协议请求就可以获取到对应的资源
- BS的本质也是CS架构 , BS中浏览器充当了客户端。
- (百度 ,博客园 , 力扣 , CSDN , B站…… )
2.网络通信
socket服务端:
1、创建socket对象
2、绑定IP与端口号(1024 ~ 65535)
3、设置监听,最大链接数
4、等待客户端的链接
6、数据的接收与发送
7、关闭
socket客户端
1、创建socket对象
2、链接服务端的IP与端口号
3、数据的接收与发送
4、关闭
1)TCP客户端
- 创建客户端套接字对象
- 和服务端建立连接
- 发送数据
- 接受数据
- 关闭套接字
import socket
if __name__ == '__main__':
# - 创建客户端套接字对象socket.AF_INET:使用的是ipv4的IP地址 socket.SOCK_STREAM:表示的是流式传输协议,也就是tcp这种传输协议
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# - 和服务端建立连接 里面传参元组(ip,port)
tcp_client_socket.connect(("192.168.2.114", 8080))
print('连接服务端成功')
while True:
# - 发送数据
data = input('请输入您要发送的数据:')
if data == 'q':
break
tcp_client_socket.send(data.encode('gbk'))
# - 接受数据 1024:单次接受数据的最大值
recv_data = tcp_client_socket.recv(1024)
print('服务端回复的数据为:', recv_data.decode('gbk'))
# - 关闭套接字
tcp_client_socket.close()
import socket
client = socket.socket()
client.connect(('127.0.0.1',8098))
while True:
msg = input('>>>')
if not msg : continue
if msg == 'q' : break
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
2)TCP服务端
具有了客户端和服务端之后,一个网络应用才可以真正的使用。
- 创建套接字对象
- 绑定ip地址和端口号
- 设置监听
- 等待客户端的连接请求
- 接受数据
- 发送数据
- 关闭套接字
import socket
if __name__ == '__main__':
# - 创建套接字对象
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# - 绑定ip地址和端口号 元组(ip,port) '':表示动态获取本地ip地址
tcp_server_socket.bind(('', 8080))
# - 设置监听 128:表示服务端排队等待连接的最大数量
tcp_server_socket.listen(128)
# - 等待客户端的连接请求
conn_socket, ip_port = tcp_server_socket.accept()
print('客户端连接成功:', ip_port)
# - 接受数据
recv_data = conn_socket.recv(1024).decode('gbk')
print('客户端发送的数据是:', recv_data)
# - 发送数据
conn_socket.send('好的,我收到了!'.encode('gbk'))
# - 关闭套接字
conn_socket.close()
tcp_server_socket.close()
import socket
server = socket.socket() # 默认是TCP协议
server.bind(('127.0.0.1',8098))
server.listen(5)
while True:
sock , address = server.accept()
data = sock.recv(1024)
print(data.decode('utf-8'))
sock.send(data)
注意点:
- 当客户端和服务端进行通信的时候必须建立连接
- TCP客户端程序一般是不需要绑定端口号的,因为客户端是主动发起连接请求的。
- TCP服务端必须要绑定端口号,否则客户端就找不到这个服务端
- listen后的套接字是被动套接字,只负责接受新的客户端的连接请求,不能收发数据。
- 关闭accept返回的这个套接字意味着和这个客户端通信完毕
- 当客户端调用close之后,服务端的recv也会解阻塞,返回的数据长度为0,服务端可以通过这个来判断客户端是否下线,反之服务端关闭关闭套接字之后,客户端recv也会解阻塞,返回的数据长度为0。
3.实现框架
1)基于socket
在web开发中程序一般会分为两个部分
- 服务器程序:负责对socke服务器进行封装,处理请求的
- 应用程序:负责具体的逻辑处理 , 具体做事的。
WSGI:是一个规范化接口,定义了Web服务器如何与python应用程序进行交互。里面把所有的http请求和解析http请求协议进行包装。
在python标准库中 , 提供了WSGI的服务器模块 wsgiref , wsgiref里面封装了socket代码,Django底层也是使用这个模块进行socket的操作
import socket
server = socket.socket() # 默认是TCP协议
server.bind(('127.0.0.1',8098))
server.listen(5)
while True:
'''
127.0.0.1 发送的响应无效 : 就是客户端与服务端之间遵循的协议不同。
我们自己的服务端必须遵循http协议的数据传输 , 否则浏览器无法响应到数据
http协议数据响应的格式
1、响应首行(http协议的版本 , 响应状态码)200 OK
2、响应头:是一些键值对
3、空行 : \r\n
4、响应体:返回给浏览器展示给用户看的数据
'''
sock , address = server.accept()
data = sock.recv(1024).decode('utf-8')
# 浏览器发送的http协议请求
print(data)
# 根据浏览器发送的http请求数据 ,切片获取到url数据
url = data.split(' ')[1]
# 以http协议发送响应数据 , 前提就是先发送一个http协议
sock.send(b'HTTP1.1 200 OK \r\n\r\n')
# 响应数据
# 根据不同的url响应不同的数据
if url == '/index/':
sock.send(b'hello world')
elif url == '/ac/':
sock.send('阿宸好帅'.encode('gbk'))
else:
sock.send('404'.encode('utf-8'))
改良版
import socket
server = socket.socket() # 默认是TCP协议
server.bind(('127.0.0.1', 8098))
server.listen(5)
# 把不同的url响应的数据封装成函数
def index(url):
return bytes(f'我是{url}响应的页面数据' , encoding='gbk')
def ac(url):
return bytes('阿宸好帅', encoding='gbk')
# 可以把url以及对应功能函数用字典进行对应关系
url_dict = {
'/index/':index,
'/ac/':ac
}
while True:
sock, address = server.accept()
data = sock.recv(1024).decode('utf-8')
# 浏览器发送的http协议请求
print(data)
url = data.split(' ')[1]
# 以http协议发送响应数据 , 前提就是先发送一个http协议
sock.send(b'HTTP1.1 200 OK \r\n\r\n')
# 定义一个变量作为程序的标志
msg = 1
# 循环遍历url对应关系的字典
for i in url_dict:
# 判断获取到的数据是否等于请求的url
if i == url:
# 代用url对应的功能函数
func = url_dict[i](url)
sock.send(func)
msg = 0
# 判断是否有对应url响应数据
if msg :
sock.send('404 not found'.encode('utf-8'))
2)基于wsgiref
from wsgiref.simple_server import make_server
# 把不同的url响应的数据封装成函数
def index(url):
# 服务器响应前端页面
with open('index.html', 'r', encoding='utf-8') as f:
data = f.read()
# 讲读取出来的页面数据进行返回给浏览器
return bytes(data.encode('utf-8'))
def ac(url):
return bytes('阿宸好帅', encoding='gbk')
# 可以把url以及对应功能函数用字典进行对应关系
url_dict = {
'/index/': index,
'/ac/': ac
}
# 响应函数
def run(environ, response):
'''
:param environ: 接收的是请求相关的所有数据 , wsgiref模块将http请求封装成字典类型的数据
:param response:响应数据 , 是一个函数
:return: 返回客户端的数据 , 以列表的形式返回
'''
# 响应数据 , 传入响应状态码, 响应头
response('200 OK', [])
# 获取请求的url
url = environ['PATH_INFO']
# 定义一个变量作为程序的标志
msg = 1
# 循环遍历url对应关系的字典
for i in url_dict:
# 判断获取到的数据是否等于请求的url
if i == url:
# 代用url对应的功能函数
response_data = url_dict[i](url)
msg = 0
# 判断是否有对应url响应数据
if msg:
response_data = b'404 not found'
# 将获取到的数据响应到浏览器中
return [response_data]
if __name__ == '__main__':
# 实例化 , 创建服务端对象 , 实时监听请求
client = make_server('127.0.0.1', 10086, run)
# 启动服务端
client.serve_forever()
3)静态页面数据响应
<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自我介绍</title>
</head>
<body>
<h1>大家好,我是靓仔</h1>
<table>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>爱好</th>
</tr>
<tr>
<td>阿宸</td>
<td>24</td>
<td>吃</td>
</tr>
</table>
</body>
</html>
3.多任务TCP服务单程序开发
思考:目前开发的服务端只能服务于一个客户端,如何实现一个服务端服务于多个客户端?
思考:如何能够同时服务于多个客户端?
实现:当客户端和服务端建立连接成功之后,创建子线程,使用子线程专门处理客户端的请求和响应,防止主线程阻塞。
import socket
import threading
def handle_client(conn_socket):
# - 接受数据
recv_data = conn_socket.recv(1024).decode('gbk')
print('客户端发送的数据是:', recv_data)
# - 发送数据
conn_socket.send('好的,我收到了!'.encode('gbk'))
# - 关闭套接字
conn_socket.close()
if __name__ == '__main__':
# - 创建套接字对象
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# - 绑定ip地址和端口号 元组(ip,port) '':表示动态获取本地ip地址
tcp_server_socket.bind(('', 8080))
# - 设置监听 128:表示服务端排队等待连接的最大数量
tcp_server_socket.listen(128)
while True:
# - 等待客户端的连接请求
conn_socket, ip_port = tcp_server_socket.accept()
print('客户端连接成功:', ip_port)
# 创建线程对象
sub = threading.Thread(target=handle_client, args=(conn_socket,))
# 启动子线程
sub.start()
三、Web框架
- web框架就是一个骨架和开发的一系列工具的集合。只需要通过一些工具稍作修饰即可完成一个作品。方便web开发 ,不需要一直做一些重复性的操作。
- web框架的存在可以减少应用开发的周期 , 提高效率与质量 ; 降低维护成本。
- 所有的文本框架的本质就是一个socket服务端;用户通过浏览器进行数据访问,浏览器就充当了socket客户端。
1.网址URL
网址又称URL, 叫统一资源定位符,通俗理解就是网络资源地址。
https://www.baidu.com/s?ie=utf-
8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=%E7%BE%8E%E5%A5%B3&fenlei=256&rsv_pq=c5db55d3000735c3&rsv_t=631fbwYxjclWOAor8jcATEev6GuHl4AzZ1tA7IIKOxDIGCc3oCVZJ0zRa6Q&rqlang=cn&rsv_enter=0&rsv_dl=tb&rsv_sug3=14&rsv_sug1=12&rsv_sug7=101&rsv_btype=i&prefixsug=%25E7%25BE%258E%25E5%25A5%25B3&rsp=0&inputT=6825&rsv_sug4=162584
-
https://
协议部分(http ftp)
-
www.baidu.com
域名部分(ip地址的别名,它是用点进行分割的英文字母和数字组成的名字,使用域名目的就是方便记住某台主机的ip地址)
-
/s
资源路径部分
-
?
查询参数部分
-
后面用&进行分割的参数
2.http协议
http协议主要规定浏览器(客户端)和web服务器通信的数据格式。
HTTP协议又称超文本传输协议,超文本不仅仅是文本数据,还包括非文本数据,图片,视频,音乐等等,而这些非文本数据又是通过链接进行加载,通俗来讲超文本数据就是带有链接的数据文本,也就是大家常说的网页数据。
传输http协议格式的数据是基于tcp传输的,发送数据之前必须建立连接。
tcp传输协议是用来保证数据的安全性,http协议用来规定数据的传输格式。
注意:http协议分别规定了请求数据和响应数据的格式,并且他们都是成对出现,即有请求必有响应。
最常见得两种请求报文为例:GET请求(获取web服务器的数据),POST请求(向web服务器提交数据).
get请求报文:
- 请求行
- 请求头
- 空行
1)HTTP GET请求报文分析
---- 请求行 ----
GET /a/b/c HTTP/1.1 # GET请求方式 请求资源路径 HTTP协议版本
---- 请求头 -----
Host: www.baidu.com # 服务器的主机地址和端口号,默认是80
Connection: keep-alive # 和服务端保持长连接
Upgrade-Insecure-Requests: 1 # 让浏览器升级不安全请求,使用https请求
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 # 用户代理,也就是客户端的名称
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 # 可接受的数据类型
Accept-Encoding: gzip, deflate # 可接受的压缩格式
Accept-Language: zh-CN,zh;q=0.9 #可接受的语言
Cookie: pgv_pvi=1246921728; # 登录用户的身份标识
---- 空行 ----
GET / HTTP/1.1\r\n
Host: www.baidu.com\r\n
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
Cookie: pgv_pvi=1246921728; \r\n
\r\n (请求头信息后面还有一个单独的’\r\n’不能省略)
说明:
每项数据之间使用:\r\n
1)HTTP POST请求报文分析
- 请求行
- 请求头
- 空行
- 请求体
---- 请求行 ----
POST /xmweb?host=www.douban.com&_t=1542884567319 HTTP/1.1 # POST请求方式 请求资源路径 HTTP协议版本
---- 请求头 ----
Host: www.douban.com # 服务器的主机地址和端口号,默认是80
Connection: keep-alive # 和服务端保持长连接
Content-Type: application/x-www-form-urlencoded # 告诉服务端请求的数据类型
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 # 客户端的名称
---- 空行 ----
---- 请求体 ----
username=hello&pass=hello # 请求参数
POST /xmweb?host=www.douban.com&_t=1542884567319 HTTP/1.1\r\n
Host: www.douban.com\r\n
Connection: keep-alive\r\n
Content-Type: application/x-www-form-urlencoded\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n
\r\n(请求头信息后面还有一个单独的’\r\n’不能省略)
username=hello&pass=hello
说明:
每项数据之间使用:\r\n
知识要点
一个HTTP请求报文可以由请求行、请求头、空行和请求体4个部分组成。
请求行是由三部分组成: 请求方式 请求资源路径 HTTP协议版本
GET方式的请求报文没有请求体,只有请求行、请求头、空行组成。
POST方式的请求报文可以有请求行、请求头、空行、请求体四部分组成。
注意:POST方式可以允许没有请求体,但是这种格式很少见。
1)HTTP 响应报文分析
- 响应行/状态行
- 响应头
- 空行
- 响应体
--- 响应行/状态行 ---
HTTP/1.1 200 OK # HTTP协议版本 状态码 状态描述
--- 响应头 ---
Server: Tengine # 服务器名称
Content-Type: text/html; charset=UTF-8 # 内容类型
Connection: keep-alive # 和客户端保持长连接
Date: Fri, 23 Nov 2018 02:01:05 GMT # 服务端的响应时间
--- 空行 ---
--- 响应体 ---
<!DOCTYPE html><html lang=“en”> …</html> # 响应给客户端的数据
HTTP/1.1 200 OK\r\n
Server: Tengine\r\n
Content-Type: text/html; charset=UTF-8\r\n
Connection: keep-alive\r\n
Date: Fri, 23 Nov 2018 02:01:05 GMT\r\n
\r\n(响应头信息后面还有一个单独的’\r\n’不能省略)
<!DOCTYPE html><html lang=“en”> …</html>
说明:
每项数据之间使用:\r\n
1)HTTP状态码
用来表示web服务器响应状态的三位数字
- 200 服务器已经成功处理了请求
- 3 开头的一般表示请求被重定向
- 400 错误请求,请求地址或者你的参数错误
- 404 请求的资源路径服务器不存在
- 500 服务器内部源码出现错误
四、静态web服务器-返回固定的页面
import socket
if __name__ == '__main__':
# 编写一个TCP服务端程序
# 创建套接字对象
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定ip地址和port端口号
tcp_server_socket.bind(('', 8091))
# 设置监听
tcp_server_socket.listen(128)
while True:
# 等待客户端的连接请求
conn_socket, ip_port = tcp_server_socket.accept()
print('客户端连接成功:', ip_port)
# 获取浏览器发送的http请求报文数据
client_request_data = conn_socket.recv(1024).decode()
print(client_request_data)
# 读取固定的页面数据,组装成为http响应报文数据发送给浏览器
with open('./index.html', 'rb') as f:
file_data = f.read()
# 相应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 响应头
response_header = 'Server:ZZT\r\n'
# 空行
# 响应体
response_body = file_data
# 组装相应报文,返回给客户端
response_data = (response_line + response_header + '\r\n').encode() + response_body
conn_socket.send(response_data)
# 服务完毕,关闭套接字
conn_socket.close()
1.静态web页面返回指定的页面
- 获取请求资源路径
- 根据用户请求的资源路径,读取指定的文件数据组装成为http响应报文发送给浏览器
- 如果用户请求资源没有,组装404状态的响应报文发送给浏览器
import socket
if __name__ == '__main__':
# 编写一个TCP服务端程序
# 创建套接字对象
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定ip地址和port端口号
tcp_server_socket.bind(('', 8091))
# 设置监听
tcp_server_socket.listen(128)
while True:
# 等待客户端的连接请求
conn_socket, ip_port = tcp_server_socket.accept()
print('客户端连接成功:', ip_port)
# 获取浏览器发送的http请求报文数据
client_request_data = conn_socket.recv(1024).decode()
# print(client_request_data)
# 获取请求资源路径
request_data = client_request_data.split(' ')
# print(request_data)
# 请求资源路径
request_path = request_data[1]
print(request_path)
# 判断是否访问主页
if request_path == '/':
request_path = '/index.html'
# 读取固定的页面数据,组装成为http响应报文数据发送给浏览器
try:
with open('.' + request_path, 'rb') as f:
file_data = f.read()
except Exception as e:
# 相应行
response_line = 'HTTP/1.1 404 Not Found\r\n'
# 响应头
response_header = 'Server:ZZT\r\n'
# 空行
# 响应体
response_body = '404 Not Found sorry'
# 组装相应报文,返回给客户端
response_data = (response_line + response_header + '\r\n' + response_body).encode()
conn_socket.send(response_data)
else:
# 相应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 响应头
response_header = 'Server:ZZT\r\n'
# 空行
# 响应体
response_body = file_data
# 组装相应报文,返回给客户端
response_data = (response_line + response_header + '\r\n').encode() + response_body
conn_socket.send(response_data)
finally:
# 服务完毕,关闭套接字
conn_socket.close()
2.多任务版静态web服务器
import socket
import threading
def handle_client_request(conn_socket):
# 获取浏览器发送的http请求报文数据
client_request_data = conn_socket.recv(1024).decode()
# print(client_request_data)
# 获取请求资源路径
request_data = client_request_data.split(' ')
print(request_data)
# 判断客户端是否关闭
if len(request_data) == 1:
conn_socket.close()
return
# 请求资源路径
request_path = request_data[1]
print(request_path)
# 判断是否访问主页
if request_path == '/':
request_path = '/index.html'
# 读取固定的页面数据,组装成为http响应报文数据发送给浏览器
try:
with open('.' + request_path, 'rb') as f:
file_data = f.read()
except Exception as e:
# 相应行
response_line = 'HTTP/1.1 404 Not Found\r\n'
# 响应头
response_header = 'Server:ZZT\r\n'
# 空行
# 响应体
response_body = '404 Not Found sorry'
# 组装相应报文,返回给客户端
response_data = (response_line + response_header + '\r\n' + response_body).encode()
conn_socket.send(response_data)
else:
# 相应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 响应头
response_header = 'Server:ZZT\r\n'
# 空行
# 响应体
response_body = file_data
# 组装相应报文,返回给客户端
response_data = (response_line + response_header + '\r\n').encode() + response_body
conn_socket.send(response_data)
finally:
# 服务完毕,关闭套接字
conn_socket.close()
if __name__ == '__main__':
# 编写一个TCP服务端程序
# 创建套接字对象
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定ip地址和port端口号
tcp_server_socket.bind(('', 8091))
# 设置监听
tcp_server_socket.listen(128)
while True:
# 等待客户端的连接请求
conn_socket, ip_port = tcp_server_socket.accept()
print('客户端连接成功:', ip_port)
# 创建线程对象
sub = threading.Thread(target=handle_client_request, args=(conn_socket,))
# 启动子线程
sub.start()
3.静态web服务器_面向对象版
- 把web服务抽象成为一个类
- 提供类的初始化方法
- 提供启动服务器的方法
import socket
import threading
class HttpServer(object):
# - 提供类的初始化方法
def __init__(self):
# 创建套接字对象
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定ip地址和port端口号
self.tcp_server_socket.bind(('', 8091))
# 设置监听
self.tcp_server_socket.listen(128)
def run(self):
while True:
# 等待客户端的连接请求
conn_socket, ip_port = self.tcp_server_socket.accept()
print('客户端连接成功:', ip_port)
# 创建线程对象
sub = threading.Thread(target=self.handle_client_request, args=(conn_socket,))
# 启动子线程
sub.start()
def handle_client_request(self, conn_socket):
# 获取浏览器发送的http请求报文数据
client_request_data = conn_socket.recv(1024).decode()
# print(client_request_data)
# 获取请求资源路径
request_data = client_request_data.split(' ')
print(request_data)
# 判断客户端是否关闭
if len(request_data) == 1:
conn_socket.close()
return
# 请求资源路径
request_path = request_data[1]
print(request_path)
# 判断是否访问主页
if request_path == '/':
request_path = '/index.html'
# 读取固定的页面数据,组装成为http响应报文数据发送给浏览器
try:
with open('.' + request_path, 'rb') as f:
file_data = f.read()
except Exception as e:
# 相应行
response_line = 'HTTP/1.1 404 Not Found\r\n'
# 响应头
response_header = 'Server:ZZT\r\n'
# 空行
# 响应体
response_body = '404 Not Found sorry'
# 组装相应报文,返回给客户端
response_data = (response_line + response_header + '\r\n' + response_body).encode()
conn_socket.send(response_data)
else:
# 相应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 响应头
response_header = 'Server:ZZT\r\n'
# 空行
# 响应体
response_body = file_data
# 组装相应报文,返回给客户端
response_data = (response_line + response_header + '\r\n').encode() + response_body
conn_socket.send(response_data)
finally:
# 服务完毕,关闭套接字
conn_socket.close()
if __name__ == '__main__':
# - 把web服务抽象成为一个类
my_web = HttpServer()
# - 提供启动服务器的方法
my_web.run()
五、阻塞与非阻塞
1.非阻塞
目标:单任务也可以同时处理多个客户端的连接请求以及收发消息
方案:设置套接字为非阻塞
import socket
import time
if __name__ == '__main__':
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socket.bind(('', 8080))
tcp_server_socket.listen(128)
# 设置套接字为非阻塞
tcp_server_socket.setblocking(False)
# 定义一个列表用来存放没有处理消息的套接字
client_socket_list = list()
while True:
time.sleep(1)
try:
new_socket, ip_port = tcp_server_socket.accept()
except Exception as e:
print('表示没有客户端连接')
else:
print('没有异常,意味着此时有客户端连接')
client_socket_list.append(new_socket)
for new_socket in client_socket_list:
try:
recv_data = new_socket.recv(1024)
except Exception as f:
print('意味着这个新的客户端没有发送消息')
else:
# 如果有接受到消息,需要考虑两种情况,一种是发送了真正的消息的,一种是客户端关闭的时候发送的空消息的。
if recv_data:
print('证明已经接受到消息')
print(recv_data)
new_socket.send('ok'.encode())
else:
# 客户端关闭了
client_socket_list.remove(new_socket)
new_socket.close()
print('这个客户端关闭了')
写一个cs架构的软件,在客户端输入指令,发送给服务端执行,服务端解析命令之后得到结果,将指令的结果返回给客户端。
Windows: dir 查看某个文件夹下面的子文件
ipconfig 查看网卡信息
tasklist 查看本机运行的所有程序
linux: ls ifconfig ps aux
import socket
if __name__ == '__main__':
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_client_socket.connect(('127.0.0.1', 8080))
while True:
send_cmd = input("请输入指令:").strip()
if send_cmd == 'q':
break
tcp_client_socket.send(send_cmd.encode())
recv_data = tcp_client_socket.recv(1024).decode()
print(recv_data)
tcp_client_socket.close()
import socket
import subprocess
if __name__ == '__main__':
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socket.bind(('', 8080))
tcp_server_socket.listen(128)
while True:
conn_socket, ip_port = tcp_server_socket.accept()
print('客户端连接成功', ip_port)
while True:
recv_data = conn_socket.recv(1024).decode()
if not recv_data:
break
print('客户端发送的指令是:', recv_data)
obj = subprocess.Popen(recv_data, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# 获取正确结果
data_out = obj.stdout.read()
# 获取错误的结果
err_data = obj.stderr.read()
# 发送结果
conn_socket.send(data_out + err_data)
conn_socket.close()
2.socket之send和recv原理解析
当创建一个tcpsocket对象的时候,就会有一个发送缓冲区和一个接受缓冲区,这个发送缓冲区和接受缓冲区实际上就是内存中的一片空间。
说明:软件是不能直接调用硬件的,需要通过操作系统去调度,发送数据是发送到发送缓冲区,接受数据是从接受缓冲区接受的。
import socket
import struct
if __name__ == '__main__':
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_client_socket.connect(('127.0.0.1', 8080))
while True:
send_cmd = input("请输入指令:").strip()
if send_cmd == 'q':
break
tcp_client_socket.send(send_cmd.encode())
# 先接受真实数据的大小
data_len = tcp_client_socket.recv(4)
recv_len = struct.unpack('i', data_len)[0]
# 在接受真正的数据
recv_size = 0 # 实际已经接受的数据的大小
recv_result = b'' # 实际接受到的数据
while recv_size < recv_len:
recv_data = tcp_client_socket.recv(1024)
recv_size += len(recv_data)
recv_result += recv_data
print(recv_result.decode('gbk'))
tcp_client_socket.close()
import socket
import subprocess
import struct
if __name__ == '__main__':
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_socket.bind(('', 8080))
tcp_server_socket.listen(128)
while True:
conn_socket, ip_port = tcp_server_socket.accept()
print('客户端连接成功', ip_port)
while True:
recv_data = conn_socket.recv(1024).decode()
if not recv_data:
break
print('客户端发送的指令是:', recv_data)
obj = subprocess.Popen(recv_data, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# 获取正确结果
data_out = obj.stdout.read()
# 获取错误的结果
err_data = obj.stderr.read()
# 得到需要发送的数据长度
send_len = len(data_out + err_data)
# 先发送需要发送的数据长度
data_len = struct.pack('i', send_len)
conn_socket.send(data_len)
# 再发送正确的结果
conn_socket.send(data_out + err_data)
conn_socket.close()
import struct
res = struct.pack('i', 10) # i:表示后面压缩的是整数
print(res, len(res))
res1 = struct.pack('i', 1000)
print(res, len(res1))
res2 = struct.unpack('i', res)[0]
print(res2)
res3 = struct.unpack('i', res1)[0]
print(res3)
六、进程之间通信
Process之间有时候有需要通信,操作系统提供了很多机制来实现进程之间通信。
socket 可以实现进程之间通信
Queue消息队列
from multiprocessing import Queue
# 创建一个消息队列对象
q = Queue(3) # 表示最多可以接受三条消息
q.put('第一条消息')
q.put('第二条消息')
q.put('第三条消息')
# print(q.full()) # 判断消息队列是否已满
print(q.get())
q.put('第四条消息')
print(q.get())
print(q.get())
print(q.get())
print(q.empty()) # 判断消息队列是否为空
import multiprocessing
import time
# 定义全局变量
my_list = []
def write_data(q):
"""向my_list里面添加数据"""
for i in range(3):
q.put(i)
print('像消息队列里面添加了:', i)
print('全部添加完成')
def read_data(q):
"""读my_list里面的数据"""
while True:
if q.empty():
break
data = q.get()
print("从消息队列里面取出了:", data)
# while not q.empty():
# data = q.get()
# print("从消息队列里面取出了:", data)
if __name__ == '__main__':
# 创建消息队列对象
q = multiprocessing.Queue()
# 创建子进程
write_process = multiprocessing.Process(target=write_data, args=(q,))
read_process = multiprocessing.Process(target=read_data, args=(q,))
# 启动子进程
write_process.start()
# 阻塞等待
write_process.join()
# time.sleep(1) # 沉睡一秒
read_process.start()