标签:ret 问题 set city ip地址 创建 优化算法 with 复习
1.基础知识
现有的软件,绝大多数是基于C/S结构,那么就需要介绍网络编程,毕竟现在的绝大多数数据还是在网络中传输。下面先说明一些网络的基础知识,不过对于从事网络工程的来说只是很简单的基础知识,
1.1 C/S架构
C/S架构中C指的是client(客户端软件),s指的是server(服务器端软件),而本章的主要学习目的是写一个基于C/S架构的软件,客户端软件与服务器端基于网络通信。现在基本的C/S架构基本是下图这样:客户端与服务器基于网络传输互相传输数据。
1.2 OSI的七层协议
了解了C/S结构的大概构成,就说一下OSI七层协议,在美国军方发展ARPA网络之后,将他公布用于学术网络知乎,就大爆发一样的发展了,由于网络协议的公开性,各家发展各自的标准,以至于各种网络之间并不能互通,国际ISO标准组织就颁发一套标准七层网络协议,七层网络协议有应用层,表示层,会话层,传输层,网络层,数据链路层,物理层
应用层(7) -------------------------------------------------- 提供应用程序间的通信
表示层 (6)-------------------------------------------------- 处理数据格式以及数据加密等
会话层 (5)-------------------------------------------------- 建立,维护管理会话
传输层 (4)-------------------------------------------------- 建立主机端到端的连接
网络层 (3)-------------------------------------------------- 寻址以及路由选择
数据链路层(2)--------------------------------------------- 提供介质访问,链路管理等
物理层(1) -------------------------------------------------- 比特流传输
一般567整合为应用程序,1234为数据流层
1.3 常用的TCP/IP的五层协议
由于IOS标准组织制定标准时间长,在厂商中TCP/IP更容易理解,虽然有一些结构性的缺陷,但是TCP/IP已经成为名副其实的标准了
主要有应用层,传输层,网络层,数据链路层,物理层
应用层 -------------------------------------------------- 用户数据
传输层 -------------------------------------------------- TCP报头+上层数据
网络层 -------------------------------------------------- IP报头+上层数据
数据链路层 --------------------------------------------- LLC报头+上层数据+FCS MAC报头+上层数据+FCS
物理层 -------------------------------------------------- 0101的Bit
以上都是由上向下传输或者是由下向上传输
1.4 TCP与UDP
基于TCP/IP协议,主要有俩种传输形式,一种是TCP,一种UDP
TCP(传输控制协议):面向连接 重传机制 确认机制 流量控制 (保证可靠)
UDP:面向无连接 低开销 传输效率高,速度快
1.4.1 TCP的三次握手与四次挥手
TCP由于传输数据,要和对端要先建立通道才可以传输数据,所以被称之为可靠的传输协议,传输数据之前需要建立通道,等通道建立成功后,发送数据片段,每发送一个数据片段,发送一个确认码ack,发送端只有在收到ack确认码才会发送下一个数据片段,否则会重新发送未被确认数据片段。由于要确认的东西很多,所以TCP的报头有20字节。这样TCP传输就很占用传输带宽。
以下图片就是三次握手以及四次挥手的过程,这个会后面网络编程中较大联系
1.4.2 UDP协议
UDP协议由于不需要和对端确认通道以及对方是否存在,只需要知道对端是谁就可以,所以UDP也被称之为不可靠传输协议。UDP协议只负责传送,不负责数据是否到达,所以低开销,传输速率高,UDP头部只有8字节。
1.4.3端口基础
端口范围在0----65535(2*16)
知名端口号(0----1023,其他软件禁止使用),
注册端口号(1024----49151,一般用于软件注册,不过一些知名的端口还是建议不使用)
随机端口号(49152----65535,一般用于客户端软件,随机端口)
2.基于c/s结构的服务器客户端的实验
2.1基础知识点-socket
对于上面网络基础了解后,我们可以这么想以后我们自己敲代码了,那我是不是就需要记住这些几层协议,传输层,网络层具体做什么,这个时候就需要一个新的模块了,socket,python中处理网络编程相关的问题,Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部。这样的话,用户就不需要再次了解下面具体怎么实施,只需要知道怎么操作socket就好了,结构如图。
2.1.1socket的套接字
family(socket家族)
socket type类型
(Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.)
服务器端套接字类型
用户端套接字类型
公用套接字类型
2.1.2 tcp建立连接的过程
2.2实验一
目的:建立一个简单的服务器客户端程序,可以建立服务器端与客户端的简单通信,例如输入一串英文字母返回大写
服务器端代码
import socket #1.买手机 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字 #2.绑定电话卡 phone.bind((‘127.0.0.1‘,9997))#服务器端必须绑定ip以及端口,整个是以集合的方式导入 #3.开机 phone.listen(5)#服务器端侦听,队列中最大值为5,不过一次只能对应一个 #4.等待接入 print("holding.............")#验证已经等待接入 conn,client_add = phone.accept() #分别是数据以及客户端的ip地址信息 #5.互相回话 cmd =conn.recv(1024)#cmd是接收的数据,最大是1024 conn.send(cmd.upper())#将接收的数据,变为大写发送回去 #6.结束通话 conn.close()#通话结束 #7.关机 phone.close()#电话关机
客户端代码
import socket #1.买手机 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字 #2.拨打 phone.connect((‘127.0.0.1‘,9997))#联络服务器,拨打服务器ip以及端口 #3.通话 cmd = input(‘---->‘) phone.send(cmd.encode(‘utf-8‘))#将要发送的信息已utf-8的形式 #4.接收数据 data = phone.recv(1024)#接收数据最大是1024字节 print(data)#打印数据 #5.关机 phone.close()#客户端关机
不过这样只实现了一次简单的发送,然后就中止了,还需要一些改善
先实现第一个,服务器端与客户端添加一个死循环,让他可以一直循环的输入输出
在来实现第二个,调用socket的内置方法,实现端口复用,加快系统内部端口的回收速率
在来实现第三个,在客户端配置一个判断,输入为空继续循环,并不输出
最后来实现第四,需要些一个判断条件,在linux环境下,接收不到数据就直接退出,windows设定一个判断条件
最后看以下实现后的代码
服务端代码
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#实现端口的复用
phone.bind((‘127.0.0.1‘,9997))#服务器端必须绑定ip以及端口,整个是以集合的方式导入
phone.listen(5)#服务器端侦听,队列中最大值为5,不过一次只能对应一个
print("holding.............")#验证已经等待接入
conn,client_add = phone.accept() #分别是数据以及客户端的ip地址信息
while True:
try:
cmd =conn.recv(1024)#cmd是接收的数据,最大是1024
if not cmd :break#只用于linux环境下
print("客户端数据:",cmd)
conn.send(cmd.upper())#将接收的数据,变为大写发送回去
except ConnectionResetError:#用于windows
break
conn.close()#通话结束
phone.close()#电话关机
客户端代码
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字
phone.connect((‘127.0.0.1‘,9997))#联络服务器,拨打服务器ip以及端口
while True:
cmd = input(‘---->‘).strip()
if not cmd:continue#如果输入为空则继续下一次循环
phone.send(cmd.encode(‘utf-8‘))#将要发送的信息已utf-8的形式
data = phone.recv(1024)#接收数据最大是1024字节
print(data.decode(‘utf-8‘))#打印数据
phone.close()#客户端关机
这样就实现了一些小bug,我们来说一下程序中系统的实现方法,对于我们写的小程序,并不是我们直接建立连接,服务器端与客户端之间直接收发数据,真正的做法就是我们把发送的数据交给操作系统,操作系统在调用物理硬件将数据发出,接收端也是发送信息交给操作系统让他从网卡这里获取收到的数据,传递给应用程序。
这里我们说一下send与recv的区别
send:不管recv还是send并不是直接接受或者发送对方的数据,而是操作自己操作系统的内存,将数据copy一份给内存,不过并不是一个send对应一个recv
recv:主要是有俩个过程wait data与copy data 俩个过程,wait data 时间是最长的,中间要经过网络传输,内存获取到数据将数据copy到应用层
补充一个:服务器端接收数据中的conn是一个套接字对象,是一个基于TCP协议建立的一个链接
2.3实验二
目的:实现多客户端连接服务器
其实挺简单的,客户端的代码多复制几份,服务器代码在加上一个循环就可以了
服务器端代码
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#实现端口的复用 phone.bind((‘127.0.0.1‘,9997))#服务器端必须绑定ip以及端口,整个是以集合的方式导入 phone.listen(5)#服务器端侦听,队列中最大值为5,不过一次只能对应一个 while True: print("holding.............")#验证已经等待接入 conn,client_add = phone.accept() #分别是数据以及客户端的ip地址信息 while True: try: cmd =conn.recv(1024)#cmd是接收的数据,最大是1024 if not cmd :break#只用于linux环境下 print("客户端数据:",cmd) conn.send(cmd.upper())#将接收的数据,变为大写发送回去 except ConnectionResetError:#用于windows break conn.close()#通话结束,每一次连接结束,都应该将连接结束然后等待新的连接 phone.close()#电话关机
由于我们设定的listen最多监听五个客户端,由于还没有学习并发进程,目前只能实现一个一个链接,上述的实现过程就是服务器端开启,客户端也开启,一个先链接,其他就等待之前链接的结束,之前一个客户端结束,则服务器端就接收另外一个客户端的数据。
2.4实验三
目的:改装现有的程序,将其变更为一个可以远程执行命令的一个小程序
补充知识点:由于我们需要调用系统内的语句,所以我们这个时候就需要很早之前学习的subprocess模块的知识,先来复习一下这个知识点
我们经常需要通过Python去执行一条系统命令或脚本,系统的shell命令是独立于你的python进程之外的,每执行一条命令,就是发起一个新进程,通过python调用系统命令或脚本的模块在python2有os.system,除了os.system可以调用系统命令,,commands,popen2等也可以,比较乱,于是官方推出了subprocess,目地是提供统一的模块来实现对系统命令或脚本的调用。
一般的使用方法:
run()方法:
标准写法
subprocess.run([‘df‘,‘-h‘],stderr=subprocess.PIPE,stdout=subprocess.PIPE,check=True)
涉及到管道|的命令需要这样写
subprocess.run(‘df -h|grep disk1‘,shell=True) #shell=True的意思是这条命令直接交给系统去执行,不需要python负责解析
#执行命令,返回命令执行状态 , 0 or 非0 >>> retcode = subprocess.call(["ls", "-l"]) #执行命令,如果命令结果为0,就正常返回,否则抛异常 >>> subprocess.check_call(["ls", "-l"]) 0 #接收字符串格式命令,返回元组形式,第1个元素是执行状态,第2个是命令结果 >>> subprocess.getstatusoutput(‘ls /bin/ls‘) (0, ‘/bin/ls‘) #接收字符串格式命令,并返回结果 >>> subprocess.getoutput(‘ls /bin/ls‘) ‘/bin/ls‘ #执行命令,并返回结果,注意是返回结果,不是打印,下例结果返回给res >>> res=subprocess.check_output([‘ls‘,‘-l‘]) >>> res b‘total 0\ndrwxr-xr-x 12 alex staff 408 Nov 2 11:05 OldBoyCRM\n‘
popen()方法
常用参数: args:shell命令,可以是字符串或者序列类型(如:list,元组) stdin, stdout, stderr:分别表示程序的标准输入、输出、错误句柄 preexec_fn:只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用 shell:同上 cwd:用于设置子进程的当前目录 env:用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。 下面这2条语句执行会有什么区别? a=subprocess.run(‘sleep 10‘,shell=True,stdout=subprocess.PIPE) a=subprocess.Popen(‘sleep 10‘,shell=True,stdout=subprocess.PIPE) 区别是Popen会在发起命令后立刻返回,而不等命令执行结果。这样的好处是什么呢? 如果你调用的命令或脚本 需要执行10分钟,你的主程序不需卡在这里等10分钟,可以继续往下走,干别的事情,每过一会,通过一个什么方法来检测一下命令是否执行完成就好了。 Popen调用后会返回一个对象,可以通过这个对象拿到命令执行结果或状态等,该对象有以下方法 poll() Check if child process has terminated. Returns returncode wait() Wait for child process to terminate. Returns returncode attribute. terminate()终止所启动的进程Terminate the process with SIGTERM kill() 杀死所启动的进程 Kill the process with SIGKILL communicate()与启动的进程交互,发送数据到stdin,并从stdout接收输出,然后等待任务结束 >>> a = subprocess.Popen(‘python3 guess_age.py‘,stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE,shell=True) >>> a.communicate(b‘22‘) (b‘your guess:try bigger\n‘, b‘‘) send_signal(signal.xxx)发送系统信号 pid 拿到所启动进程的进程号
复习完了,我们来写一下,修改后的小程序
服务器端代码
import socket import subprocess phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#实现端口的复用 phone.bind((‘127.0.0.1‘,9997))#服务器端必须绑定ip以及端口,整个是以集合的方式导入 phone.listen(5)#服务器端侦听,队列中最大值为5,不过一次只能对应一个 print("holding.............")#验证已经等待接入 conn,client_add = phone.accept() #分别是数据以及客户端的ip地址信息 while True: try: cmd =conn.recv(1024)#cmd是接收的数据,最大是1024 if not cmd :break#只用于linux环境下 obj = subprocess.Popen(cmd.decode(‘gbk‘),shell=True,#跳出python,用系统自带的系统解析器解析,由于是windows,解码使用gbk stdout=subprocess.PIPE,#输出正确的输出结果 stderr=subprocess.PIPE #输出错误的输出结果 ) stdout = obj.stdout.read() #正确输出 stderr = obj.stderr.read() #错误输出 conn.send(stdout+stderr)#将系统执行的结果发送回去 except ConnectionResetError:#用于windows break conn.close()#通话结束 phone.close()#电话关机
客户端实验结果
C:\Users\gao-bq.KDDICAN\AppData\Local\Programs\Python\Python35\python.exe D:/PycharmProjects/s2018/day6/实验三/客户端.py ---->ping 192.168.0.1 正在 Ping 192.168.0.1 具有 32 字节的数据: 来自 192.168.0.1 的回复: 字节=32 时间=1ms TTL=64 来自 192.168.0.1 的回复: 字节=32 时间=1ms TTL=64 来自 192.168.0.1 的回复: 字节=32 时间=4ms TTL=64 来自 192.168.0.1 的回复: 字节=32 时间=2ms TTL=64 192.168.0.1 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失), 往返行程的估计时间(以毫秒为单位): 最短 = 1ms,最长 = 4ms,平均 = 2ms ---->ipconfig \A 错误: 无法识别或不完整的命令行。
这样貌似正确与错误的结果都得到了,小兴奋
这个时候貌似就实现了一个可以远程实现ssh服务的小程序,不过这个有一个点需要注意,命令结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码,且只能从管道里读一次结果。
简单的SSH远程服务算是实现了,不过还有一些坑还没实现,下个实验填坑的同时将程序做一个提升。
2.5实验四
实验现象:根据上面写的小程序,我们就可以放肆的去练习了,不过我们在实验的时候还是有bug产生,先看实验现象
C:\Users\gao-bq.KDDICAN\AppData\Local\Programs\Python\Python35\python.exe D:/PycharmProjects/s2018/day6/实验四/客户端.py ---->dir 驱动器 D 中的卷没有标签。 卷的序列号是 B8C1-218B D:\PycharmProjects\s2018\day6\实验四 的目录 2018/06/16 10:22 <DIR> . 2018/06/16 10:22 <DIR> .. 2018/06/13 00:53 0 __init__.py 2018/06/14 00:40 607 客户端.py 2018/06/16 10:22 1,317 服务器端.py 3 个文件 1,924 字节 2 个目录 79,045,222,400 可用字节 ---->ipconfig Windows IP 配置 以太网适配器 以太网: 媒体状态 . . . . . . . . . . . . : 媒体已断开连接 连接特定的 DNS 后缀 . . . . . . . : 无线局域网适配器 本地连接* 1: 媒体状态 . . . . . . . . . . . . : 媒体已断开连接 连接特定的 DNS 后缀 . . . . . . . : 无线局域网适配器 WLAN: 连接特定的 DNS 后缀 . . . . . . . : DHCP HOST 本地链接 IPv6 地址. . . . . . . . : fe80::6443:8467:1b69:5693%10 IPv4 地址 . . . . . . . . . . . . : 192.168.0.102 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 192.168.0.1 无线局域网适配器 本地连接* 14: 连接特定的 DNS 后缀 . . . . . . . : 本地链接 IPv6 地址. . . . . . . . : fe80::560:b85b:e1c7:8a63%2 IPv4 地址 . . . . . . . . . . . . : 192.168.137.1 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 以太网适配器 蓝牙网络连接 2: 媒体状态 . . . . . . . . . . . . : 媒体已断开连接 连接特定的 DNS 后缀 . . . . . . . : 隧道适配器 Teredo ---->dir Tunneling Pseudo-Interface: 连接特定的 DNS 后缀 . . . . . . . : IPv6 地址 . . . . . . . . . . . . : 2001:0:9d38:953c:4ee:519:c273:54a5 本地链接 IPv6 地址. . . . . . . . : fe80::4ee:519:c273:54a5%19 默认网关. . . . . . . . . . . . . : :: ---->dir 驱动器 D 中的卷没有标签。 卷的序列号是 B8C1-218B D:\PycharmProjects\s2018\day6\实验四 的目录 2018/06/16 10:22 <DIR> . 2018/06/16 10:22 <DIR> .. 2018/06/13 00:53 0 __init__.py 2018/06/14 00:40 607 客户端.py 2018/06/16 10:22 1,317 服务器端.py 3 个文件 1,924 字节 2 个目录 79,045,222,400 可用字节 ---->ipconfig /all 驱动器 D 中的卷没有标签。 卷的序列号是 B8C1-218B D:\PycharmProjects\s2018\day6\实验四 的目录 2018/06/16 10:22 <DIR> . 2018/06/16 10:22 <DIR> .. 2018/06/13 00:53 0 __init__.py 2018/06/14 00:40 607 客户端.py 2018/06/16 10:22 1,317 服务器端.py 3 个文件 1,924 字节 2 个目录 79,045,222,400 可用字节
哎,我们可以看到,我们需要的东西怎么都滞后了或者是混乱了,本来一个命令下就应该全部看到,结果分俩次,而第二个命令又没有显示,反而成为了上一个命令结余的部分,这样不就错了吗,这样说明上面那个小程序还是有很多BUG的,不过这种现象就叫做黏包现象。
改善目的:去除服务器发送时的‘+’号,改善客户端接收接收,不管一个命令结果是多大,都可以在一次命令行下都返回回来
补充知识1:由于TCP连接,是面向连接的,在客户端设定接收最大1024的时候,在收一个超过1024字节的结果的时候,会因为一次性收不完,只在下一次结果中体现出来,收到的结果并不会丢失。而这种在管道内多个包粘在一起的就叫做粘包现象。
TCP是面向连接,面向流的,为了将多个发往接收端的包,更有效的发送给对象,采用优化算法,将多个间隔较小的且数据量的数据合并成一个大的数据,然后进行封包
改善1:那么我们就可以这么改
conn.send(stdout+stderr)#原本的 conn.send (stdout)#修改后,将一个拆分为俩个,由于俩个发送间隔短,所以俩个根据TCP的算法会一起打包发送 conn.send(stderr)
补充知识2:在解决上面+号问题之后,我们就要解决第二个问题,怎么才能一个命令接收全部回复呢,要吗就是把接收端这边最大值不设定成1024,设置成9999999这种无限大,可是这种也不合适啊,那么就剩下一个方法,就是在发送数据之前,将数据包的大小告诉我,我来调整接收大小,这个就像TCP传输的过程中头文件,那么这样就需要一个新的模块,struct模块。那么下面说一下struct模块了,不过这个模块也没有深入学习,查找了各方资料,只学习了一部分
struct模块的用处
目前只学习了其中的pack与unpack俩个用法,就暂时只说这俩个把
pack 按照给定的格式(fmt),把数据转换成字符串(字节流),并将该字符串返回. 返回值格式为string
unpack 按照给定的格式(fmt)解析字节流,并返回解析结果 返回值格式为tuple
那么我们就用这个先改写一下吧
服务器端代码
import socket import subprocess import struct phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#实现端口的复用 phone.bind((‘127.0.0.1‘,9997))#服务器端必须绑定ip以及端口,整个是以集合的方式导入 phone.listen(5)#服务器端侦听,队列中最大值为5,不过一次只能对应一个 print("holding.............")#验证已经等待接入 conn,client_add = phone.accept() #分别是数据以及客户端的ip地址信息 print(client_add) while True: try: cmd =conn.recv(1024)#cmd是接收的数据,最大是1024 if not cmd :break#只用于linux环境下 obj = subprocess.Popen(cmd.decode(‘gbk‘),shell=True,#跳出python,用系统自带的解析器解析 stdout=subprocess.PIPE,#输出正确的输出结果 stderr=subprocess.PIPE #输出错误的输出结果 ) stdout = obj.stdout.read() stderr = obj.stderr.read() #发送数据 #第一步:制作固定长度的报头 total_size = len(stdout) + len(stderr) # 确定要发数据的总大小 header = struct.pack(‘i‘, total_size) # 制作报头,将数据大小打包进去 #第二步:发送报头信息 conn.send(header) # 发送报头 #第三步:发送真实数据 conn.send(stdout)#发送数据 conn.send(stderr)#发送数据 except ConnectionResetError:#用于windows break conn.close()#通话结束 phone.close()#电话关机
客户端代码
import socket import struct phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字 phone.connect((‘127.0.0.1‘,9997))#联络服务器,拨打服务器ip以及端口 while True: cmd = input(‘---->‘).strip() if not cmd:continue#如果输入为空则继续下一次循环 phone.send(cmd.encode(‘gbk‘))#将要发送的信息已utf-8的形式 #接收数据 #第一步:接收报头 header = phone.recv(4)#这里接收4的原因是在服务器端固定报头长度为4,i为4,l为8 #第二步:从报头中接收真实的描述信息 total_size = struct.unpack(‘i‘,header)[0] #第三步:接收真实的数据 recv_size = 0 recv_data =b‘‘ while recv_size<total_size: res = phone .recv(1024) recv_data += res recv_size += len(res) print(recv_data.decode(‘gbk‘))#打印数据 phone.close()#客户端关机
看上面的代码,虽然是实现了,不过还是有些问题的,对于报头也太过于简单了,虽然是一个自定义协议,但是这报头简单的不像话了,就像报头的话,也应该包含多个信息
对此修改了最新的版本,这个代码下面的实验也会用到
服务器端
import socket import subprocess import struct import json phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#实现端口的复用 phone.bind((‘127.0.0.1‘,9997))#服务器端必须绑定ip以及端口,整个是以集合的方式导入 phone.listen(5)#服务器端侦听,队列中最大值为5,不过一次只能对应一个 print("holding.............")#验证已经等待接入 conn,client_add = phone.accept() #分别是数据以及客户端的ip地址信息 print(client_add) while True: try: cmd =conn.recv(1024)#cmd是接收的数据,最大是1024 if not cmd :break#只用于linux环境下 obj = subprocess.Popen(cmd.decode(‘gbk‘),shell=True,#跳出python,用系统自带的解析器解析 stdout=subprocess.PIPE,#输出正确的输出结果 stderr=subprocess.PIPE #输出错误的输出结果 ) stdout = obj.stdout.read() stderr = obj.stderr.read() #发送数据 #第一步:制作固定长度的报头 header_dict ={ ‘file_name‘:‘a.txt‘, ‘md5‘:‘xxxxx‘, ‘total_size‘:len(stdout) + len(stderr), }#报头字典 header_json = json.dumps(header_dict)#将报头转换为json模式 header_bytes = header_json.encode(‘gbk‘)#将json模式的数据转换为byte类型 #第二步,发送报头长度 conn.send(struct.pack(‘i‘, len(header_bytes))) # 发送报头的长度 #第三步:发送报头信息 conn.send(header_bytes) # 发送报头 #第四步:发送真实数据 conn.send(stdout)#发送数据 conn.send(stderr)#发送数据 except ConnectionResetError:#用于windows break conn.close()#通话结束 phone.close()#电话关机
用户端代码
import socket import struct import json phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字 phone.connect((‘127.0.0.1‘,9997))#联络服务器,拨打服务器ip以及端口 while True: cmd = input(‘---->‘).strip() if not cmd:continue#如果输入为空则继续下一次循环 phone.send(cmd.encode(‘gbk‘))#将要发送的信息已utf-8的形式 #接收数据 #第一步:接收报头长度 obj = phone.recv(4) header_size = struct.unpack(‘i‘,obj)[0] #第二步:接收真实的报头信息 header_bytes = phone.recv(header_size) #第三步:从报头中接收真实的描述信息 header_json = header_bytes.decode(‘gbk‘) #从接收的包中反解码出json格式的包 header_dic = json.loads(header_json)#从json包中反解码字典 print(header_dic) total_size = header_dic[‘total_size‘]#从字典中获取文件大小 #第四步:接收真实的数据 recv_size = 0 recv_data =b‘‘ while recv_size<total_size: res = phone .recv(1024) recv_data += res recv_size += len(res) print(recv_data.decode(‘gbk‘))#打印数据 phone.close()#客户端关机
2.6实验五
目的:用上面的代码实现简单的FTP小程序
服务器端代码
import socket import struct import json import os phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#实现端口的复用 phone.bind((‘127.0.0.1‘,9997))#服务器端必须绑定ip以及端口,整个是以集合的方式导入 phone.listen(5)#服务器端侦听,队列中最大值为5,不过一次只能对应一个 print("holding.............")#验证已经等待接入 conn,client_add = phone.accept() #分别是数据以及客户端的ip地址信息 print(client_add) while True: try: #1.收命令 rec =conn.recv(8096)#接收文件 if not rec :break#只用于linux环境下 #2.解析命令 cmds = rec.decode(‘utf-8‘).split() filname = cmds[1] #发送数据 #第一步:制作固定长度的报头 header_dict ={ ‘file_name‘:‘filename‘, ‘md5‘:‘xxxxx‘, ‘total_size‘:os.path.getsize(filname) }#报头字典 header_json = json.dumps(header_dict)#将报头转换为json模式 header_bytes = header_json.encode(‘utf-8‘)#将json模式的数据转换为byte类型 #第二步,发送报头长度 conn.send(struct.pack(‘i‘, len(header_bytes))) # 发送报头的长度 #第三步:发送报头信息 conn.send(header_bytes) # 发送报头 #第四步:发送真实数据 with open(filname,‘rb‘) as f: for line in f: conn.send(line) except ConnectionResetError:#用于windows break conn.close()#通话结束 phone.close()#电话关机
客户端代码
import socket import struct import json phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#实例化一个对象,括号内第一个是基于网络通信的套接字,第二个是基于TCP的套接字 phone.connect((‘127.0.0.1‘,9997))#联络服务器,拨打服务器ip以及端口 while True: cmd = input(‘---->‘).strip() if not cmd:continue#如果输入为空则继续下一次循环 phone.send(cmd.encode(‘utf-8‘))#将要发送的信息已utf-8的形式 #接收数据 #第一步:接收报头长度 obj = phone.recv(4) header_size = struct.unpack(‘i‘,obj)[0] #第二步:接收真实的报头信息 header_bytes = phone.recv(header_size) #第三步:从报头中接收真实的描述信息 header_json = header_bytes.decode(‘gbk‘) #从接收的包中反解码出json格式的包 header_dic = json.loads(header_json)#从json包中反解码字典 print(header_dic) total_size = header_dic[‘total_size‘]#从字典中获取文件大小 filename = header_dic[‘file_name‘] #第四步:接收真实的数据 with open(filename,‘wb‘)as f: recv_size = 0 while recv_size<total_size: line = phone .recv(1024) f.write(line) recv_size += len(line) phone.close()#客户端关机
就这样就实现了,不过还有些要改善的
2.7
2.8
2.9
标签:ret 问题 set city ip地址 创建 优化算法 with 复习
原文地址:https://www.cnblogs.com/gbq-dog/p/9180999.html