网络通信和多线程

1.网络通信

1.1 UDP通信

UDP 英文全拼 (User Datagram Protocol) 简称用户数据报协议, 它是无连接的、不可靠的网络传输协议。

优点 : 不需要连接 资源开销小 传输速度快 每个数据包64K
缺点 : 数据传输不安全容易丢包 没有流量控制.会导致缓冲区的数据堆积。

函数 socket.socket 创建一个 socket,该函数带有两个参数:
Address Family:IP地址类型; AF_INET表示ipv4类型、AF_INET6表示ipv6类型; Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)

// 发送接收数据-客户端
import socket

# 1. 创建udp套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 2. 准备接收方的地址
dest_addr = ('127.0.0.1', 8080)

# 3. 从键盘获取数据
send_data = input("请输入要发送的数据:")

# 4. 发送数据到指定的电脑上
udp_socket.sendto(send_data.encode('utf-8'), dest_addr)

# 5. 等待接收对方发送的数据
recv_data = udp_socket.recvfrom(1024)  # 1024表示本次接收的最大字节数

# 6. 显示对方发送的数据
# 接收到的数据recv_data是一个元组
# 第1个元素是对方发送的数据
# 第2个元素是对方的ip和端口
print(recv_data[0].decode('gbk'))
print(recv_data[1])

# 7. 关闭套接字
udp_socket.close()
// 发送接收数据 -服务器
#coding=utf-8

from socket import *

# 1. 创建套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)

# 2. 绑定本地的相关信息,如果一个网络程序不绑定,则系统会随机分配
local_addr = ('', 7788) #  ip地址和端口号,ip一般不用写,表示本机的任何一个ip
udp_socket.bind(local_addr)

# 3. 等待接收对方发送的数据
recv_data = udp_socket.recvfrom(1024) #  1024表示本次接收的最大字节数

# 4. 显示接收到的数据
print(recv_data[0].decode('gbk'))

# 5. 关闭套接字

1.2 TCP通信

13.2 TCP

TCP:英文全拼(Transmission Control Protocol)简称传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层通信协议, TCP通信需要经过创建连接、数据传送、终止连接三个步骤。

优点: 可靠,稳定 适合传输大量数据
缺点: 传输速度慢 占用系统资源高

// TCP客户端
import socket

# 创建tcp socket
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 目的信息
server_ip = input("请输入服务器ip:")
server_port = int(input("请输入服务器port:"))

# 链接服务器
tcp_client_socket.connect((server_ip, server_port))

# 提示用户输入数据
#send_data = input("请输入要发送的数据:")
send_data = "".join([x for x in range(10)])
tcp_client_socket.send(send_data.encode("gbk"))

# 接收对方发送过来的数据,最大接收1024个字节
recvData = tcp_client_socket.recv(1024)
print('接收到的数据为:', recvData.decode('gbk'))

# 关闭套接字
tcp_client_socket.close()
//TCP服务器
import socket

# 创建socket
tcp_server_socket = socket(socket.AF_INET, socket.SOCK_STREAM)

# 本地信息
address = ('127.0.0.1', 8080)

# 绑定
tcp_server_socket.bind(address)


# 设置监听
# 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的链接了
# 128:表示最大等待连接数
tcp_server_socket.listen(128)

# 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务
# client_socket用来为这个客户端服务
# tcp_server_socket就可以省下来专门等待其他新客户端的链接
client_socket, clientAddr = tcp_server_socket.accept()

# 接收对方发送过来的数据
recv_data = client_socket.recv(1024)  # 接收1024个字节
print('接收到的数据为:', recv_data.decode('gbk'))

# 发送一些数据到客户端
client_socket.send("thank you !".encode('gbk'))

# 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接
client_socket.close()

2. 文件下载器

2.1 服务端

import socket
import os

def main():
    # 创建tcp服务端socket
    tcp_serve_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置socket选项,防止程序退出端口不立即释放的问题
    tcp_serve_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 绑定端口
    tcp_serve_socket.bind(("", 9090))
    # 设置监听,把主动套接字改成被动套接字,被动套接字只能接收客户端的连接请求,不能收发消息
    tcp_serve_socket.listen(128)
    # 循环接收客户端的连接请求, 提示:现在的下载是同步下载,一个用户下载完成以后另外一个用户才能下载
    while True:
        # 接收客户端的连接请求
        service_client_socket, ip_port = tcp_serve_socket.accept()
        # 代码执行到说明解阻塞,说明连接建立成功
        file_name_data = service_client_socket.recv(1024)
        # 解码数据
        file_name = file_name_data.decode("gbk")
        print(file_name, ip_port)
        if os.path.exists(file_name):
            # 文件存在
            with open(file_name, "rb") as file:
                # 读取文件数据
                while True:
                    file_data = file.read(1024)
                    if file_data:
                        # 发送文件数据给客户端
                        service_client_socket.send(file_data)
                    else:
                        break
        else:
            print("文件不存在")
        # 终止和客户端服务
        service_client_socket.close()
    # 终止提供处理连接请的服务
    tcp_serve_socket.close()

