python自动化运维之路~DAY7

                          python自动化运维之路~DAY7

                                                      作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

 

一.客户端/服务器架构

  C/S 架构是一种典型的两层架构,其全称是Client/Server,即客户端服务器端架构,其客户端包含一个或多个在用户的电脑上运行的程序,而服务器端有两种,一种是数据库服务器端,客户端通过数据库连接访问服务器端的数据;另一种是Socket服务器端,服务器端的程序通过Socket与客户端的程序通信。
  C/S 架构也可以看做是胖客户端架构。因为客户端需要实现绝大多数的业务逻辑和界面展示。这种架构中,作为客户端的部分需要承受很大的压力,因为显示逻辑和事务处理都包含在其中,通过与数据库的交互(通常是SQL或存储过程的实现)来达到持久化数据,以此满足实际项目的需要。

  说道了c/s架构,那我们就不得不扯一下b/s架构了,B/S架构的全称为Browser/Server,即浏览器/服务器结构。Browser指的是Web浏览器,极少数事务逻辑在前端实现,但主要事务逻辑在服务器端实现,Browser客户端,WebApp服务器端和DB端构成所谓的三层架构。B/S架构的系统无须特别安装,只有Web浏览器即可。
  B/S架构中,显示逻辑交给了Web浏览器,事务处理逻辑在放在了WebApp上,这样就避免了庞大的胖客户端,减少了客户端的压力。因为客户端包含的逻辑很少,因此也被成为瘦客户端。

 

二.socket层  

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

  也有人将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序;而程序的pid是同一台机器上不同进程或者线程的标识

  相信学计算机专业的小伙伴们OSI七层模型应该很了解吧,如果你忘记了可以扫一眼:http://www.cnblogs.com/yinzhengjie/p/6552995.html,其实socket就是取代了会话层和表示层的位置,将这2个层合并成了一个“socket抽象层”,那么它到底在哪里呢?还是用图来说话,一目了然。

                                  

 

 

三.套接字发展史及分类

  套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 

1.套接字有两种

a>.基于文件类型的套接字家族

  套接字家族的名字:AF_UNIX

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

b>.基于网络类型的套接字家族(这是我们常用的接口)

  套接字家族的名字:AF_INET

  (还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

 

四.套接字工作流程

  一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。    生活中的场景就解释了这工作原理,也许TCP/IP协议族就是诞生于生活中,这也不一定。

      

                                           图3       

  先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束.

 

五.下面让我们一起写一个基于tcp的一次套接字通信:

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import socket #导入socket模块
 8 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #表示这个套接字是基于网络通信(socket.AF_INET)的,而且是基于TCP协议(“socket.SOCK_STREAM”)工作的。建立套接字的过程。
 9 phone.bind(('127.0.0.1',8080))  #绑定IP地址及端口,以此来确定他是这个网络唯一的服务。
10 phone.listen(5)   #等待的最大连接池,我这里写死了,其实可以通过读取配置文件将这个参数取出来的。
11 Handshake,addr = phone.accept()   #以元组的形式接受2个值,其中Handshake类似建立三次握手,addr表示接受客户端的IP地址和端口。
12 print("tcp的连接:>>>",Handshake)
13 print("客户端的地址:>>>",addr)
14 data = Handshake.recv(1024)  #表示接受消息消息,这里是只接受1024个字节,不管对方发了多少消息,都只接受1024个字节,这算是这个程序的一个小bug!,其实这个data就是一个bytes类型字符
15 print('from client msg :%s'%data)
16 Handshake.send(data.upper())  #发送消息,我这里讲接受的数据大写一下发送给客户端,注意,发送的仍然是bytes类型哟!
17 Handshake.close() #关闭连接
18 phone.close()  #关闭套接字
19 
20 
21 
22 #以上代码执行结果如下:
23 tcp的连接:>>> <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 51748)>
24 客户端的地址:>>> ('127.0.0.1', 51748)
25 from client msg :b'hello'
socket服务端,需要先运行该脚本哟
 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import  socket
 8 client =  socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #建立套接字
 9 client.connect(("127.0.0.1",8080)) #连接服务端
