# python-lesson **Repository Path**: BlackHouseStudio/python-lesson ## Basic Information - **Project Name**: python-lesson - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-12-22 - **Last Updated**: 2023-12-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # python-lesson #### 介绍 这是一个python 学习过程的测试项目 1、 整数运算 - + - - - * - ** 幂运算 比如 2**3=2的3次方=2*2*2=8 - / 结果是浮点数 比如 10/2 = 5.0 - // 结果是整数 比如 7//2 = 3 - a = int("100") 可以实现类型转换 2、进制 - 二进制 0B或0b - 八进制 0O或0o - 十六进制 0X或0x 3、浮点数 float 用a*b的10次方,比如3.14 可以表示成314E-2 或 314e-2 4、类型转换 int() float() round():四舍五入 5、逻辑运算 - and a and b a为 False 直接返回 False,a为True 时直接返回 b - or a or b a为True时直接返回True,a为False时直接返回b - ! 6、同一运算符 - is 判断两个标识符是不是同一对象 - is not 判断两个标识符是不是不同对象 - is 和==的区别 is比较的对象地址,== 比较的对象的值是否相符,默认调用对象的__eq__()方法 7、整数缓存 Python仅仅对比较小的数整数 命令行中缓存范围 [-5,256]; 在文件中执行时,解释器做了部分优化,范围是 [-5,任意正整数] 8、字符串 python3支持Unicode,可以表示世界上任何书面语言的字符,默认是16位的Unicode, - ord() 可以把字符转为对象的 Unicode 码,比如:ord('A') 65 - chr() 可以把Unicode值转为对象的字符 比如 chr(65) A - len() 可以计算字符串的长度 - + 必须两边类型一致,“a”+“b"="ab" 或者直接使用空格 “A” “B” = “AB” - * 字符串复制 “AB”*3=“ABABAB” - input(‘提示用户输入内容的提示’) 字符的输入 - str() 将其他类型的数据转为字符串 - 使用 [ ] 提取字符,字符串本质就是字符序列,可以直接在后面加 【】里指定偏移量,提取该位置的单个字符 - 正向搜索,最左侧第一个字符偏移量是 0 ,最后一个是 长度-1; - 反向搜索,最右侧第一个字符偏移量是 -1,最后一个是 -len(str) - replace() 替换某个字符串 a.replace('待替换工字符','要要替换的') - 切片操作 slice - a[ start : end ] 从游标 start 到 end -1 位置的字符串全部截取 - a[ start: ] 游标从 start 开始到最后 截取 - a[ : end ] 游标从开始到 end-1 截取 - a[ start : end : step ] 游标从 start 开始 到 end-1 截止,步长是step - 游标为负数的几个情况 - "12345678"[-3:] 从倒数第3个开始 ”678“ - end 下标超过时,不影响, - ”12345“[::-1] step 为负数 - split()分割 a.split() 传参,以参数分割,不给参数,默认使用空白字符(换行符/空格/制表符) - join() 合并 ”*“.join(["a","b","c"]) = "a*b*c" +拼接和join比较,+性能不如join,+每执行一次产生一个新对象, join 仅会产生一个新对象; - 成员操作符,判断是否包含某个字符串 in \ not in 比如 ”a" in "abcdef" true - startswidth() 是否以某字符串开始 ”abc“.startswith('a') True - endswith() 是否以某字符串结束 ”abc".endswith("c") True - find() 第一次出现指定字符串的下标 “abcdefg".find("cd") 结果:2 - rfind() 最后一次出现指定字符串的下标 "abcdefgab".rfind("ab“) 结果:7 - count() 统计出现的次数 ”aaaabcdef“.count("aa") - 去除首尾信息,不加参数默认去除空格 - strip() 同时去除首尾的信息 - lstrip() 只去除首部信息 - rstrip() 只去除尾部信息 - capitalize() 首字母大写 - title() 每个单词首字母大写 - upper() 全大写; - lower() 全小写 - swapcase() 大小写切换; 格式排版 往字符串两边填充内容,第一个参数是填充后字符总长度,第二个参数是要填充的内容,为空则填充空格 - center() 居中 - ljust() 靠左 - rjust() 靠右 字符串的判断 - isalnum() 是否为字母或数字 - isalpha() 是否全为字母或汉字 - isdigit() 是否全为数字 - isspace() 是否为空白符 - isupper() 字母是否全大写 - islower() 字母是否全小写 format()字符串格式化,使用 {} 占位 填充与对齐 占位符里面 加 : ,带填充的字符,不指定用空格填充,^ < > 分别代表 居中/左对齐/右对齐 a="我叫{},今年{}岁" a.format("lidu",23) '我叫lidu,今年23岁' a = "我叫{0},今年{1},{0}这个名字很nice" a.format(李杜,23) '我叫李杜,今年34,李杜这个名字很nice' a="我叫{name},今年{age}" a.format(name="张三",age=23) '我叫张三,今年23' ### 填充 a='我叫{name:*^8},今年{age}' a.format(name="lidu",age=3) '我叫**lidu**,今年3' 数字格式化 浮点数通过 f,整数通过d,^<>对齐方式,不填默认右对齐 - {:.2f} 保留两们小数 - {:+.2f} 带符号,保留两们小数 - {:.0f} 不带小数 - {:0<5d} 宽度为5,数字补0,填充在右边 - {:0^5d} 宽度为5,数字补0,填充在中间 - {:0>5d} 宽度为5,数字补0,填充在左边 - {:,} 逗号分隔数字,"{:,}".format(982373423423423)= '982,373,423,423,423' - {:.2%} 百分比格式 2 表示保留2位小数 "{:.2%}".format(0.23699)='23.70%' - {:.2e} 指数记法 "{:.1e}".format(12300)='1.2e+04' 、 "{:.2e}".format(0.0000123)='1.23e-05' 字符串驻留机制: 仅保存一份且不可变字符串的方法,不同的值被存放在字符串驻留池中。对于符合标识符规则的字符串(仅包含下划线,字母和数字)会启用字符串驻留机制。 例如: a="abc" b="abc" a is b True a="我爱你,中国" b="我爱你,中国" a is b False 9、转义字符,可以使用 \+特殊字符 - \b 退格 10、打印 print 默认打印后会换行 ,不想换行,可以:print("abc",end="") 字典,是的于java里的map,是一个可变序列,key不可重复 - 创建 - {} 通过花括号创建; a = {"name":"张三","age":18} - dict() - dict(name=‘张三’,'age'=18) - dict([("name","张三"),("age",18)]) - zip() - 通过formkeys可以创建值为空的字典 k = ["name","age"] v = ["张三",18] ss = zip(k,v) ss dict(ss) {'name': '张三', 'age': 18} a = dict.fromkeys(["name",'age']) a {'name': None, 'age': None} 字典元素的访问 - 通过键值 key 直接访问 比如,a['name'],键值不存在会报错 - 通过get,key不存在时可以给默认值, a.get(key,默认值) - 通过items,获取所有键值对 - 通过keys, 获取所有key集合 - 通过len() 获取所有个数 - 通过 in 获取 key是否在字典内 ”key" in dic 字典元素的更新 - 通过key值直接新增 - 通过update 删除 - del - pop - popitem - 集合,无序可变,元素不可重复,跟java的set差不多 - 新建 - 使用花括号 {} 如:a={3,4,5} - 使用set() ,可将列表、元组对象转为集合。如果有重复,只保留一个 如:set([1,2,3]) - 删除 remove(obj) - 清空 clear - 集合相关操作 - 并集 a | b 或者 a.union(b) - 交集 a & b 或者 a.intersection(b) - 差集 a - b 或者 a.difference(b) 条件控制 条件为 整数是,0为False 为字符串时,空串和Null均为False,和js差不多,集合时,为空则为False,条件里不得出现赋值语句 if 条件: 执行诗句 elif 条件: 执行语句 else: 执行语句 while、for 循环可以附带一个else 语句(可选)。如果for 、while 语句没有被break结束,则会执行else子句,否则不执行。 使用zip进行并且迭代 a = [1,2,3,4] b = ['a', 'b', 'c', 'd', 'e'] for x in zip(a,b): x 面向对象 类的相关方法 obj = Student("lidu",100) - 构造函数 __init__(self, [参数]) - dir 查看所有属性 dir(obj) - __dict__ 查看所有字段属性 obj.__dict__() - isinstance 查看对象的类型是否匹配 isinstance(obj, 类名) - mro 查看类的继承层次结构 - super() 获取父类的方法, 代表父类的定义,不是父类对象 10、可变字符串 # 导入包 import io sio = io.StringIO("Hello,Workd") sio.getvalue() 'Hello,Workd' sio.seek(9) 9 sio.write('ld!') 3 sio.getvalue() 'Hello,world!' 11、基本操作运算 # 位操作 a=0b1001 b=0b1000 # bin() 将数字转换为二进制表示 bin(a&b) '0b1000' bin(a|b) '0b1001' bin(a^b) '0b1' #位移 a=0b100 bin(a<<1) '0b1000' a<<1 #左移一位相当于乘以2,移两位相当于乘以4 8 a>>1 #右移两位相当于除以2,右移两位相当于除以4 2 bin(a>>1) '0b10' 3+2 5 "3"+"2" '32' [1,2,3,]+[4,5,6] [1, 2, 3, 4, 5, 6] 3*2 6 "LIDU"*2 'LIDULIDU' [1,2,3]*2 [1, 2, 3, 1, 2, 3] 注:C和JAVA是可以使用++ 或 -- 实现自增或自减,但是是Python不可以 12、序列 序列是一种数据存储方式,用来存储一系列的数据。在内存中,序列就是一块用来存放多个值的连续的内存空间。列表元素的操作如下: 字符串和列表都是序列,一个字符串是一个字符序列,一个列表是任何元素的序列。 变量的删除 del 变量名称 列表对象的创建 - 基本语法创建 a=[1,2,3,5] - list()方法创建 a=list() - list() 不传参直接初始化一个空序列 - list("字符串") 将字符串拆分成单个字符序列 ["字","符","串"] - list(range(10)) - range([start,] end [,step]) - start:可选,表示起始数字,默认是0 - end:必选,表示结尾数字 - step:可选,表示步长,默认是1 - 推导式生成列表 a = [x*2 for x in range(10)] #通过循环创建多个元素 a [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] [x*2 for x in range(100) if x%9==0] #通过 if 过滤元素 [0, 18, 36, 54, 72, 90, 108, 126, 144, 162, 180, 198] 列表元素的增加或删除 当列表增加和删除元素时,列表会自动进行内存管理,大大减少程序员的负担。但这个特点涉及列表元素的大量移动,效率较低。除非必要,我们一般只在列表的尾部增加或删除元素 - append 真正列表尾部添加新元素,速度最快,推荐使用。 - + 操作,并不是正直的尾部添加元素,会复制到新的列表,这样会涉及大量的复制操作(不推荐) - extend(),拼接两个数组,属于原地操作,不创建新的对象。 - insert(index,val), 在指定位置插入元素,会将插入位置后的元素往后移动 - index 位置 - val 要插入的值 - remove() 删除首次出现的指定的元素,若不存在会抛出异常。 a.remove(obj) - del 删除指定元素,会涉及到后面元素的拷贝 del a[1] - pop 操作的列表下标,不传参数默认删除最后一个 a.pop(1) 列表元素的访问和计数 - 通过下标获取 比如 a[2] 下标不存在会报异常 - index(obj, [start, [end]]) 获取指定元素首次出现的索引 返回元素下标 , start 和 end 可以指定搜索范围 - count(obj) 可以返回指定元素在列表出现的次数 成员资格判断,判断列表是否存在某个元素 - 使用count() 统计出现的次数,返回0则表示不存在; - 使用 in 来判断列表是否存在某个元素 比如 obj in list, 存在则返回true - 使用 not in 列表切片 a[ start : end : step] 排序 - 升序 sort() 原表修改 a.sort() - 降序 sort(reverse=true) - 打乱顺序 import random random.shuffle(a) 内置函数sorted排序,区别是创建了新表 - 升序 sorted(a) - 降序 sorted(a,reverse=True) 内置函数reversed 逆序,返回迭代器,与list的reverse不同的是,也不对原列表做任何修改,并且只返回一个迭代器对象; a=[1,2,3,4,5] - 通过序列的reverse, a.reverse() - 通过内置函数reversed() reversed(a),返回的迭代器只能使用一次,用完就为空了 - 还可以通过切片 a[::-1] a=[6, 5, 5, 4, 3, 2, 1] a.reverse() a [1, 2, 3, 4, 5, 5, 6] id(a) 4411398400 a.reverse() id(a) 4411398400 a [6, 5, 5, 4, 3, 2, 1] a = reversed(a) id(a) 4411144512 a list(a) [1, 2, 3, 4, 5, 5, 6] # 通过切片 a[::-1] - max(list) 取列表最大值 - min(list) 取列表最小值 - sum(list) 列表值求和 a [5, 4, 3, 2, 1] max(a) 5 min(a) 1 sum(a) 15 元组 列表属于可变序列,可以修改任意列表中的元素。而无组属于不可变序列,不能修改元组中的元素。因此,元组没有增加、修改、删除元素相关的方法。 - 元组的创建 - 通过 ()创建 , 比如 a = (1,2,3) 或者取消括号 a = 1i,2,3 均可,如果只有一个元素时,需要在后面加上逗号, 如 a=(1,) 或者 a=1, - 通过tuple()创建,比如 tuple(), 创建一个空的元组对象, - tuple("abc") - tuple(range(3)) - tuple([2,3,4]) - 元组的删除 del a; - zip(列表1,列表2,列表3.。。)将多个列表对应位置的元素组合成为元组,并返回这个zip对象 a = [1,2,3] b=[10,20,30] c=[100,200,300] z = zip(a,b,c) a [1, 2, 3] z list(z) [(1, 10, 100), (2, 20, 200), (3, 30, 300)] 对象 - 私有属性、私有方法 class Employee: #私有类变量 __company = "所属公司" def __init__(self,name,age): self.name = name self.__age = age #私有属性 def __work(self): #私有方法 print("___work____") #内部调用私有方法可以直接调用 print("age:{0}".format(self.__age)) e = Employee() #私有属性调用 print(e._Employee__age) #私有方法调用 e._Employee__work() #私有类变量 print(Employee.__Employee_company) - @property 装饰器 可以将一个方法的调用变成属性调用。 - 第一种方法,直接在类里面使用 age = property(get_age, set_age) class Student: def __init__(self, name, age): self.name = name self.__age = age @property def age(self): return self.__age @age.setter def age(self, age): if not isinstance(age, int): raise TypeError("年龄输入不对,应该为整数") - 第二种方法 class Student: def __init__(self, name, age): self.name = name self.__age = age def get_age(self): return self.__age def set_age(self, age): if not isinstance(age, int): raise TypeError("年龄输入不对,应该为整数") self.__age = age age = property(get_age, set_age) - 面向对象三大特征明说(封装、继承、多态) - 成员继承:子类百度币了父类构造方法之外的所在成员 - 方法重写:子类可以重新定义父类中的方法,这样就会覆盖父类的方法,也称为重写 - 支持多重继承,一个子类可以有多个直接父类 - 重写 __str__()方法,相当于java的重写toString方法 - 通过super()获取父类的定义,调用父类的方法, - 多态 - 多态是方法的多态,属性没有多态 - 多态的存在有2个必要条件,继承、方法重写 - 特殊的方法 - 特殊属性 - 拷贝 - 导入copy模块 import copy - 浅复制 copy.copy(target) - 深复制 copy.deepcopy(target) - 组合 - 继承,实现子类拥有父类的方法和属性 - 组合,构造方法传参的方式,调用参数的方式来使用我们需要的方法或属性 - 设计模式 - 工厂模式 - 单例模式 class MySingleton: __obj = None __init_flag = True def __new__(cls, *args, **kwargs): if cls.__obj == None: cls.__obj = object.__new(cls) return cls.__obj def __init__(self,name): if MySingleton.__init_flag: self.name = name MySingleton.__init_flag = False 模块,只要是 .py为后缀的文件,都可以称为模块,在导入模块的时候,模块中的代码会被执行一遍 可以使用 模块里的 "__name__" 来检测当前模块是不是主入口,主入口是值为 __main__,否则输出为 文件名 模块中可以包含: 1、变量 2、函数 3、class 对象 4、可执行的代码 模块导入的方式: 1、import 模块名,使用模块名.属性 来调用模块成员 2、from 模块名 import 成员 ,可以直接使用模块里的成员 3、from 模块名 import *,可以直接使用模块里的全部成员 3.1、模块中未使用 __all__ = [] 3.2 、模块中使用 __all__ = ["成员"] ,可在导入中直接使用模块中 __all__ 中声明的成员,未声明的不可使用 包:package 新建包后,包里会产生一个 __iinit__.py 模块 类中中 __init__ 为初始方法 包中 __init__.py 为初始模块,首次使用包中的模块时, __init__.py模块会被执行一次 __init__.py 就是普通的模块,可以存放变量,类,函数,但是不会这样写,主要是让你更方便的使用模块 1、可以直接要导入模块中导入包就可以,在__init__.py 中再导入具体的包 入口:import 包 from 包 import * __init__.py :导入各模块 from .模块 import * 这种方式等于在入口文件中使用 import 包.模块 from 包.模块 import * 2、自定义模块在其他项目的使用 1、将模块所在的路径,手动加入到sys.pth中 import sys sys.path.append("模块所在的绝色路径") import 模块 使用模块的元素 2、将模块发布到系统目录 1.模块的发布 a.确定的发布的模块(目录结构) 1.--setup.py 2.--package 3. --自定义模块 b.setup的编辑工作 setup() from distutils.cor import setup setup(name="压缩包的名字",version="1.0",description="描述",author="大卫",py_modules=["package1.模块1"...]) c.构建模块 python setup.py build d.发布模块 python setup.py sdist 2.模块的安装 a.通过命令完成安装(推荐) 更安全 i.找到之前发布的压缩包,解压操作 ii.python setup.py install b.暴力安装 i.直接将要安装的包,以及模块,复制到对应的系统目录中 异常处理 try: 代码块 except: 异常处理 多个异常时,从上往下匹配 try: 要执行的代码块 except Exception as e: print(e.args) else: print("执行成功的,没有异常则会输出") finally: print("不管怎么样,都会执行的。。") 自定义异常 # 自定义异常 class GenderException(BaseException): def __init__(self): super().__init__() self.error_message = '性别异常' # 自定义异常抛出 raise GenderException() 模块导入 import sys # 如何查看模块路径 print(sys.path) # 如果要导入的模块在[sys.path]中没有,可以通过 sys.path.append("相对路径/绝对路径") 被引入的模块有更新后,模块需要重载: 1、 from imp import reload 2、 reload(目标模块) 3、 重新调用即可 == :用来比较两个变量的值是否相等 is:用来比较两个变量是否指向同一个对象 基本类型: 小整数池:[-5, 256] ,其中的数值全局共享一份,在集成的开发工具中会有偏差 进制转换: 十进制转其他进制:返回的都是字符串 十进制→二进制 bin(num) bin(100)='0b1100100' 十进制→八进制 oct(num) oct(100)='0o144' 十进制→十六进制 hex(num) hex(100)='0x64 其他进制转10进制 二进制→十进制 int('0b1100100', 2) 八进制→十进制 int('0o144', 8) 十六进制→十进制 int('0x64', 16) 原码、反码、补码 正数: 原码 = 反码 = 补码 负数: 最高位为符号位,-1 的原、反、补码如下: 原码: 1000 0001 反码: 1111 1110 (原码基础上,除符号位外,所有其他位取反) 补码: 1111 1111 (反码 + 1) 补码继续取反再 + 1又可以得到原码 计算机减法的,实际运算的时候是做加法,比如计算 1+1,电脑实际上是按 1 + (-1)的方式计算,但是是负数是计算是取反码进行计算的,计算过程如下 1:原码 0000 0001 -1:补码 1111 1111 结果 0000 0000 位运算: 1.<<: a << n 相当于 a*2^n 2.>>: a >> n 相当于 a/2^n 3.&: 按位与 4.|: 按位或 5.^: 按位异或 a.两个二进制的操作数,相同为0,不同为1 b.对同一个数字异常两次,得到的就是他本身 i.可用于一个数字的简单加密 6.~:取反 5取反 等于 -6 ~5=-6 验证: 0000 0101 5的原码 1111 1010 补码:计算过程中得到是补码,因为是负数,故 取反+1 即可得到原码,得到如下数字 1000 0110 -6 属性私有化的问题 1.xx 一般情况下使用的变量 2._xx 跟第一种的区别仅仅是使用 【 from 模块 import * 】 这种方式,无法使用这个变量 3.__xx 私有属性,或者私有方法 4.__xx__ Magic Method,魔法方法,自定义方法避免与魔法方法重名 5.xx_ 用来区分同名的方法 多进程 程序:是一个指令的集合 进程:正在执行的程序 程序运行时,首先会创建一个主进程,主进程可以创建子进程,子进程依赖于主进程,如果主进程结束,程序会退出 子进程不能再创建子进程了, python提供了 multiprocessing,这个包可以从单进程到并发执行的转换 进程的使用 from multiprocessing import Process 导包 Process(target, name, args) - target 表示调用的对象,即子进程要执行的任务 - args 表示调用对象的位置参数元组, args=(1,) - name 为子进程赋值,表示子进程的名字 Process的常用方法 - start() 启动进程,并调用该子进程中的 run() - run() 进程启动时运行的方法,也就是实例化是的target指定的对象 - terminate() 强制终止进程,但是不会进行任务垃圾清理操作 - is_alive() 用来判断进行是否仍然在运行 - join([timeout]) 让主进程等于进程终止,timeout是可选的等待超时时间,如果未指定则一直等待 Process用常用属性 - name 当前进程的实例别名,默认为Process-N,N为从1开始递增的整数; - pid 当前进程实例的PID值 多个进程时,每个进程都有自己的内存空间,不会共享,父子进程也是一样 进程池的方式创建多进程 from multiprocessing import Pool import time def work(number): print(number) time.sleep(1) if __name__ == '__main__': # 进程初始化,参数为进程池的数量,不传默认为CPU核数量 po = Pool(5) for i in range(100): po.apply_async(work, (i,)) po.close() po.join() Pool常用函数解析: - apply_async: 使用非阻塞方式调用func - apply: 使用阻塞方式调用func - close: 关闭Pool,使其不再接受新的任务 - terminate: 不管任务是否完成,立即终止 - join: 主进程阻塞,等等子进程的执行完成,必须在close或者terminate之后 网络基础协议 根据TCP/IP协议簇功能的不同,将它分为几种层次 四层: - 网络接口层(链路层) - 网络层 - 传输层 - 应用层 七层: - 物理层 - 数据链路层 - 网络层 - 传输层 - 会话层 - 表示层 - 应用层 Socket编程 套接字 socket通过网络完成进程间通信的方式,socket是应用层和传输层之间的桥梁 socket本质是编辑接口(API):socket是对TCP/IP协议的封装 socket之间的连接过程可分为三个步骤: - 服务器监听 - 客户端请求 - 连接确认 Socket收发数据 from socket import * # 构造socket对象 # AF_INET 规定使用的类型是IPV4, # SOCK_STREAM 代表UDP协议,保密性要求不高,但是速度有要求,可靠性不高 # SOCK_DGRAM 代表TCP协议,保密性要求高,速度要求不高,可靠性高 s = socket(AF_INET, SOCK_DGRAM) # 设置socket的监听ip和端口,''默认为本机IP s.bind(('', 1234)) # 设置发送的地址信息 addr = ('192.168.10.141', 8081) # 消息发送 # 发送的内容要转成字节流,并且要与接收方保持一致的编码格式 s.sendto('要发送的内容'.encode('gb2312'), addr) # 接收数据,参数为接收的最大数据大小(字节) rdata = s.recvfrom(2048) # rdata 数据分两部分,为一个元组 第一个是发送端传过来的数据,但是需要解决,并且与发送的编码保持一致 msg = rdata[0].decode('gb2312') 第二个是发送端的信息,同样是一个元组 第一个是IP,第二个是端口 s.close() TFTP介绍 TFTP服务器默认端口是69,请求下载文件后,会新开一个端口与请求端发送数据 # 构造下载请求 import struct from socket import * filename = 'dog.jpg' # 构造下载请求数据:'1test.jpg0octet0' # !H8sb5sb: !表示按照网络传输数据要求的形式来组织数据(占位的格式) # H 表示后面的1替换成占两个字节 # 8s 相当于8个s,占8个字节 # b 占一个字节 download_task_data = struct.pack("!H%dsb5sb"%len(filename),1,filename.decode(),0,b'octet',0) s = socket(AF_INET, SOCK_DGRAM) # 向TFTP服务器发送下载请求 s.sendto(download_task_data,('localhost',69')) # 打开一个文件,a:表示以追加模式打开,如果文件不存在,会创建新文件,b:表示以二进制的形式打开 file = open(filename, 'ab') while True: # 接收数据 recv_data = s.recvfrom(1024) # 获取数据块编号 handle_code, ack_num = struct.unpack('!HH',recv_data[0][:4]) # 接收服务器发送文件流的新随机端口 rand_port = recv_data[1][1] if int(handle_code) == 5: print("文件不存在") break # 数据写入 f.write(recv_data[0][4:]) if len(recv_data[0]<516): break # 构造ack确认数据,并发送,回复ACK确认包 ack_data = struct.pack("!HH", 4, ack_num) s.sendto(ack_data,('localhost', rand_port)) 多进程tcp服务器实现 from socket import * from multiprocessing import Process def deal_with_client(net_socket, dest_addr): net_socket.send(b"connect confirm!!") while True: data = net_socket.recv(1024) if len(data) == 0: break print(f"{dest_addr}给我发送了数据:{data.decode()}") print(f"{dest_addr}与我断开了连接。。。") net_socket.close() def main(): s = socket(AF_INET, SOCK_STREAM) s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) s.bind(("192.168.50.141", 9999)) s.listen(5) try: while True: print("服务器等待其他进行连接。。。") ns, addr = s.accept() print("有客户端与我建立连接:", addr) p = Process(target=deal_with_client, args=(ns, addr)) p.start() # 因为子进程已经从主进程中copy了一份引用,所以父进程的ns也没什么用了 ns.close() finally: s.close() if __name__ == "__main__": main()