网络编程
网络通信的概念
网络是用物理链路将各个孤立的工作站或主机相连在一起,组成数据链路,从而达到资源共享和通信的目的。
使用网络的目的,就是为了联通多方然后进行通信,即把数据从一方传递给另外一方。
前面学习的编程都是单机的,即不能和其他电脑上的程序进行通信。为了让在不同的电脑上运行的软件之间能够互相传递数据,就需要借助网络的功能。
- 使用网络能够把多方链接在一起,然后可以进行数据传递
- 所谓的网络编程就是让不同的电脑上的软件能够进行数据传递,即进程之间的通信
什么是IP地址
-
在网络编程中,如果一台主机想和另一台主机进行沟通和共享数据,首先要做的第一件事情就是要找到对方。在互联网通信中,我们使用IP地址来查询到各个主机。
-
ip地址要在同一个网段才能通信。
-
ip地址是使用八个二进制,11111111: 255
-
ip地址:点分十进制:255.255.255.0
ip地址: 192.168.31.100
子网掩码: 255.255.255.0
网段就是子网掩码和ip地址做与运算
网段:192.168.31.X ==> X表示的主机位
如果子网掩码是255.255.255.0 那么网络里最多可以放主机数目为
X ==> 254
主机位全0表示网络位 192.168.31.0
主机位全1表示广播位 192.168.31.255
- 要判断ip地址是否在同一个网段,需要配合子网掩码进行与运算以后才能确定。
ip:192.168.2.10
ip:192.168.3.20
mask:255.255.0.0
网段:192.168.X.X
支持主机:256 * 256 - 2
IP地址分类
每一个ip地址包括两部分:网络地址和主机地址
- A类地址:网络位7位,(不是8位,因为最高位必须是0),主机位24位
01111111.11111111.11111111.11111111
子网掩码:255.0.0.0
地址范围:1.0.0.1~126.255.255.254(127开头,0.0.0.0都是特殊的ip地址)
- B类地址:网络位14位,(前两位是10),主机位16位
地址范围:128.1.0.1~191.255.255.254
子网掩码:255.255.0.0
- C类地址:网络位21位,(前三位110),主机位8位
地址范围:192.0.1.1~233.255.255.254
子网掩码:255.255.255.0
- D类地址
D类IP地址第一个字节以“1110”开始,它是一个专门保留的地址,并不指向特定的网络,目前这一类地址被用来在多点广播(Multicast)中。
- E类地址
以“1111”开始,为将来使用保留,仅作实验和开发用。
- 私有地址
国际规定有一部分IP地址是用于我们的局域网使用,也就是属于私网IP,不在公网中使用的,它们的范围:
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0~192.168.255.255
注意事项:
- 每一个字节都为0的地址(0.0.0.0)对应于当前主机。
- IP地址中的每一个字节都为1的IP地址(255.255.255.255)是当前子网的广播地址。
- IP地址中凡是以“1111”开头的E类IP地址都保留用于将来和实验使用。
- IP地址中不能以十进制“127”作为开头,该类地址中数字127.0.0.1到127.255.255.255用于回路测试,如:127.0.0.1可以代表本机IP地址,用 http://127.0.0.1 就可以测试本机中配置的Web服务器。
- 网络ID的第一个8位组也不能全置为“0”,全“0”表示本地网络。
网络通信原理
DHCP:用来分配IP地址
如果用户接入主机,那么DHCP就会从地址池中选IP地址自动分配。
例如,DHCP地址池:192.168.31.100~192.168.31.200
如果处理的资源在局域网中找不到,不在同一个网段,就可以找默认网关。
在网络里,都是通过IP地址查找的主机。
DNS:将域名进行解析
域名 :www.baidu.com ==> IP :182.61.200.6
网络连接原理
- 直接通信
- 两台电脑之间通过网线连接是可以直接通信的,但是需要提前设置好ip地址以及网络掩码。
- IP地址需要控制在同一网段内。
- 使用集线器通信
- 当有多台电脑需要组成一个网时,那么可以通过集线器(Hub)将其连接在一起。
- 一般情况下集线器的接口较少。
- 集线器有个缺点,它以广播的方式进行发送任何数据,即如果集线器接收到来自A电脑的数据本来是想转发给B电脑,如果此时它还连接着另外两台电脑C、D,那么它会把这个数据给每个电脑都发送一份,因此会导致网络拥堵。
- 使用交换机通信
- 克服了集线器以广播发送数据的缺点,当需要广播的时候发送广播,当需要单播的时候又能够以单播的方式进行发送。
- 它已经替代了之前的集线器。
- 企业中就是用交换机来完成多台电脑设备的连接成网络的。
- 使用路由器连接多个网络
可以连接多个局域网。
- 复杂的通信过程
- 在浏览器中输入一个网址时,需要将它先解析出IP地址来。
- 当得到IP地址之后,浏览器以tcp的方式3次握手连接服务器。
- 以tcp的方式发送http协议的请求数据给服务器。
- 服务器tcp的方式回应http协议的应答数据给浏览器。
总结:
- MAC地址:在设备与设备之间数据通信时用来标记收发方向(网卡的序列号)
- IP地址:在逻辑上标记一台电脑,用来指引数据包的收发方向(相当于电脑的序列号)
- 网络掩码:用来区分IP地址的网络号和主机号
- 默认网关:当需要发送的数据包的目的ip不在本网段内时,就会发送给默认的一台电脑,成为网关
- 集线器:已过时,用来连接多台电脑,缺点:每次收发数据都进行广播,网络会变的拥堵
- 交换机:集线器的升级版,有学习功能知道需要发送给哪台设备,根据需要进行单播、广播
- 路由器:连接多个不同的网段,让它们之间可以进行收发数据,每次收到数据后,IP不变,但是MAC地址会变化
- DNS:用来解析出IP(类似电话簿)
- http服务器:提供浏览器能够访问到的数据
端口和套接字
端口就像一个房子的门,是出入这间房子的必经之路。如果一个程序需要收发网络数据,那么就需要有这样的端口。
在Linux系统中,端口可以有65536(2的16次方)个之多。
既然有这么多,操作系统为了统一管理,所以进行了编号,这就是端口号。
- 端口号:
端口是通过端口号来标记的,端口号只有整数,范围是从0到65535.端口号不是随意使用的,而是按照一定的规定进行分配。
- 知名端口号:
知名端口号是众所周知的端口号,范围从0到1023,以理解为,一些常用的功能使用的号码是规定好的。好比电话号码110、10086、10010一样。一般情况下,如果一个程序需要使用知名端口的需要有root权限。
- 动态端口号:
动态端口的范围是从1024到65535。
之所以成为动态端口,是因为它一般不固定分配某种服务,而是动态分配。
动态分配是指当一个系统程序或应用程序需要网络通信时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用。
当这个程序关闭时,同时也就释放了所占用的端口号。
- 端口号作用
一台拥有IP地址的主机可以提供许多服务,比如 HTTP(万维网服务)、FTP(文件传输)、SMTP(电子邮件)等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务?显然不能只靠IP地址,因为IP地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区分不同的服务的。需要注意的是,端口并不是一一对应。比如 你的电脑作为客户机访问一台www服务器时,www服务器使用“80”端口与你的电脑通信,但你的电脑则可能使用“3457”这样的端口。
同一台电脑之间,用PID来区分不同的程序。
socket简介:
- 不同的电脑上的进程之间如何通信
首要解决的问题是如何唯一标识一个进程,否则通信无从谈起。在1台电脑上可以通过进程号(PID)来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议解决了这个问题。网络层的“IP地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用进程(进程)。这样利用IP地址、协议、端口就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其他进程进行交互。
- 什么是socket
socket(简称 套接字)是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于Socket来完成通信的。
例如:每天浏览网页、QQ聊天、收发email等等。
UDP协议
UDP是 User Datagram Protocol 的简称,中文名是用户数据报协议。在通信开始之前,不需要建立相关的链接,只需要发送数据即可,类似于生活中“写信”。
UDP发送数据
import socket
# 不同电脑之间的通信需要使用socket
# socket可以在不同的电脑间通信;还可以在同一个电脑的不同程序之间通信
# 1.创建socket,并连接
# AF_INET:表示这个socket是用来进行网络连接
# SOCK_DGRAM:表示连接是一个UDP连接
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2.发送数据
# data:要发送的数据,它是二进制的数据
# address:发送给谁,参数是一个元组()
# 第0个表示目标的IP地址,第1个表示程序的端口号
# 给 192.168.31.199 这台主机的 9000 端口上发送了 hello
# 端口号:0~65536 0~1023 不要用,系统一些重要的服务在使用
# 找一个空闲的端口号
s.sendto('下午好'.encode('utf8'), ('192.168.31.199', 9090))
# 3.关闭socket
s.close()
UDP接受数据
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定IP地址和端口号
s.bind(('192.168.31.199', 9090))
# recvfrom 接受数据,是一个阻塞的方法,会等待
# content = s.recvfrom(1024)
# print(content)
# 接收到的数据是一个元组,元组里有两个元素
# 第0个元素是接收到的数据
# 第1个元素是发送方的IP地址和端口号
data, addr = s.recvfrom(1024) # 拆包
print('从{}地址{}端口号接收到了消息,内容是:{}'.format(addr[0], addr[1], data.decode('utf8')))
s.close()
要进行聊天,需要绑定端口号。否则,接收方只能接收消息,不能发送消息。
TCP协议
服务器和客户端
服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。客户端(Client)也被称为用户端,是指与服务器相对应,为客户提供本地服务的程序。客户端服务器架构又被称为主从式架构,简称C/S架构,是一种网络架构,它把客户端与服务器分开来,一个客户端软件的实例都可以向一个服务器或应用程序服务器发出请求。
UDP:没有严格的客户端和服务器的区别。
TCP:有客户端和服务器。(面向连接的协议)
# TCP客户端
import socket
# 基于tcp协议的socket连接
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 在发送数据之前,必须要先和服务器建立连接
s.connect(('192.168.31.199', 9090)) # 调用 connect 方法连接到服务器
s.send('hello'.encode('utf8'))
s.close()
# TCP服务器
import socket
# 创建一个socket连接
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('192.168.31.199', 9090))
# 1000 1120 ==> 120排队
# 1000 1130 ==> 128排队 会报错,多了2个
s.listen(128) # 把socket变成一个被动监听的socket
x = s.accept() # 接收客户端的请求
# 接收到的结果是一个元组,元组里有两个元素
# 第0个元素是客服端的socket连接,第1个元素是客户端的IP地址和端口号
client_socket, client_addr = s.accept()
data = client_socket.recv(1024) # 使用recv获取数据
print('接收到了{}客户端{}端口号发送的数据,内容是{}'.format(client_addr[0], client_addr[1], data.decode('utf8')))
文件下载案例
demo.txt
1000phone hello python
mobiletrain 大数据
1000phone java
mobiletrain html5
mobiletrain 云计算
# 文件下载服务器
import socket, os
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('192.168.31.199', 9090))
server_socket.listen(128)
# 接收客服端的请求
client_socket, client_addr = server_socket.accept()
file_name = client_socket.recv(1024)
if os.path.isfile(file_name):
with open(file_name, 'rb') as file:
content = file.read()
client_socket.send(content)
else:
print('文件不存在')
# 文件下载客户端
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.31.199', 9090))
file_name = input('请输入您要下载的文件名:')
s.send(file_name.encode('utf8'))
with open(file_name, 'wb') as file:
while True:
content = s.recv(1024)
if not content:
break
file.write(content)
s.close()
多线程实现多任务
import threading
import time
def dance():
for i in range(50):
time.sleep(0.2)
print('我正在跳舞')
def sing():
for i in range(50):
time.sleep(0.2)
print('我正在唱歌')
# 多任务同时执行
# Python里执行多任务:多线程、多进程、多进程+多线程
# dance()
# sing()
# target 需要的是一个函数,用来指定线程需要执行的任务
t1 = threading.Thread(target=dance) # 创建了线程1
t2 = threading.Thread(target=sing) # 创建了线程2
# 启动线程
t1.start()
t2.start()
多线程版聊天
import socket
import threading
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('192.168.31.199', 8080))
def send_msg():
while True:
msg = input('请输入您要发送的内容:')
s.sendto(msg.encode('utf8'), ('192.168.31.199', 9090))
if msg == 'exit':
break
def recv_msg():
while True:
data, addr = s.recvfrom(1024)
print('接收到了{}地址{}端口的消息:{}'.format(addr[0], addr[1], data.decode('utf8')),
file=open('消息记录.txt', 'a', encoding='utf8'))
t1 = threading.Thread(target=send_msg())
t2 = threading.Thread(target=recv_msg())
t1.start()
t2.start()
多线程共享全局变量
import time
import threading
# 多个线程可以同时操作一个全局变量(多个线程共享全局变量)
# 线程安全问题
ticket = 20
def sell_ticket():
global ticket
while True:
if ticket > 0:
time.sleep(1)
ticket -= 1
print('{}卖出一张票,还剩{}张'.format(threading.current_thread().name, ticket))
else:
print('票卖完了')
break
t1 = threading.Thread(target=sell_ticket, name='线程1')
t2 = threading.Thread(target=sell_ticket, name='线程2')
t1.start()
t2.start()
线程锁的使用
- 同步:
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。同步就是协同步调,按预定的先后次序进行运行。线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
- 互斥锁:
互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其它线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其它的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
import time
import threading
ticket = 20
# 创建一把锁
lock = threading.Lock()
def sell_ticket():
global ticket
while True:
lock.acquire() # 加同步锁
if ticket > 0:
time.sleep(1)
ticket -= 1
lock.release()
print('{}卖出一张票,还剩{}张'.format(threading.current_thread().name, ticket))
else:
lock.release()
print('票卖完了')
break
t1 = threading.Thread(target=sell_ticket, name='线程1')
t2 = threading.Thread(target=sell_ticket, name='线程2')
t1.start()
t2.start()
- 总结:
锁的好处:
- 确保了某段关键代码只能由一个线程从头到尾完整地执行
锁的坏处:
- 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。
- 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁。
线程间通信
线程之间有时需要通信,操作系统提供了很多机制来实现进程间的通信,其中我们使用最多的是队列Queue。
import threading
import queue
import time
def produce():
for i in range(10):
time.sleep(0.5)
print('{}生产+++++面包{}'.format(threading.current_thread().name, i))
q.put('{}{}'.format(threading.current_thread().name, i))
def consumer():
while True:
time.sleep(1)
# q.get()是一个阻塞的方法
print('{}买到-----面包{}'.format(threading.current_thread().name, q.get()))
q = queue.Queue() # 创建一个队列
# 三条生产线
pa = threading.Thread(target=produce, name='pa')
pb = threading.Thread(target=produce, name='pb')
pc = threading.Thread(target=produce, name='pc')
# 三条消费线
ca = threading.Thread(target=consumer, name='ca')
cb = threading.Thread(target=consumer, name='cb')
cc = threading.Thread(target=consumer, name='cc')
pa.start()
pb.start()
pc.start()
ca.start()
cb.start()
cc.start()
多进程的使用
- 进程
程序:例如xxx.py这是程序,是一个静态的。
进程:一个程序运行起来后,代码+用到的资源 ==> 称之为进程,它是操作系统分配资源的基本单元。
不仅可以通过线程完成多任务,进程也是可以的。
- 进程的状态
工作中,任务数往往大于cpu的核数,即一定有一些任务正在执行,而另外一些任务在等待cpu进行执行,因此导致了有了不同的状态。
- 就绪态:运行的条件都已经满足,正在等待cpu的执行。
- 执行态:cpu正在执行其功能。
- 等待态:等待某些条件满足,例如一个程序sleep了,此时就处于等待态。
import multiprocessing
import time
import os
def dance(n):
for i in range(n):
time.sleep(0.5)
print('正在跳舞,pid={}'.format(os.getpid()))
def sing(m):
for i in range(m):
time.sleep(0.5)
print('正在唱歌,pid={}'.format(os.getpid()))
if __name__ == '__main__':
print('主进程的pid={}'.format(os.getpid()))
# 创建了两个进程
# target 用来表示执行的任务
# args 用来传参,类型是一个元组
p1 = multiprocessing.Process(target=dance, args=(20,))
p2 = multiprocessing.Process(target=sing, args=(50,))
p1.start()
p2.start()
进程和线程的区别
- 功能
进程:能够完成多任务,比如 在一台电脑上能够同时运行多个QQ
线程:能够完成多任务,比如 一个QQ中的多个聊天窗口
- 定义的不同
进程:系统进行资源分配和调度的一个独立单位。
线程:进程的一个实体,是cpu调度和分派的基本单位。它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如 程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
- 区别
- 一个程序至少有一个进程,一个进程至少有一个线程。
- 线程的划分尺度小于进程(资源比进程小),使得多线程程序的并发性高。
- 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
- 线程不能够独立执行,必须依存在进程中。
- 可以将进程理解为工厂中的一条流水线,而其中的线程就是这个流水线上的工人。
- 优缺点
线程执行开销小,但不利于资源的管理和保护;进程正相反。
多进程不能共享全局变量
import os
import threading
import multiprocessing
n = 100
def test():
global n
n += 1
print('test==={}里n的值是{}'.format(os.getpid(), n))
def demo():
global n
n += 1
print('demo==={}里n的值是{}'.format(os.getpid(), n))
# test() # 101
# demo() # 102
# 在同一个主进程里的两个子线程
# 线程之间可以共享同一进程的全局变量
# t1 = threading.Thread(target=test)
# t2 = threading.Thread(target=demo)
# t1.start() # 101
# t2.start() # 102
# 不同进程访问全局变量,各自保存一份全局变量,不会共享全局变量
if __name__ == '__main__':
p1 = multiprocessing.Process(target=test)
p2 = multiprocessing.Process(target=demo)
p1.start() # test===4396里n的值是101
p2.start() # demo===8908里n的值是101
进程间通信
import os
import multiprocessing
import time
def producer(x):
for i in range(10):
time.sleep(0.5)
print('生产了++++++pid{}{}'.format(os.getpid(), i))
x.put('pid{}{}'.format(os.getpid(), i))
def consumer(x):
for i in range(10):
time.sleep(0.3)
print('消费了------{}'.format(x.get()))
if __name__ == '__main__':
q = multiprocessing.Queue()
p1 = multiprocessing.Process(target=producer, args=(q,))
p2 = multiprocessing.Process(target=producer, args=(q,))
p3 = multiprocessing.Process(target=producer, args=(q,))
p1.start()
p2.start()
p3.start()
c2 = multiprocessing.Process(target=consumer, args=(q,))
c2.start()
队列的使用
import multiprocessing
import queue
# q1 = multiprocessing.Queue() # 进程间通信
# q2 = queue.Queue() # 线程间通信
# 创建队列时,可以指定最大长度。默认值是0,表示不限
q = multiprocessing.Queue(5)
q.put('hello')
q.put('good')
q.put('yes')
q.put('ok')
q.put('hi')
# print(q.full()) # True 队满
# q.put('how') # 无法放进去,队满
# block = True:表示是阻塞,如果队列已经满了,就等待
# timeout 超时,等待多久以后程序会出错,单位是秒
# q.put('how', block=True, timeout=1)
# 等价于 q.put('how', block=False)
# q.put_nowait('how')
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
# print(q.get()) # 取不到,取完了
# q.get(block=True, timeout=10)
q.get_nowait()
进程池的使用
当需要创建的子进程数量不多时,可以直接利用 multiprocessing 中的 Process 动态生成多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到 multiprocessing 模块提供的 Pool 方法。
初始化 Pool 时,可以指定一个最大进程数,当有新的请求提交到 Pool 中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。
from multiprocessing import Pool
import os
import time
import random
def worker(msg):
t_start = time.time()
print('%s开始执行,pid为%d' % (msg, os.getpid()))
# random.random()随机生成0~1之间的浮点数
time.sleep(random.random() * 2)
t_stop = time.time()
print(msg, '执行完毕,耗时%0.2f' % (t_stop - t_start))
if __name__ == '__main__':
po = Pool(3) # 定义一个进程池,最大进程数3
for i in range(10):
# Pool().apply_async(要调用的目标,(传递给目标的参数元组,))
# 每次循环将会用空闲出来的子进程去调用目标
po.apply_async(worker, (i,))
print('----start----')
po.close() # 关闭进程池,关闭后po不再接收新的请求
po.join() # 等待po中所有子进程执行完成,必须放在close语句之后
print('----end----')
如果要使用Pool创建进程,就需要使用 multiprocessing.Manager() 中的 Queue()。进程池间通信类似于进程间通信,就是Queue类不同。
join方法的使用
# join 线程和进程都有join方法
import time
import threading
x = 10
def test(a, b):
time.sleep(1)
global x
x = a + b
# test(1, 1)
# print(x) # 2
t = threading.Thread(target=test, args=(1, 1))
t.start()
t.join() # 让主线程等待
print(x) # 10(子线程在做,但主线程已经打印了) 2(加入了join方法)
HTTP协议
在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。而浏览器和服务器之间的传输协议是HTTP,所以:
- HTML;是一种用来定义网页的文本,会HTML,就可以编写网页。
- HTTP是在网络上传输HTML的协议,用来浏览器和服务器的通信。
Chrome浏览器提供了一套完整地调试工具,非常适合Web开发。
HTTP协议:Hyper Text Transfer Protocol 超文本传输协议
协议的作用就是用来传输超文本 HTML(Hyper Text Markup Language)
HTML:超文本标记语言
HTTP:用来传输超文本的一个协议
简单HTTP服务器搭建
使用浏览器给服务器发送请求,HTTP请求头(Request Headers)。
服务器返回,HTTP响应头(Response Headers)。
import socket
port = int(input('请输入端口号:'))
# HTTP服务器都是基于TCP的socket连接
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', port))
server_socket.listen(128)
print('server is running at 0.0.0.0:{}'.format(port))
while True:
client_socket, client_addr = server_socket.accept()
# 从客户端的socket里获取数据
data = client_socket.recv(1024).decode('utf8')
print('接收到的数据{}'.format(data))
# 返回内容之前,需要先设置HTTP响应头
# 设置一个响应头就换一行
client_socket.send('HTTP/1.1 200 OK\n'.encode('utf8'))
client_socket.send('content-type:text/html\n'.encode('utf8'))
# 所有的响应头设置完成以后,再换行
client_socket.send('\n'.encode('utf8'))
# 发送内容
client_socket.send(client_addr[0].encode('utf8'))
HTTP请求头
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('172.20.10.9', 8090))
server_socket.listen(128)
while True:
client_socket, client_addr = server_socket.accept()
data = client_socket.recv(1024).decode('utf8')
print('接收到的数据{}'.format(data))
"""
# GET 请求方式,GET/POST/PUT/DELETE......
# /index.html?name=jack&age=18 /后面跟请求的路径以及请求参数
# HTTP/1.1 HTTP版本号
接收到的数据GET /index.html?name=jack&age=18 HTTP/1.1
# 请求的服务器地址
Host: 172.20.10.9:8090
Connection: keep-alive
Upgrade-Insecure-Requests: 1
# UA 用户代理,最开始设计的目的,是为了能从请求头里辨识浏览器的类型
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 Edg/87.0.664.75
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
"""
client_socket.send('HTTP/1.1 200 OK\n'.encode('utf8'))
client_socket.send('content-type:text/html\n'.encode('utf8'))
client_socket.send('\n'.encode('utf8'))
client_socket.send(client_addr[0].encode('utf8'))
IP地址的绑定
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# ip地址只能通过ip地址访问
# server_socket.bind(('172.20.10.9', 8090))
# 能通过 127.0.0.1 和 localhost 来访问
# server_socket.bind(('127.0.0.1', 8090)) # 本机
# 127.0.0.1 和 0.0.0.0 都表示本机
# 0.0.0.0 表示所有的可用地址
server_socket.bind(('0.0.0.0', 8090))
server_socket.listen(128)
while True:
client_socket, client_addr = server_socket.accept()
data = client_socket.recv(1024).decode('utf8')
print('接收到{}的数据{}'.format(client_addr[0], data))
client_socket.send('HTTP/1.1 200 OK\n'.encode('utf8'))
client_socket.send('content-type:text/html\n'.encode('utf8'))
client_socket.send('\n'.encode('utf8'))
client_socket.send(client_addr[0].encode('utf8'))
根据不同的请求返回不同的内容
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('0.0.0.0', 8090))
server_socket.listen(128)
while True:
client_socket, client_addr = server_socket.accept()
data = client_socket.recv(1024).decode('utf8') # 浏览器发送过的数据有可能是空
path = ''
if data: # # 浏览器发送过的数据有可能是空
path = data.splitlines()[0].split(' ')[1]
print('请求的路径是{}'.format(path))
# 响应体
response_body = 'hello world'
# 响应头
response_header = 'HTTP/1.1 200 OK\n' # 200 OK 成功了
if path == '/login':
response_body = '欢迎来到登录页面'
elif path == '/register':
response_body = '欢迎来到注册页面'
elif path == '/':
response_body = '欢迎来到首页'
else:
# 页面未找到 404 Page Not Found
response_header = 'HTTP/1.1 404 Page Not Found\n'
response_body = '404 Page Not Found'
response_header += 'content-type:text/html;charset=utf8\n'
response_header += '\n'
# 响应
response = response_header + response_body
# 发送给浏览器
client_socket.send(response.encode('utf8'))
面向对象的服务器封装
import socket
class MyServer(object):
def __init__(self, ip, port):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.bind((ip, port))
self.socket.listen(128)
def run_forever(self):
while True:
client_socket, client_addr = self.socket.accept()
data = client_socket.recv(1024).decode('utf8')
path = ''
if data:
path = data.splitlines()[0].split(' ')[1]
response_header = 'HTTP/1.1 200 OK\n'
if path == '/login':
response_body = '欢迎来到登录页面'
elif path == '/register':
response_body = '欢迎来到注册页面'
elif path == '/':
response_body = '欢迎来到首页'
else:
response_header = 'HTTP/1.1 404 Page Not Found\n'
response_body = '404 Page Not Found'
response_header += 'content-type:text/html;charset=utf8\n'
response_header += '\n'
response = response_header + response_body
client_socket.send(response.encode('utf8'))
server = MyServer('0.0.0.0', 8090)
server.run_forever()
WSGI服务器的介绍
from wsgiref.simple_server import make_server
# demo_app 需要两个参数
# 第0个参数,表示环境(电脑的环境;请求路径相关的环境)
# 第1个参数,是一个函数,用来返回响应头
# 这个函数需要一个返回值,返回值是一个列表
# 列表里只有一个元素,是一个二进制,表示返回给浏览器的数据
def demo_app(environ, start_response):
# environ 是一个字典(太长太长了就不列举了,自己运行查看,保存了很多数据)
# print(environ)
# 其中重要的一个是 PATH_INFO 能够获取到用户的访问路径
start_response('200 OK', [('Content-Type', 'text/html;charset=utf8')])
return ['hello'.encode('utf8')] # 浏览器显示的内容
if __name__ == '__main__':
# demo_app 是一个函数,用来处理用户的请求
httpd = make_server('', 8000, demo_app)
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
# 代码的作用是打开电脑的浏览器,并在浏览器里输入地址 http://localhost:8000/xyz?abc
# import webbrowser
# webbrowser.open('http://localhost:8000/xyz?abc')
# 处理一次请求
# httpd.handle_request() # serve one request, then exit
httpd.serve_forever() # 服务器在后台一直运行
from wsgiref.simple_server import make_server
def demo_app(environ, start_response):
path = environ['PATH_INFO']
print('path={}'.format(path))
start_response('200 OK', [('Content-Type', 'text/html;charset=utf8')])
return ['hello'.encode('utf8')]
if __name__ == '__main__':
httpd = make_server('', 8080, demo_app)
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
httpd.serve_forever()
WSGI不同路径返回不同内容
from wsgiref.simple_server import make_server
def demo_app(environ, start_response):
path = environ['PATH_INFO']
# 状态码: RESTFULL ==> 前后端分离
# 2XX:OK,请求响应成功
# 3XX:重定向
# 4XX:客户端错误 404:客户端访问了一个不存在的地址 405:请求方式不被允许
# 5XX:服务器的错误
status_code = '200 OK' # 默认状态码是 200
if path == '/':
response = '欢迎来到我的首页'
elif path == '/test':
response = '欢迎来到test页面'
elif path == '/demo':
response = '欢迎来到demo页面'
else:
status_code = '404 Not Found' # 如果页面没有设置,返回404
response = '页面走丢了'
start_response(status_code, [('Content-Type', 'text/html;charset=utf8')])
return [response.encode('utf8')]
if __name__ == '__main__':
httpd = make_server('', 8080, demo_app)
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
httpd.serve_forever()
读取文件并返回给浏览器
import json
from wsgiref.simple_server import make_server
def demo_app(environ, start_response):
path = environ['PATH_INFO']
# print(environ['QUERY_STRING']) # QUERY_STRING ==> 获取到客户端GET请求方式传递的参数
status_code = '200 OK'
if path == '/':
response = '欢迎来到我的首页'
elif path == '/test':
response = json.dumps({'name': 'zhangsan', 'age': 18})
elif path == '/demo':
with open('pages/xxxx.txt', 'r', encoding='utf8') as file:
response = file.read()
elif path == '/hello':
with open('pages/hello.html', 'r', encoding='utf8') as file:
response = file.read()
elif path == '/info':
# 查询数据库,获取到用户名
name = 'jack'
with open('pages/info.html', 'r', encoding='utf8') as file:
# flask, django 模板,渲染引擎
response = file.read().format(username=name, age=18, gender='男')
else:
status_code = '404 Not Found'
response = '页面走丢了'
start_response(status_code, [('Content-Type', 'text/html;charset=utf8')])
return [response.encode('utf8')]
if __name__ == '__main__':
httpd = make_server('', 8080, demo_app)
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
httpd.serve_forever()
方法的封装
import json
from wsgiref.simple_server import make_server
def load_file(file_name, **kwargs):
try:
with open('pages/' + file_name, 'r', encoding='utf8') as file:
content = file.read()
if kwargs: # kwargs = {'username': 'zhangsan', 'age': 19, 'gender': 'male'}
# 内容:{username},欢迎回来,你今年{age}岁了,你的性别是{gender}
content = content.format(**kwargs) # 拆包
return content
except FileNotFoundError:
print('文件未找到')
def demo_app(environ, start_response):
path = environ['PATH_INFO']
status_code = '200 OK'
if path == '/':
response = '欢迎来到我的首页'
elif path == '/test':
response = json.dumps({'name': 'zhangsan', 'age': 18})
elif path == '/demo':
response = load_file('xxxx.txt')
elif path == '/hello':
response = load_file('hello.html')
elif path == '/info':
response = load_file('info.html', username='zhangsan', age=19, gender='male')
else:
status_code = '404 Not Found'
response = '页面走丢了'
start_response(status_code, [('Content-Type', 'text/html;charset=utf8')])
return [response.encode('utf8')]
if __name__ == '__main__':
httpd = make_server('', 8080, demo_app)
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
httpd.serve_forever()
使用字典管理请求路径
import json
from wsgiref.simple_server import make_server
def load_file(file_name, **kwargs):
try:
with open('pages/' + file_name, 'r', encoding='utf8') as file:
content = file.read()
if kwargs: # kwargs = {'username': 'zhangsan', 'age': 19, 'gender': 'male'}
# 内容:{username},欢迎回来,你今年{age}岁了,你的性别是{gender}
content = content.format(**kwargs) # 拆包
return content
except FileNotFoundError:
print('文件未找到')
def index():
return '欢迎来到我的首页'
def show_test():
return json.dumps({'name': 'zhangsan', 'age': 18})
def show_demo():
return load_file('xxxx.txt')
def show_hello():
return load_file('hello.html')
def show_info():
return load_file('info.html', username='zhangsan', age=19, gender='male')
def show_foo():
return '我是foo'
url = {'/': index, '/test': show_test, '/demo': show_demo, '/hello': show_hello, '/info': show_info, '/foo': show_foo}
def demo_app(environ, start_response):
path = environ['PATH_INFO']
status_code = '200 OK'
method = url.get(path)
if method:
response = method()
else:
status_code = '404 Not Found'
response = '页面走丢了'
start_response(status_code, [('Content-Type', 'text/html;charset=utf8')])
return [response.encode('utf8')]
if __name__ == '__main__':
httpd = make_server('', 8080, demo_app)
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
httpd.serve_forever()
requests模块的介绍
# requests 模块是第三方的模块,可以用来发送网络连接
# pip install requests
import requests
response = requests.get('http://127.0.0.1:8090')
# 结果是一个 Response 对象
# print(response) # <Response [200]>
# content 指的是返回的结果,是一个二进制,可以用来传递图片
print(response.content.decode('utf8'))
# 获取到的结果就是一个文本
print(response.text)
print(response.status_code) # 200
# 如果返回的结果是一个 json 字符串,可以解析 json 字符串
# print(response.json())
r = requests.get('http://127.0.0.1:8090/test')
# 获取到json字符串
print(r.text, type(r.text)) # {"name": "zhangsan", "age": 18} <class 'str'>
j = r.json() # 把json字符串解析
print(j, type(j))