10 client.send("hello".encode("utf-8"))  #发送数据
11 data = client.recv(1024)  #接受数据
12 print(data)
13 client.close()
14 
15 
16 #以上代码执行结果如下:
17 tcp的连接:>>> <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 51748)>
18 客户端的地址:>>> ('127.0.0.1', 51748)
19 from client msg :b'hello'
socket客户端代码

 

六.基于tcp的套接字通信(链接循环和通信循环)

  socket服务端代码如下:【注意:要先运行服务端代码,再运行客户端代码。】

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import socket #导入socket模块
 8 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #建立套接字类型
 9 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #可以避免TCP端口被占用的情况,这是一家提供好的接口。
10 phone.bind(('127.0.0.1',8080))  #绑定IP地址及端口。
11 phone.listen(5)
12 while True:  #连接循环
13     Handshake,addr = phone.accept()   #以元组的形式接受2个值,其中Handshake类似建立三次握手,addr表示接受客户端的IP地址和端口。
14     print("client:",addr)
15     while True:  #通讯循环
16         try:
17             data = Handshake.recv(1024)  #表示接受消息消息,这里是只接受1024个字节,不管对方发了多少消息,都只接受1024个字节,这算是这个程序的一个小bug!,其实这个data就是一个bytes类型字符
18             if not data:break #针对linux,客户端断开连接的异常处理.
19             print('from client msg :%s'%data)
20             Handshake.send(data.upper())  #发送消息,我这里讲接受的数据大写一下发送给客户端,注意,发送的仍然是bytes类型哟!
21         except Exception:
22             break
23     Handshake.close() #关闭连接
24 phone.close()  #关闭套接字
25 
26 
27 #以上代码执行结果如下:
28 client: ('127.0.0.1', 52761)
29 from client msg :b'yinzhengjie'
30 from client msg :b'your are a good boy!'
31 from client msg :b'you can do it!'

  socket客户端代码如下:

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import  socket
 8 client =  socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #建立套接字
 9 client.connect(("127.0.0.1",8080)) #连接服务端
10 while True:
11     msg = input("请输入英文字符串哟>>> ")
12     if not msg: continue  # 可以避免输入的是空格,加这行代码就是为了不让其卡主。
13     client.send(msg.encode("utf-8"))  #发送数据
14     data = client.recv(1024)
15     print(data)
16 client.close()
17 
18 
19 
20 #以上代码执行操作如下:
21 请输入英文字符串哟>>> yinzhengjie
22 b'YINZHENGJIE'
23 请输入英文字符串哟>>> 
24 请输入英文字符串哟>>> your are a good boy!
25 b'YOUR ARE A GOOD BOY!'
26 请输入英文字符串哟>>> 
27 请输入英文字符串哟>>> 
28 请输入英文字符串哟>>> you can do it!
29 b'YOU CAN DO IT!'
30 请输入英文字符串哟>>> 
31 请输入英文字符串哟>>> 

 

七.利用socket进行远程执行命令程序编写

  服务端代码如下:【注意:要先运行服务端哟~】

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import socket #导入socket模块
 8 import subprocess
 9 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #建立套接字类型
10 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #可以避免TCP端口被占用的情况,这是一家提供好的接口。
11 phone.bind(('127.0.0.1',8080))  #绑定IP地址及端口。
12 phone.listen(5)
13 while True:  #连接循环
14     Handshake,addr = phone.accept()   #以元组的形式接受2个值,其中Handshake类似建立三次握手,addr表示接受客户端的IP地址和端口。
15     print("client:",addr)
16     while True:  #通讯循环
17         try:
18             cmd = Handshake.recv(1024)  #表示接受消息消息,这里是只接受1024个字节,不管对方发了多少消息,都只接受1024个字节,这算是这个程序的一个小bug!,其实这个data就是一个bytes类型字符
19             if not cmd:break #针对linux,客户端断开连接的异常处理.
20             print('from client msg :%s'%cmd)
21             res = subprocess.Popen(cmd.decode("utf-8"),
22                                    shell = True,
23                                    stdout = subprocess.PIPE,
24                                    stderr = subprocess.PIPE)
25             err = res.stderr.read()
26             if err:
27                 back_msg = err
28             else:
29                 back_msg = res.stdout.read()
30             Handshake.send(back_msg)
31         except Exception:
32             break
33     Handshake.close() #关闭连接
34 phone.close()  #关闭套接字
35 
36 
37 
38 
39 #以上代码执行结果如下:
40 client: ('127.0.0.1', 53450)
41 from client msg :b'dir'
42 from client msg :b'3'

  客户端代码如下:

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import  socket
 8 client =  socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #建立套接字
 9 client.connect(("127.0.0.1",8080)) #连接服务端
