python - 网络编程
一,引入你现在已经学会了写python代码,假如你写了两个python文件a.py和b.py,分别去运行,你就会发现,这两个python的文件分别运行的很好。但是如果这两个程序之间想要传递一个数据,你要怎么做呢? 这个问题以你现在的知识就可以解决了,我们可以创建一个文件,把a.py想要传递的内容写到文件中,然后b.py从这个文件中读取内容就可以了。 但是当你的a.py和b.py分别在不同电脑上的时候,你要怎么办呢? 类似的机制有计算机网盘,qq等等。我们可以在我们的电脑上和别人聊天,可以在自己的电脑上向网盘中上传、下载内容。这些都是两个程序在通信。 二,软件开发的架构我们了解的涉及到两个程序之间通讯的应用大致可以分为两种: 第一种是应用类:qq、微信、网盘、优酷这一类是属于需要安装的桌面应用 第二种是web类:比如百度、知乎、博客园等使用浏览器访问就可以直接使用的应用 这些应用的本质其实都是两个程序之间的通讯。而这两个分类又对应了两个软件开发的架构~ 1,C/S 架构C/S即:Client与Server ,中文意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的。 这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。 ?2,B/S 架构B/S即:Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。 Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的资源(网页资源),客户端Browser浏览器就能进行增删改查。 三,网络基础1,一个程序如何在网络上找到另一个程序?首先,程序必须要启动,其次,必须有这台机器的地址,我们都知道我们人的地址大概就是国家省市区街道楼门牌号这样字。那么每一台联网的机器在网络上也有自己的地址,它的地址是怎么表示的呢? 就是使用一串数字来表示的,例如:100.4.5.6 IP地址是指互联网协议地址(英语:Internet Protocol Address,又译为网际协议地址),是IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)。IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0 ~255之间的十进制整数。例:点分十进IP地址(100.4.5.6),实际上是32位二进制数(01100100.00000100.00000101.00000110<span style="color: #000000;">)。什么是端口? <span style="color: #800000;">"<span style="color: #800000;">端口<span style="color: #800000;">"是英文port的意译,可以认为是设备与外界通讯交流的出口。 因此ip地址精确到具体的一台电脑,而端口精确到具体的程序。 2,osi七层模型人们按照分工不同把互联网协议从逻辑上划分了层级: <div class="cnblogs_code" onclick="cnblogs_code_show('69714ff2-47cb-449b-8f8a-b408d0f95f83')"> 一,面向连接的传输服务 —-1<span style="color: #000000;">,传输特征: 3,socket 概念socket层 理解socket Socket是应用层与 +
4,套接字 (socket) 的发展史起源 :?套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。? 基于文件类型的套接字家族套接字家族的名字:AF_UNIX unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信 基于网络类型的套接字家族套接字家族的名字:AF_INET (还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET) ?5,tcp协议和udp协议TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。 UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统?(DNS);视频流;IP语音(VoIP)。 先上个图: <div class="cnblogs_code" onclick="cnblogs_code_show('6efe2f13-ab2f-4f7d-a0f9-52509a070b9b')"> 1234<span style="color: #000000;">,绑定地址(IP 端口号)7<span style="color: #000000;">,收发消息5<span style="color: #000000;">,将套接字设置可监听
8<span style="color: #000000;">,关闭套接字
12
格式: connect()
功能: 发起连接请求
参数: 是一个元组,表示服务器的地址
3<span style="color: #000000;">,消息收发recv() send() 4<span style="color: #000000;">,关闭套接字 close() 5<span style="color: #000000;">,recv() 特性 如果连接双方断开连接,则recv会立即结束阻塞返回空字符串 当接收缓冲区为空的时候会阻塞 如果recv一次接收不完缓存去内容,下一次会继续接收 6<span style="color: #000000;">,send()特性 如果一段不存在,另一端还在试图send操作时会产生BrokenPipeError异常 当发送缓冲区慢的时候会阻塞 7<span style="color: #000000;">,网络收发缓冲区 发送和接收消息均先放到缓冲区中,在进行处理 即 recv和send实际是从缓冲区接收内容,向缓冲区发送消息 四,套接字 (socket) 初使用1,基于TCP协议的sockettcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端 =,8898))
sk.listen()
conn,addr = sk.accept()
ret = conn.recv(1024)
(ret)
conn.send(b)
conn.close()
sk.close()
= socket.socket()
sk.connect((,8898))
sk.send(b= sk.recv(1024)
问题 : 有的人在重启服务端时可能会遇到 OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
socket =1)
sk.bind((,addr = sk.accept()
ret = conn.recv(1024)
(ret)
conn.send(b)
conn.close()
sk.close()
2,基于UDP协议的socketudp是无链接的,启动服务之后可以直接接收消息,不需要提前建立链接 1,简单使用= socket.socket(type=socket.SOCK_DGRAM)
udp_sk.bind((,9000))
msg,addr = udp_sk.recvfrom(1024,addr)
udp_sk.close()
=(,9000=socket.socket(type==udp_sk.recvfrom(1024(back_msg.decode(),addr)
2,QQ聊天=(,8081=socket.socket(type =<span style="color: #0000ff;">while<span style="color: #000000;"> True:
qq_msg,addr=udp_server_sock.recvfrom(1024<span style="color: #000000;">) <span style="color: #0000ff;">print(<span style="color: #800000;">'<span style="color: #800000;">来自[%s:%s]的一条消息: 33[1;44m%s 33[0m<span style="color: #800000;">' %(addr[0],addr[1],qq_msg.decode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'<span style="color: #000000;">))) back_msg=input(<span style="color: #800000;">'<span style="color: #800000;">回复消息: <span style="color: #800000;">'<span style="color: #000000;">).strip()
=1024=socket.socket(type =qq_name_dic=<span style="color: #000000;">{
<span style="color: #800000;">'<span style="color: #800000;">悟空<span style="color: #800000;">':(<span style="color: #800000;">'<span style="color: #800000;">127.0.0.1<span style="color: #800000;">',8081<span style="color: #000000;">),<span style="color: #800000;">'<span style="color: #800000;">哪吒<span style="color: #800000;">':(<span style="color: #800000;">'<span style="color: #800000;">127.0.0.1<span style="color: #800000;">',<span style="color: #800000;">'<span style="color: #800000;">八戒<span style="color: #800000;">':(<span style="color: #800000;">'<span style="color: #800000;">127.0.0.1<span style="color: #800000;">',<span style="color: #800000;">'<span style="color: #800000;">沙僧<span style="color: #800000;">':(<span style="color: #800000;">'<span style="color: #800000;">127.0.0.1<span style="color: #800000;">',} <span style="color: #0000ff;">while<span style="color: #000000;"> True:
udp_client_socket.close() 3,时间服务器 socket *
time ip_port = (<span style="color: #800000;">'<span style="color: #800000;">127.0.0.1<span style="color: #800000;">',9000<span style="color: #000000;">)
bufsize = 1024<span style="color: #000000;"> tcp_server =<span style="color: #000000;"> socket(AF_INET,SOCK_DGRAM) <span style="color: #0000ff;">while<span style="color: #000000;"> True:
tcp_server.close() socket *=(,9000=1024tcp_client=<span style="color: #000000;">socket(AF_INET,SOCK_DGRAM)
<span style="color: #0000ff;">while<span style="color: #000000;"> True:
msg=input(<span style="color: #800000;">'<span style="color: #800000;">请输入时间格式(例%Y %m %d)>>: <span style="color: #800000;">'<span style="color: #000000;">).strip() tcp_client.sendto(msg.encode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'<span style="color: #000000;">),ip_port)
3,socket 参数的详解socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
创建socket对象的参数说明 :1,黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd) res=subprocess.Popen(cmd.decode(===的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码
且只能从管道里读一次结果 同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。 基于tcp协议实现的黏包 socket *
ip_port=(<span style="color: #800000;">'<span style="color: #800000;">127.0.0.1<span style="color: #800000;">',8888<span style="color: #000000;">)
BUFSIZE=1024<span style="color: #000000;"> tcp_socket_server=<span style="color: #000000;">socket(AF_INET,SOCK_STREAM) <span style="color: #0000ff;">while<span style="color: #000000;"> True:
=1024=(,8888s=<span style="color: #000000;">socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=<span style="color: #000000;">s.connect_ex(ip_port) <span style="color: #0000ff;">while<span style="color: #000000;"> True: msg=input(<span style="color: #800000;">'<span style="color: #800000;">>>: <span style="color: #800000;">'<span style="color: #000000;">).strip() <span style="color: #0000ff;">if len(msg) == 0:<span style="color: #0000ff;">continue <span style="color: #0000ff;">if msg == <span style="color: #800000;">'<span style="color: #800000;">quit<span style="color: #800000;">':<span style="color: #0000ff;">break<span style="color: #000000;">
基于udp协议实现的黏包 socket *
ip_port=(<span style="color: #800000;">'<span style="color: #800000;">127.0.0.1<span style="color: #800000;">',9000<span style="color: #000000;">)
bufsize=1024<span style="color: #000000;"> udp_server=<span style="color: #000000;">socket(AF_INET,SOCK_DGRAM) <span style="color: #0000ff;">while<span style="color: #000000;"> True:
<span style="color: #000000;"> udp_server.sendto(stderr,addr) socket *=(,9000=1024udp_client=<span style="color: #000000;">socket(AF_INET,SOCK_DGRAM)
<span style="color: #0000ff;">while<span style="color: #000000;"> True:
msg=input(<span style="color: #800000;">'<span style="color: #800000;">>>: <span style="color: #800000;">'<span style="color: #000000;">).strip() udp_client.sendto(msg.encode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'<span style="color: #000000;">),ip_port) err,addr=<span style="color: #000000;">udp_client.recvfrom(bufsize) out,addr=<span style="color: #000000;">udp_client.recvfrom(bufsize) <span style="color: #0000ff;">if<span style="color: #000000;"> err: <span style="color: #0000ff;">print(<span style="color: #800000;">'<span style="color: #800000;">error : %s<span style="color: #800000;">'%err.decode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'),end=<span style="color: #800000;">''<span style="color: #000000;">) <span style="color: #0000ff;">if<span style="color: #000000;"> out: <span style="color: #0000ff;">print(out.decode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'),end=<span style="color: #800000;">'') 注意:只有TCP有粘包现象,UDP永远不会粘包 2,黏包成因1,TCP协议中的数据传递tcp协议的拆包机制 面向流的通信特点和Nagle算法基于tcp协议特点的黏包现象成因/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。
例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束 此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。 2,UDP不会发生黏包补充说明: 用UDP协议发送时,用sendto函数最大能发送数据的长度为:65535- IP头(20) – UDP头(8
用TCP协议发送时,由于TCP是数据流协议,因此不存在包大小的限制(暂不考虑缓冲区的大小),这是指在用send函数时,数据长度参数不受限制。而实际上,所指定的这段数据并不一定会一次性发送出去,如果这段数据比较长,会被分段发送,如果比较短,可能会等待和下一次数据一起发送。</span></pre>
3,会发生黏包的两种情况情况一? 发送方的缓存机制 发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包) socket *=(,8080tcp_socket_server=<span style="color: #000000;">socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5<span style="color: #000000;">) conn,addr =<span style="color: #000000;">tcp_socket_server.accept()data1 =conn.recv(10<span style="color: #000000;">)data2=conn.recv(10<span style="color: #000000;">) <span style="color: #0000ff;">print(<span style="color: #800000;">'<span style="color: #800000;">-----><span style="color: #800000;">',data1.decode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'<span style="color: #000000;">)) <span style="color: #0000ff;">print(<span style="color: #800000;">'<span style="color: #800000;">-----><span style="color: #800000;">',data2.decode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'<span style="color: #000000;">)) conn.close() =1024=(,8080s=<span style="color: #000000;">socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=<span style="color: #000000;">s.connect_ex(ip_port) s.send( <span style="color: #800000;">'<span style="color: #800000;">hello<span style="color: #800000;">'.encode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'<span style="color: #000000;">))s.send(<span style="color: #800000;">'<span style="color: #800000;">egg<span style="color: #800000;">'.encode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">')) ?情况二? 接收方的缓存机制 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)? socket *=(,addr=data1=conn.recv(2) <span style="color: #008000;">#<span style="color: #008000;">一次没有收完整
data2=conn.recv(10)<span style="color: #008000;">#<span style="color: #008000;">下次收的时候,会先取旧的数据,然后取新的 <span style="color: #0000ff;">print (<span style="color: #800000;">'<span style="color: #800000;">-----><span style="color: #800000;">',data2.decode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'<span style="color: #000000;">))conn.close() =1024=(,socket.SOCK_STREAM)
res=s.send(<span style="color: #800000;">'<span style="color: #800000;">hello egg<span style="color: #800000;">'.encode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'))
总结:黏包现象只发生在tcp协议中: 1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。 2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的 3,黏包的解决方案解决方案一问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。 =(,8080=1s.bind(ip_port)
s.listen(5<span style="color: #000000;">) <span style="color: #0000ff;">while<span style="color: #000000;"> True: conn,addr=<span style="color: #000000;">s.accept() <span style="color: #0000ff;">print(<span style="color: #800000;">'<span style="color: #800000;">客户端<span style="color: #800000;">'<span style="color: #000000;">,addr) <span style="color: #0000ff;">while<span style="color: #000000;"> True: msg=conn.recv(1024<span style="color: #000000;">) <span style="color: #0000ff;">if <span style="color: #0000ff;">not msg:<span style="color: #0000ff;">break<span style="color: #000000;"> res=subprocess.Popen(msg.decode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'), stdin=<span style="color: #000000;">subprocess.PIPE, stderr=<span style="color: #000000;">subprocess.PIPE, stdout=<span style="color: #000000;">subprocess.PIPE) err=<span style="color: #000000;">res.stderr.read() <span style="color: #0000ff;">if<span style="color: #000000;"> err: ret=<span style="color: #000000;">err <span style="color: #0000ff;">else<span style="color: #000000;">: ret=<span style="color: #000000;">res.stdout.read() data_length=<span style="color: #000000;">len(ret) conn.send(str(data_length).encode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'<span style="color: #000000;">)) data=conn.recv(1024).decode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'<span style="color: #000000;">) <span style="color: #0000ff;">if data == <span style="color: #800000;">'<span style="color: #800000;">recv_ready<span style="color: #800000;">'<span style="color: #000000;">: conn.sendall(ret) conn.close() ==s.connect_ex((,8080<span style="color: #0000ff;">while<span style="color: #000000;"> True:
msg=input(<span style="color: #800000;">'<span style="color: #800000;">>>: <span style="color: #800000;">'<span style="color: #000000;">).strip() <span style="color: #0000ff;">if len(msg) == 0:<span style="color: #0000ff;">continue <span style="color: #0000ff;">if msg == <span style="color: #800000;">'<span style="color: #800000;">quit<span style="color: #800000;">':<span style="color: #0000ff;">break<span style="color: #000000;">
解决方案进阶刚刚的方法,问题在于我们我们在发送 我们可以借助一个模块,这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了。 struct模块该模块可以把一个类型,如数字,转成固定长度的bytes >>> struct.pack(,2222222222111struct.error: <span style="color: #800000;">'<span style="color: #800000;">i<span style="color: #800000;">' format requires -2147483648 <= number <= 2147483647 <span style="color: #008000;">#<span style="color: #008000;">这个是范围
<span style="color: #008000;"># <span style="color: #008000;">为避免粘包,必须自定制报头header={<span style="color: #800000;">'<span style="color: #800000;">file_size<span style="color: #800000;">':1073741824000,<span style="color: #800000;">'<span style="color: #800000;">file_name<span style="color: #800000;">':<span style="color: #800000;">'<span style="color: #800000;">/a/b/c/d/e/a.txt<span style="color: #800000;">',<span style="color: #800000;">'<span style="color: #800000;">md5<span style="color: #800000;">':<span style="color: #800000;">'<span style="color: #800000;">8f6fbf8347faa4924a76856701edb0f3<span style="color: #800000;">'} <span style="color: #008000;">#<span style="color: #008000;">1T数据,文件路径和md5值 <span style="color: #008000;">#<span style="color: #008000;">为了该报头能传送,需要序列化并且转为bytes <span style="color: #008000;">#<span style="color: #008000;">为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节 <span style="color: #008000;">#<span style="color: #008000;">客户端开始发送 <span style="color: #008000;">#<span style="color: #008000;">服务端开始接收 <span style="color: #008000;">#<span style="color: #008000;">最后根据报头的内容提取真实的数据,比如 values1 = (1,<span style="color: #800000;">'<span style="color: #800000;">abc<span style="color: #800000;">'.encode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'),2.7<span style="color: #000000;">)
values2 = (<span style="color: #800000;">'<span style="color: #800000;">defg<span style="color: #800000;">'.encode(<span style="color: #800000;">'<span style="color: #800000;">utf-8<span style="color: #800000;">'),101<span style="color: #000000;">) s1 = struct.Struct(<span style="color: #800000;">'<span style="color: #800000;">I3sf<span style="color: #800000;">'<span style="color: #000000;">) s2 = struct.Struct(<span style="color: #800000;">'<span style="color: #800000;">4sI<span style="color: #800000;">'<span style="color: #000000;">) <span style="color: #0000ff;">print<span style="color: #000000;">(s1.size,s2.size) <span style="color: #008000;"> print(t)<span style="color: #000000;"> s1.pack_into(prebuffer,<span style="color: #000000;">values1) <span style="color: #0000ff;">print(<span style="color: #800000;">'<span style="color: #800000;">After pack<span style="color: #800000;">'<span style="color: #000000;">,binascii.hexlify(prebuffer)) s3=struct.Struct(<span style="color: #800000;">'<span style="color: #800000;">ii<span style="color: #800000;">'<span style="color: #000000;">) 使用struct解决黏包借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。 <table style="height: 99px; width: 506px;" border="0"> <tr> <td>发送时</td> <td>接收时</td> </tr> <tr> <td>先发送struct转换好的数据长度4字节</td> <td>先接受4个字节使用struct转换成数字来获取要接收的数据长度</td> </tr> <tr> <td>再发送数据</td> <td>再按照长度接收数据 </td> </tr> |
conn,addr=<span style="color: #000000;">phone.accept()
<span style="color: #0000ff;">while<span style="color: #000000;"> True:
cmd=conn.recv(1024<span style="color: #000000;">)
<span style="color: #0000ff;">if <span style="color: #0000ff;">not cmd:<span style="color: #0000ff;">break
<span style="color: #0000ff;">print(<span style="color: #800000;">'<span style="color: #800000;">cmd: %s<span style="color: #800000;">' %<span style="color: #000000;">cmd)
res</span>=subprocess.Popen(cmd.decode(<span style="color: #800000;">'</span><span style="color: #800000;">utf-8</span><span style="color: #800000;">'</span><span style="color: #000000;">),stderr</span>=<span style="color: #000000;">subprocess.PIPE)
err</span>=<span style="color: #000000;">res.stderr.read()
</span><span style="color: #0000ff;">print</span><span style="color: #000000;">(err)
</span><span style="color: #0000ff;">if</span><span style="color: #000000;"> err:
back_msg</span>=<span style="color: #000000;">err
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">:
back_msg</span>=<span style="color: #000000;">res.stdout.read()
conn.send(struct.pack(</span><span style="color: #800000;">'</span><span style="color: #800000;">i</span><span style="color: #800000;">'</span>,len(back_msg))) <span style="color: #008000;">#</span><span style="color: #008000;">先发back_msg的长度</span>
conn.sendall(back_msg) <span style="color: #008000;">#</span><span style="color: #008000;">在发真实的内容</span>
<span style="color: #000000;">
conn.close()
msg=input(<span style="color: #800000;">'<span style="color: #800000;">>>: <span style="color: #800000;">'<span style="color: #000000;">).strip()
<span style="color: #0000ff;">if len(msg) == 0:<span style="color: #0000ff;">continue
<span style="color: #0000ff;">if msg == <span style="color: #800000;">'<span style="color: #800000;">quit<span style="color: #800000;">':<span style="color: #0000ff;">break<span style="color: #000000;">
s.send(msg.encode(</span><span style="color: #800000;">'</span><span style="color: #800000;">utf-8</span><span style="color: #800000;">'</span><span style="color: #000000;">))
l</span>=s.recv(4<span style="color: #000000;">)
x</span>=struct.unpack(<span style="color: #800000;">'</span><span style="color: #800000;">i</span><span style="color: #800000;">'</span><span style="color: #000000;">,l)[0]
</span><span style="color: #0000ff;">print</span><span style="color: #000000;">(type(x),x)
</span><span style="color: #008000;">#</span><span style="color: #008000;"> print(struct.unpack('I',l))</span>
r_s=<span style="color: #000000;">0
data</span>=b<span style="color: #800000;">''</span>
<span style="color: #0000ff;">while</span> r_s <<span style="color: #000000;"> x:
r_d</span>=s.recv(1024<span style="color: #000000;">)
data</span>+=<span style="color: #000000;">r_d
r_s</span>+=<span style="color: #000000;">len(r_d)
</span><span style="color: #008000;">#</span><span style="color: #008000;"> print(data.decode('utf-8'))</span>
<span style="color: #0000ff;">print</span>(data.decode(<span style="color: #800000;">'</span><span style="color: #800000;">gbk</span><span style="color: #800000;">'</span>)) <span style="color: #008000;">#</span><span style="color: #008000;">windows默认gbk编码</span></pre>
我们还可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)
headers</span>={<span style="color: #800000;">'</span><span style="color: #800000;">data_size</span><span style="color: #800000;">'</span><span style="color: #000000;">:len(back_msg)}
head_json</span>=<span style="color: #000000;">json.dumps(headers)
head_json_bytes</span>=bytes(head_json,encoding=<span style="color: #800000;">'</span><span style="color: #800000;">utf-8</span><span style="color: #800000;">'</span><span style="color: #000000;">)
conn.send(struct.pack(</span><span style="color: #800000;">'</span><span style="color: #800000;">i</span><span style="color: #800000;">'</span>,len(head_json_bytes))) <span style="color: #008000;">#</span><span style="color: #008000;">先发报头的长度</span>
conn.send(head_json_bytes) <span style="color: #008000;">#</span><span style="color: #008000;">再发报头</span>
conn.sendall(back_msg) <span style="color: #008000;">#</span><span style="color: #008000;">在发真实的内容</span>
<span style="color: #000000;">
conn.close()