手机python编程软件西西 -14.python-CS编程

一.客户端/服务器架构

1.C/S架构:

(1)硬件C/S架构(打印机)

(2)软件C/S架构(web服务)

2.生活中的C/S架构:

饭店是S端,所有食客是C端

3.C/S架构与socket的关系:

socke就是为了完成C/S架构的开发

二.互联 协议osi七层

1.一个完整的计算机系统由硬件,操作系统,应用软件三者组成,具备了这三个条件,一台计算机系统就可以自己跟自己玩了,如果要跟别人一起玩,就需要上 ,互联 的核心就是由一堆协议组成,协议就是标准,全世界人通信的标准是英语,如果把计算机比作人,互联 协议就是计算机界的英语。所有的计算机都学会了互联 协议,那所有的计算机都可以按照统一的标准去收发信息从而完成通信了。人们按照分工不同把互联 协议从逻辑上划分了层级。

第一层物理层:目的是传输的连接介质(光缆,双绞线,无线电波),物理层就可以发送电信 010101

第二层数据链路层:负责把发送的电信 010101分组后电信 就有意义了,以太 协议,包含 头(原mac和目标mac)和数据部分,有了原mac和目标mac就可以基于广播的方式发送(mac标识在这个局域 的哪一个位置)

第三层 络层:IP协议标识一个 络的(IP在标识不同的子 在哪里),发送方式首先通过IP地址跟子 掩码地址计算得到一个自己的 络地址和目标 络地址,如果在一个子 里面可直接通信,如果不在一个子 里这个包就会送给 关, 关在转发到一样的子 里

第四层传输层:基于TCP和UDP协议端口方式

TCP协议三次握手和四次挥手

(1)三次握手:是你得让对方知道你已经知道他知道,确定我和对方都准备好了再传输数据

发起一次握手:客户端与服务器发生连接首先要发syn包请求到达服务器端(syn包:原IP,目标IP,原端口,目标端口)

收到一次握手:当服务器端服务器收到syn之后,他的状态就会转变成SYN_RECV(指的是某一个连接) 服务器会创建一个socket_buff

发起二次握手:服务器会回一个包syn+ack

收到二次握手:客户端收到包之后,客户端已经相应了,客户端回第三个包ack发起三次握手

收到三次握手:当服务器收到第三个包之建立好3次握手状态就会变成establi

(2)传输上层数据:

当两端只有建立establi之后,客户端才能传输上层数据

session建立TCP/IP会话

发起一个请求要index.html文件—-有数据服务器返回数据

(3)四次断开:四次断开时客户端和服务器都可以发起断开,因为会牵扯到数据传输所以要四次断开(谁先发完包谁就主动发起断开连接)

发起一次断开:客户端发起了一个fin请求给服务端,客户端会进入fin_wait_1状态(主动断开连接请求)

发起二,三次断开:服务器收到fin他会把自己的状态设置成CLOSE_WAIT,他给客户端回一个ack,代表收到fin这个事(被动断开一端会出现CLOSE_WAIT)

发起四次断开:客户端收到后会把状态设置成FIN_WAIT2,等待服务器最后发送的FIN,等收到最后的FIN,状态会变成TIME_WAIT(主动断开的一端会出现TIME_WAIT),当客户收到fin回一个ack(TIME_WAIT默认停留一分钟 等ack数据包)到此整个通讯结束

第五层会话层:解除或建立与其他接点的联系

第六层表示层:数据格式化,代码转换,数据加密

第七层应用层:本机开启一个软件会监听这个端口(跑TCP或UDP协议),端口会跟IP地址还有mac信息相绑定标识了哪一个子 当中一个程序

三.socket

1.socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在socket接口的后面,对于用户来说,一组简单的接口就是全部,让socket去组织数据,以符合指定的协议,所以我们无需深入理解TCP/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的

2.socket的五个协议:组成一个逻辑上的文件

(1)原端口-sport

(2)原IP-sip

(3)目标端口-dport:对端主机交给哪个进程的

(4)目标IP-dip

(5)协议-tcp/ip

只要其中任意一个发生改变就不是同一个socket

四.套接字发展及分类

1.一开始,套接字被设计用在同一台主机上多个应用程序之间通讯,这也被称进程间通讯或IPC。套接字有俩种(或者称为有俩个种族),分别是基于文件型的和基于 络型的

2.基于文件类型的套接字家族:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,俩个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

3.基于 络类型的套接字家族:AF_INET

AF_INET是一种广泛的一个,python支持多种地址家族,但是由于无名指关心 络编程,所以大部分时候我们只使用AF_INET

五.TCP/IP 套接字

1.结合现实生活的工作流程

TCP服务端 TCP客户端

socket()–买手机 socket()

bind()–绑定一个手机卡

lister()–开机

accept()–等电话,拿到一个电话链接 connect()拨电话把请求给服务端accept()

read()–收消息 write()发消息给服务端read进行一系列处理

write()–发消息 服务端回应数据给客户端read(),再执行write

close()–断开电话链接

close()–关机 客户端关闭链接close()