10 while True:
11     msg = input("请输入你想要执行的windows命令>>> ").strip()
12     if not msg: continue  # 可以避免输入的是空格,加这行代码就是为了不让其卡主。
13     client.send(msg.encode("utf-8"))  #发送数据
14     data = client.recv(1024)
15     print(data.decode("gbk"))  #由于在windows上运行,对其进行解码即可。
16 client.close()
17 
18 
19 
20 #以上代码执行结果如下:
21 请输入你想要执行的windows命令>>> dir
22  驱动器 D 中的卷是 虚拟机专用
23  卷的序列号是 005D-B1D2
24 
25  D:\untitled\DAY7 的目录
26 
27 2017/05/06  22:34    <DIR>          .
28 2017/05/06  22:34    <DIR>          ..
29 2017/04/23  22:06    <DIR>          .idea
30 2017/05/06  22:27               742 socket客户端.py
31 2017/05/06  22:02               581 socket客户端1.py
32 2017/05/06  22:34             1,747 socket服务端.py
33                3 个文件          3,070 字节
34                3 个目录 338,435,780,608 可用字节
35 
36 请输入你想要执行的windows命令>>> 3
37 '3' 不是内部或外部命令,也不是可运行的程序
38 或批处理文件。
39 
40 请输入你想要执行的windows命令>>> .

八.粘包及解决方法

1.什么是粘包

  声明一点:只有TCP连接才会有粘包的现象,UDP是没有粘包现象的,这一点,我们得通过案例来看一下。

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 import socket
 7 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 8 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
 9 server.bind(("127.0.0.1",80))
10 server.listen()
11 
12 Handshake,addr = server.accept()
13 data = Handshake.recv(3)  #只接收3个字节
14 print(data)
15 data_1 = Handshake.recv(10)
16 print(data_1)
17 server.close()
18 
19 
20 #以上代码执行结果如下:
21 b'yin'     #我们发现,客户端2次发送的数据被拆分了,data和data_1得到了不同的字符串
22 b'zhengjiehe'
服务端代码
 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 import socket
 7 client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 8 client.connect(("127.0.0.1",80))
 9 client.send("yinzhengjie".encode("utf-8"))  #向服务端发送一个字符串是"yinzhengjie"
10 client.send("hello".encode("utf-8"))
11 client.close()
客户端代码【注:需要先执行服务端】

  通过以上的案例,我们发现客户端发送的字符和服务端接受的字符并不一致,两者补兵影响,因此,我们需要了解一下底层在传输的过程,首先我们用IDE编写的代码将用Python解释器(用户态)执行,客户端在发送数据的时候,会先将数据发送到自己本地缓存(内核态)中,然后再通过底层的传输到另外一台机器(服务端)中的缓存,服务端在取数据的时候会在它本地上取数据,每次取数据的大小都是有它自己定义的。也就是说客户端和服务端各有2块缓存且里面的数据并不互相影响!同理,如果服务端要给客户端回包的话也会先将数据发到自己的缓存中,然后再通过底层的技术将数据发送给客户端。

  注意:我们可以这样理解:用户态就是基于软件来说的,内核态是基于操作系统来说的!

 

 2.粘包的解决思路

了解TCP与UDP:

  所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

  此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

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

  b>.UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。  

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

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

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

  

解决粘包的思路:

  综上所述。我们想要解决粘包问题,可以让客户端和服务端来共同设置一个标准,比如客户端告诉服务端指定一个协议,就是在传输数据的时候客户端告诉服务端每次传输数据的长度,然后服务端每次按照这个长度来接受这个数据包,就可以避免乱码的问题了。

 

服务端代码如下:

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import socket #导入socket模块
 8 import subprocess
 9 import struct #可以把数字打包成一个二进制并且长度固定
