一、用套接字实现简单通信
1.1服务端
import socket soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #绑定IP地址和端口号 soc.bind(('192.168.11.195',8080)) #处于监听状态 soc.listen(5) #准备接收数据 conn,addr = soc.accept() #接收数据,最大为1024字节 data= conn.recv(1024) # 打印客户端发来的信息 print('客户端发来的数据:',data) #发送信息 conn.send(b'sjdiuamjnd') # 关闭通信 conn.close() #关闭连接 soc.close()
1.2 客户端
import socket soc = socket.socket() #连接IP地址和端口号 soc.connect(('192.168.11.195',8080)) #发送消息 soc.send(b'adndhbv') #接收1024个字节 data = soc.recv(1024) #打印接收的数据 print('服务端接受的数据:',data) #关闭连接 soc.close()
二、用套接字实现通信循环
由于简单通信,客户端和服务器发一次数据就端来连接了,我们现在要用一种方法让他实现,客户端一直向服务器发消息,并且在服务器上显示内容而连接不断开。
2.1服务端
import socket soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #绑定IP地址和端口号 soc.bind(('192.168.11.195',8080)) #处于监听状态 soc.listen(5) #准备接收数据 conn,addr = soc.accept() #接收数据,最大为1024字节 while True: try: data= conn.recv(1024) #每次都会卡到这里,有数据发过来的时候才会执行 # 打印客户端发来的信息 print('客户端发来的数据:',data) except Exception: #异常处理 break conn.close() # 关闭通信 #关闭连接 soc.close()
2.2客户端
import socket soc = socket.socket() #连接IP地址和端口号 soc.connect(('192.168.11.195',8080)) #发送消息 while True: #加入循环 data = input('请输入你要发送的数据>>>') data= bytes(data,encoding='utf8') #将数据转化为bytes格式 soc.send(data) #关闭连接 soc.close()
三、用套接字实现连接循环
由于通信循环也只是一个客户端和一个服务器进行交互,如果有其他的客户端想要连接这个服务端,就连不进去,我们怎样去实现一台服务器可以和不仅是一个客户端建立连接呢?我们就需要连接循环,只要一个客户端和服务器断了,服务器还可以等待其他客户端来连接它。
### 3.1服务端
import socket soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #绑定IP地址和端口号 soc.bind(('192.168.11.195',8080)) #处于监听状态 soc.listen(5) #半连接池的大小 #准备接收数据 while True: #加入循环 print('等待客户端连接>>>>') conn,addr = soc.accept() #每次都会卡到这里,有客户端连接才能执行下一步 print(f'客户端{addr}连接成功') #接收数据,最大为1024字节 while True: try: data= conn.recv(1024) #每次都会卡到这里,有数据发过来的时候才会执行 # 打印客户端发来的信息 print('客户端发来的数据:',data) # 处理linux客户端断开,如果在window下这段代码根本不会执行(即便是客服端发了空,这个地方也不会走到) if len(data) == 0: break except Exception: #异常处理 break conn.close() # 关闭通信 #关闭连接 soc.close() ------------------------------------------------------------------------------------- 等待客户端连接>>>> 客户端('192.168.11.195', 51460)连接成功 客户端发来的数据: b'xcv' 客户端发来的数据: b'c' 等待客户端连接>>>> 客户端('192.168.11.195', 51467)连接成功 客户端发来的数据: b'zxcv'
### 3.2 客户端
import socket soc = socket.socket() #连接IP地址和端口号 soc.connect(('192.168.11.195',8080)) #发送消息 while True: #加入循环 data = input('请输入你要发送的数据>>>') data= bytes(data,encoding='utf8') #将数据转化为bytes格式 soc.send(data) #关闭连接 soc.close()
四、模拟SSH(远程执行命令)功能
4.1 subprocess模块
-
什么是subprocess模块?
执行系统命令的模块
import subprocess #导入模块 #执行系统dir命令,把执行的正确结果放到stdout管道中,把错误的命令放在stderr管道中(dir:查看当前文件夹下的所有文件,tasklist:查看当前正在运行的系统任务) obj=subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) #拿到正确结果的管道,读出里面的内容 yes = obj.stdout.read() no = obj.stdeer.read() #windows的命令是gbk编码,所以用gbk解码 print('错误信息',str(yes,encoding= 'gbk')) print('错误信息',str(no,encoding= 'gbk'))
4.2服务端
import socket import subprocess soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #绑定IP地址和端口号 soc.bind(('192.168.11.195',8080)) #处于监听状态 soc.listen(5) #准备接收数据 while True: #加入循环 print('等待客户端连接>>>>') conn,addr = soc.accept() #每次都会卡到这里,有客户端连接才能执行下一步 print(f'客户端{addr}连接成功') #接收数据,最大为1024字节 while True: try: data= conn.recv(1024) #每次都会卡到这里,有数据发过来的时候才会执行 # 打印客户端发来的信息 print('客户端发来的数据:',data) # 处理linux客户端断开,如果在window下这段代码根本不会执行(即便是客服端发了空,这个地方也不会走到) if len(data) == 0: break print(data) obj = subprocess.Popen(str(data,encoding='utf8'),shell = True,stdout = subprocess.PIPE,stderr = subprocess.PIPE) msg = obj.stdout.read() #将数据读出来 conn.send(msg) #将读取的数据通过网络发送给c端 print(111) except Exception: #异常处理 break conn.close() # 关闭通信 #关闭连接 soc.close()
4.3客户端
import socket soc = socket.socket() #连接IP地址和端口号 soc.connect(('192.168.11.195',8080)) #发送消息 while True: #加入循环 data_inp = input('请输入你要发送的数据>>>') #输入需要执行的命令 data_inp= bytes(data_inp,encoding='utf8') #将数据转化为bytes格式 soc.send(data_inp) #将转化好的数据发送到服务端 data_rec = soc.recv(1024) #接收来自服务端的数据数据 print(str(data_rec,encoding='gbk')) #因为Windows是gbk编码,所以用gbk解码 #关闭连接 soc.close()
五、粘包问题
我们先来看看一个粘包的例子
服务器 --------------------------------------------------------------------------- import socket soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #绑定IP地址和端口号 soc.bind(('192.168.11.195',8080)) #处于监听状态 soc.listen(5) #准备接收数据 while True: #加入循环 print('等待客户端连接>>>>') conn,addr = soc.accept() #每次都会卡到这里,有客户端连接才能执行下一步 print(f'客户端{addr}连接成功') #接收数据,最大为1024字节 while True: try: data= conn.recv(1024) #每次都会卡到这里,有数据发过来的时候才会执行 # 打印客户端发来的信息 print('客户端发来的数据:',data) # 处理linux客户端断开,如果在window下这段代码根本不会执行(即便是客服端发了空,这个地方也不会走到) if len(data) == 0: break except Exception: #异常处理 break conn.close() # 关闭通信 #关闭连接 soc.close() ----------------------------------------------------------------- 等待客户端连接>>>> 客户端('192.168.11.195', 51569)连接成功 客户端发来的数据: b'aaa' 客户端发来的数据: b'bbbccc'
客户端 --------------------------------------------------------------------------------------- import socket soc = socket.socket() #连接IP地址和端口号 soc.connect(('192.168.11.195',8080)) #发送消息 while True: #加入循环 data = input('请输入你要发送的数据>>>') data= bytes(data,encoding='utf8') #将数据转化为bytes格式 soc.send(b'aaa') soc.send(b'bbb') soc.send(b'ccc') #关闭连接 soc.close()
5.1 发生粘包的两种情况
- 发送端需要等缓冲区满才发送出去,造成粘包(发送时间间隔很短,数据又很小,会合到一起,产生粘包)
- 接收端不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次手的时候还是从缓冲区呐上次一流的数据,产生粘包)
5.2 解决粘包的处理办法
1)发送端在发送数据前让接收端知道字节流(数据)的大小,为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据。
服务器 ----------------------------------------------------------------------------------------- import socket import subprocess import struct soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #绑定IP地址和端口号 soc.bind(('192.168.11.195',8080)) #处于监听状态 soc.listen(5) #准备接收数据 while True: #加入循环 print('等待客户端连接>>>>') conn,addr = soc.accept() #每次都会卡到这里,有客户端连接才能执行下一步 print(f'客户端{addr}连接成功') #接收数据,最大为1024字节 while True: try: data= conn.recv(1024) #每次都会卡到这里,有数据发过来的时候才会执行 # 打印客户端发来的信息 print('客户端发来的数据:',data) # 处理linux客户端断开,如果在window下这段代码根本不会执行(即便是客服端发了空,这个地方也不会走到) if len(data) == 0: break print(data) obj = subprocess.Popen(str(data,encoding='utf8'),shell = True,stdout = subprocess.PIPE,stderr = subprocess.PIPE) msg = obj.stdout.read() #将数据读出来 # 先取出要发送数据长度length #接下来是给客户端数据 length = len(msg) # head 是固定四个字节 data_head = struct.pack('i', length) #将数据的长度的信息打包压缩成4个字节,作为数据头,先发过去 # 发了数据头 conn.send(data_head) # 发送真正的内容 conn.send(msg) #将读取的数据通过网络发送给c端 except Exception: #异常处理 break conn.close() # 关闭通信 #关闭连接 soc.close()
客户端 ------------------------------------------------------------------------------------- import socket import struct ##把一个数字打包成固定长度的4字节 soc = socket.socket() #连接IP地址和端口号 soc.connect(('192.168.11.195',8080)) #发送消息 while True: #加入循环 data_inp = input('请输入你要发送的数据>>>') #输入需要执行的命令 data_inp= bytes(data_inp,encoding='utf8') #将数据转化为bytes格式 soc.send(data_inp) #将转化好的数据发送到服务端 #接下来是收服务器发来的数据 data_head = soc.recv(4) #先收四个字节,作为数据头部 # 对数据头解压缩,得到数据的长度有多长,因为解压缩出来的数据是一个元组,所以要取出第一个数据才知道数据的长度 length= struct.unpack('i',data_head)[0] count = 0 data_total = b'' #数据先定义为空 while count<length: if length<1024: #如果接收的数据小于1024,直接接收数据大小,也就是把所有数据都接收到 data = soc.recv(length) break else:#如果接收的数据小于1024 if length-count>=1024:#总数据长度-count(目前收到多少,count就是多少) 如果还大于1024 ,再收1024 data= soc.recv(1024) else:#总数据长度-count(目前收到多少,count就是多少) 如果小于1024,只收剩下的部分就可以 data= soc.recv(length-count) data_total += data count +=len(data) print(str(data_total,encoding='gbk')) #因为Windows是gbk编码,所以用gbk解码 #关闭连接 soc.close()import socket import struct ##把一个数字打包成固定长度的4字节 soc = socket.socket() #连接IP地址和端口号 soc.connect(('192.168.11.195',8080)) #发送消息 while True: #加入循环 data_inp = input('请输入你要发送的数据>>>') #输入需要执行的命令 data_inp= bytes(data_inp,encoding='utf8') #将数据转化为bytes格式 soc.send(data_inp) #将转化好的数据发送到服务端 #接下来是收服务器发来的数据 data_head = soc.recv(4) #先收四个字节,作为数据头部 # 对数据头解压缩,得到数据的长度有多长,因为解压缩出来的数据是一个元组,所以要取出第一个数据才知道数据的长度 length= struct.unpack('i',data_head)[0] count = 0 data_total = b'' #数据先定义为空 while count<length: if length<1024: #如果接收的数据小于1024,直接接收数据大小,也就是把所有数据都接收到 data = soc.recv(length) break else:#如果接收的数据小于1024 if length-count>=1024:#总数据长度-count(目前收到多少,count就是多少) 如果还大于1024 ,再收1024 data= soc.recv(1024) else:#总数据长度-count(目前收到多少,count就是多少) 如果小于1024,只收剩下的部分就可以 data= soc.recv(length-count) data_total += data count +=len(data) print(str(data_total,encoding='gbk')) #因为Windows是gbk编码,所以用gbk解码 #关闭连接 soc.close()
2)我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个字节足够用了)
-
发送时:
先发报头长度
再编码报头内容然后发送
最后发真实内容
-
接收时:
先手报头长度,用struct取出来
根据取出的长度收取报头内容,然后解码,反序列化
从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容
服务端 -------------------------------------------------------------------------------- import socket import subprocess import struct import json soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #绑定IP地址和端口号 soc.bind(('192.168.11.195',8080)) #处于监听状态 soc.listen(5) #准备接收数据 while True: #加入循环 print('等待客户端连接>>>>') conn,addr = soc.accept() #每次都会卡到这里,有客户端连接才能执行下一步 print(f'客户端{addr}连接成功') #接收数据,最大为1024字节 while True: try: data= conn.recv(1024) #每次都会卡到这里,有数据发过来的时候才会执行 # 打印客户端发来的信息 print('客户端发来的数据:',data) # 处理linux客户端断开,如果在window下这段代码根本不会执行(即便是客服端发了空,这个地方也不会走到) if len(data) == 0: break print(data) obj = subprocess.Popen(str(data,encoding='utf8'),shell = True,stdout = subprocess.PIPE,stderr = subprocess.PIPE) msg = obj.stdout.read() #将数据读出来 # 先取出要发送数据长度length #接下来是给客户端传数据) dic= {'size':len(msg)} #定义一个字典,把数据长度放到字典里面 dic_bytes = (json.dumps(dic).encode('utf8')) #将字典序列化,转为bytes格式 # head 字典的长度是固定四个字节的长度 data_head = struct.pack('i', len(dic_bytes)) #将数据字典的长度的信息打包压缩成4个字节,作为数据头,先发过去 # print(data_head) #b'\x0f\x00\x00\x00' # 发送数据头 conn.send(data_head) #发送4个字节的长度 # 发送真正的数据头部 conn.send(dic_bytes) #发送真正的数据内容 conn.send(msg) #将读取的数据通过网络发送给c端 except Exception: #异常处理 break conn.close() # 关闭通信 #关闭连接 soc.close()
客户端 ------------------------------------------------------------------------------------- import socket import struct ##把一个数字打包成固定长度的4字节 import json soc = socket.socket() #连接IP地址和端口号 soc.connect(('192.168.11.195',8080)) #发送消息 while True: #加入循环 data_inp = input('请输入你要发送的数据>>>') #输入需要执行的命令 data_inp= bytes(data_inp,encoding='utf8') #将数据转化为bytes格式 soc.send(data_inp) #将转化好的数据发送到服务端(接下来其实就是把这一句做一个改造,让他更牛x) #接下来是收服务器发来的数据 head_dic_len = soc.recv(4) #先收四个字节,这四个字节是头部字典的长度 # 对头部字典解压缩,得到字典的长度有多长,因为解压缩出来的数据是一个元组,所以要取出第一个数据才知道数据的长度 dic_len= struct.unpack('i',head_dic_len)[0] dic_byte = soc.recv(dic_len) # byte 字典的长度,#收真正的头部字典 data_head = json.loads(dic_byte) #反序列化 print(data_head) #是一个字典{'size': 17396} length = data_head['size'] count = 0 data_total = b'' #数据先定义为空 print(length) #17396 while count<length: if length<1024: #如果接收的数据小于1024,直接接收数据大小,也就是把所有数据都接收到 data = soc.recv(length) break else:#如果接收的数据小于1024 if length-count>=1024:#总数据长度-count(目前收到多少,count就是多少) 如果还大于1024 ,再收1024 data= soc.recv(1024) else:#总数据长度-count(目前收到多少,count就是多少) 如果小于1024,只收剩下的部分就可以 data= soc.recv(length-count) data_total += data count +=len(data) print(str(data_total,encoding='gbk')) #因为Windows是gbk编码,所以用gbk解码 #关闭连接 soc.close()