2.socket()模块函数用法

importsocket

socket.socket(socket_family,socket_type,protocal=0) #socket_family可以是AF_UNIX或AF_INET。socket_type可以是SOCK_STREAM或SOCK_DGRAM。protocol一般不填,默认值为0

#获取tcp/ip套接字

tcpSock =socket.socket(socket.AF_INET,socket.SOCK_STREAM)#获取udp/ip套接字

udpSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

(1)服务端套接字函数:

s.bind():绑定(主机,端口 )到套接字

s.listen():开始TCP监听

saccept():被动接受TCP客户端的连接,(阻塞式)等待连接的到来

(2)客户端套接字函数

s.connect():主动初始化TCP服务器连接

s.connect_ex():connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

(3)公共用途的套接字函数

s.recv():接收TCP数据

s.send():发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)

s.sendall():发送完整的TCP数据(本质就是循环调用send,sendall在待发数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)

s.recvfrom():接收UDP数据

s.sendto():发送UDP数据

s.getpeername():连接到当前套接字的远端的地址

s.getsockname():当前套接字的地址

s.getsockopt():返回指定套接字的参数

s.setsockopt():设置指定套接字的参数

s.close():关闭套接字

(4)面向锁的套接字方法

s.setblocking()设置套接字的阻塞与非阻塞模式

s.settimeout()设置阻塞套接字操作的超时时间

s.gettimeout()得到阻塞套接字操作的超时时间

(5)面向文件的套接字的函数

s.fileno():套接字的文件描述符

s.makefile():创建一个与该套接字相关的文件

3.用socket方式创建一个服务端和一个客户端使两者进行通信

服务端:

import socket #导入socket模块

#建立三次握手

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #第一步:socket.socket产生一个对象传俩个参数(socket.AF_INET基于 络通讯,socket.SOCK_STREAM表TCP协议)给phone创建套接字

phone.bind((“127.0.0.1”,8000)) #第二步:把IP地址和访问和端绑定到套接字

phone.listen(5) #第三步:监听链接listen(5)最多可以有五个建立好三次握手后的backlog(半连接池)等着,后面的需要排队等着

conn,addr=phone.accept() #第四步:phone.accept()相当于拿到了TCP三次握手的结果是个元祖解压给给conn(三次握手的连接)和addr

#数据传输,基于TCP三次握手建立好的双向连接才能完成数据传输,收发消息基于 络方式二进制方式

msg=conn.recv(1024) #第八步:收消息

print(“客户端发来的消息是:”,msg)

conn.send(msg.upper())#第九步:服务端给客户端回一个大写的msg

#四次挥手

conn.close() #第十一步:关闭三次握手连接,触发的是四次挥手

phone.close() #第十二步:关闭socket,把这个程序关掉

客户端:

importsocket

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #第五步:socket.socket产生一个对象传俩个参数(socket.AF_INET基于 络通讯,socket.SOCK_STREAM表TCP协议)给phone

phone.connect((“127.0.0.1”,8000)) #第六步:找到服务端的IP和端口

phone.send(“hello”.encode(“utf-8”)) #第七步:客户端发要把消息hello进行二进制编码给服务端的msg

data=phone.recv(1024) #第十步:客户端接收服务端消息打印

print(“客户端收到服务端的发来的消息:”,data)

服务端打印:

客户端发来的消息是: b”hello”

客户端打印:

客户端收到服务端的发来的消息: b”HELLO”

4.基于TCP套接字的通讯流程:创建服务端循环链接请求俩台客户度收发发消息

服务端:

#import socket

from socket import * #由于socket模块中有太多的属性。所以使用from module import *语句。就可以把socket模块里的所有属性都带到我们的命名空间里,这样能大幅减少我们的代码

#把变量提取出来ip_port=(“127.0.0.1”,8080)

back_log=5 #半连接池最多可以有5个建立好三次握手后的连接等待buffer_size=1024 #1024代表接收字节

tcp_server=socket(AF_INET,SOCK_STREAM) #第一步:产生一个对象传俩个参数(socket.AF_INET基于 络通讯,socket.SOCK_STREAM表TCP协议)给tcp_server创建服务器套接字

tcp_server.bind(ip_port) #第二步:把IP地址和访问和端口 绑定到套接字

tcp_server.listen(back_log) #第三步:监听链接,listen(5)最多可以有五个建立好三次握手后的backlog(半连接池)等着,后面的需要排队等着

while True: #第四步:服务端做连接循环的接,可以做到接收多个人发的连接

print(“服务端开始运行了”)

conn,addr=tcp_server.accept() #第五步:tcp_server.accept()相当于拿到了TCP三次握手的结果是个元祖解压给给conn(三次握手的连接)和addr服务端阻塞

print(“双向链接是”,conn) #打印conn:

print(“客户端地址”,addr) #打印addr:

while True: #第六步:给收消息和发消息加上通讯循环就可以多次收发消息

try: #做一个异常处理

data=conn.recv(buffer_size) #第七步:服务端收客户端消息,recv是用户态应用程序发起的( 卡来接收消息交给操作系统到内核态内存,应用程序从里面取,如果是空会卡住)

