Python Socket編程入門教程
這是用來快速學(xué)習(xí) Python Socket 套接字編程的指南和教程。Python 的 Socket 編程跟 C 語言很像。
Python 官方關(guān)于 Socket 的函數(shù)請(qǐng)看 http://docs.python.org/library/socket.html
基本上,Socket 是任何一種計(jì)算機(jī)網(wǎng)絡(luò)通訊中最基礎(chǔ)的內(nèi)容。例如當(dāng)你在瀏覽器地址欄中輸入 www.dbjr.com.cn 時(shí),你會(huì)打開一個(gè)套接字,然后連接到 www.dbjr.com.cn 并讀取響應(yīng)的頁面然后然后顯示出來。而其他一些聊天客戶端如 gtalk 和 skype 也是類似。任何網(wǎng)絡(luò)通訊都是通過 Socket 來完成的。
寫在開頭
本教程假設(shè)你已經(jīng)有一些基本的 Python 編程的知識(shí)。
讓我們開始 Socket 編程吧。
創(chuàng)建 Socket
首先要做的就是創(chuàng)建一個(gè) Socket,socket 的 socket 函數(shù)可以實(shí)現(xiàn),代碼如下:
#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'
函數(shù) socket.socket 創(chuàng)建了一個(gè) Socket,并返回 Socket 的描述符可用于其他 Socket 相關(guān)的函數(shù)。
上述代碼使用了下面兩個(gè)屬性來創(chuàng)建 Socket:
地址簇 : AF_INET (IPv4)
類型: SOCK_STREAM (使用 TCP 傳輸控制協(xié)議)
錯(cuò)誤處理
如果 socket 函數(shù)失敗了,python 將拋出一個(gè)名為 socket.error 的異常,這個(gè)異常必須予以處理:
#handling errors in python socket programs
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'
好了,假設(shè)你已經(jīng)成功創(chuàng)建了 Socket,下一步該做什么呢?接下來我們將使用這個(gè) Socket 來連接到服務(wù)器。
注意:
與 SOCK_STREAM 相對(duì)應(yīng)的其他類型是 SOCK_DGRAM 用于 UDP 通訊協(xié)議,UDP 通訊是非連接 Socket,在這篇文章中我們只討論 SOCK_STREAM ,或者叫 TCP 。
連接到服務(wù)器
連接到服務(wù)器需要服務(wù)器地址和端口號(hào),這里使用的是 www.dbjr.com.cn 和 80 端口。
首先獲取遠(yuǎn)程主機(jī)的 IP 地址
連接到遠(yuǎn)程主機(jī)之前,我們需要知道它的 IP 地址,在 Python 中,獲取 IP 地址是很簡(jiǎ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.dbjr.com.cn'
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
我們已經(jīng)有 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.dbjr.com.cn'
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
現(xiàn)在運(yùn)行程序
$ python client.py
Socket Created
Ip address of www.dbjr.com.cn is 61.145.122.155
Socket Connected to www.dbjr.com.cn on ip 61.145.122.155
這段程序創(chuàng)建了一個(gè) Socket 并進(jìn)行連接,試試使用其他一些不存在的端口(如81)會(huì)是怎樣?這個(gè)邏輯相當(dāng)于構(gòu)建了一個(gè)端口掃描器。
已經(jīng)連接上了,接下來就是往服務(wù)器上發(fā)送數(shù)據(jù)。
友情提示
使用 SOCK_STREAM/TCP 套接字才有“連接”的概念。連接意味著可靠的數(shù)據(jù)流通訊機(jī)制,可以同時(shí)有多個(gè)數(shù)據(jù)流??梢韵胂蟪梢粋€(gè)數(shù)據(jù)互不干擾的管道。另外一個(gè)重要的提示是:數(shù)據(jù)包的發(fā)送和接收是有順序的。
其他一些 Socket 如 UDP、ICMP 和 ARP 沒有“連接”的概念,它們是無連接通訊,意味著你可從任何人或者給任何人發(fā)送和接收數(shù)據(jù)包。
發(fā)送數(shù)據(jù)
sendall 函數(shù)用于簡(jiǎn)單的發(fā)送數(shù)據(jù),我們來向 oschina 發(fā)送一些數(shù)據(jù):
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.dbjr.com.cn'
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
#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'
上述例子中,首先連接到目標(biāo)服務(wù)器,然后發(fā)送字符串?dāng)?shù)據(jù) "GET / HTTP/1.1\r\n\r\n" ,這是一個(gè) HTTP 協(xié)議的命令,用來獲取網(wǎng)站首頁的內(nèi)容。
接下來需要讀取服務(wù)器返回的數(shù)據(jù)。
接收數(shù)據(jù)
recv 函數(shù)用于從 socket 接收數(shù)據(jù):
#Socket client example in python
import socket #for sockets
import sys #for exit
#create an INET, STREAMing socket
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
print 'Failed to create socket'
sys.exit()
print 'Socket Created'
host = 'jb51.net';
port = 80;
try:
remote_ip = socket.gethostbyname( host )
except socket.gaierror:
#could not resolve
print 'Hostname could not be resolved. Exiting'
sys.exit()
#Connect to remote server
s.connect((remote_ip , port))
print 'Socket Connected to ' + host + ' on ip ' + remote_ip
#Send some data to remote server
message = "GET / HTTP/1.1\r\nHost: jb51.net\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'
#Now receive data
reply = s.recv(4096)
print reply
下面是上述程序執(zhí)行的結(jié)果:
$ python client.py
Socket Created
Ip address of jb51.net is 61.145.122.
Socket Connected to jb51.net on ip 61.145.122.155
Message send successfully
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Wed, 24 Oct 2012 13:26:46 GMT
Content-Type: text/html
Content-Length: 178
Connection: keep-alive
Keep-Alive: timeout=20
Location: http://www.dbjr.com.cn/
jb51.net 回應(yīng)了我們所請(qǐng)求的 URL 的內(nèi)容,很簡(jiǎn)單。數(shù)據(jù)接收完了,可以關(guān)閉 Socket 了。
關(guān)閉 socket
close 函數(shù)用于關(guān)閉 Socket:
這就是了。
讓我們回顧一下
上述的示例中我們學(xué)到了如何:
1. 創(chuàng)建 Socket
2. 連接到遠(yuǎn)程服務(wù)器
3. 發(fā)送數(shù)據(jù)
4. 接收回應(yīng)
當(dāng)你用瀏覽器打開 www.dbjr.com.cn 時(shí),其過程也是一樣。包含兩種類型,分別是客戶端和服務(wù)器,客戶端連接到服務(wù)器并讀取數(shù)據(jù),服務(wù)器使用 Socket 接收進(jìn)入的連接并提供數(shù)據(jù)。因此在這里 www.dbjr.com.cn 是服務(wù)器端,而你的瀏覽器是客戶端。
接下來我們開始在服務(wù)器端做點(diǎn)編碼。
服務(wù)器端編程
服務(wù)器端編程主要包括下面幾步:
1. 打開 socket
2. 綁定到一個(gè)地址和端口
3. 偵聽進(jìn)來的連接
4. 接受連接
5. 讀寫數(shù)據(jù)
我們已經(jīng)學(xué)習(xí)過如何打開 Socket 了,下面是綁定到指定的地址和端口上。
綁定 Socket
bind 函數(shù)用于將 Socket 綁定到一個(gè)特定的地址和端口,它需要一個(gè)類似 connect 函數(shù)所需的 sockaddr_in 結(jié)構(gòu)體。
示例代碼:
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'
綁定完成后,就需要讓 Socket 開始偵聽連接。很顯然,你不能將兩個(gè)不同的 Socket 綁定到同一個(gè)端口之上。
連接偵聽
綁定 Socket 之后就可以開始偵聽連接,我們需要將 Socket 變成偵聽模式。socket 的 listen 函數(shù)用于實(shí)現(xiàn)偵聽模式:
s.listen(10)
print 'Socket now listening'
listen 函數(shù)所需的參數(shù)成為 backlog,用來控制程序忙時(shí)可保持等待狀態(tài)的連接數(shù)。這里我們傳遞的是 10,意味著如果已經(jīng)有 10 個(gè)連接在等待處理,那么第 11 個(gè)連接將會(huì)被拒絕。當(dāng)檢查了 socket_accept 后這個(gè)會(huì)更加清晰。
接受連接
示例代碼:
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()
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])
輸出
運(yùn)行該程序?qū)?huì)顯示:
Socket created
Socket bind complete
Socket now listening
現(xiàn)在這個(gè)程序開始等待連接進(jìn)入,端口是 8888,請(qǐng)不要關(guān)閉這個(gè)程序,我們來通過 telnet 程序來進(jìn)行測(cè)試。
打開命令行窗口并輸入:
It will immediately show
$ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
而服務(wù)器端窗口顯示的是:
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954
我們可看到客戶端已經(jīng)成功連接到服務(wù)器。
上面例子我們接收到連接并立即關(guān)閉,這樣的程序沒什么實(shí)際的價(jià)值,連接建立后一般會(huì)有大量的事情需要處理,因此讓我們來給客戶端做出點(diǎn)回應(yīng)吧。
sendall 函數(shù)可通過 Socket 給客戶端發(fā)送數(shù)據(jù):
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()
繼續(xù)運(yùn)行上述代碼,然后打開另外一個(gè)命令行窗口輸入下面命令:
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.
可看到客戶端接收到來自服務(wù)器端的回應(yīng)內(nèi)容。
上面的例子還是一樣,服務(wù)器端回應(yīng)后就立即退出了。而一些真正的服務(wù)器像 www.dbjr.com.cn 是一直在運(yùn)行的,時(shí)刻接受連接請(qǐng)求。
也就是說服務(wù)器端應(yīng)該一直處于運(yùn)行狀態(tài),否則就不能成為“服務(wù)”,因此我們要讓服務(wù)器端一直運(yùn)行,最簡(jiǎn)單的方法就是把 accept 方法放在一個(gè)循環(huán)內(nèi)。
一直在運(yùn)行的服務(wù)器
對(duì)上述代碼稍作改動(dòng):
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'
#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()
很簡(jiǎn)單只是加多一個(gè) while 1 語句而已。
繼續(xù)運(yùn)行服務(wù)器,然后打開另外三個(gè)命令行窗口。每個(gè)窗口都使用 telnet 命令連接到服務(wù)器:
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.
服務(wù)器所在的終端窗口顯示的是:
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239
你看服務(wù)器再也不退出了,好吧,用 Ctrl+C 關(guān)閉服務(wù)器,所有的 telnet 終端將會(huì)顯示 "Connection closed by foreign host."
已經(jīng)很不錯(cuò)了,但是這樣的通訊效率太低了,服務(wù)器程序使用循環(huán)來接受連接并發(fā)送回應(yīng),這相當(dāng)于是一次最多處理一個(gè)客戶端的請(qǐng)求,而我們要求服務(wù)器可同時(shí)處理多個(gè)請(qǐng)求。
處理多個(gè)連接
為了處理多個(gè)連接,我們需要一個(gè)獨(dú)立的處理代碼在主服務(wù)器接收到連接時(shí)運(yùn)行。一種方法是使用線程,服務(wù)器接收到連接然后創(chuàng)建一個(gè)線程來處理連接收發(fā)數(shù)據(jù),然后主服務(wù)器程序返回去接收新的連接。
下面是我們使用線程來處理連接請(qǐng)求:
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()
運(yùn)行上述服務(wù)端程序,然后像之前一樣打開三個(gè)終端窗口并執(zhí)行 telent 命令:
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv
服務(wù)器端所在終端窗口輸出信息如下:
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731
線程接管了連接并返回相應(yīng)數(shù)據(jù)給客戶端。
這便是我們所要介紹的服務(wù)器端編程。
結(jié)論
到這里為止,你已經(jīng)學(xué)習(xí)了 Python 的 Socket 基本編程,你可自己動(dòng)手編寫一些例子來強(qiáng)化這些知識(shí)。
你可能會(huì)遇見一些問題:Bind failed. Error Code : 98 Message Address already in use,碰見這種問題只需要簡(jiǎn)單更改服務(wù)器端口即可。
相關(guān)文章
如何使用Flask-Migrate拓展數(shù)據(jù)庫表結(jié)構(gòu)
這篇文章主要介紹了如何使用Flask-Migrate拓展數(shù)據(jù)庫表結(jié)構(gòu),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07Python?Nuitka打包的實(shí)現(xiàn)步驟
在Python應(yīng)用程序開發(fā)中,打包是將代碼和依賴項(xiàng)組合成可執(zhí)行文件或庫的關(guān)鍵步驟之一,本文主要介紹了Python?Nuitka打包的實(shí)現(xiàn)步驟,感興趣的可以了解一下2023-12-12

python 使用遞歸的方式實(shí)現(xiàn)語義圖片分割功能

python list是否包含另一個(gè)list所有元素的實(shí)例

Python數(shù)據(jù)結(jié)構(gòu)列表

解決python 3 urllib 沒有 urlencode 屬性的問題