10 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #建立套接字类型
11 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #可以避免TCP端口被占用的情况,这是一家提供好的接口。
12 phone.bind(('127.0.0.1',8080))  #绑定IP地址及端口。
13 phone.listen(5)
14 while True:  #连接循环
15     Handshake,addr = phone.accept()   #以元组的形式接受2个值,其中Handshake类似建立三次握手,addr表示接受客户端的IP地址和端口。
16     print("client:",addr)
17     while True:  #通讯循环
18         try:
19             cmd = Handshake.recv(1024)  #表示接受消息消息,这里是只接受1024个字节,不管对方发了多少消息,都只接受1024个字节,这算是这个程序的一个小bug!,其实这个data就是一个bytes类型字符
20             if not cmd:break #针对linux,客户端断开连接的异常处理.
21             print('from client msg :%s'%cmd)
22             res = subprocess.Popen(cmd.decode("utf-8"),
23                                    shell = True,
24                                    stdout = subprocess.PIPE,
25                                    stderr = subprocess.PIPE)
26             err = res.stderr.read()
27             if err:
28                 back_msg = err
29             else:
30                 back_msg = res.stdout.read()
31             Handshake.send(struct.pack('i',len(back_msg)))  #struct.pack方法表示将(“i”[int])后面的数字打包成4个字节
32             Handshake.send(back_msg)
33         except Exception:
34             break
35     Handshake.close() #关闭连接
36 phone.close()  #关闭套接字
37 
38 
39 
40 #以上代码如下:
41 client: ('127.0.0.1', 53429)
42 from client msg :b'ipconfig'
43 from client msg :b'aa'

客户端代码如下:

  1 #!/usr/bin/env python
  2 #_*_coding:utf-8_*_
  3 #@author :yinzhengjie
  4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
  5 #EMAIL:y1053419035@qq.com
  6 import socket
  7 import struct #可以把数字打包成一个二进制并且长度固定为4个字节。
  8 client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  9 client.connect(("127.0.0.1",8080))
 10 
 11 while True:
 12     cmd = input("请输入一些字符串>>>:").strip()
 13     if not cmd:continue
 14     client.send(cmd.encode("utf-8"))
 15     data = client.recv(4)
 16     data_size = struct.unpack('i',data)[0] #struct.unpack方法表示将接受的元组data解压并取值该院组的第一个元素,该元素默认是一个整数型(int)
 17     recv_size = 0
 18     recv_bytes = b''
 19     while   recv_size < data_size: #判读如果接受的字符不等于固定数据大小就执行以下代码
 20         res = client.recv(1024)
 21         recv_bytes += res
 22         recv_size += len(res)
 23         print(recv_bytes.decode("gbk"))
 24 
 25 #以上代码执行结果如下:
 26 请输入一些字符串>>>:ipconfig
 27 
 28 Windows IP 配置
 29 
 30 
 31 以太网适配器 本地连接 2:
 32 
 33    媒体状态  . . . . . . . . . . . . : 媒体已断开
 34    连接特定的 DNS 后缀 . . . . . . . : 
 35 
 36 以太网适配器 本地连接:
 37 
 38    连接特定的 DNS 后缀 . . . . . . . : 
 39    本地链接 IPv6 地址. . . . . . . . : fe80::a029:2749:90ac:92b8%11
 40    IPv4 地址 . . . . . . . . . . . . : 200.200.200.102
 41    子网掩码  . . . . . . . . . . . . : 255.255.255.0
 42    默认网关. . . . . . . . . . . . . : 200.200.200.1
 43 
 44 以太网适配器 VMware Network Adapter VMnet1:
 45 
 46    连接特定的 DNS 后缀 . . . . . . . : localdomain
 47    本地链接 IPv6 地址. . . . . . . . : fe80::c90d:d960:2d31:561a%19
 48    IPv4 地址 . . . . . . . . . . . . : 192.168.137.1
 49    子网掩码  . . . . . . . . . . . . : 255.255.255.0
 50    默认网关. . . . . . . . . . . . . : 
 51 
 52 以太网适配器 VMware Network Adapter VMnet8:
 53 
 54    连接特定的 DNS 后缀 . . . . . . . : localdomain
 55    本地链接 IPv6 地址. . . . . . . . : fe80::d1a8:7945:469f:718%20
 56    IPv4 地址 . . . . . . . . . . . . : 192.168.254.1
 57    子网掩码  . . . . . . . . . . . . 
 58 
 59 Windows IP 配置
 60 
 61 
 62 以太网适配器 本地连接 2:
 63 
 64    媒体状态  . . . . . . . . . . . . : 媒体已断开
 65    连接特定的 DNS 后缀 . . . . . . . : 
 66 
 67 以太网适配器 本地连接:
 68 
 69    连接特定的 DNS 后缀 . . . . . . . : 
 70    本地链接 IPv6 地址. . . . . . . . : fe80::a029:2749:90ac:92b8%11
 71    IPv4 地址 . . . . . . . . . . . . : 200.200.200.102
 72    子网掩码  . . . . . . . . . . . . : 255.255.255.0
 73    默认网关. . . . . . . . . . . . . : 200.200.200.1
 74 
 75 以太网适配器 VMware Network Adapter VMnet1:
 76 
 77    连接特定的 DNS 后缀 . . . . . . . : localdomain
 78    本地链接 IPv6 地址. . . . . . . . : fe80::c90d:d960:2d31:561a%19
 79    IPv4 地址 . . . . . . . . . . . . : 192.168.137.1
 80    子网掩码  . . . . . . . . . . . . : 255.255.255.0
 81    默认网关. . . . . . . . . . . . . : 
 82 
 83 以太网适配器 VMware Network Adapter VMnet8:
 84 
 85    连接特定的 DNS 后缀 . . . . . . . : localdomain
 86    本地链接 IPv6 地址. . . . . . . . : fe80::d1a8:7945:469f:718%20
 87    IPv4 地址 . . . . . . . . . . . . : 192.168.254.1
 88    子网掩码  . . . . . . . . . . . . : 255.255.255.0
 89    默认网关. . . . . . . . . . . . . : 
 90 
 91 隧道适配器 isatap.{25F96CF4-4EED-4B7F-9537-3230585CB663}:
 92 
 93    媒体状态  . . . . . . . . . . . . : 媒体已断开
 94    连接特定的 DNS 后缀 . . . . . . . : 
 95 
 96 隧道适配器 isatap.{13AC9C15-E77A-45EA-B1E7-4009A1FB9270}:
 97 
 98    媒体状态  . . . . . . . . . . . . : 媒体已断开
 99    连接特定的 DNS 后缀 . . . . . . . : 