print(“客户端发来的消息是”,data.decode(“utf-8”))

conn.send(data.upper())#第八步:服务端发送data字节格式通过upper()转大写回给客户端

except Exception: #当客户端断掉不要影响程序的中断

break #跳出当前客户端收发消息这个循环,跳到服务端循环接消息的位置

conn.close() #第九步:关闭三次握手连接,触发的是四次挥手,关闭客户端套接字

tcp_server.close()#第十步:关闭socket,把这个程序关掉,关闭服务器套接字

客户端1:

#import socket

from socket import *ip_port=(“127.0.0.1”,8080)

back_log=5buffer_size=1024tcp_client=socket(AF_INET,SOCK_STREAM) #第一步:客户端产生一个对象传俩个参数(socket.AF_INET基于 络通讯,socket.SOCK_STREAM表TCP协议)给tcp_client创建客户端套接字

tcp_client.connect(ip_port) #第二步:客户端连接服务器端的IP和端口

while True: #第三步:给发消息和收消息加上循环可以循环发收消息

msg=input(“>>:”).strip() #第四步:客户端让用户输入方式发消息

if not msg:continue #第五步:客户端做判断如果输入为空从新输入

tcp_client.send(msg.encode(“utf-8”)) #第六步:客户端把用户输入的消息进行二进制编码给服务端的msg(socket发消息会从用户态内存send给内核态内存,发到内核态的内存由操作系统接收,操作系统操作 卡发送出去)

print(“客户端已经发送消息”)

data=tcp_client.recv(buffer_size) #第七步:客户端接收服务端字节格式

print(“收到服务端发来的消息”,data.decode(“utf-8”)) #通过解码看服务端发送的消息

tcp_client.close() #第八步:关闭客户端套接字

客户端2:

#import socket

from socket import *ip_port=(“127.0.0.1”,8080)

back_log=5buffer_size=1024tcp_client=socket(AF_INET,SOCK_STREAM) #第一步:客户端产生一个对象传俩个参数(socket.AF_INET基于 络通讯,socket.SOCK_STREAM表TCP协议,流式套接字)给tcp_client创建客户端套接字

tcp_client.connect(ip_port) #第二步:客户端连接服务器端的IP和端口

while True: #第三步:给发消息和收消息加上循环可以循环发收消息

msg=input(“>>: “).strip() #第四步:客户端让用户输入方式发消息

if not msg:continue #第五步:客户端做判断如果输入为空从新输入

tcp_client.send(msg.encode(“utf-8”)) #第六步:客户端把用户输入的消息进行二进制编码给服务端的msg(socket发消息会从用户态内存send给内核态内存,发到内核态的内存由操作系统接收,操作系统操作 卡发送出去)

print(“客户端已经发送消息”)

data=tcp_client.recv(buffer_size) #第七步:客户端接收服务端字节格式

print(“收到服务端发来的消息”,data.decode(“utf-8”)) #通过解码看服务端发送的消息

tcp_client.close() #第八步:关闭客户端套接字

当客户端1输入>>: xiaoxi

客户端1返回:

客户端已经发送消息

收到服务端发来的消息 XIAOXI

服务端返回:

服务端开始运行了

双向链接是

客户端地址 (“127.0.0.1”, 60931)

客户端发来的消息是 xiaoxi

当客户端1断开连接且客户端2输入:>>: daxi

客户端2返回:

客户端已经发送消息

收到服务端发来的消息 DAXI

服务端返回:

服务端开始运行了

双向链接是

客户端地址 (“127.0.0.1”, 60932)

客户端发来的消息是 daxi

六.基于UDP的套接字通讯流程(由于udp无连接,所以可以同时多个客户端去跟服务端通讯)

服务端:

from socket import * #导入模块

ip_port=(“127.0.0.1”,8080)

buffer_size=1024udp_server=socket(AF_INET,SOCK_DGRAM) #第一步:服务端产生套接字对象传俩个参数(AF_INET基于 络,SOCK_DGRAM数据 套接字类型)

udp_server.bind(ip_port) #第二步:绑定服务器套接字IP和端口

while True: #第三步:服务器通讯循环

data,addr=udp_server.recvfrom(buffer_size) #第四步:服务端收客户端消息,addr是给我发消息的客户端IP和端口,recvfrom收的时候缓冲区如果没有的话拿到空

print(“接收客户端发来的IP和端口”,addr) #打印客发来消息的客户端的端口和IP

print(“接收客户端发来的消息内容”,data) #打印客户端收来的消息

udp_server.sendto(data.upper(),addr) #第五步:服务端发送data字节格式通过upper()转大写回给客户端ddr的IP和端口

udp_server.close()#第六步:关闭服务器套接字

客户端1:

from socket import *ip_port=(“127.0.0.1”,8080)

buffer_size=1024udp_client=socket(AF_INET,SOCK_DGRAM) #第一步:创建客户端产生套接字对象传俩个参数(AF_INET基于 络,SOCK_DGRAM数据 套接字类型)

