目录
Python中Socket编程 1
一、Socket概述 1
二、python中socket模块使用 1
三、socket之聊天室 3
四、socket之端口探测 7
五、scapy之tcp端口探测 11
socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
socket即是一种特殊的文件,socket函数就是对其进行的操作(读/写IO、打开、关闭)
socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】
通给指定地址簇和socket类型来进行创建
Socket地址簇 | 描述 |
---|---|
socket.AF_UNIX | 只能够用于单一的Unix系统进程间通信 |
socket.AF_INET | 服务器之间网络通信IPv4 |
socket.AF_INET6 | IPv6 |
socket类型 | 描述 |
socket.SOCK_STREAM | 流式socket , for TCP |
socket.SOCK_DGRAM | 数据报式socket , for UDP |
socket.SOCK_RAW | 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 |
socket.SOCK_SEQPACKET | 可靠的连续数据包服务 |
创建TCP Socket: | s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) |
创建UDP Socket: | s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) |
Socket常用函数 | 描述 |
sk.bind(address) | 将socket绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。 |
sk.listen(backlog) | 开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。 |
sk.connect(address) | 连接到address处的socket。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 |
sk.connect_ex(address) | 同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061 |
sk.close() | 关闭socket |
sk.recv(bufsize) | 接受socket的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。 |
sk.send(string) | 将string中的数据发送到连接的socket。 |
sk.sendall(string) | 将string中的数据发送到连接的socket,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。 |
sk.settimeout(timeout) | 设置socket操作的超时期,timeout是一个浮点数,单位是秒。超时期应该在刚创建socket时设置. |
sk.accept() | 接受连接并返回(conn,address),其中conn是新的socket对象,可以用来接收和发送数据。address是连接客户端的地址。 |
编程思路
服务端:
1 创建socket,因为socket是特殊的文件(需要close),所以通过with
as来自动释放文件资源
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as sk:
2 绑定套接字到本地IP与端口,开始监听连接
s.bind((HOST,PORT))
s.listen()
3接受客户端的连接请求,
conn, addr = s.accept()
4 通过accept创建的新socket对象接收数据并发送给对方数据
为了保证能收到所有发送来的信息,通过while循环来一直接受数据,直到数据为空
服务器端代码
import socket
HOST = '127.0.0.1'
PORT = 33333
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
s.bind((HOST,PORT))
s.listen()
conn, addr = s.accept()
with conn:
print('conn by',addr)
while True:
data = conn.recv(1024)
if not data:
break
print(data)
conn.sendall(b'hello,client')
客户端代码
import socket
HOST = '127.0.0.1'
PORT = 33333
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
s.connect((HOST,PORT))
s.sendall(b'hello,friend')
data = s.recv(1024)
print('rece',repr(data))
服务器
问题1:首先服务器能同时连接多个客户端
Socket.listen()后通过while循环
sk.accept()不断接收新的连接并创建socket
问题2:服务器要连接客户端后能一直接受数据,直到接受到空数据
多个连接之间肯定是独立的,并行的
所以通过线程的方式来实现
问题3:服务器要指定客户端端口和地址后进行回复信息
通过while 循环来一直接收input数据来指定发送
但是上面为了处理多个客户端时,也用了while
所以将连接多个客户端写入函数通过线程来实现,使之并行
服务器代码
from socket import *
import threading
def text_recv(CliSock,addr):
while True:
data = CliSock.recv(1024)
print(addr,"send to you:",data)
if not data:
break
def socket_up():
SerSock = socket(AF_INET, SOCK_STREAM)
SerSock.bind(('127.0.0.1',58787))
SerSock.listen()
while True:
CliSock, addr = SerSock.accept()
print('connnecting from:', addr)
Host[str(addr[1])] = CliSock
t = threading.Thread(target=text_recv,args=(CliSock,addr))
t.start()
if __name__ == '__main__':
Host = {}
t = threading.Thread(target=socket_up)
t.start()
while True:
try:
target_info = input('please input your target : msg,port >\n ').split(',')
msg = target_info[0]
port = target_info[1]
conn = Host[port]
conn.sendall(msg.encode())
except Exception as e:
print(e)
客户端
同服务端问题3一致
客户端代码
import threading
from socket import *
def text_recv(SerSock):
while True:
data = SerSock.recv(1024)
print("send to you:",data)
if not data:
break
if __name__ == '__main__':
CliSock = socket(AF_INET, SOCK_STREAM)
CliSock.connect(('127.0.0.1',58787))
t = threading.Thread(target=text_recv, args=(CliSock,))
t.start()
while True:
data1 = input('please input want send > \n')
CliSock.send(data1.encode())
if not data1:
break
探测原理:当通过socket连接目标主机未开放端口时会传出异常
例:ConnectionRefusedError: [WinError 10061]
由于目标计算机积极拒绝,无法连接。
编程思路
1 通过线程创建socket,对目标主机的范围端口进行尝试连接
2.对于没有抛出异常的端口尝试接受数据了解端口信息
问题:有些端口开放但不能接收到信息,就会将进程卡在接受数据那
解决:通过给socket对象设置settimeout()属性来限制超时时间,当超过时间时抛出新的异常:即timeout
所以 except timeout 也是存活端口,但是端口信息没有获取到
3.将存活端口和端口信息加入到字典中,最后遍历输出
实现代码
import threading
from socket import *
def port_find(p):
try:
with socket(AF_INET, SOCK_STREAM) as sk:
sk.connect((HOST, p))
sk.settimeout(3)
alive[p] = sk.recv(1024).strip().decode()
print("port[{}] is alive\n".format(p), end='')
except timeout:
print("port[{}] is alive\n".format(p), end='')
alive[p] = 'this not have info'
except:
pass
if __name__ == '__main__':
HOST = '10.30.25.199'
alive = {}
threads = []
for p in range(1, 65535):
t = threading.Thread(target=port_find, args=(p,))
threads.append(t)
t.start()
for t1 in threads:
t1.join()
for k,v in alive.items():
print(k,'--->',v)
connect_ex探究使用
connect_ex相对于connect来说多了返回值,连接成功时返回 0
,连接失败时候返回编码,例如:10061
那么就可以通过返回值来对是否存活进行判断
但是还是需要接收那些没有数据的端口,所以设置settimeout,然后在进一步尝试执行,成功就记录收到的数据,失败就表示端口开放但是没有数据
实现代码
import threading
from socket import *
def port_find(p):
with socket(AF_INET, SOCK_STREAM) as sk:
re = sk.connect_ex((HOST, p))
sk.settimeout(3)
if re == 0:
print("port[{}] is alive\n".format(p), end='')
try:
alive[p] = sk.recv(1024).strip().decode()
except:
alive[p] = 'this not have info'
else:
print("port[{}] is dead\n".format(p), end='')
if __name__ == '__main__':
HOST = '10.30.25.199'
alive = {}
threads = []
for p in range(1, 65535):
t = threading.Thread(target=port_find, args=(p,))
threads.append(t)
t.start()
for t1 in threads:
t1.join()
for k,v in alive.items():
print(k,'--->',v)
探究:因为线程开启太多会损害性能
那么如何用两个线程去完成socket的端口探测?
这里用到了python的队列(queue)
编程思想
将扫描的端口一次性put加入到队列中
调用二个子线程开始并行
回调函数中while不断利用get从队列取出端口号进行扫描
就实现了两个线程的并行扫描,互不干涉
实现代码
from socket import *
from queue import Queue
import threading
def port_find():
while not port_que.empty():
p = port_que.get()
with socket(AF_INET, SOCK_STREAM) as sk:
re = sk.connect_ex((HOST, p))
sk.settimeout(3)
if re == 0:
print("port[{}] is alive\n".format(p), end='')
try:
alive[p] = sk.recv(1024).strip().decode()
except:
alive[p] = 'this not have info'
else:
print("port[{}] is dead\n".format(p), end='')
if __name__ == '__main__':
HOST = '192.168.84.130'
alive = {}
threads = []
port_que = Queue()
for i in range(30):
port_que.put(i)
t1 = threading.Thread(target=port_find)
t2 = threading.Thread(target=port_find)
threads.append(t1)
threads.append(t2)
t1.start()
t2.start()
for t in threads:
t.join()
for k, v in alive.items():
print(k, '--->', v)
抓包分析
失败流程:1.SYN-> 2.RST,ACK->
成功流程:1.SYN-> 2.SYN,ACK-> 3.ACK->
可知:Socket是通过tcp三次握手进行端口探测
scapy中TCP(flags=‘S’)可以实现第一次握手,发送SYN包
flags=‘SA’–SYN,ACK包,flags=’RA’—RST,ACK包
编程思路
先用scapy构造一个 flags 的值为 S 的报文,S 代表SYN,就是请求建立一个 TCP
连接,在接收到服务器返回的报文中,如果 flagw
上的值为SA(SYN,ACK),那么就代表服务器确定接收到发送的连接请求并同意建立连接,这时候客户端再回应一个AR(ACK,RST)报文,确定接收到服务器的响应,建立连接后又立刻断开。
实现代码
from scapy.all import *
from scapy.layers.inet import IP, TCP
def port_scan(port):
packet = IP(dst=ip)/TCP(dport=port,flags='S') # 构造一个 flags 的值为 S 的报文
result = sr1(packet,timeout=2,verbose=0)
if result.haslayer('TCP'):
if result['TCP'].flags == 'SA': # 判断目标主机是否返回 SYN+ACK
send = sr1(IP(dst=ip)/TCP(dport=port,flags='AR'),timeout=2,verbose=0) # 向目标主机发送 ACK+RST
print('[+] {} is open'.format(port))
elif result['TCP'].flags == 'RA':
pass
if __name__ == '__main__':
ip = '192.168.84.130'
threads = []
for p in range(1, 30):
t = threading.Thread(target=port_scan, args=(p,))
threads.append(t)
t.start()
for t in threads:
t.join()