Tcp和Udp&多任务

UDP

1.UDP网络发送数据

# coding=utf-8

import socket

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

# 2. 准备接收方的地址
# '192.168.1.103'表示目的ip地址
# 8080表示目的端口
dest_addr = ('192.168.1.103', 8080)  # 注意 是元组,ip是字符串,端口是数字

# 3. 定义发送的数据
send_data = input("请输入要发送的数据:")

# 4. 发送数据到指定的电脑上的指定程序中
udp_socket.sendto(send_data.encode('gbk'), dest_addr)

# 5. 关闭套接字
udp_socket.close()

2.UDP网络接收数据

#coding=utf-8

import socket

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

# 2.绑定ip和端口  绑定自身ip地址
udp_socket.bind(('192.168.15.130', 11000))

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

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

# 5. 关闭套接字
udp_socket.close()

案例:

# UDP聊天软件
import socket


def send_msg(udp_socket):
    """获取键盘数据,并将其发送给对方"""
    # 1. 从键盘输入数据
    msg = input("\n请输入要发送的数据:")
    # 2. 输入对方的ip地址
    dest_ip = input("\n请输入对方的ip地址:")
    # 3. 输入对方的port
    dest_port = int(input("\n请输入对方的port:"))
    # 4. 发送数据
    udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))


def recv_msg(udp_socket):
    """接收数据并显示"""
    # 1. 接收数据
    recv_msg = udp_socket.recvfrom(1024)
    # 2. 解码
    recv_ip = recv_msg[1]
    recv_msg = recv_msg[0].decode("utf-8")
    # 3. 显示接收到的数据
    print(">>>%s:%s" % (str(recv_ip), recv_msg))


def main():
    # 1. 创建套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 2. 绑定本地信息
    udp_socket.bind(("", 7890))
    while True:
        # 3. 选择功能
        print("="*30)
        print("1:发送消息")
        print("2:接收消息")
        print("="*30)
        op_num = input("请输入要操作的功能序号:")

        # 4. 根据选择调用相应的函数
        if op_num == "1":
            send_msg(udp_socket)
        elif op_num == "2":
            recv_msg(udp_socket)
        else:
            print("输入有误,请重新输入...")

if __name__ == "__main__":
    main()
# 模仿飞秋消息格式实现广播

# 飞秋消息格式如下
#版本:消息序号:用户名:电脑名:功能(32表示发送消息):发送的消息内容
"1:123456789:莉莉:水果电脑:32:有时间么?"

import socket

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

s.bind(("", 8080))

# 2. 设置UDP套接字允许其广播(注意如果udp套接字需要广播,则一定要添加此语句)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

dest_info = ("<broadcast>", 2425)  # <broadcast>会自动改为本局域网的广播ip
send_data = "1:123456789:莉莉:水果电脑:32:有时间么?"

# 3. 发送广播数据
s.sendto(send_data.encode('gbk'), dest_info)

# 4. 关闭套接字
s.close()

TCP

1.TCP网络编程客户端

# 只发数据示例
from socket import *

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

# 2. 链接服务器
tcp_client_socket.connect(("192.168.1.104", 8080))

# 提示用户输入数据
send_data = input("请输入要发送的数据:")

# 3. 向服务器发送数据
tcp_client_socket.send(send_data.encode("utf-8"))

# 4. 关闭套接字
tcp_client_socket.close()

# ————————————————————————————————————————————————————————————————-

# 可以发送数据并接收相关回应
import socket


# 1. 创建TCP套接字
client_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 链接服务器
client_s.connect(("192.168.1.104", 8080))

# 3. 获取要发送的数据
send_content = input("请输入要发送的数据内容:")

# 4.1 发送数据
client_s.send(send_content.encode("utf-8"))

# 4.2 接收数据
recv_content = client_s.recv(1024)
print(recv_content.decode("gbk"))

# 5. 关闭套接字
client_s.close()

2.TCP网络编程服务器

# 只能接收一次数据版本
import socket


# 1. 创建TCP套接字
server_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 绑定本地信息
server_s.bind(("", 7788))

# 3. 设置为被动的
server_s.listen(128)

# 4. 等待客户端链接
new_s, client_info = server_s.accept()

# 5. 用新的套接字为已经连接好的客户端服务器
recv_content = new_s.recv(1024)
print("%s>>>%s" % (str(client_info), recv_content.decode("gbk")))