while True: #第二步:通讯循环

msg=input(“>>:”).strip() #客户端让用户输入方式发消息

udp_client.sendto(msg.encode(“utf-8”),ip_port) #第三步:客户端把用户输入的消息进行二进制编码给服务端的,udp协议发包没有连接,只能每次发包指定sendto要发给服务端的地址跟端口

data,addr=udp_client.recvfrom(buffer_size) #第四步:客户端接收服务端字节格式

print(“客户端接收到服务端返回数据”,data)

客户端2:

from socket import *ip_port=(“127.0.0.1”,8080)

buffer_size=1024udp_client=socket(AF_INET,SOCK_DGRAM) #第一步:创建客户端产生套接字对象传俩个参数(AF_INET基于 络,SOCK_DGRAM数据 套接字类型)

while True: #第二步:通讯循环

msg=input(“>>:”).strip() #客户端让用户输入方式发消息

udp_client.sendto(msg.encode(“utf-8”),ip_port) #第三步:客户端把用户输入的消息进行二进制编码给服务端的,udp协议发包没有连接,只能每次发包指定sendto要发给服务端的地址跟端口

data,addr=udp_client.recvfrom(buffer_size) #第四步:客户端接收服务端字节格式

print(“客户端接收到服务端返回数据”, data)

客户端1输入:>>: xiaoxi

返回:

客户端接收到服务端返回数据 b”XIAOXI”

客户端2输入:>>: daxi

返回:

客户端接收到服务端返回数据 b”DAXI”

服务端返回:

接收客户端发来的IP和端口 (“127.0.0.1”, 61907)

接收客户端发来的消息内容 b”xiaoxi”

接收客户端发来的IP和端口 (“127.0.0.1”, 61908)

接收客户端发来的消息内容 b”daxi”

基于udp实现ntp服务

服务端:

from socket import *

importtime

ip_port=(“127.0.0.1”,8080)

buffer_size=1024udp_server=socket(AF_INET,SOCK_DGRAM) #udp_server.bind(ip_port)whileTrue:

data,addr=udp_server.recvfrom(buffer_size)print(data)if not data: #判断客户端发来的的空的情况下

fmt=”%Y-%m-%d %X” #返回默认时间

else:

fmt=data.decode(“utf-8”) #如果客户端输入格式就把传的值发来

back_time=time.strftime(fmt)

udp_server.sendto(back_time.encode(“utf-8”),addr) #把服务端的时间以encode字符串形式返回给客户端

客户端:

from socket import *ip_port=(“127.0.0.1”,8080)

buffer_size=1024udp_client=socket(AF_INET,SOCK_DGRAM)whileTrue:

msg=input(“>>:”).strip()

udp_client.sendto(msg.encode(“utf-8”),ip_port) #客户端发给服务端一个消息

data,addr=udp_client.recvfrom(buffer_size) #服务端返回的时间

print(“ntp服务器的标准时间是”,data.decode(“utf-8”))

客户端输入:>>: %Y

客户端返回:

ntp服务器的标准时间是 2018

服务端返回:

b”%Y”

客户端输入空>>:

客户端返回:

ntp服务器的标准时间是 2018-11-07 13:39:37

服务端返回:

b””

b””

七.recv与recvfrom的区别

tcp:send发消息,recv收消息

udp:sendto发消息,recvfrom收消息是元祖的形式

发消息二者类似,收消息确实有区别

1.tcp协议:

(1)如果收消息缓冲区里的数据为空,那么recv就会堵塞

(2)tcp基于链接通信,如果一端端口链接,那另外一端的链接也跟着完蛋recv将会堵塞,收到的是空

2.udp协议:

(1)如果收消息缓冲区里的数据为空,recvfrom不会堵塞

(2)recvfrom收的数据小于sendinto发送的数据时,数据丢失

(3)只有sendinto发送数据没有recvfrom收数据,数据丢失

注意:

(1)单独运行udp的客户端,并不会 错,相反tcp会 错,因为udp协议只负责把包发出去,对方收不收,根本不管,而tcp基于链接的,必须有一个服务端先运行着,客户端去跟服务端建立链接然后依托于链接才能传递消息,任何一方试图把链接摧毁都会导致对方程序崩溃

(2) udp程序,注释任何一条客户端的sendinto,服务端都会卡住,因为服务端有几个recvfrom就要对应几个sendinto,哪怕是sendinto(b”)那也要有

3.总结

(1)udp的sendinto不用管是否是一个正在运行的服务端,可以己端一个劲的发消息

(2)udp的recvfrom是阻塞的,一个recvfrom(x)必须对一个一个sendinto(y),收完了x个字节的数据就算完成,若y>x数据就丢失,这意味udp根本不会粘包,但是会丢数据,不可靠

(3)tcp的协议数据不会丢,己端总是在收到ack时才会清楚缓冲区内容。数据是可靠的,但是会粘包

八.粘包