100 
101 隧道适配器 isatap.localdomain:
102 
103    媒体状态  . . . . . . . . . . . . : 媒体已断开
104    连接特定的 DNS 后缀 . . . . . . . : localdomain
105 
106 隧道适配器 6TO4 Adapter:
107 
108    连接特定的 DNS 后缀 . . . . . . . : 
109    IPv6 地址 . . . . . . . . . . . . : 2002:c8c8:c866::c8c8:c866
110    默认网关. . . . . . . . . . . . . : 2002:c058:6301::c058:6301
111 
112 隧道适配器 Teredo Tunneling Pseudo-Interface:
113 
114    媒体状态  . . . . . . . . . . . . : 媒体已断开
115    连接特定的 DNS 后缀 . . . . . . . : 
116 
117 请输入一些字符串>>>:
118 请输入一些字符串>>>:aa
119 'aa' 不是内部或外部命令,也不是可运行的程序
120 或批处理文件。
121 
122 请输入一些字符串>>>:
123 请输入一些字符串>>>:

 

九.自定制报头解决粘包

实现思路:

    我们在上面可以看到为了解决粘包带来的困扰,我们可以将数据的大小提前发送给服务端,然后服务端只按照这个大小去接受数据,就可以解决粘包的问题,这个思路属实很棒棒哟,利用“struct”模块的 “i”(整数型)参数将文件的大小传递给服务端,但是,这个"i”的大小是友上限的哟!比如你要传送一个2G的文件就会报错了,那么我们又该如何处理呢?这个时候我们可以用字典将这个大小进行封装,再用json将该字典序列化成字符串,再将字符串进行encode(编码),然后将编码后的长度和内容发送给服务端即可。

 

 

