TCP 是一種面向連接的傳輸層協議,TCP Socket 是基於一種 Client-Server 的編程模型,服務端監聽客戶端的連接請求,一旦建立連接即可以進行傳輸數據。那麼對 TCP Socket 編程的介紹也分為客戶端和服務端:
客戶端編程創建 socket首先要創建 socket,用 Python 中 socket 模塊的函數 socket 就可以完成:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print 'Socket Created'
函數 socket.socket 創建一個 socket,返回該 socket 的描述符,將在後面相關函數中使用。該函數帶有兩個參數:
註:由於本文主要概述一下 Python Socket 編程的過程,因此不會對相關函數參數、返回值進行詳細介紹,需要了解的可以查看相關手冊
錯誤處理
如果創建 socket 函數失敗,會拋出一個 socket.error 的異常,需要捕獲:
import socket import sys try: 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'
那麼到目前為止已成功創建了 socket,接下來我們將用這個 socket 來連接某個伺服器,就連 www.google.com 吧。
連接伺服器本文開始也提到了,socket 使用 (IP位址,協議,埠號) 來標識一個進程,那麼我們要想和伺服器進行通信,就需要知道它的 IP位址以及埠號。
獲得遠程主機的 IP 地址
Python 提供了一個簡單的函數 socket.gethostbyname 來獲得遠程主機的 IP 地址:
host = 'www.google.com'port = 80 try: remote_ip = socket.gethostbyname( host ) except socket.gaierror: print 'Hostname could not be resolved. Exiting' sys.exit() print 'Ip address of ' + host + ' is ' + remote_ip
現在我們知道了伺服器的 IP 地址,就可以使用連接函數 connect 連接到該 IP 的某個特定的埠上了,下面例子連接到 80 埠上(是 HTTP 服務的默認埠):
s.connect((remote_ip , port)) print 'Socket Connected to ' + host + ' on ip ' + remote_ip
運行該程序:
$ pythonclient.pySocket createdIpofremotehostwww.google.comis 173.194.38.145Socket Connectedtowww.google.comonip 173.194.38.145
發送數據上面說明連接到 www.google.com 已經成功了,接下面我們可以向伺服器發送一些數據,例如發送字符串 GET / HTTP/1.1rnrn ,這是一個 HTTP 請求網頁內容的命令。
message = "GET / HTTP/1.1rnrn" try : s.sendall(message)except socket.error: print 'Send failed' sys.exit() print 'Message send successfully'
發送完數據之後,客戶端還需要接受伺服器的響應。
接收數據函數 recv 可以用來接收 socket 的數據:
reply = s.recv(4096) print reply
一起運行的結果如下:
Socket createdIpofremotehostwww.google.comis 173.194.38.145Socket Connectedtowww.google.comonip 173.194.38.145MessagesendsuccessfullyHTTP/1.1 302 FoundCache-Control: privateContent-Type: text/html; charset=UTF-8Location: http://www.google.com.sg/?gfe_rd=cr&ei=PlqJVLCREovW8gfF0oG4CQContent-Length: 262Date: Thu, 11 Dec 2014 08:47:58 GMTServer: GFE/2.0Alternate-Protocol: 80:quic,p=0.02 <HTML><HEAD><metahttp-equiv="content-type" content="text/html;charset=utf-8"><TITLE>302 Moved</TITLE></HEAD><BODY><H1>302 Moved</H1>Thedocumenthasmoved<A HREF="http://www.google.com.sg/?gfe_rd=cr&ei=PlqJVLCREovW8gfF0oG4CQ">here</A>.</BODY></HTML>
關閉 socket當我們不想再次請求伺服器數據時,可以將該 socket 關閉,結束這次通信:
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 結構作為參數:
import socketimport sys HOST = '' PORT = 8888 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'
綁定完成之後,接下來就是監聽連接了。
監聽連接函數 listen 可以將 socket 置於監聽模式:
s.listen(10)print 'Socket now listening'
該函數帶有一個參數稱為 backlog ,用來控制連接的個數。如果設為 10,那麼有 10 個連接正在等待處理,此時第 11 個請求過來時將會被拒絕。
接收連接當有客戶端向伺服器發送連接請求時,伺服器會接收連接:
conn, addr = s.accept() print 'Connected with ' + addr[0] + ':' + str(addr[1])
運行該程序的,輸出結果如下:
$ pythonserver.pySocket createdSocket bindcompleteSocket nowlistening
此時,該程序在 8888 埠上等待請求的到來。不要關掉這個程序,讓它一直運行,現在客戶端可以通過該埠連接到 socket。我們用 telnet 客戶端來測試,打開一個終端,輸入 telnet localhost 8888 :
$ telnetlocalhost 8888Trying 127.0.0.1...Connectedtolocalhost.Escapecharacteris '^]'.Connectionclosedbyforeignhost.
這時服務端輸出會顯示:
$ pythonserver.pySocket createdSocket bindcompleteSocket nowlisteningConnectedwith 127.0.0.1:59954
我們觀察到客戶端已經連接上伺服器了。在建立連接之後,我們可以用來與客戶端進行通信。下面例子演示的是,伺服器建立連接之後,接收客戶端發送來的數據,並立即將數據發送回去,下面是完整的服務端程序:
import socketimport sys HOST = '' PORT = 8888 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' conn, addr = s.accept() print 'Connected with ' + addr[0] + ':' + str(addr[1]) data = conn.recv(1024)conn.sendall(data) conn.close()s.close()
在一個終端中運行這個程序,打開另一個終端,使用 telnet 連接伺服器,隨便輸入字符串,你會看到:
$ telnetlocalhost 8888Trying 127.0.0.1...Connectedtolocalhost.Escapecharacteris '^]'.happyhappyConnectionclosedbyforeignhost.
客戶端(telnet)接收了伺服器的響應。
我們在完成一次響應之後伺服器立即斷開了連接,而像 www.google.com 這樣的伺服器總是一直等待接收連接的。我們需要將上面的伺服器程序改造成一直運行,最簡單的辦法是將 accept 放到一個循環中,那麼就可以一直接收連接了。
保持服務我們可以將代碼改成這樣讓伺服器一直工作:
import socketimport sys HOST = '' PORT = 5000 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' while 1: 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()
現在在一個終端下運行上面的伺服器程序,再開啟三個終端,分別用 telnet 去連接,如果一個終端連接之後不輸入數據其他終端是沒辦法進行連接的,而且每個終端只能服務一次就斷開連接。這從代碼上也是可以看出來的。
這顯然也不是我們想要的,我們希望多個客戶端可以隨時建立連接,而且每個客戶端可以跟伺服器進行多次通信,這該怎麼修改呢?
處理連接為了處理每個連接,我們需要將處理的程序與主程序的接收連接分開。一種方法可以使用線程來實現,主服務程序接收連接,創建一個線程來處理該連接的通信,然後伺服器回到接收其他連接的邏輯上來。
import socketimport sysfrom thread import * HOST = '' PORT = 8888 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' def clientthread(conn): conn.send('Welcome to the server. Type something and hit entern') while True: data = conn.recv(1024) reply = 'OK...' + data if not data: break conn.sendall(reply) conn.close() while 1: conn, addr = s.accept() print 'Connected with ' + addr[0] + ':' + str(addr[1]) start_new_thread(clientthread ,(conn,)) s.close()
再次運行上面的程序,打開三個終端來與主伺服器建立 telnet 連接,這時候三個客戶端可以隨時接入,而且每個客戶端可以與主伺服器進行多次通信。
telnet 終端下可能輸出如下:
$ telnetlocalhost 8888Trying 127.0.0.1...Connectedtolocalhost.Escapecharacteris '^]'.Welcometotheserver. Type somethingand hitenterhiOK...hiasdOK...asdcvOK...cv
要結束 telnet 的連接,按下 Ctrl-] 鍵,再輸入 close 命令。
伺服器終端的輸出可能是這樣的:
$ pythonserver.pySocket createdSocket bindcompleteSocket nowlisteningConnectedwith 127.0.0.1:60730Connectedwith 127.0.0.1:60731
python學習資料交流群:516107834 每天會更新視頻資料,可以隨便下載。