Python中Socket程式設計
目錄
Python中Socket程式設計
一、Socket概述
socket通常也稱作"套接字",用於描述IP地址和埠,是一個通訊鏈的控制代碼,應用程式通常通過"套接字"向網路發出請求或者應答網路請求。
socket即是一種特殊的檔案,socket函式就是對其進行的操作(讀/寫IO、開啟、關閉)
socket模組是針對 伺服器端 和 客戶端Socket 進行【開啟】【讀寫】【關閉】
二、python中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是連線客戶端的地址。 |
三、socket之聊天室
簡單收發
程式設計思路
服務端:
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))
socket聊天室
伺服器
問題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之埠探測
探測原理:當通過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)
五、scapy之tcp埠探測
抓包分析
失敗流程: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()