服务端代码如下:

 

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import socket  #提供套接字接口
 8 import struct  #利用其的“i”参数对数据进行归纳1
 9 import json  #用于序列胡
10 import subprocess  #用于调用系统命令
11 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #定义套接字类型,属于网络接口用的是TCP协议。
12 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加,用于避免端口被占用的情况。
13 phone.bind(('127.0.0.1',8080)) #设置坚听端口
14 phone.listen(5) #设置最大链接数
15 while True: #链接循环
16     conn,addr=phone.accept()
17     print('client :',addr)
18     while True: #通讯循环
19         try:
20             cmd=conn.recv(1024)
21             if not cmd:break #针对linux,客户端断开链接的异常处理
22             print('from client msg :%s' %cmd)
23             res=subprocess.Popen(cmd.decode('utf-8'),
24                              shell=True,
25                              stdout=subprocess.PIPE,
26                              stderr=subprocess.PIPE)
27             err=res.stderr.read()
28             if err:
29                 back_msg=err
30             else:
31                 back_msg=res.stdout.read()
32             #第一阶段:制作报头
33             head_dic={
34                 'data_size':len(back_msg)
35             }
36             head_json=json.dumps(head_dic)  #将定义好的字典用json将其序列化成字符串。
37             head_bytes=head_json.encode('utf-8')  #由于传输传输过程中必须用字节进行传输,因此需要编码。
38             #第二阶段:发送报头的长度
39             conn.send(struct.pack('i',len(head_bytes)))
40             #第三阶段:发报头
41             conn.send(head_bytes)
42             #第四阶段:发真实数据
43             conn.sendall(back_msg)
44         except Exception:
45             break
46     conn.close()
47 phone.close()
48 
49 
50 #以上代码执行结果如下:
51 client : ('127.0.0.1', 54567)
52 from client msg :b'route print'
53 from client msg :b'echo My name is Yinzhengjie'
54 from client msg :b'echo I am a good boy!'

 