1.什么是粘包:由于收发消息都是在操作自己的缓冲区,自己的缓冲区根本不归你的应用程序管,而是由操作系统控制的,基于TCP协议工作的话,收消息会收一堆放在自己缓冲区,客户端第一次收到的少了,第二次收还会从缓冲区里接收上一次没收完的,这就是粘包,所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的。此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够的数据后才发送一个TCP段,若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

2.TCP(transport control protocol,传输控制协议):是面向连接的,面向流的,提高可靠性服务。收发俩端(客户端和服务端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。

4.tcp是基于数据流的,于是收发消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据 的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。

5.俩种情况下会发生粘包

(1)发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据量很小,会合到一起,产生粘包)

模拟客户端一次发送三条很小量的数据产生粘包

客户端:

from socket import *ip_port=(“127.0.0.1”,8080)

back_log=5buffer_size=1024tcp_client=socket(AF_INET,SOCK_STREAM)

tcp_client.connect(ip_port)

tcp_client.send(“wang”.encode(“utf-8”))

tcp_client.send(“xixi”.encode(“utf-8”))

tcp_client.send(“good”.encode(“utf-8”))

服务端:

from socket import *ip_port=(“127.0.0.1”,8080)

back_log=5buffer_size=1024tcp_server=socket(AF_INET,SOCK_STREAM)

tcp_server.bind(ip_port)

tcp_server.listen(back_log)

conn,addr=tcp_server.accept()

data1=conn.recv(buffer_size)print(“第1次数据”,data1)

data2=conn.recv(buffer_size)print(“第2次数据”,data2)

data3=conn.recv(buffer_size)print(“第3次数据”,data3)

客户端运行后服务端接收信息:

第1次数据 b”wangxixigood”

第2次数据 b””

第3次数据 b”

(2)接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

模拟客户端一次发送一条很大量的数据,接收端接收很小产生粘包

客户端:

from socket import *ip_port=(“127.0.0.1”,8080)

back_log=5buffer_size=1024tcp_client=socket(AF_INET,SOCK_STREAM)

tcp_client.connect(ip_port)

tcp_client.send(“wangxixigood”.encode(“utf-8”))

服务端:

from socket import *ip_port=(“127.0.0.1”,8080)

back_log=5buffer_size=1024tcp_server=socket(AF_INET,SOCK_STREAM)

tcp_server.bind(ip_port)

tcp_server.listen(back_log)

conn,addr=tcp_server.accept()

data1=conn.recv(2) #一次收俩字节

print(“第1次数据”,data1)

data2=conn.recv(2) #一次收俩个字节

print(“第2次数据”,data2)

客户端运行后服务端接收信息:

第1次数据 b”wa”

第2次数据 b”ng”

6.解决粘包的处理方法

(1)粘包问题根源在于,接收端不知道发送端将要传送的字节流长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据

(2)为字节流加上自定义固定长度 头, 头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的 头,然后再取真实数据。

通过解决粘包问题基于tcp实现客户端远程向服务端执行命令

客户端:

from socket import *

import struct #解决粘包问题

ip_port=(“127.0.0.1”,8080)

back_log=5buffer_size=1024

###启动客户端连接服务端

tcp_client=socket(AF_INET,SOCK_STREAM) #客户端产生一个对象传俩个参数(socket.AF_INET基于 络通讯,socket.SOCK_STREAM表TCP协议)给tcp_server

tcp_client.connect(ip_port) #客户端连接服务器端的IP和端口

###客户端给服务端发消息

while True: #第一步:给发消息和收消息加上循环可以循环发收消息

cmd=input(“>>:”).strip() #第二步:用户输入的命令赋值给cmd

if not cmd:continue #客户端不可以发空

if cmd == “quit”:break #给客户端加上退出功能

tcp_client.send(cmd.encode(“utf-8”)) #第三步:客户端把用户输入的命令send发给服务端

#第二步:struct解决粘包

length_data = tcp_client.recv(4) #客户端就收recv4个字节包含服务端发来的长度的数据是byte类型

length = struct.unpack(“i”, length_data)[0] #用struct.unpack解码收到的byte类型是元祖的形式,元祖的第一个元素就是数据的长度,加上”i”代表解的是整型赋值给length收到的长度

recv_size = 0 #定一个接收的尺寸默认值0

recv_msg=b”” #最后得到的结果recv_msg

while recv_size

recv_msg += tcp_client.recv(buffer_size) #客户端接收recv_msg尺寸加上tcp_client.recv(buffer_size)内容

recv_size=len(recv_msg) #recv_size收了多少真实数据等于len(recv_msg)真实长度=1024字节

#recv_msg = “”.join(iter(partial(tcp_client.recv, buffer_size), b””)) #partial把buffer_size传给tcp_client.recv函数的第一个参数,iter无穷执行(partial(tcp_client.recv, buffer_size)直达运行结果遇到缓冲区为空时候停掉,通过.join转换成字符串形式

print(“命令的执行结果是”,recv_msg.decode(“gbk”))

tcp_client.close()

服务端:

from socket import *

import subprocess #subprocess模块执行命令

import struct #解决粘包问题

ip_port=(“127.0.0.1”,8080)

back_log=5buffer_size=1024

####启动服务端后