2.2 客户端

import socket

def main():
    # 创建tcp客户端socket
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 建立连接
    tcp_client_socket.connect(("192.168.107.160", 9090))
    # 获取用户输入文件名
    file_name = input("请输入您要下载的文件名:")
    # 使用gbk进行编码
    file_name_data = file_name.encode("gbk")
    # 代码执行到此,说明连接建立成功
    tcp_client_socket.send(file_name_data)
    with open("/home/python/Desktop/" + file_name, "wb") as file:
        # 循环接收服务端发送的文件二进制数据
        while True:
            # 获取服务端文件数据
            file_data = tcp_client_socket.recv(1024)
            if file_data:
                # 写入到指定文件
                file.write(file_data)
            else:
                break

    # 关闭socket
    tcp_client_socket.close()

3.多线程

3.1 线程

线程的特点:线程之间执行是无序的 主线程会等待所有的子线程结束后才结束(线程主要处理cpu密集型)。

import threading
import time

# 唱歌任务
def sing(num):
    # 扩展: 获取当前线程
    # print("sing当前执行的线程为:", threading.current_thread())
    for i in range(num):
        print("正在唱歌...%d" % i)
        time.sleep(1)

# 跳舞任务
def dance(num):
    # 扩展: 获取当前线程
    # print("dance当前执行的线程为:", threading.current_thread())
    for i in range(num):
        print("正在跳舞...%d" % i)
        time.sleep(1)


if __name__ == '__main__':
    # 扩展: 获取当前线程
    # print("当前执行的线程为:", threading.current_thread())
    # target: 线程执行的函数名
    # args: 表示以元组的方式给函数传参
    # kwargs: 表示以字典的方式给函数传参
    sing_thread = threading.Thread(target=sing, args=(3, ))

    # 创建跳舞的线程
    dance_thread = threading.Thread(target=dance, kwargs={"num": 3},daemon=True)
    
     # 守护主线程方式2
    # sing_thread.setDaemon(True)

    # 开启线程
    sing_thread.start()
    dance_thread.start()

使用互斥锁

# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()

import threading

# 定义全局变量
g_num = 0

# 创建全局互斥锁
lock = threading.Lock()


# 循环一次给全局变量加1
def sum_num1():
    # 上锁
    lock.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1

    print("sum1:", g_num)
    # 释放锁
    lock.release()


# 循环一次给全局变量加1
def sum_num2():
    # 上锁
    lock.acquire()
    for i in range(1000000):
        global g_num
        g_num += 1
    print("sum2:", g_num)
    # 释放锁
    lock.release()


if __name__ == '__main__':
    # 创建两个线程
    first_thread = threading.Thread(target=sum_num1)
    second_thread = threading.Thread(target=sum_num2)
    # 启动线程
    first_thread.start()
    second_thread.start()

3.2 进程

一个程序至少有一个进程,一个进程至少有一个线程,多进程可以完成多任务(进程主要处理 IO 密集型)

import multiprocessing


# 显示人员信息
def show_info(name, age):
    print(name, age)


if __name__ == '__main__':
    # 创建子进程
    # 1. group:进程组,目前必须使用None,一般不用设置
    # 2. target:执行目标函数
    # 3. name: 进程名称
    # 4. args: 以元组方式给函数传参
    # 5. kwargs: 以字典方式给函数传参
    sub_process = multiprocessing.Process(target=show_info, name="myprocess",
                                          args=("zhangsan", 18))
	
	# 设置守护主进程,主进程退出后子进程直接销毁,不再执行子进程中的代码
    # sub_process.daemon = True
    # 启动进程
    sub_process.start()

    # sub_process = multiprocessing.Process(target=show_info, name="myprocess",
    #                                       kwargs={"name": "李四", "age": 20})
  
  
    # # 启动进程
    # sub_process.start()
    
    # 让主进程等待1秒钟
    time.sleep(1)
    print("主进程执行完成了啦")
    # 让子进程直接销毁,表示终止执行, 主进程退出之前,把所有的子进程直接销毁就可以了
    sub_process.terminate()

3.3 队列

