python 学习
时间:2022-09-04 03:00:00
python 学习
开发常用网站
- 博客园
- csdn
- 51cto
- 开源中国
- github
- 知乎
- 简书
1 网络编程
1-1 网络基本概念
- 基本概念
- 数据共享
- IP地址
- 值:192.168.0.0.1
- 功能:在网络上标记计算机
- 私有 ip
在众多网络中,国际规定有一部分IP地址用于我们的局域网,即私网IP,不再围为:
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0.0~192.168.255.255注意: IP 地址 127.0.0.1 ~ 127.255.255.255 用于回路测试
- Linux、Windows查看网卡信息
- Linux
- sudo ifconfig ens40 up/down
- Windows
- ipconfig
lo:本地网卡
ens40:外网网卡
- ipconfig
- IP地址的分类-ipv4和ipv6介绍
- ipv4 : 0-255.0-255.0-255.1-255
192.168.0.0.1- A 0
- B 10
- C 110
- D 1110 多点广播
- E 11110 保留
0/255 不能用;1-254用
类型 | 网络号 | 网络号 |
---|---|---|
A | 0******* | ..******** |
B | 10******.******** | . |
C | 110*****.. | ******** |
- ipv6
fe80::4a3:4ee2:d0cd:f011
- (重点)端口
- 给出哪个过程? port
- 端口用端口号标记,端口号用程序标记。
- 端口分类:知名端口、动态端口
端口号不是随意使用的,而是按照一定的规定分配的。
- 知名端口:0~1023
- 动态端口:1024~65535
- socket介绍
- 以前如何通信不同电脑上的过程?
- 创建 socket
import socket # 创建 tcp 套接字 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # ... 这里使用套接字的功能(省略)... # 不使用时,关闭套接字 s.close() # 创建 udp 套接字 s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # ... 这里使用套接字的功能(省略)... # 不使用时,关闭套接字 s.close()
说明:
套接字的使用过程与文件的使用过程非常相似
1.创建套接字
2.使用套接字收/发数据
三、关闭套接字
1-2 udp 用户数据报告协议
运行模式
- python3 ***.py
交互模式 - python3
- ipython3
- (重点)udp 发送数据 demo
# demo #!/usr/bin/env python3 import socket def main(): # 创建一个tcp套接字 udp_socket = socket.socket
(socket .AF_INET ,socket .SOCK_DGRAM ) # 从 键盘发送数据 send_data = input ( "请输入要发送的数据:" ) # 可以使用套接字收发数据 # 元组 dest_ip / b"hhahah" 字符类型,加一个 b 转译 udp_socket .sendto ( send_data .encode ( 'utf-8' ) , ( "10.10.22.68" , 8088 ) ) # 关闭套接字 udp_socket .close ( ) if __name__ == "__main__" : main ( ) # demo-2 循环发 #!/usr/bin/env python3 import socket def main ( ) : # 创建一个tcp套接字 udp_socket = socket .socket (socket .AF_INET ,socket .SOCK_DGRAM ) while True : # 从键盘发送数据 send_data = input ( "请输入要发送的数据:" ) # 如果输入的数据是 exit,那么久退出程序 if send_data == 'exit' : break ; # 可以使用套接字收发数据 # 元组 dest_ip / b"hhahah" 字符类型,加一个 b 转译 udp_socket .sendto ( send_data .encode ( 'utf-8' ) , ( "10.10.22.68" , 8088 ) ) # 关闭套接字 udp_socket .close ( ) if __name__ == "__main__" : main ( )
- (重点)接收 udp 数据
注意:windows 的编码默认是 gbk
#!/usr/bin/env python3
import socket
def main():
# 1 创建套接字
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 2 绑定一个本地信息
localaddr = ("",7788)
udp_socket.bind(localaddr) # 必须绑定自己的 ip,以及 port
# 3 接收数据
while True:
recv_data = udp_socket.recvfrom(1024) # 1024 本次接收的最大字节数
# recv_data 这个变量中存储的是一个元组,(接收到的数据,(发送方的IP,port))
recv_msg = recv_data[0] # 存储接收的数据
send_addr = recv_data[1] # 存储发送方的地址信息
# 4 打印接收的数据
# print(recv_data)
print("%s:%s"%(str(send_addr),recv_msg.decode("utf-8")))
# 5 关闭套接字
udp_socket.close()
if __name__ == "__main__":
main()
- 总结:udp 发送/接收 数据流程
udp 发送数据流程 | udp 接收数据流程 |
---|---|
1.绑定套接字 2.发送数据 3.关闭套接字 | 1.绑定套接字 2.绑定本地信息 3.接收打印数据 4.关闭套接字 |
- (重点)端口绑定的问题
- 同一个端口不允许同一时刻,被用两次
- 发送方可以不绑定端口,接收方必须先绑定端口
- 输入对方 ip、port、全双工、半双公、单工等
-
- recvfrom 在数据没到来之前,会堵塞程序
-
- 单工:只能收;
-
- 半双工:能收发,不能同一时刻操作;
-
- 全双工:同一时刻能收能发;
-
- socket: 全双工
-
- 案例:udp 聊天机器
#!/usr/bin/env python3
import socket
def send_msg(udp_socket):
"""发送消息"""
dest_ip = input("请输入对方的 ip:")
dest_port = int(input("请输入对方的 port:"))
send_data = input("请输入要发送的消息:")
udp_socket.sendto( send_data.encode('utf-8'), (dest_ip, dest_port))
def recv_msg(udp_socket):
"""接收数据"""
recv_data = udp_socket.recvfrom(1024) # 1024 本次接收的最大字节数
print("%s:%s" % (str(recv_data[1]), recv_data[0].decode("utf-8"))) # windows 是 gbk
def main():
# 创建套接字
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 绑定信息
udp_socket.bind(("",7788))
# 循环来处理的事情
while True:
# 发送
send_msg(udp_socket)
# 接收
recv_msg(udp_socket)
if __name__ == "__main__":
main()
1-3 tcp 客户端 - 传输协议
- 创建 tcp 套接字
- ** 连接服务器 **
- 发送/接收 数据
- 关闭套接字
#!/usr/bin/env python3
import socket
def main():
# 创建 tcp 套接字
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
server_ip = input("请输入服务器的ip:")
server_port = int(input("请输入服务器的port:"))
server_addr = (server_ip,server_port)
tcp_client_socket.connect(server_addr)
# 发送/接收 数据
send_data = input("请输入发送的数据:")
tcp_client_socket.send(send_data.encode("utf-8"))
# 关闭套接字
tcp_client_socket.close()
if __name__ == "__main__":
main()
1-4 tcp 服务器
- socket 创建一个套接字
- bind 绑定 ip和port
- listen 使套接字变为可以被动连接
- accept 等待客户端的连接
- recv/send 接收发送数据
监听套接字负责接收新的套接字
accept产生新的套接字,为客户服务
#!/usr/bin/env python3
import socket
def main():
# 买个手机(创建套接字 socket)
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 插入手机卡(绑定本地信息 bind)
tcp_server_socket.bind("",7890)
# 将手机设置为正常的接听状态 (让默认的套接字由主动变为被动 listen)
tcp_server_socket.listen(128) # 128同一时刻客户端连接服务器的数量
# 循环为多个客户端服务
while True:
print("等待一个新的客户端的到来。。。")
# 等待别人的电话的到来(等待客户端的连接 accept)
new_client_socket,client_addr = tcp_server_socket.accept()
print("一个新的客户端已经到来%s" % str(client_addr))
# 循环多次为同一个客户端服务多次
while True:
# 接收对方发来的数据
revc_data = new_client_socket.recv(1024) # 接收1024个字节
print("客户端发送过来的请求是:%s" % recv_data.decode('gbk'))
# 如果 recv 解堵塞,那么有2种方式
# 1. 客户端发送过来数据
# 2. 客户端调用close导致了 recv 解堵塞
if revc_data:
# 回送一部分数据给客户端
new_client_socket.send("thank you!".encode('gbk'))
else:
break
# 关闭accept返回的套接字,意味着不会再为这个客户端服务
new_client_socket.close()
print("已经服务完毕。。。")
# 如果将监听套接字关闭了,那么会导致不能再吃等待客户端的到来,即 xxxx.accept就会失败
tcp_server_socket.close()
if __name__ == "__main__":
main()
1-5 tcp 下载文件
文件下载器
#!/usr/bin/env python3
#coding:utf-8
# client
import socket,os
def
def main():
# 创建套接字
tcp_socket = socket.socket()
# 获取服务器的 ip port
tcp_socket.connect()
# 连接服务器
# 将文件名字发送到服务器
file_name = input("请输入文件名称:")
# 接收文件数据
# 保存接收数据到一个文件中
# 关闭套接字
if __name__ == "__main__":
main()
#!/usr/bin/env python3
# coding:utf-8
# server
import socket,os
def
def main():
if __name__ == "__main__":
main()
udp 与 tcp
udp: 写信的模型,不安全
tcp: 打电话的模型,比较安全;比较稳定; 采用发送应答机制
— 对比 —
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠传输,不使用流量控制和拥塞控制 可靠传输,使用流量控制和拥塞控制 | |
连接对象个数 | 支持一对一,一对多,多对一和多对多交互通信 只能是一对一通信 | |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
适用场景 | 适用于实时应用(IP电话、视频会议、直播等) | 适用于要求可靠传输的应用,例如文件传输 |
udp | tcp | |
---|---|---|
客户端流程 | socket\bind\sendto/recvfrom\close | socket\connect\send/recv\close |
服务端流程 | socket\bind\listen\accept\recv/send\close |
tcp 总结
- tcp服务器一般情况下都需要绑定,否则客户端找不到这个服务器
- tcp客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机
- tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的
- 当客户端需要链接服务器时,就需要使用connect进行连接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信
- 当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务
- listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的
- 关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信
- 关闭accept返回的套接字意味着这个客户端已经服务完毕
- 当客户端的套接字调用close后,服务端会recv解堵塞,并且返回的长度为0,因此可以通过返回数据的长度来区别客户端是否已经下线
2 多任务
简介
- 多任务
什么叫“多任务”,简单地说,就是操作系统可以同时做多个任务。- 单核CPU 要实现多任务,通过调度算法实现,如:时间片轮转、优先级调度等;四核CPU相当于4个单核CPU。
- 并发: 任务量大于CPU核数,通过操作系统的各种调度算法,实现多个任务“一起”执行(实际上由于切换任务的速度非常快,只是看上去一起执行,并没有真正的同时执行。)
- 并行: 任务量小于等于CPU核数,级任务是真正的一起执行的。
- 进程
进程是具有一定独立功能的程序(就是一坨代码,还没有运行时叫程序)关于某个数据集合上的一次运行活动 (是运行的程序),进程是系统进行资源分配的单位。- 线程
线程是进程的一个实体,是CPU调度的单位 ,它是比进程更小的能独立运行的基本单位。线程基本上不拥有系统资源,只拥有一点运行中必不可少的资源(如:程序计数器、一组寄存器和栈)。
进程和线程之间的关系: 举个简单的例子:一个手机中运行了好多后台的APP程序,如微信、QQ、支付宝…,其中,一个进程就是运行中的QQ,而QQ中可以跟不同的人进行聊天,每个聊天的窗口就是一个线程,你可以同时跟好多人聊天(即,开好多个聊天窗口,也就是说一个进程中可以由好多线程 ),但是当一个聊天窗口卡死了,QQ就不能运行了(一个线程死掉就等于整个进程死掉 ),只能强制把它关了然后重启,但是你QQ挂了,并不影响你的微信和支付宝的运行(进程有独立的地址空间,一个进程崩溃后,不会对其他进程产生影响 ),同时你可以在不同的聊天窗口发送相同的QQ表情包,但是你不能在微信里发送QQ里的表情包(同进程里的多线程之间共享内存数据,不同进程之间是相互独立的,各有个的内存空间 )。
#!/usr/bin/env python3
#coding=utf-8
import threading
import time
def sing():
"""唱歌5秒钟"""
for i in range(5):
print("---唱歌---")
time.sleep(1)
def dance():
"""跳舞5秒钟"""
for i in range(5):
print("---跳舞---")
time.sleep(1)
def main():
t1 = threading.Thread(target=sing) # 注意:不能写成 sing();加括号是调用函数,不加括号是找到函数的位置
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
if __name__ == "__main__":
main()
2-1 线程
- 线程的运行是没有先后顺序的
- 查看程序中所有线程数量:threading.enumerate()
- 当调用Thread的时候,不会创建线程;调用start才会运行子线程
- 延时的话可以保证先运行哪个程序,再运行哪个程序
线程创建的两种方法:
- Thread(target=函数名);简单优先使用
- 通过继承Thread类完成创建线程;必须定义 run 方法
#!/usr/bin/env python3
#coding=utf-8
import threading
import time
def test1():
for i in range(5):
print("----test1---%d---" % i)
time.sleep(1)
# 如果创建Thread时执行的函数,运行结束那么意味着 这个子线程结束了....
def main():
# 在调用Thread之前先打印当前线程信息
print(threading.enumerate())
t1 = threading.Thread(target=test1)
# 在调用Thread之后打印
print(threading.enumerate())
t1.start()
# 在调用start之后打印
print(threading.enumerate())
if __name__ == "__main__":
main()
#!/usr/bin/env python3
#coding=utf-8
import threading
import time
def test1():
for i in range(5):
print("----test1---%d---" % i)
time.sleep(1)
# 如果创建Thread时执行的函数,运行结束那么意味着 这个子线程结束了....
def test2():
for i in range(10):
print("----test2---%d---" % i)
time.sleep(1)
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
t2.start()
while True:
if len(threading.enumerate()) == 1:
break;
print(threading.enumerate())
time.sleep(1)
if __name__ == "__main__":
main()
import threading
import time
# 通过继承 Thread 类完成创建线程
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm"+self.name+'@'+str(i) # name属性中保存的是当前线程的名字
print(msg)
self.login()
def login(self):
print("---login---")
if __name__ == '__main__':
t = MyThread()
t.start()
- 多线程共享全局变量
在一个函数中对全局变量进行修改的时候,到底是否需要使用global进行说明。
要看是否对全局变量的执行指向进行了修改,
如果修改了执行,即让全局变量指向了一个新的地方,那么必须使用 global
如果,仅仅是修改了指向空间中的数据,此时不用必须使用global
import threading
import time
g_num = 100
def test1():
global g_num
g_num += 1
print("---in test1 g_num=%d ---" % g_num)
def test2():
print("---in test2 g_num=%d ---" % g_num)
def test3(temp):
temp.append(33)
print("---in test3 g_num=%s ---" % str(temp))
g_nums = [11, 22]
def main():
# target 指定将来 这个线程去那个函数执行代码
# args 指定将来调用函数的时候传递什么数据过去
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t3 = threading.Thread(target=test3, args=(g_nums,)) # 元组
t1.start()
time.sleep(1)
t2.start()
time.sleep(1)
t3.start()
print("---in main thread g_num=%d ---" % g_num)
if __name__ == "__main__":
main()
- 创建线程时指定传递的参数、多线程共享全局变量的问题
- 资源竞争
import threading
import time
# 假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各对g_num加10次,g_num的最终的结果应该为20。
# 但是由于是多线程同时操作,有可能出现下面情况:
# 1、在g_num=0时,t1取得g_num=0。此时系统把t1调度为”sleeping”状态,把t2转换为”running”状态,t2也获得g_num=0
# 2、然后t2对得到的值进行加1并赋给g_num,使得g_num=1
# 3、然后系统又把t2调度为”sleeping”,把t1转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。
# 4、这样导致虽然t1和t2都对g_num加1,但结果仍然是g_num=1
g_num = 0
def test1(num):
global g_num
for i in range(num):
g_num += 1
print("---in test1 g_num=%d ---" % g_num)
def test2(num):
global g_num
for i in range(num):
g_num += 1
print("---in test2 g_num=%d ---" % g_num)
def main():
"""开启两个线程,修改同一个全局变量"""
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
time.sleep(5)
print("---in thread g_num=%d ---" % g_num)
if __name__ == "__main__":
main()
-
-
同步概念
同步就是协同步调,按预定的先后次序进行运行,如:你说完,我再说。 ‘同’字从字面上容易理解为一起动作
其实不是,‘同’字应是指协同,协助,互相配合。 -
互斥锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
类似于数据库操作的事务。
互斥锁为资源引入一大状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;
直到该线程释放资源,将资源的状态变成”非锁定“,其他的线程才能再次锁定该资源。
互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
基本操作:
创建锁 -