tcp_server=socket(AF_INET,SOCK_STREAM) #第一步:产生一个对象传俩个参数(socket.AF_INET基于 络通讯,socket.SOCK_STREAM表TCP协议)给tcp_server

tcp_server.bind(ip_port) #第二步:绑定IP地址和访问和端口

tcp_server.listen(back_log) #第三步:listen(5)最多可以有五个建立好三次握手后的backlog(半连接池)等着,后面的需要排队等着

####客户端连接服务端后

whileTrue: #做连接循环

conn,addr=tcp_server.accept() #第一步:tcp_server.accept()拿到了TCP三次握手的结果是个元祖解压给给conn(三次握手的连接)和addr服务端阻塞

print(“打印出接收过来的客户端链接”,addr)while True: #第二步:服务端做通信循环的接,可以做到接收多个人发的连接

###开始收第一个客户端发来的消息

try: #第一步:做异常处理防止客户端非正常断开

cmd=conn.recv(buffer_size) #第二步:服务端收客户端消息,recv是用户态应用程序发起的

if not cmd:break #第三步:如果收到的cmd为空的话跳出通讯循环(解决客户端tcp_client.close断开服务端造成死循环问题)

print(“打印出客户端所发出的命令”,cmd)#执行命令,得到命令的运行结果cmd_res

res=subprocess.Popen(cmd.decode(“utf-8”),shell=True, #第四步:把接过来的字节命令转码decode(“utf-8”)

stderr=subprocess.PIPE, #subprocess把stderr标准错误输出的结果交给管道PIPE,res拿到的是subprocess.Popen的对象

stdout=subprocess.PIPE, #stdout标准输出

stdin=subprocess.PIPE) #stdin标准输入

#有了subprocess.Popen的对象,就可以通过stdout.read读取管道的内容获取cmd的运行结果,读取后管道PIPE里的内容就空了

err=res.stderr.read() #从错误的管道里读信息赋值给err

if err: #如果读取err有值代表出错

cmd_res=err #cmd_res读取错误信息

else:

cmd_res=res.stdout.read() #如果err没有值,cmd_res读取管道里的正确的值

###服务端发消息回给客户端

if not cmd_res: #第一步:当命令正常执行且cmd_res没有返回值的情况下防止接收到空

cmd_res=”执行成功”.encode(“gbk”) #赋值一个返回值

#第二步:解决粘包: 第一步发数据长度,第二步发数据内容,把数据长度封装在固定的大小范围内后返回给客户端

length = len(cmd_res) #计算cmd_res的长度赋值给length

data_length = struct.pack(“i”, length) #struct.pack获取的长度length值直接打成整型byte形式,”i”是固定长度是4个字节的赋值给data_length

#相当于给cmd_res这个数据流封装了一个 文头叫data_length,因为cmd_res基于TCP的字节流,只要是字节流代表没有消息的边界,没有边界定一个边界data_length数据头(cmd_res的长度)

conn.send(data_length) #服务端send,把data_length数据长度发给客户端

conn.send(cmd_res) #服务端send,把得到的运行结果cmd_res通过conn.send发给客户端

except Exception as e: #收到异常处理错误

print(e)break #断开通讯循环

九.socketserver模块实现TCP和UDP的并发

socketserver有俩大类:

1.第一个类:server类基于基本的socket帮你处理连接的

(1)BaseServer类:祖宗类

(2)TCPServer类继承BaseServer类:处理TCP连接

UnixStremServer类继承TCPServer:处理TCP连接用在Unix系统上

(3)UDPServer类继承TCPServer:处理UDP连接

UnixDatagramServer类继承UDPServer:处理UDP连接用在Unix系统上

2.第二个类:request类帮你处理通信的

(1)BaseRequestHandler类:数据通信,每一个请求来都会呼叫handle()方法,继承一个类定义handle()方法

(2)StreamRequestHandler类继承BaseRequestHandler类:数据流

(3)DatagramRequestHand类继承BaseRequestHandler类:数据

3.进程并发

(1)ForkingUDPServer类进程优先继承ForkingMixIn类没有继承UDPServer类

(2)ForkinTCPServer类进程优先继承ForkingMixIn类没有继承TCPServer类

4.线程并发

(1)ThreadingUDPServer类线程优先继承ThreadingMixIn类没有继承UDPServer类

(2)ThreadingTCPServer类线程优先继承ThreadingMixIn类没有继承TCPServer类

5.利用socketserver模块实现TCP多客户端连接并发

服务端代码:

importsocketserver#客户端连接到服务端会进入通讯循环,一进入通讯循环就是调MyServer里的__init__函数里的handle方法接收conn和addr

“””def __init__(self, request, client_address, server):

self.request = request

self.client_address = client_address

self.server = server

self.setup()

try:

self.handle()

finally:

self.finish()”””

class MyServer(socketserver.BaseRequestHandler): #定一个类MyServer继承socketserver下面的BaseRequestHandler类(客户端每来一个新的连接用MyServer类实例化得到一个实例,然后跟你进行通信)

