ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

网络编程和多线程

网络编程和多线程 1.网络编程网络将具有独立功能的多台计算机通过通信链路和通信设备连接起来在网络管理软件及网络通信协议下实现资源共享和信息传递的虚拟平台网络编程编写基于网络通信的软件或程序网络编程作用用来实现网络互连的不同计算机上运行的程序间可以进行数据交互1.1 网络编程三要素网编三要素IP地址是网络中的网络接口网卡的唯一标识用来定位计算机端口号用于在计算机中标识进程的逻辑地址通过端口号来找到指定的进程协议定义通信规则符合协议则可以通信不符合则无法正常通信1.1.1 ip地址IP地址是网络中的网络接口网卡的唯一标识用来定位计算机查看 IP 地址Linux 和 mac OS使用 ifconfig 命令Windows使用 ipconfig 命令检查网络是否正常使用 ping 命令ping www.baidu.com检查外网是否连通ping 局域网 ip 检查本机和局域网内另一台机器连通性验证是否在同一个局域网ping 127.0.0.1 回环地址检查本机 TCP/IP 协议栈是否正常工作1.1.2 端口号端口号用于在计算机中标识进程的逻辑地址通过端口号来找到指定的进程知名端口号指众所周知的端口号范围从 0 到 1023动态端口号一般程序员开发应用程序使用的端口号为动态端口号范围从 1024 到 65535netstat -ano命令‑aAll显示全部连接监听端口、已建立连接、TIME_WAIT 等‑nNumeric直接显示数字 IP 和端口不解析域名执行速度更快‑oOwner显示每个连接对应的进程 PID最重要1.1.3 协议TCP协议是一种面向连接的可靠的基于字节流的传输层通信协议TCP通信步骤创建连接传输数据关闭连接面向连接通信双方在传输数据之前需要建立连接可靠传输通过一套机制保证数据接收方收到完整无误、顺序正确的数据。TCP创建连接三次握手就是指 TCP 在建立连接的时候需要客户端和服务端总共发送3次包以确认连接的建立第一次握手客户端向服务端发送请求等待客户端确认第二次握手服务端收到请求后知道客户端请求建立连接回复给客户端以确认连接请求第三次握手客户端收到确认后再次发送请求确认服务器客户端收到正确请求后如果正确则连接建立成功完成三次握手随后客户端与服务端之间就可以传输数据了TCP断开连接四次挥手TCP连接是双向的A连接BB连接A都要断开第一次挥手当主机A可以是客户端也可以是服务端完成数据传输后提出断开连接请求第二次挥手主机B收到请求后作出回应确认这一方向上的TCP连接断开第三次挥手主机B完成数据传输后提出断开连接请求第四次挥手主机A收到请求后作出回应确认这一方向上的TCP连接断开1.2 套接字Socket1.2.1 基本概念套接字SocketIP地址端口号是操作系统提供给应用程序的编程接口API是进程间通信的一个工具注意应用程序不用直接操作 TCP/UDP 报文调用 socket 函数交给操作系统内核完成封装、发送、接收Socket 通俗理解网络通信的端点两个程序通过 Socket 完成跨主机数据传输Socket 通信网络编程最典型的实现方法即通信双方都拥有自己的 Socket 对象数据在 Socket 之间通过数据包UDP协议或者字节流TCP协议的形式进行传输1.2.2 Socket类Socket套接字能实现不同主机之间的进程间通信。Python 中有专门的 Socket 类import socket注意要使用 Socket 类需要使用socket 模块下的 socket 类来创建实例对象案例创建一个 Socket 对象1.3 TCP程序开发流程1.3.1 TCP程序开发流程TCP网络应用程序开发分为TCP客户端程序开发ps浏览器TCP服务端程序开发注意客户端程序指运行在用户设备上的程序主动发起连接主动去找服务器建立 TCP 连接服务端程序指运行在服务器设备上的程序专门为客户端提供数据服务被动等待连接守在 IP 端口上监听别人来连服务器端 socket创建监听套接字也叫做被动套接字只负责接受新来的客户端连接accept在服务器端该函数会返回一个新的套接字通信套接字用来和客户端收发信息close在服务器端关闭和客户端1客户端2...客户端n的通信套接字不主动关闭与所有的客户端通信结束服务器端关闭监听套接字被动套接字不主动关闭参数解释当服务器端有了socket对象后常用于服务器端的函数如下当客户端有了socket对象后常用于客户端的函数如下能同时应用于客户端和服务器端的函数如下案例客户端和服务器端相互发送一句话消息服务器端代码如下import socket #1.创建服务器端socket对象用于监听客户端连接 listen_socket socket.socket(socket.AF_INET,socket.SOCK_STREAM) #2.绑定IP地址和端口号 listen_socket.bind((127.0.0.1,8080)) #3.开始监听客户端连接 listen_socket.listen(5) #4.接受客户端连接,返回通信socket对象和客户端地址 communication_socket,client_address listen_socket.accept() #5.接收客户端数据,最多接收1024字节 data communication_socket.recv(1024).decode(utf-8) #6.发送数据给客户端,b标识将字符串转为2进制形式(有中文不行) communication_socket.sendall(bHello, Client!) #7.关闭通信socket对象,监听socket一般不关闭 communication_socket.close()客户端代码如下import socket #1.创建socket对象 client_socket socket.socket(socket.AF_INET,socket.SOCK_STREAM) #2.连接服务器 client_socket.connect((127.0.0.1,8080)) #3.发送数据 client_socket.sendall(bHello, Server!) #4.接收数据 data client_socket.recv(1024).decode(utf-8) print(f客户端收到:{data}) #5.关闭socket对象 client_socket.close() print(客户端已关闭)1.3.2 编解码问题在网络中数据是以二进制编码的形式进行传递的所以在网络中传输数据的时候就牵涉到数据的编码和解码问题。编码 encode字符串 → 字节 bytes发送数据用人看懂的文字转成网络可以传输的二进制字节。解码decode字节 bytes → 字符串接收数据用网卡收到的二进制字节转回人能看懂的文字注意只要出现乱码问题就一定是编解码方式不一致的原因英文字母数字特殊符号在任何码表中都占一个字节并且UTF‑8、GBK、ASCII 这几种常用编码里它们的单字节二进制和 ASCII 完全一样中文无法使用 ASCII 编码它在gbk中占2个字节在utf-8中占3个字节编码语法字符串.encode码表解码语法二进制.decode码表二进制数据的特殊写法b英文字母 数字 特殊符号该方式针对中文无效案例中文字符串编解码1.3.3 端口号重用问题扩展意义不大当客户端和服务端建立连接后服务端程序退出后端口号不会立即释放需要大概等1-2mins解决办法有两种更换服务端端口号设置端口号复用让服务端程序退出后端口号立即释放一般目前的电脑都自动支持端口号复用设置端口号复用代码如下1.3.4 TCP网络程序注意点TCP服务端程序必须绑定端口号否则客户端找不到这个 TCP 服务端程序为了更稳定建议把 IP 也绑定accept () 前的套接字是被动套接字监听套接字只负责接收新的客户端的连接请求不能收发消息accept函数调用后当 TCP 客户端程序和 TCP 服务端程序连接成功后 TCP 服务器端程序会产生一个新的套接字通信套接字用于收发客户端消息对于服务器端的被动 socket关闭时需慎重当客户端的套接字调用 close 后服务器端的 recv 会解阻塞返回的数据长度为 0用于判断客户端是否已经下线1.3.5 文件上传与文件下载文件上传客户端给服务端传送文件文件下载服务端给客户端传送文件下列代码以文件上传为例文件下载与此同理只需调换收发双方即可案例客户端给服务器端上传文件服务端代码如下import socket #1.创建服务器端socket对象用于监听客户端连接 listen_socket socket.socket(socket.AF_INET,socket.SOCK_STREAM) #2.绑定IP地址和端口号 listen_socket.bind((127.0.0.1,8081)) #3.开始监听客户端连接 listen_socket.listen(5) #4.接受客户端连接,返回通信socket对象和客户端地址 communication_socket,client_address listen_socket.accept() #5.读取客户端上传的文件数据 #5.1 关联目的地文件 with open(./Data/received_file.txt,ab) as f: # 5.2 循环读取数据 while True: #5.3 读取数据 data communication_socket.recv(8192) #5.4 判断数据是否为空 if len(data) 0: break #5.5 将读取到的数据写入本地文件 f.write(data) #6.关闭通信socket对象,监听socket一般不关闭 communication_socket.close() print(文件上传成功)客户端代码如下import socket #1.创建socket对象 client_socket socket.socket(socket.AF_INET,socket.SOCK_STREAM) #2.连接服务器 client_socket.connect((127.0.0.1,8081)) #3.发送文件 #3.1 关联数据源文件 with open(./Data/send_file.txt, rb) as f: #3.2 循环读取数据 while True: #3.3 具体的读取操作 data f.read(8192) #3.4 及时发送数据 client_socket.sendall(data) #3.5 判断数据是否读取完毕 if len(data) 0: break #4.关闭socket对象 client_socket.close() print(客户端已关闭)2.多任务2.1 多任务概念多任务在同一时间段内计算机内执行多个任务多任务作用多个任务同时执行能够充分利用CPU资源大大提高程序执行效率多任务的两种表现形式并行同一时间点同时执行多个任务并发同一时间段内交替执行多个任务给人的感觉好像是并行但其实同一时间点只有一个任务在执行多任务有两种实现手段进程和线程注意单核CPU是并发执行多任务的多核是并行执行多任务的图解如下2.2. 进程进程是CPU分配资源的最小单位是操作系统进行资源分配和调度运行的基本单位在不引入线程的前提下一个进程就是程序的一次执行进行中2.2.1 多进程实现Python 中多进程的基本方式程序运行起来形成主进程main在主进程上创建子进程进程的创建步骤1.导入工具包import multiprocessing2.通过包内的进程类创建实例进程对象子进程对象 multiprocessing.Process()3.启动进程执行任务进程对象.start()2.2.1.1 多进程执行无参函数案例使用多进程来模拟一边编写代码一边听音乐import multiprocessing as mul import time #coding函数 def coding(): for i in range(3): print(编写coding中......) time.sleep(1) #听音乐函数 def listen_music(): for i in range(3): print(听音乐中......) time.sleep(1) if __name__ __main__: #分别创建进程对象 coding_process mul.Process(targetcoding) music_process mul.Process(targetlisten_music) #执行进程 coding_process.start() music_process.start()注意由于我的电脑是多核处理器两个进程是并行进行的2.2.1.2 多进程执行有参函数案例使用多进程模拟小明一边编写 num 行代码一边听 count 首音乐的功能import multiprocessing as mul import time #coding函数 def coding(num): for i in range(num): print(编写coding中......) time.sleep(2) #听音乐函数 def listen_music(count): for i in range(count): print(听音乐中......) time.sleep(2) if __name__ __main__: #分别创建进程对象 coding_process mul.Process(targetcoding, args(5,)) music_process mul.Process(targetlisten_music, args(5,)) #执行进程 coding_process.start() music_process.start()2.2.2 获取进程的PID进程标识符 / 进程编号PID在一台计算机内唯一标识一个进程方便进程管理在一个操作系统中一个进程的进程号是唯一的进程号可以反复使用获取进程编号目的验证主进程和子进程的关系可以得知子进程是由哪个主进程创建出来的获取进程编号的两种操作获取当前进程号获取当前父进程号获取进程编号需要导入 os 模块1.os.getpid/ 子进程名.pid获取当前进程编号import os pid os.getpid() #或者 pid multiprocessing.current_process().pid2.os.getppid获取父进程编号import os ppid os.getppid()注意main 函数中创建的进程如果没有特殊指定他的父进程都是 main 进程而 main 进程的父进程是 pycharm2.2.3 进程注意事项2.2.3.1 进程之间不共享数据进程之间不共享全局变量子进程相当于父进程的“副本”main 内的资源子进程完全没有连副本都不存在main 外资源拷贝一份案例定义一个公共容器一个进程负责写数据一个进程负责读数据import multiprocessing as mul import time #1.定义公共容器 list1 [] #2.定义写函数 def write(): for i in range(10): list1.append(i) print(f添加数据:{i}) print(fwrite函数:{list1}) #3.定义读函数 def read(): time.sleep(3) print(fread函数:{list1}) #4.main外资源 print(f我是main外资源,看我执行了几次) #5.主函数main if __name__ __main__: #6. 创建两个子进程 p1 mul.Process(targetwrite) p2 mul.Process(targetread) #7. 启动子进程 p1.start() p2.start() #8.main内资源 print(f我是main内资源,看我执行了几次)结果如下我是main外资源,看我执行了几次 我是main内资源,看我执行了几次 我是main外资源,看我执行了几次 添加数据:0 添加数据:1 添加数据:2 添加数据:3 添加数据:4 添加数据:5 添加数据:6 添加数据:7 添加数据:8 添加数据:9 write函数:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 我是main外资源,看我执行了几次 read函数:[]图解2.2.3.2 主进程会等待所有子进程执行结束再结束默认情况下主进程会等待所有子进程执行结束再结束为了不让主进程等待子进程有如下几种方法方法1子进程设置守护主进程推荐人在塔在塔亡人亡目的主进程退出子进程便销毁不让主进程再等待子进程结束代码如下注意守护进程退出非守护进程不受影响非守护进程退出所有守护进程立即跟着退出方法2子进程自己主动终止不推荐守护进程或子进程提前结束2.3 线程线程是CPU调度的基本单位。每个进程至少都有一个线程而这个线程就是我们通常讲的主线程作用充分利用CPU资源用来实现多任务编程提高程序执行效率进程是CPU资源分配的基本单位一旦创建一个进程就会分配一定的资源线程依附于进程执行线程间共享同一进程的资源图解如下2.3.1 多线程实现线程的创建步骤1.导入工具包import threading2.通过包内的线程类创建实例线程对象线程对象 threading.Thread()3.启动进程执行任务线程对象.start()2.3.1.1 多线程执行无参函数案例使用多线程来模拟一边写代码一边听音乐import threading import time #coding函数 def coding(): for i in range(3): print(编写coding中......) time.sleep(1) #听音乐函数 def listen_music(): for i in range(3): print(听音乐中......) time.sleep(1) if __name__ __main__: #分别创建线程对象 coding_thread threading.Thread(targetcoding) music_thread threading.Thread(targetlisten_music) #执行进程 coding_thread.start() music_thread.start()图解如下2.3.1.2 多线程执行有参函数案例使用多线程模拟小明一边编写 num 行代码一边听 count 首音乐的功能import threading import time #coding函数 def coding(num): for i in range(num): print(编写coding中......) time.sleep(2) #听音乐函数 def listen_music(count): for i in range(count): print(听音乐中......) time.sleep(2) if __name__ __main__: #分别创建进程对象 coding_thread threading.Thread(targetcoding, args(5,)) music_thread threading.Thread(targetlisten_music, args(5,)) #执行进程 coding_thread.start() music_thread.start()2.3.2 线程注意事项2.3.2.1 线程之间执行是无序的线程之间执行是无序的它是由 CPU 调度决定的CPU 调度哪个线程哪个线程就执行没有调度的线程是不能执行的CPU调度资源的策略均分时间片抢占式调度2.3.2.2 主线程会等待所有子线程执行结束后再结束默认情况下主线程会等待所有子线程执行结束后再结束。我们可以设置守护主线程来解决守护主线程主线程退出子线程便跟着销毁不再执行守护主线程方式线程名 threading.Thread(target 函数名, daemon True)2.3.2.2 线程之间共享同进程的资源线程之间共享全局变量但是可能会出现安全问题导致错误可以使用互斥锁来解决为什么会有安全问题比如线程1还没有来得及执行完函数的完整动作就被线程2抢走了资源就可能出现问题互斥锁当线程获得资源时便给该资源上锁其他线程无法访问当线程的函数动作执行完毕后释放资源其他的线程便可以访问2.3.3 互斥锁互斥同一资源同一时间只允许一个任务使用同步多个任务按预定次序、协调先后执行同一时间必须互斥访问互斥锁对共享数据进行锁定保证同一时间只有一个线程去操作注意互斥锁是多个线程一起去抢抢到锁的线程先执行没有抢到锁的线程进行等待等锁使用完释放后其他线程再去抢这个锁死锁多个进程 / 线程互相持有对方需要的资源又同时等待对方释放资源谁都无法继续执行若无外力介入将永远阻塞这就是死锁。互斥锁的使用流程1.创建互斥锁对象mutex threading.Lock()2.上锁mutex.acquire()3.释放锁mutex.release()案例定义两个函数实现循环10000次每循环一次给全局变量1创建两个子线程执行对应的两个函数import threading #1.定义全局变量 global_num 0 #2.创建互斥锁实例对象用于实现对全局变量的互斥访问 mutex threading.Lock() #3.定义两个函数实现加法 def func1(): #4.互斥锁 #4.1 加锁 mutex.acquire() for i in range(10000): global global_num global_num 1 #4.2 解锁 mutex.release() print(ffunc1:, global_num) def func2(): #4.互斥锁 #4.1 加锁 mutex.acquire() for i in range(10000): global global_num global_num 1 #4.2 解锁 mutex.release() print(ffunc2:, global_num) if __name__ __main__: #创建子线程 sum1_thread threading.Thread(targetfunc1) sum2_thread threading.Thread(targetfunc2) #启动线程 sum1_thread.start() sum2_thread.start()小结2.4 线程和进程关系进程和线程都是完成多任务的一种方式2.4.1 关系对比线程是依附于进程的没有进程就没有线程一个进程默认提供一条线程进程可以创建多个线程2.4.2 区别对比进程之间不共享全局变量线程之间共享全局变量但是要注意资源竞争问题可以用互斥锁解决创建进程的资源开销大于创建线程的资源开销进程是操作系统资源分配的基本单位线程是CPU调度的基本单位线程无法独立执行必须依附在进程中Python 中多进程开发比单进程多线程开发稳定性要强2.4.3 优缺点对比进程优点可以使用CPU的多核并行执行资源隔离稳定性强缺点资源开销大线程优点资源开销小缺点无法使用CPU的多核并行执行只能串行执行共享同进程资源存在资源竞争
返回列表