# 6. 关闭套接字
new_s.close()
server_s.close()

# ——————————————————————————————————————————————————————————————————

# 可以接收多条数据版本
import socket


# 1. 创建TCP套接字
server_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 绑定本地信息
server_s.bind(("", 7788))

# 3. 设置为被动的
server_s.listen(128)

# 4. 等待客户端链接
new_s, client_info = server_s.accept()

# 5. 用新的套接字为已经连接好的客户端服务器
while True:
    recv_content = new_s.recv(1024)
    print("%s>>>%s" % (str(client_info), recv_content.decode("gbk")))

    if not recv_content:
        # 当客户端调用了close后,recv返回值为空,此时服务套接字就可以close了
        # 6. 关闭服务套接字
        new_s.close()
        break

# 7. 关闭监听套接字
server_s.close()

案例:
模拟文件下载器

# 客户端
from socket import *


def main():

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

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

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

    # 输入需要下载的文件名
    file_name = input("请输入要下载的文件名:")

    # 发送文件下载请求
    tcp_client_socket.send(file_name.encode("utf-8"))

    # 接收对方发送过来的数据,最大接收1024个字节(1K)
    recv_data = tcp_client_socket.recv(1024)
    # print('接收到的数据为:', recv_data.decode('utf-8'))
    # 如果接收到数据再创建文件,否则不创建
    if recv_data:
        with open("[接收]"+file_name, "wb") as f:
            f.write(recv_data)

    # 关闭套接字
    tcp_client_socket.close()


if __name__ == "__main__":
    main()
# 服务器
from socket import *
import sys


def get_file_content(file_name):
    """获取文件的内容"""
    try:
        with open(file_name, "rb") as f:
            content = f.read()
        return content
    except:
        print("没有下载的文件:%s" % file_name)


def main():

    if len(sys.argv) != 2:
        print("请按照如下方式运行:python3 xxx.py 7890")
        return
    else:
        # 运行方式为python3 xxx.py 7890
        port = int(sys.argv[1])


    # 创建socket
    tcp_server_socket = socket(AF_INET, SOCK_STREAM)
    # 本地信息
    address = ('', port)
    # 绑定本地信息
    tcp_server_socket.bind(address)
    # 将主动套接字变为被动套接字
    tcp_server_socket.listen(128)

    while True:
        # 等待客户端的链接,即为这个客户端发送文件
        client_socket, clientAddr = tcp_server_socket.accept()
        # 接收对方发送过来的数据
        recv_data = client_socket.recv(1024)  # 接收1024个字节
        file_name = recv_data.decode("utf-8")
        print("对方请求下载的文件名为:%s" % file_name)
        file_content = get_file_content(file_name)
        # 发送文件的数据给客户端
        # 因为获取打开文件时是以rb方式打开,所以file_content中的数据已经是二进制的格式,因此不需要encode编码
        if file_content:
            client_socket.send(file_content)
        # 关闭这个套接字
        client_socket.close()

    # 关闭监听套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()

Tcp三次握手和四次挥手

在这里插入图片描述

多任务

并行和并发
  • 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
  • 并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的
  • 真正的"并行"只能在多核CPU上实现,现实中由于任务数量远远多于CPU的核心数量,所以基本上都是“并发”。操作系统会自动把很多任务轮流调度到每个核心上执行。

多线程

  1. 什么是线程
线程是一个抽象的概念,可以把它想象成一个实体,是CPU调度和分派的基本单位。
  1. python通过线程实现多任务
在Python中如果想使用线程实现多任务,可以使用thread模块 但是它比较底层,即意味着过程较为复杂不方便使用;推荐使用threading模块,它是对thread做了一些包装的,可以更加方便使用

3.线程tcp并发服务器

import socket
import threading


class HandleData(threading.Thread):
    """用来接收和处理数据的线程"""

    def __init__(self, client_socket):
        super().__init__()
        # 初始化时传入要处理的客户端对象
        self.client_socket = client_socket

    def run(self):
        # 接收/发送数据
        while True:
            # 接收数据
            recv_content = self.client_socket.recv(1024)
            if len(recv_content) != 0:
                print(recv_content)
                # 把收到的数据返回
                self.client_socket.send(recv_content)
            else:
                # 如果收到空数据 就结束
                self.client_socket.close()
                break

    def __del__(self):
        # 线程对象销毁时 结束socket
        self.client_socket.close()