Queue的使用可以使用multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是一个消息列队程序,首先用一个小实例来演示一下Queue的工作原理

import multiprocessing
import time

if __name__ == '__main__':
    # 创建消息队列, 3:表示队列中最大消息个数
    queue = multiprocessing.Queue(3)
    # 放入数据
    queue.put(1)
    queue.put("hello")
    queue.put([3,5])
    # 总结: 队列可以放入任意数据类型
    # 提示: 如果队列满了,需要等待队列有空闲位置才能放入数据,否则一直等待
    # queue.put((5,6))
    # 提示: 如果队列满了,不等待队列有空闲位置,如果放入不成功直接崩溃
    # queue.put_nowait((5,6))
    # 建议: 向队列放入数据统一使用put

    # 查看队列是否满了
    # print(queue.full())

    # 注意点:queue.empty()判断队列是否空了不可靠
    # 查看队列是否空了
    # print(queue.empty())

    # 解决办法: 1. 加延时操作 2. 使用判断队列的个数,不使用empty
    # time.sleep(0.01)
    if queue.qsize() == 0:
        print("队列为空")
    else:
        print("队列不为空")

    # 获取队列的个数
    size = queue.qsize()
    print(size)

    # 获取数据
    value = queue.get()
    print(value)

3.4 进程池

同步进程池
进程池同步执行任务表示进程池中的进程在执行任务的时候一个执行完成另外一个才能执行,如果没有执行完会等待上一个进程执行。

import multiprocessing
import time


# 拷贝任务
def work():
    print("复制中...", multiprocessing.current_process().pid)
    time.sleep(0.5)

if __name__ == '__main__':
    # 创建进程池
    # 3:进程池中进程的最大个数
    pool = multiprocessing.Pool(3)
    # 模拟大批量的任务,让进程池去执行
    for i in range(5):
        # 循环让进程池执行对应的work任务
        # 同步执行任务,一个任务执行完成以后另外一个任务才能执行
        pool.apply(work)

异步进程池
进程池异步执行任务表示进程池中的进程同时执行任务,进程之间不会等待。

# 进程池:池子里面放的进程,进程池会根据任务执行情况自动创建进程,而且尽量少创建进程,合理利用进程池中的进程完成多任务
import multiprocessing
import time


# 拷贝任务
def work():
    print("复制中...", multiprocessing.current_process().pid)
    # 获取当前进程的守护状态
    # 提示:使用进程池创建的进程是守护主进程的状态,默认自己通过Process创建的进程是不是守住主进程的状态
    # print(multiprocessing.current_process().daemon)
    time.sleep(0.5)

if __name__ == '__main__':
    # 创建进程池
    # 3:进程池中进程的最大个数
    pool = multiprocessing.Pool(3)
    # 模拟大批量的任务,让进程池去执行
    for i in range(5):
        # 循环让进程池执行对应的work任务
        # 同步执行任务,一个任务执行完成以后另外一个任务才能执行
        # pool.apply(work)
        # 异步执行,任务执行不会等待,多个任务一起执行
        pool.apply_async(work)

    # 关闭进程池,意思告诉主进程以后不会有新的任务添加进来
    pool.close()
    # 主进程等待进程池执行完成以后程序再退出
    pool.join()

3.5 协程

greenlet实现协程

pip3 install greenlet
import time
import greenlet


# 任务1
def work1():
    for i in range(5):
        print("work1...")
        time.sleep(0.2)
        # 切换到协程2里面执行对应的任务
        g2.switch()


# 任务2
def work2():
    for i in range(5):
        print("work2...")
        time.sleep(0.2)
        # 切换到第一个协程执行对应的任务
        g1.switch()
```python
在这里插入代码片

if name == ‘main’:
# 创建协程指定对应的任务
g1 = greenlet.greenlet(work1)
g2 = greenlet.greenlet(work2)

# 切换到第一个协程执行对应的任务
g1.switch()
Gevent实现协程

```python
import gevent
import time
from gevent import monkey

# 打补丁,让gevent框架识别耗时操作,比如:time.sleep,网络请求延时
monkey.patch_all()


# 任务1
def work1(num):
    for i in range(num):
        print("work1....")
        time.sleep(0.2)
        # gevent.sleep(0.2)

# 任务1
def work2(num):
    for i in range(num):
        print("work2....")
        time.sleep(0.2)
        # gevent.sleep(0.2)



if __name__ == '__main__':
    # 创建协程指定对应的任务
    g1 = gevent.spawn(work1, 3)
    g2 = gevent.spawn(work2, 3)

    # 主线程等待协程执行完成以后程序再退出
    g1.join()
    g2.join()
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值