客户端代码如下:

 

  1 #!/usr/bin/env python
  2 #_*_coding:utf-8_*_
  3 #@author :yinzhengjie
  4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
  5 #EMAIL:y1053419035@qq.com
  6 
  7 
  8 import socket
  9 import struct
 10 import json
 11 client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 12 client.connect(('127.0.0.1',8080))
 13 
 14 while True:
 15     cmd=input('请输出您想执行的操作系统的命令>>: ').strip()
 16     if not cmd:continue
 17     client.send(cmd.encode('utf-8'))
 18     #收报头的长度
 19     head=client.recv(4)  #收4个字节
 20     head_size=struct.unpack('i',head)[0]  #计算收到的4个字节的长度。
 21     #收报头(根据报头长度)
 22     head_bytes=client.recv(head_size)  #按照之前计算的长度来接受相应的数据。
 23     head_json=head_bytes.decode('utf-8') #将接受到的数据解码成字符串。
 24     head_dic=json.loads(head_json) #将接收到字符串用json将其反序列化,这要就知道这个字符串所对应的数据类型,比如字典,元祖,列表等等。
 25     data_size=head_dic['data_size'] #取出真实数据的长度
 26     #收真实的数据
 27     recv_size=0
 28     recv_bytes=b''
 29     while recv_size < data_size: #当接受的数据大小没有到真实数据的长度就会一直收。
 30         res=client.recv(1024)
 31         recv_bytes+=res
 32         recv_size+=len(res)
 33     print(recv_bytes.decode('gbk'))  #将真实的数据进行解码,windows默认是gbk编码,所以我这里用gbk进行解码。
 34 
 35 
 36 #以上代码执行结果如下:
 37 请输出您想执行的操作系统的命令>>: route print
 38 ===========================================================================
 39 接口列表
 40   5...00 ff 6c 43 2b 31 ......Sangfor SSL VPN CS Support System VNIC
 41   6...40 16 7e b4 92 99 ......Realtek PCIe GBE Family Controller
 42   3...00 50 56 c0 00 01 ......VMware Virtual Ethernet Adapter for VMnet1
 43   4...00 50 56 c0 00 08 ......VMware Virtual Ethernet Adapter for VMnet8
 44   1...........................Software Loopback Interface 1
 45 ===========================================================================
 46 
 47 IPv4 路由表
 48 ===========================================================================
 49 活动路由:
 50 网络目标        网络掩码          网关       接口   跃点数
 51           0.0.0.0          0.0.0.0     172.16.3.254     172.16.3.210    276
 52         127.0.0.0        255.0.0.0            在链路上         127.0.0.1    306
 53         127.0.0.1  255.255.255.255            在链路上         127.0.0.1    306
 54   127.255.255.255  255.255.255.255            在链路上         127.0.0.1    306
 55        172.16.3.0    255.255.255.0            在链路上      172.16.3.210    276
 56      172.16.3.210  255.255.255.255            在链路上      172.16.3.210    276
 57      172.16.3.255  255.255.255.255            在链路上      172.16.3.210    276
 58     192.168.102.0    255.255.255.0            在链路上     192.168.102.1    276
 59     192.168.102.1  255.255.255.255            在链路上     192.168.102.1    276
 60   192.168.102.255  255.255.255.255            在链路上     192.168.102.1    276
 61     192.168.109.0    255.255.255.0            在链路上     192.168.109.1    276
 62     192.168.109.1  255.255.255.255            在链路上     192.168.109.1    276
 63   192.168.109.255  255.255.255.255            在链路上     192.168.109.1    276
 64         224.0.0.0        240.0.0.0            在链路上         127.0.0.1    306
 65         224.0.0.0        240.0.0.0            在链路上      172.16.3.210    276
 66         224.0.0.0        240.0.0.0            在链路上     192.168.109.1    276
 67         224.0.0.0        240.0.0.0            在链路上     192.168.102.1    276
 68   255.255.255.255  255.255.255.255            在链路上         127.0.0.1    306
 69   255.255.255.255  255.255.255.255            在链路上      172.16.3.210    276
 70   255.255.255.255  255.255.255.255            在链路上     192.168.109.1    276
 71   255.255.255.255  255.255.255.255            在链路上     192.168.102.1    276
 72 ===========================================================================
 73 永久路由:
 74   网络地址          网络掩码  网关地址  跃点数
 75           0.0.0.0          0.0.0.0     172.16.3.254     默认 
 76 ===========================================================================
 77 
 78 IPv6 路由表
 79 ===========================================================================
 80 活动路由:
 81  接口跃点数网络目标                网关
 82   1    306 ::1/128                  在链路上
 83   6    276 fe80::/64                在链路上
 84   4    276 fe80::/64                在链路上
 85   3    276 fe80::/64                在链路上
 86   3    276 fe80::1d6f:cc8f:d15b:587a/128
 87                                     在链路上
 88   4    276 fe80::4c80:600a:2d95:cafe/128
 89                                     在链路上
 90   6    276 fe80::8997:a1b8:34bc:8194/128
 91                                     在链路上
 92   1    306 ff00::/8                 在链路上
 93   6    276 ff00::/8                 在链路上
 94   4    276 ff00::/8                 在链路上
 95   3    276 ff00::/8                 在链路上
 96 ===========================================================================
 97 永久路由:
 98  99 
100 请输出您想执行的操作系统的命令>>: echo My name is Yinzhengjie
101 My name is Yinzhengjie
102 
103 请输出您想执行的操作系统的命令>>: echo I am a good boy!
104 I am a good boy!
105 
106 请输出您想执行的操作系统的命令>>: 

 

十.定义UDP套接字

  基于UDP的套接字是不用监听端口的。

 

UDP服务端代码如下:

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import socket
 8 ip_port=('127.0.0.1',9000)  #定义IP和端口
 9 BUFSIZE=1024 #定义接受的数据大小
10 udp_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)   #定义套接字类型,属于网络接口用的是UDP协议。
11 udp_client.bind(ip_port)
12 while True:
13     msg,addr=udp_client.recvfrom(BUFSIZE)  #收消息
14     print(msg,addr)
15     udp_client.sendto(msg.upper(),addr)  #发消息,这里是将发来的字符串字母大写
16 
17 
18 #以上代码执行结果如下:
19 b'yinzhengjie' ('127.0.0.1', 59963)
20 b'ifconfig' ('127.0.0.1', 59963)
21 b'show run' ('127.0.0.1', 59963)
22 b'dis this' ('127.0.0.1', 59963)

 

