Python黑帽编程2.8 套接字编程
Python黑帽編程2.8 套接字編程
套接字編程在本系列教程中地位并不是很突出,但是我們觀察網(wǎng)絡應用,絕大多數(shù)都是基于Socket來做的,哪怕是絕大多數(shù)的木馬程序也是如此。官方關于socket編程的文檔地址為https://docs.python.org/2/library/socket.html,我承認我看起來都很費勁。
套接字為BSD UNIX系統(tǒng)核心的一部分,而且他們也被許多其他類似UNIX的操作系統(tǒng)包括Linux所采納。許多非BSD UNIX系統(tǒng)(如ms-dos,windows,os/2,mac os及大部分主機環(huán)境)都以庫形式提供對套接字的支持。
三種最流行的套接字類型是:stream,datagram和raw。stream和datagram套接字可以直接與TCP協(xié)議進行接口,而raw套接字則接口到IP協(xié)議。但套接字并不限于TCP/IP。
Python 提供了兩個基本的套接字模塊:
l? 第一個是 socket,它提供了標準的 BSD Sockets API。
l? 第二個是 socketServer, 它提供了服務器中心類,可以簡化網(wǎng)絡服務器的開發(fā)。
本節(jié)課程主要關注socket模塊的使用。
2.8.1 socket函數(shù)
socket函數(shù)用來創(chuàng)建socket對象,使用前需要導入socket模塊。我們先看一下socket函數(shù)的使用方式:
socket.socket([family[, type[, proto]]])
我們看到socket函數(shù)接收三個參數(shù),下面分別介紹:
family——套接字對象使用的地址族,可選值:AF_INET——IPv4地址族,AF_INET6——IPv6地址族,AF_UNIX——針對類UNIX系統(tǒng)的套接字,如圖2所示。
圖2
type可選參數(shù)如圖3所示。
圖3
proto是協(xié)議數(shù),默認是0,通常不需要關心該參數(shù)。
熟悉了三個參數(shù)的含義,創(chuàng)建一個TCP或者UDP的socket對象就很容易了。
創(chuàng)建TCP Socket:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
創(chuàng)建UDP Socket:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
關于socket對象的各種方法的使用說明,大家可以去查官方文檔,我這里就不貼了,下面我們邊實踐,邊學習。
2.8.2 創(chuàng)建socket
我們首先來創(chuàng)建一個TCP的socket對象,代碼如下:
#Socket client example in python
import socket?? #for sockets
?
#create an AF_INET, STREAM socket (TCP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket Created'
創(chuàng)建一個socket對象很簡單,第一步導入socket模塊,第二步使用socket.socket方法創(chuàng)建一個socket對象,參數(shù)在2.8.1節(jié)已經(jīng)做了說明。運行結(jié)果如下:
圖4
2.8.3 客戶端編程
創(chuàng)建socket之后,現(xiàn)在我們看看如何創(chuàng)建client端,連接一個服務端,這里我們選擇知乎(www.zhihu.com)。socket類有個gethostbyname方法,可以用來把域名轉(zhuǎn)換為ip地址,例如:
import socket?? #for sockets
import sys? #for exit
?
try:
??? #create an AF_INET, STREAM socket (TCP)
??? s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
??? print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
??? sys.exit();
?
print 'Socket Created'
?
host = 'www.zhihu.com'
?
try:
??? remote_ip = socket.gethostbyname( host )
except socket.gaierror:
??? #could not resolve
??? print 'Hostname could not be resolved. Exiting'
??? sys.exit()
????
print 'Ip address of ' + host + ' is ' + remote_ip
上面的代碼,我們首先導入了sys模塊,用于在出現(xiàn)異常的情況下調(diào)用sys.exit()退出。同時,完善了異常捕獲,創(chuàng)建socket可能會引發(fā)socket.error異常,gethostbyname方法可能會引發(fā)socket.gaierror異常,我們在編程過程中要注意這些錯誤。
運行結(jié)果如下:
圖5
現(xiàn)在我們獲得了ip,端口我們使用80,現(xiàn)在可以連接服務器了。
import socket?? #for sockets
import sys? #for exit
?
try:
??? #create an AF_INET, STREAM socket (TCP)
??? s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
??? print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
??? sys.exit();
?
print 'Socket Created'
?
host = 'www.zhihu.com'
port = 80
?
try:
??? remote_ip = socket.gethostbyname( host )
?
except socket.gaierror:
??? #could not resolve
??? print 'Hostname could not be resolved. Exiting'
??? sys.exit()
????
print 'Ip address of ' + host + ' is ' + remote_ip
?
#Connect to remote server
s.connect((remote_ip , port))
?
print 'Socket Connected to ' + host + ' on ip ' + remote_ip
注意上面加粗的代碼,使用socket.connect方法連接服務端,參數(shù)為ip地址和端口。
連接上服務器之后,現(xiàn)在可以向服務器發(fā)送信息了。
我們在上面代碼的基礎上,添加下面的代碼:
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"
?
try :
??? #Set the whole string
??? s.sendall(message)
except socket.error:
??? #Send failed
??? print 'Send failed'
??? sys.exit()
?
print 'Message send successfully'
首先定義了要發(fā)送的信息,準備發(fā)送一個http協(xié)議的get請求。隨后我們使用socket.sendall方法發(fā)送信息。信息發(fā)送之后,如果沒有異常產(chǎn)生,應該會得到知乎服務器的響應內(nèi)容。那么如何接收信息呢?繼續(xù)添加如下代碼:
#Now receive data
reply = s.recv(4096)
?
print reply
我們使用socket.recv方法來接收數(shù)據(jù),參數(shù)是接收的數(shù)據(jù)長度。現(xiàn)在我們來看一下運行結(jié)果:
圖6
最后,接收完數(shù)據(jù)之后,不要忘了關閉socket:
s.close()
現(xiàn)在我們來總結(jié)下客戶端socket編程的基本流程:
1.????? 創(chuàng)建套接字
2.????? 連接服務端
3.????? 發(fā)送數(shù)據(jù)
4.????? 接收數(shù)據(jù)
5.????? 關閉連接
下面看一下,一個基本的服務端,要做哪些工作。
2.8.4 服務端編程
和客戶端編程類似,我們首先創(chuàng)建一個socket對象。
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
隨后,我們要把socket綁定到本機的IP和端口上。
try:
??? s.bind((HOST, PORT))
except socket.error , msg:
??? print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
綁定ip和端口使用的是bind方法。
服務端不會主動連接其他主機,而是等待客戶端連接,這需要進入監(jiān)聽狀態(tài)。
s.listen(10)
print 'Socket now listening'
listen方法接收一個參數(shù),用來指定可以同時掛起的連接數(shù)。
監(jiān)聽模式之后,如果有客戶端連接進來,如何接收連接呢?需要使用accept方法。
#wait to accept a connection - blocking call
conn, addr =s.accept()
accept方法會返回一個代表當前鏈接的connection對象和客戶端的ip地址。接下來就可以使用conn對象來接收和發(fā)送數(shù)據(jù)了,關閉連接也使用conn對象。
data = conn.recv(1024)
conn.sendall(data)
?
conn.close()
下面我們看一下服務端的完整代碼:
import socket
import sys
?
HOST = ''?? # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
?
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
?
try:
??? s.bind((HOST, PORT))
except socket.error , msg:
??? print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
??? sys.exit()
????
print 'Socket bind complete'
?
s.listen(10)
print 'Socket now listening'
#wait to accept a connection - blocking call
conn, addr = s.accept()
print 'Connected with ' + addr[0] + ':' + str(addr[1])
?
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
?
conn.close()
s.close()
將代碼保存為socket_server.py,從終端啟動,如下圖:
圖7
然后再啟動一個終端,使用telnet連接。
圖8
此時可以看到服務端打印了連接的客戶端,接下來咋telnet終端輸入字符,回車發(fā)送過去。結(jié)果如圖7。
圖9
從圖7可以看到telnet終端顯示了服務端返回的信息。
2.8.5 改進服務端
作為服務端,只能接收一次連接,和客戶端進行有限次數(shù)的交互是不行的,要一直能接收來自客戶端的連接,下面我們改進服務端。
import socket
import sys
?
HOST = ''?? # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port
?
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
?
try:
??? s.bind((HOST, PORT))
except socket.error , msg:
??? print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
??? sys.exit()
????
print 'Socket bind complete'
?
s.listen(10)
print 'Socket now listening'
?
#now keep talking with the client
while 1:
??? #wait to accept a connection - blocking call
??? conn, addr = s.accept()
??? print 'Connected with ' + addr[0] + ':' + str(addr[1])
????
??? data = conn.recv(1024)
??? reply = 'OK...' + data
??? if not data:
??????? break
????
??? conn.sendall(reply)
?
conn.close()
s.close()
修改后的代碼只是將接收客戶端連接的代碼放到一個while true循環(huán)里,這樣就可以不停的接收連接。保存上面的代碼,在終端打開等待連接,然后啟動三個終端使用telnet進行連接,結(jié)果如下:
圖10
這樣改起來,可以不停的接收新的連接了,但是每次只能接收一個連接,有新連接的時候,舊連接就會斷掉,還是不夠完美。想要分別對待每一個新入的連接,需要多線程登場了。
import socket
import sys
from thread import *
?
HOST = ''?? # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
?
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
?
#Bind socket to local host and port
try:
??? s.bind((HOST, PORT))
except socket.error , msg:
??? print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
??? sys.exit()
????
print 'Socket bind complete'
?
#Start listening on socket
s.listen(10)
print 'Socket now listening'
?
#Function for handling connections. This will be used to create threads
def clientthread(conn):
??? #Sending message to connected client
??? conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string
????
??? #infinite loop so that function do not terminate and thread do not end.
??? while True:
????????
??????? #Receiving from client
??????? data = conn.recv(1024)
??????? reply = 'OK...' + data
??????? if not data:
??????????? break
????
??????? conn.sendall(reply)
????
??? #came out of loop
??? conn.close()
?
#now keep talking with the client
while 1:
??? #wait to accept a connection - blocking call
??? conn, addr = s.accept()
??? print 'Connected with ' + addr[0] + ':' + str(addr[1])
????
??? #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
??? start_new_thread(clientthread ,(conn,))
?
s.close()
使用多線程,首先要導入thread模塊。隨后我們定義了一個名為clientthread的方法,該方法接收每個客戶端連接對象,在內(nèi)部啟動一個新的while循環(huán),這樣可以保證客戶端和服務端可以一直連接,反復通信。在外層,仍然保留while循環(huán),每次有客戶端連接的時候,生成一個連接對象conn,然后使用thread模塊的start_new_thread方法啟動一個新的線程,新線程調(diào)用clientthread方法,傳入剛接收的客戶端連接對象。
我們將上面的代碼保存為socket_thread.py,然后從終端啟動,在啟動多個終端使用telnet進行連接,連接之后發(fā)消息測試,結(jié)果如下:
圖11
2.8.6 小結(jié)
本節(jié)我們學習了Python中socket的基本使用方法,并演示了基于TCP協(xié)議的客戶端和服務端編程方法。這里大家需要額外補充的知識點是多線程的使用,我們做網(wǎng)絡掃描、爬蟲等程序,都是離不開多線程應用的。
?
下一節(jié)是第二章的最后一節(jié),和大家聊一聊面向?qū)ο缶幊趟枷爰?/span>Python的面向?qū)ο缶幊谭椒ā?/span>
第2.8節(jié)《面向?qū)ο缶幊趟枷爰?/span>Python的面向?qū)ο缶幊谭椒?/span>》已經(jīng)在微信訂閱號搶先發(fā)布,心急的同學進入訂閱號(二維碼在下方),從菜單“網(wǎng)絡安全”—>”Python黑帽編程”進入即可。
?
查看完整系列教程,請關注我的微信訂閱號(xuanhun521,下方二維碼),回復“python”。問題討論請加qq群:Hacking (1群):303242737?? Hacking (2群):147098303。
?
?
總結(jié)
以上是生活随笔為你收集整理的Python黑帽编程2.8 套接字编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle 之索引,同义词 ,关键词,
- 下一篇: 通过 getResources 找不到j