Python Socket 網路程式設計
Socket 是程序間通訊的一種方式,它與其他程序間通訊的一個主要不同是:它能實現不同主機間的程序間通訊,我們網路上各種各樣的服務大多都是基於 Socket 來完成通訊的,例如我們每天瀏覽網頁、QQ 聊天、收發 email 等等。要解決網路上兩臺主機之間的程序通訊問題,首先要唯一標識該程序,在 TCP/IP 網路協議中,就是通過 (IP地址,協議,埠號) 三元組來標識程序的,解決了程序標識問題,就有了通訊的基礎了。
本文主要介紹使用 Python 進行 TCP Socket 網路程式設計,假設你已經具有初步的網路知識及 Python 基本語法知識。
TCP 是一種面向連線的傳輸層協議,TCP Socket 是基於一種 Client-Server 的程式設計模型,服務端監聽客戶端的連線請求,一旦建立連線即可以進行傳輸資料。那麼對 TCP Socket 程式設計的介紹也分為客戶端和服務端:
客戶端程式設計
建立 socket
首先要建立 socket,用 Python 中 socket 模組的函式 socket
就可以完成:
12345678 | #Socket client example in pythonimportsocket#for sockets#create an AF_INET, STREAM socket (TCP)s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)print'Socket Created' |
函式 socket.socket
建立一個 socket,返回該 socket 的描述符,將在後面相關函式中使用。該函式帶有兩個引數:
- Address Family:可以選擇
AF_INET
(用於 Internet 程序間通訊) 或者AF_UNIX
- Type:套接字型別,可以是
SOCKET_STREAM
(流式套接字,主要用於 TCP 協議)或者SOCKET_DGRAM
(資料報套接字,主要用於 UDP 協議)
注:由於本文主要概述一下 Python Socket 程式設計的過程,因此不會對相關函式引數、返回值進行詳細介紹,需要了解的可以檢視相關手冊
錯誤處理
如果建立 socket 函式失敗,會丟擲一個 socket.error
的異常,需要捕獲:
12345678910111213 | #handling errors in python socket programsimportsocket#for socketsimportsys#for exittry:#create an AF_INET, STREAM socket (TCP)s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)exceptsocket.error,msg:print'Failed to create socket. Error code: '+str(msg[0])+' , Error message : '+msg[1]sys.exit();print'Socket Created' |
那麼到目前為止已成功建立了 socket,接下來我們將用這個 socket 來連線某個伺服器,就連 www.google.com 吧。
連線伺服器
本文開始也提到了,socket 使用 (IP地址,協議,埠號) 來標識一個程序,那麼我們要想和伺服器進行通訊,就需要知道它的 IP地址以及埠號。
獲得遠端主機的 IP 地址
Python 提供了一個簡單的函式 socket.gethostbyname
來獲得遠端主機的 IP 地址:
123456789101112 | host='www.google.com'port=80try:remote_ip=socket.gethostbyname(host)exceptsocket.gaierror:#could not resolveprint'Hostname could not be resolved. Exiting'sys.exit()print'Ip address of '+host+' is '+remote_ip |
現在我們知道了伺服器的 IP 地址,就可以使用連線函式 connect
連線到該 IP 的某個特定的埠上了,下面例子連線到 80 埠上(是 HTTP 服務的預設埠):
1234 | #Connect to remote servers.connect((remote_ip,port))print'Socket Connected to '+host+' on ip '+remote_ip |
執行該程式:
Python1234 | $python client.pySocketcreatedIp of remote host www.google.com is173.194.38.145SocketConnected to www.google.com on ip173.194.38.145 |
傳送資料
上面說明連線到 www.google.com 已經成功了,接下面我們可以向伺服器傳送一些資料,例如傳送字串 GET / HTTP/1.1rnrn
,這是一個 HTTP 請求網頁內容的命令。
123456789101112 | #Send some data to remote servermessage="GET / HTTP/1.1rnrn"try:#Set the whole strings.sendall(message)exceptsocket.error:#Send failedprint'Send failed'sys.exit()print'Message send successfully' |
傳送完資料之後,客戶端還需要接受伺服器的響應。
接收資料
函式 recv
可以用來接收 socket 的資料:
1234 | #Now receive datareply=s.recv(4096)printreply |
一起執行的結果如下:
Python12345678910111213141516171819 | SocketcreatedIp of remote host www.google.com is173.194.38.145SocketConnected to www.google.com on ip173.194.38.145Message send successfullyHTTP/1.1302FoundCache-Control:privateContent-Type:text/html;charset=UTF-8Location:http://www.google.com.sg/?gfe_rd=cr&ei=PlqJVLCREovW8gfF0oG4CQContent-Length:262Date:Thu,11Dec201408:47:58GMTServer:GFE/2.0Alternate-Protocol:80:quic,p=0.02<HTML><HEAD><meta http-equiv="content-type"content="text/html;charset=utf-8"><TITLE>302Moved</TITLE></HEAD><BODY><H1>302Moved</H1>The document has moved<AHREF="http://www.google.com.sg/?gfe_rd=cr&ei=PlqJVLCREovW8gfF0oG4CQ">here</A>.</BODY></HTML> |
關閉 socket
當我們不想再次請求伺服器資料時,可以將該 socket 關閉,結束這次通訊:
Python1 | s.close() |
小結
上面我們學到了如何:
- 建立 socket
- 連線到遠端伺服器
- 傳送資料
- 接收資料
- 關閉 socket
當我們開啟 www.google.com 時,瀏覽器所做的就是這些,知道這些是非常有意義的。在 socket 中具有這種行為特徵的被稱為CLIENT,客戶端主要是連線遠端系統獲取資料。
socket 中另一種行為稱為SERVER,伺服器使用 socket 來接收連線以及提供資料,和客戶端正好相反。所以 www.google.com 是伺服器,你的瀏覽器是客戶端,或者更準確地說,www.google.com 是 HTTP 伺服器,你的瀏覽器是 HTTP 客戶端。
那麼上面介紹了客戶端的程式設計,現在輪到伺服器端如果使用 socket 了。
伺服器端程式設計
伺服器端主要做以下工作:
- 開啟 socket
- 繫結到特定的地址以及埠上
- 監聽連線
- 建立連線
- 接收/傳送資料
上面已經介紹瞭如何建立 socket 了,下面一步是繫結。
繫結 socket
函式 bind
可以用來將 socket 繫結到特定的地址和埠上,它需要一個 sockaddr_in
結構作為引數:
12345678910111213141516 | importsocketimportsysHOST=''# Symbolic name meaning all available interfacesPORT=8888# Arbitrary non-privileged ports=socket.socket(socket.AF_INET,socket.SOCK_STREAM)print'Socket created'try:s.bind((HOST,PORT))exceptsocket.error,msg:print'Bind failed. Error Code : '+str(msg[0])+' Message '+msg[1]sys.exit()print'Socket bind complete' |
繫結完成之後,接下來就是監聽連線了。
監聽連線
函式 listen
可以將 socket 置於監聽模式:
12 | s.listen(10)print'Socket now listening' |
該函式帶有一個引數稱為 backlog,用來控制連線的個數。如果設為 10,那麼有 10 個連線正在等待處理,此時第 11 個請求過來時將會被拒絕。
接收連線
當有客戶端向伺服器傳送連線請求時,伺服器會接收連線:
Python12345 | #wait to accept a connection - blocking callconn,addr=s.accept()#display client informationprint'Connected with '+addr[0]+':'+str(addr[1]) |
執行該程式的,輸出結果如下:
Python1234 | $python server.pySocketcreatedSocketbind completeSocketnow listening |
此時,該程式在 8888 埠上等待請求的到來。不要關掉這個程式,讓它一直執行,現在客戶端可以通過該埠連線到 socket。我們用 telnet 客戶端來測試,開啟一個終端,輸入 telnet localhost 8888
:
12345 | $telnet localhost8888Trying127.0.0.1...Connected to localhost.Escape character is'^]'.Connection closed by foreign host. |
這時服務端輸出會顯示:
Python