UDP客户端代码如下:

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import socket
 8 ip_port=('127.0.0.1',9000)
 9 BUFSIZE=1024
10 udp_server =socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
11 
12 while True:
13     msg=input('请输入小写英文字母>>: ').strip()
14     if not msg:continue
15     udp_server.sendto(msg.encode('utf-8'),ip_port)  #发送数据
16     back_msg,addr=udp_server.recvfrom(BUFSIZE)       #接收数据
17     print(back_msg.decode('utf-8'),addr)
18 
19 
20 #以上代码执行结果如下:
21 请输入小写英文字母>>: yinzhengjie
22 YINZHENGJIE ('127.0.0.1', 9000)
23 请输入小写英文字母>>: ifconfig
24 IFCONFIG ('127.0.0.1', 9000)
25 请输入小写英文字母>>: show run
26 SHOW RUN ('127.0.0.1', 9000)
27 请输入小写英文字母>>: dis this
28 DIS THIS ('127.0.0.1', 9000)
29 请输入小写英文字母>>:

 

利用socket的UDP协议编写简单的聊天对话:

服务端代码:

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import socket
 8 ip_port=('127.0.0.1',8081)
 9 udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #定义套接字类型为网络接口的UDP协议
10 udp_server_sock.bind(ip_port)  #绑定端口
11 
12 while True:
13     qq_msg,addr=udp_server_sock.recvfrom(1024)  #收消息
14     print('来自[%s:%s]的一条消息:\033[31;1m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
15     back_msg=input('回复消息: ').strip()
16     udp_server_sock.sendto(back_msg.encode('utf-8'),addr)  #发消息
17 
18 
19 
20 #以上代码执行结果如下:
21 来自[127.0.0.1:53089]的一条消息:11111
22 回复消息: 2222
23 来自[127.0.0.1:51730]的一条消息:山无棱,天地合,乃敢与君绝
24 回复消息: 海可枯,石可烂,激情永不散
25 来自[127.0.0.1:51730]的一条消息:紫薇
26 回复消息: 尔康
27 来自[127.0.0.1:51730]的一条消息:是你
28 回复消息: 是我
29 来自[127.0.0.1:51730]的一条消息:有钱吗?借点
30 回复消息: 不是本人

客户端代码如下:

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/
 5 #EMAIL:y1053419035@qq.com
 6 import socket
 7 BUFSIZE=1024
 8 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 9 qq_name_dic={
10     '尹正杰':('127.0.0.1',8081),
11     '紫薇':('127.0.0.1',8081),
12     '尔康':('127.0.0.1',8081),
13 }
14 print("\033[32;1m您的好友如下:\033[0m")
15 for i in qq_name_dic.keys():
16     print(i)
17 while True:
18     qq_name=input('请选择聊天的对象:>>>').strip()
19     if qq_name == "quit":
20         print("欢迎您使用本软件,下次再见")
21         break
22     while True:
23         msg=input('请输入消息,回车发送: ').strip()
24         if msg == 'quit':break
25         if not msg or not qq_name or qq_name not in qq_name_dic:continue
26         udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])  #发消息
27         back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)   #收消息
28         print('来自[%s:%s]的一条消息:\033[33;1m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
29 udp_client_socket.close()
30 
31 
32 #以上代码执行结果如下:
33 您的好友如下:
34 尹正杰
35 紫薇
36 尔康
37 请选择聊天的对象:>>>紫薇
38 请输入消息,回车发送: 山无棱,天地合,乃敢与君绝
39 来自[127.0.0.1:8081]的一条消息:海可枯,石可烂,激情永不散
40 请输入消息,回车发送: 紫薇
41 来自[127.0.0.1:8081]的一条消息:尔康
42 请输入消息,回车发送: 是你
43 来自[127.0.0.1:8081]的一条消息:是我
44 请输入消息,回车发送: 有钱吗?借点
45 来自[127.0.0.1:8081]的一条消息:不是本人
46 请输入消息,回车发送: ····

 

转载于:https://www.cnblogs.com/yinzhengjie/p/6561644.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值