class TCPServer(threading.Thread):
    """tcp服务器的线程 ,接收不同客户端的连接"""

    def __init__(self, port):
        # 注意  这里必须调用父类的初始化方法,否则很多重要的初始化就不会执行
        # threading.Thread.__init__(self)
        super().__init__()

        # 创建套接字
        self.server_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # 绑定本地信息
        self.server_s.bind(("", port))

        # 将套接字由默认的主动链接模式改为被动模式(监听模块)
        self.server_s.listen(128)

    def run(self):
        # 开启循环 可以一直接收不同的客户端
        while True:
            # 等待客户端进行链接
            new_s, client_info = self.server_s.accept()
            print('连接%s成功' % str(client_info))

            # 创建一个处理客户端的线程对象,并且开启线程
            HandleData(new_s).start()

    def __del__(self):
        # 关闭套接字
        self.server_s.close()


def main():
    # 创建服务器的线程对象
    tcp_server = TCPServer(12000)  # 12000表示TCP要绑定的端口
    # 开启服务器线程
    tcp_server.start()


if __name__ == '__main__':
    main()

4.可同时收发数据的UDP聊天程序

import socket
import threading


def send_msg(udp_socket):
    """获取键盘数据,并将其发送给对方"""
    while True:

        print("1: 发送数据")
        print("2: 退出程序")
        op = input("请输入操作序号:")
        if op == "1":
            # 1. 输入对方的ip地址
            dest_ip = input("\n请输入对方的ip地址:")
            # 2. 输入对方的port
            dest_port = int(input("\n请输入对方的port:"))
            while True:
                # 3. 从键盘输入数据
                msg = input("\n请输入要发送的数据:")
                if msg:
                    # 4. 发送数据
                    udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))
                else:
                    # 要是没有输入内容则认为是要重新输入ip、port
                    break
        elif op == "2":
            # 结束发送
            break
    # 结束,关闭套接字
    udp_socket.close()


def recv_msg(udp_socket):
    """接收数据并显示"""
    while True:
        try:
            # 1. 接收数据
            recv_msg = udp_socket.recvfrom(1024)
        except:
            break
        else:
            # 2. 解码
            recv_ip = recv_msg[1]
            recv_msg = recv_msg[0].decode("utf-8")
            # 3. 显示接收到的数据
            print("接收到来自于%s的数据:%s" % (str(recv_ip), recv_msg))


def main():
    # 1. 创建套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 2. 绑定本地信息
    udp_socket.bind(("", 7890))

    # 3. 创建一个新的线程,用来接收数据
    udp_r = threading.Thread(target=recv_msg, args=(udp_socket,))

    # 4. 创建一个新的线程,用来发送数据
    udp_s = threading.Thread(target=send_msg, args=(udp_socket,))

    # 5. 运行创建的子线程
    udp_r.start()
    udp_s.start()

if __name__ == "__main__":
    main()

5.多线程存在的问题

如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确

6.解决方法 - 使用互斥锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

互斥锁用法

# 创建锁
mutex = threading.Lock()

# 锁定
mutex.acquire()

# 释放
mutex.release()

多进程

1.什么是进程

一个程序运行起来后,代码+用到的资源 称之为进程,它是操作系统分配资源的基本单元。

2.实现多任务

在python中使用使用进程实现多任务的方式有3种:

创建Process对象
基础Process类,创建自己的对象,实现run方法
使用进程池片

3.创建进程

# 唱歌与跳舞
import time
import threading # 线程的包

def dance():
    while True:
        print("跳舞")
        time.sleep(1)



def song():
    while True:
        print("唱歌")
        time.sleep(1)


def main():
    """一边唱歌一边跳舞"""
    threading.Thread(target=dance).start()

    threading.Thread(target=song).start()


if __name__ == '__main__':
    main()


start():启动子进程实例(创建子进程)
is_alive():判断子进程是否还在活着
join([timeout]):是否等待子进程执行结束,或等待多少秒
terminate():不管任务是否完成,立即终止子进程

4.进程PID(进程号)

pid是唯一标识进程的号
os.getpid()获取当前进程的pid
os.getppid()获取父进程的pid