def handle(self): #定义handle方法收发消息,handle属于MyServer类的函数属性,一次连接的self是实例(包含俩个信息request和client_address)

print(“conn is:”,self.request) #conn:接收的连接

print(“addr is:”,self.client_address) #addr:是给我发消息的客户端IP和端口

while True: #给发消息和收消息加上通信循环可以循环收发消息

try: #在通信循环做异常处理防止客户端非正常断开

###收消息

data=self.request.recv(1024) #self.request相当于conn.recv

if not data:break #解决不断的收

print(“收到客户端的消息是”,data,self.client_address) #client_address是到底那个客户端发的

###发消息

self.request.sendall(data.upper()) #self.request相当于conn.sendall

except Exception as e: #收到异常处理错误

print(e)break #断开通讯循环

if __name__ == “__main__”:

s=socketserver.ThreadingTCPServer((“127.0.0.1”,8080),MyServer) #调socketserver模块下面的ThreadingTCPServer(多线程的TCP服务端)类处理连接,这个类传俩个参数IP端口元祖形式,第二个参数是MyServer类,ThreadingTCPServer类加上括 实例化得到结果赋值给s

#s=socketserver.ForkingTCPServer((“127.0.0.1”,8080),MyServer) #多进程TCP服务端(linux系统实现)

#s里包含了以下信息

print(s.server_address)print(s.RequestHandlerClass)print(MyServer)print(s.socket)print(s.server_address)# s.serve_forever() #socketserver.ThreadingTCPServer内置的s.serve_forevery方法完成连接循环,连接循环里面套一个通信循环,MyServer这个类进行实例化得到的实例跟客户端进行通讯

客户端1代码:

from socket import *ip_port=(“127.0.0.1”,8080)

back_log=5buffer_size=1024tcp_client=socket(AF_INET,SOCK_STREAM)

tcp_client.connect(ip_port)whileTrue:

msg=input(“>>:”).strip()if not msg:continue

if msg == “quit”:breaktcp_client.send(msg.encode(“utf-8”))

data=tcp_client.recv(buffer_size)print(“收到服务端发来的消息:”,data.decode(“utf-8”))

tcp_client.close()

客户端2代码:

from socket import *ip_port=(“127.0.0.1”,8080)

back_log=5buffer_size=1024tcp_client=socket(AF_INET,SOCK_STREAM)

tcp_client.connect(ip_port)whileTrue:

msg=input(“>>:”).strip()if not msg:continue

if msg == “quit”:breaktcp_client.send(msg.encode(“utf-8”))

data=tcp_client.recv(buffer_size)print(“收到服务端发来的消息:”,data.decode(“utf-8”))

tcp_client.close()

服务端客户端启动后:

服务端返回:

(“127.0.0.1”, 8080)

(“127.0.0.1”, 8080)

conn is:

addr is: (“127.0.0.1”, 50129)

conn is:

addr is: (“127.0.0.1”, 50130)

客户端1执行>>: xixi

客户端1返回:

收到服务端发来的消息: XIXI

客户端2执行:>>: yaoyao

客户端2返回:

收到服务端发来的消息: YAOYAO

服务端返回:

收到客户端的消息是 b”xixi” (“127.0.0.1”, 50129)

收到客户端的消息是 b”yaoyao” (“127.0.0.1”, 50130)

6.利用hmac+加盐的方式来实现认证客户端的链接合法性

客户端:

from socket import *

import struct #解决粘包问题

importhmac,os

secret_key=b”wang xi xi”

defconn_auth(conn):

msg=conn.recv(32)

h=hmac.new(secret_key,msg)

digest=h.digest()

conn.sendall(digest)def client_handler(ip_port,bufsize=1024):###启动客户端连接服务端

tcp_client=socket(AF_INET,SOCK_STREAM) #客户端产生一个对象传俩个参数(socket.AF_INET基于 络通讯,socket.SOCK_STREAM表TCP协议)给tcp_server

tcp_client.connect(ip_port) #客户端连接服务器端的IP和端口

conn_auth(tcp_client)###客户端给服务端发消息

while True: #第一步:给发消息和收消息加上循环可以循环发收消息

cmd=input(“>>:”).strip() #第二步:用户输入的命令赋值给cmd

if not cmd:continue #客户端不可以发空

if cmd == “quit”:break #给客户端加上退出功能

tcp_client.send(cmd.encode(“utf-8”)) #第三步:客户端把用户输入的命令send发给服务端

#第二步:struct解决粘包

length_data = tcp_client.recv(4) #客户端就收recv4个字节包含服务端发来的长度的数据是byte类型

length = struct.unpack(“i”, length_data)[0] #用struct.unpack解码收到的byte类型是元祖的形式,元祖的第一个元素就是数据的长度,加上”i”代表解的是整型赋值给length收到的长度

recv_size = 0 #定一个接收的尺寸默认值0

recv_msg=b”” #最后得到的结果recv_msg

while recv_size

recv_msg += tcp_client.recv(bufsize) #客户端接收recv_msg尺寸加上tcp_client.recv(buffer_size)内容