5.进程间通信-Queue
使用

  1. 使用Queue()时,若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头)
  2. Queue的几个方法功能说明: Queue.qsize():返回当前队列包含的消息数量
    Queue.empty():如果队列为空,返回True,反之False
    Queue.full():如果队列满了,返回True,反之False Queue.get([block[,
    timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True
    如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出Queue.Empty异常
    如果block值为False,消息列队如果为空,则会立刻抛出Queue.Empty异常
    Queue.get_nowait():相当Queue.get(False) Queue.put(item,[block[,
    timeout]]):将item消息写入队列,block默认值为True
    如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出Queue.Full异常
    如果block值为False,消息列队如果没有空间可写入,则会立刻抛出Queue.Full异常
    Queue.put_nowait(item):相当Queue.put(item, False)
    实例
# 进程使用队列地行通讯
# 创建一个进程的队列,一个进程放,一个进程取
# 队列就是进程之间进行通讯

import multiprocessing
import time

# 取
def get_data(queue):
    while True:
        # 判断如果不空就获取数据
        if not queue.empty():
            data = queue.get()
            print("取到的数据:", data)
            time.sleep(1)


# 放
def put_data(queue):
    for temp in range(1000000):
        # 判断如果不满 就添加数据
        if not queue.full():
            queue.put(temp)
            time.sleep(1)


def main():
    queue = multiprocessing.Queue(3)
    """一个读一个取"""
    # 这个把数据 放到队列中
    multiprocessing.Process(target=put_data, args=(queue,)).start()
    # 这个得到队列的数据
    multiprocessing.Process(target=get_data, args=(queue,)).start()


if __name__ == '__main__':
    main()

6.进程池

  • 当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。

  • 初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务

示例:

from multiprocessing import Pool
import os
import random
import time

def worker(num):
    for i in range(5):
        print('===pid=%d==num=%d='%(os.getpid(),num))
        time.sleep(1)

# 3表示进程池中最多有三个进程一起执行
pool=Pool(3)

for i in range(10):
    print('---%d---'%i)
    # 向进程中添加任务
    # 注意:如果添加的任务数量超过了进程池中进程的个数的话,那么就不会接着往进程池中添加,
    #       如果还没有执行的话,他会等待前面的进程结束,然后在往
    # 进程池中添加新进程
    pool.apply_async(worker,(i,))

pool.close() # 关闭进程池
pool.join()  # 主进程在这里等待,只有子进程全部结束之后,在会开启主线程

用法

apply_async(func[, args[, kwds]]):使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
close():关闭Pool,使其不再接受新的任务;
terminate():不管任务是否完成,立即终止;
join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;

进程池里的通信:

  • 需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue()

7.案例:

# 文件夹copy器


import multiprocessing
import os
import time
import random


def copy_file(queue, file_name,source_folder_name,  dest_folder_name):
    """copy文件到指定的路径"""
    f_read = open(source_folder_name + "/" + file_name, "rb")
    f_write = open(dest_folder_name + "/" + file_name, "wb")
    while True:
        time.sleep(random.random())
        content = f_read.read(1024)
        if content:
            f_write.write(content)
        else:
            break
    f_read.close()
    f_write.close()

    # 发送已经拷贝完毕的文件名字
    queue.put(file_name)


def main():
    # 获取要复制的文件夹
    source_folder_name = input("请输入要复制文件夹名字:")

    # 整理目标文件夹
    dest_folder_name = source_folder_name + "[副本]"

    # 创建目标文件夹
    try:
        os.mkdir(dest_folder_name)
    except:
        pass  # 如果文件夹已经存在,那么创建会失败

    # 获取这个文件夹中所有的普通文件名
    file_names = os.listdir(source_folder_name)

    # 创建Queue
    queue = multiprocessing.Manager().Queue()

    # 创建进程池
    pool = multiprocessing.Pool(3)

    for file_name in file_names:
        # 向进程池中添加任务
        pool.apply_async(copy_file, args=(queue, file_name, source_folder_name, dest_folder_name))

    # 主进程显示进度
    pool.close()

    all_file_num = len(file_names)
    while True:
        file_name = queue.get()
        if file_name in file_names:
            file_names.remove(file_name)

        copy_rate = (all_file_num-len(file_names))*100/all_file_num
        print("\r%.2f...(%s)" % (copy_rate, file_name) + " "*50, end="")
        if copy_rate >= 100:
            break
    print()


if __name__ == "__main__":
    main()

协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值