recv_size=len(recv_msg) #recv_size收了多少真实数据等于len(recv_msg)真实长度=1024字节

#recv_msg = “”.join(iter(partial(tcp_client.recv, buffer_size), b””)) #partial把buffer_size传给tcp_client.recv函数的第一个参数,iter无穷执行(partial(tcp_client.recv, buffer_size)直达运行结果遇到缓冲区为空时候停掉,通过.join转换成字符串形式

print(“命令的执行结果是”,recv_msg.decode(“gbk”))

tcp_client.close()if __name__ == “__main__”:

ip_port=(“127.0.0.1”,9999)

bufsize=1024client_handler(ip_port,bufsize)

服务端:

#_*_coding:utf-8_*_

from socket import *

import subprocess #subprocess模块执行命令

import struct #解决粘包问题

importhmac,osimportsocketserver#第一步:客户端验证

secret_key=b”wang xi xi”

def conn_auth(conn): #定义认证客户端链接函数

print(“开始验证新链接的合法性”)

msg=os.urandom(32) #产生位32字节的随机数

conn.sendall(msg) #发送给客户端

h=hmac.new(secret_key,msg) #把secret_key自定义的盐和msg产生的32位随机数添加到hmac里得到的对象是h

digest=h.digest() #拿到对象h用digest()得到数字形式赋值(32位随机字符串和加盐得到值)给digest

respone=conn.recv(len(digest)) #服务端conn会收跟客户端发过来跟respone长度一样的字节

return hmac.compare_digest(respone,digest) #hmac.compare_digest比较respone和digest这俩个数字是不是一样结果产生布尔值交给通讯里的if判断

#第三步:处理通讯

def data_handler(conn,bufsize=1024):if not conn_auth(conn): #判断链接是否合法把conn(收发消息)链接传给定义的认证函数

print(“该链接不合法,关闭”)

conn.close()return

print(“链接合法,开始通信”)while True: #收发消息做通讯循环

try:

cmd= conn.recv(bufsize) #第二步:服务端收客户端消息,recv是用户态应用程序发起的

if not cmd:break #如果收到的cmd为空的话跳出通讯循环(解决客户端tcp_client.close断开服务端造成死循环问题)

print(“打印出客户端所发出的命令”, cmd)#执行命令,得到命令的运行结果cmd_res

res = subprocess.Popen(cmd.decode(“utf-8”), shell=True, #第四步:把接过来的字节命令转码decode(“utf-8”)

stderr=subprocess.PIPE,#subprocess把stderr标准错误输出的结果交给管道PIPE,res拿到的是subprocess.Popen的对象

stdout=subprocess.PIPE, #stdout标准输出

stdin=subprocess.PIPE) #stdin标准输入

#有了subprocess.Popen的对象,就可以通过stdout.read读取管道的内容获取cmd的运行结果,读取后管道PIPE里的内容就空了

err = res.stderr.read() #从错误的管道里读信息赋值给err

if err: #如果读取err有值代表出错

cmd_res = err #cmd_res读取错误信息

else:

cmd_res= res.stdout.read() #如果err没有值,cmd_res读取管道里的正确的值

###服务端发消息回给客户端

if not cmd_res: #第一步:当命令正常执行且cmd_res没有返回值的情况下防止接收到空

cmd_res = “执行成功”.encode(“gbk”) #赋值一个返回值

#第二步:解决粘包: 第一步发数据长度,第二步发数据内容,把数据长度封装在固定的大小范围内后返回给客户端

length = len(cmd_res) #计算cmd_res的长度赋值给length

data_length = struct.pack(“i”, length) #struct.pack获取的长度length值直接打成整型byte形式,”i”是固定长度是4个字节的赋值给data_length

#相当于给cmd_res这个数据流封装了一个 文头叫data_length,因为cmd_res基于TCP的字节流,只要是字节流代表没有消息的边界,没有边界定一个边界data_length数据头(cmd_res的长度)

conn.send(data_length) #服务端send,把data_length数据长度发给客户端

conn.send(cmd_res) #服务端send,把得到的运行结果cmd_res通过conn.send发给客户端

except Exception as e: #收到异常处理错误

print(e)break #断开通讯循环

#第二步:处理链接

def server_handler(ip_port,bufsize,backlog=5):

tcp_socket_server=socket(AF_INET,SOCK_STREAM) #得到socket对象

tcp_socket_server.bind(ip_port) #绑定

tcp_socket_server.listen(backlog)while True: #做链接循环

conn,addr=tcp_socket_server.accept()print(“新连接[%s:%s]” %(addr[0],addr[1]))

data_handler(conn,bufsize)#调用data_handler通讯循环把conn和bufsize传进去

if __name__ == “__main__”:

ip_port=(“127.0.0.1”,9999)

bufsize=1024server_handler(ip_port,bufsize)

文章知识点与官方知识档案匹配,可进一步学习相关知识Python入门技能树首页概览210888 人正在系统学习中 相关资源:绞车提升能力及钢丝绳验算软件煤矿用_绞车提升能力计算软件-C#…

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年10月9日
下一篇 2020年10月9日

相关推荐