詳解Python Socket網(wǎng)絡(luò)編程
Socket 是進(jìn)程間通信的一種方式,它與其他進(jìn)程間通信的一個(gè)主要不同是:它能實(shí)現(xiàn)不同主機(jī)間的進(jìn)程間通信,我們網(wǎng)絡(luò)上各種各樣的服務(wù)大多都是基于 Socket 來(lái)完成通信的,例如我們每天瀏覽網(wǎng)頁(yè)、QQ 聊天、收發(fā) email 等等。要解決網(wǎng)絡(luò)上兩臺(tái)主機(jī)之間的進(jìn)程通信問(wèn)題,首先要唯一標(biāo)識(shí)該進(jìn)程,在 TCP/IP 網(wǎng)絡(luò)協(xié)議中,就是通過(guò) (IP地址,協(xié)議,端口號(hào)) 三元組來(lái)標(biāo)識(shí)進(jìn)程的,解決了進(jìn)程標(biāo)識(shí)問(wèn)題,就有了通信的基礎(chǔ)了。
本文主要介紹使用Python 進(jìn)行TCP Socket 網(wǎng)絡(luò)編程,假設(shè)你已經(jīng)具有初步的網(wǎng)絡(luò)知識(shí)及Python 基本語(yǔ)法知識(shí)。
TCP 是一種面向連接的傳輸層協(xié)議,TCP Socket 是基于一種 Client-Server 的編程模型,服務(wù)端監(jiān)聽客戶端的連接請(qǐng)求,一旦建立連接即可以進(jìn)行傳輸數(shù)據(jù)。那么對(duì) TCP Socket 編程的介紹也分為客戶端和服務(wù)端:
一、客戶端編程
創(chuàng)建socket
首先要?jiǎng)?chuàng)建 socket,用 Python 中 socket 模塊的函數(shù) 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'
函數(shù)socket.socket 創(chuàng)建一個(gè) socket,返回該 socket 的描述符,將在后面相關(guān)函數(shù)中使用。該函數(shù)帶有兩個(gè)參數(shù):
Address Family:可以選擇 AF_INET(用于 Internet 進(jìn)程間通信) 或者 AF_UNIX(用于同一臺(tái)機(jī)器進(jìn)程間通信)
Type:套接字類型,可以是 SOCKET_STREAM(流式套接字,主要用于 TCP 協(xié)議)或者SOCKET_DGRAM(數(shù)據(jù)報(bào)套接字,主要用于 UDP 協(xié)議)
注:由于本文主要概述一下 Python Socket 編程的過(guò)程,因此不會(huì)對(duì)相關(guān)函數(shù)參數(shù)、返回值進(jìn)行詳細(xì)介紹,需要了解的可以查看相關(guān)手冊(cè)
錯(cuò)誤處理
如果創(chuàng)建 socket 函數(shù)失敗,會(huì)拋出一個(gè) socket.error 的異常,需要捕獲:
#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'
那么到目前為止已成功創(chuàng)建了 socket,接下來(lái)我們將用這個(gè) socket 來(lái)連接某個(gè)服務(wù)器,就連 www.google.com 吧。
連接服務(wù)器
本文開始也提到了,socket 使用 (IP地址,協(xié)議,端口號(hào)) 來(lái)標(biāo)識(shí)一個(gè)進(jìn)程,那么我們要想和服務(wù)器進(jìn)行通信,就需要知道它的 IP地址以及端口號(hào)。
獲得遠(yuǎn)程主機(jī)的 IP 地址
Python 提供了一個(gè)簡(jiǎn)單的函數(shù) socket.gethostbyname 來(lái)獲得遠(yuǎn)程主機(jī)的 IP 地址:
host = 'www.google.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
現(xiàn)在我們知道了服務(wù)器的 IP 地址,就可以使用連接函數(shù) connect 連接到該 IP 的某個(gè)特定的端口上了,下面例子連接到 80 端口上(是 HTTP 服務(wù)的默認(rèn)端口):
#Connect to remote server s.connect((remote_ip , port)) print 'Socket Connected to ' + host + ' on ip ' + remote_ip
運(yùn)行該程序:
$ python client.py Socket created Ip of remote host www.google.com is 173.194.38.145 Socket Connected to www.google.com on ip 173.194.38.145
發(fā)送數(shù)據(jù)
上面說(shuō)明連接到 www.google.com 已經(jīng)成功了,接下面我們可以向服務(wù)器發(fā)送一些數(shù)據(jù),例如發(fā)送字符串GET / HTTP/1.1\r\n\r\n,這是一個(gè) HTTP 請(qǐng)求網(wǎng)頁(yè)內(nèi)容的命令。
#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ā)送完數(shù)據(jù)之后,客戶端還需要接受服務(wù)器的響應(yīng)。
接收數(shù)據(jù)
函數(shù) recv 可以用來(lái)接收 socket 的數(shù)據(jù):
#Now receive data reply = s.recv(4096) print reply
一起運(yùn)行的結(jié)果如下:
Socket created Ip of remote host www.google.com is 173.194.38.145 Socket Connected to www.google.com on ip 173.194.38.145 Message send successfully HTTP/1.1 302 Found Cache-Control: private Content-Type: text/html; charset=UTF-8 Location: http://www.google.com.sg/?gfe_rd=cr&ei=PlqJVLCREovW8gfF0oG4CQ Content-Length: 262 Date: Thu, 11 Dec 2014 08:47:58 GMT Server: GFE/2.0 Alternate-Protocol: 80:quic,p=0.02 <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>302 Moved</TITLE></HEAD><BODY> <H1>302 Moved</H1> The document has moved <A >here</A>. </BODY></HTML>
關(guān)閉 socket
當(dāng)我們不想再次請(qǐng)求服務(wù)器數(shù)據(jù)時(shí),可以將該 socket 關(guān)閉,結(jié)束這次通信:
s.close()
小結(jié)
上面我們學(xué)到了如何:
- 創(chuàng)建 socket
- 連接到遠(yuǎn)程服務(wù)器
- 發(fā)送數(shù)據(jù)
- 接收數(shù)據(jù)
- 關(guān)閉 socket
當(dāng)我們打開www.google.com 時(shí),瀏覽器所做的就是這些,知道這些是非常有意義的。在 socket 中具有這種行為特征的被稱為CLIENT,客戶端主要是連接遠(yuǎn)程系統(tǒng)獲取數(shù)據(jù)。
socket 中另一種行為稱為SERVER,服務(wù)器使用 socket 來(lái)接收連接以及提供數(shù)據(jù),和客戶端正好相反。所以 www.google.com 是服務(wù)器,你的瀏覽器是客戶端,或者更準(zhǔn)確地說(shuō),www.google.com 是 HTTP 服務(wù)器,你的瀏覽器是 HTTP 客戶端。
那么上面介紹了客戶端的編程,現(xiàn)在輪到服務(wù)器端如果使用 socket 了。
二、服務(wù)器端編程
服務(wù)器端主要做以下工作:
- 打開 socket
- 綁定到特定的地址以及端口上
- 監(jiān)聽連接
- 建立連接
- 接收/發(fā)送數(shù)據(jù)
上面已經(jīng)介紹了如何創(chuàng)建 socket 了,下面一步是綁定。
綁定socket
函數(shù) bind 可以用來(lái)將 socket 綁定到特定的地址和端口上,它需要一個(gè) sockaddr_in 結(jié)構(gòu)作為參數(shù):
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'
綁定完成之后,接下來(lái)就是監(jiān)聽連接了。
監(jiān)聽連接
函數(shù) listen 可以將 socket 置于監(jiān)聽模式:
s.listen(10) print 'Socket now listening'
該函數(shù)帶有一個(gè)參數(shù)稱為 backlog,用來(lái)控制連接的個(gè)數(shù)。如果設(shè)為 10,那么有 10 個(gè)連接正在等待處理,此時(shí)第 11 個(gè)請(qǐng)求過(guò)來(lái)時(shí)將會(huì)被拒絕。
接收連接
當(dāng)有客戶端向服務(wù)器發(fā)送連接請(qǐng)求時(shí),服務(wù)器會(huì)接收連接:
#wait to accept a connection - blocking call conn, addr = s.accept() #display client information print 'Connected with ' + addr[0] + ':' + str(addr[1])
運(yùn)行該程序的,輸出結(jié)果如下:
$ python server.py Socket created Socket bind complete Socket now listening
此時(shí),該程序在 8888 端口上等待請(qǐng)求的到來(lái)。不要關(guān)掉這個(gè)程序,讓它一直運(yùn)行,現(xiàn)在客戶端可以通過(guò)該端口連接到 socket。我們用 telnet 客戶端來(lái)測(cè)試,打開一個(gè)終端,輸入 telnet localhost 8888:
$ telnet localhost 8888 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Connection closed by foreign host.
這時(shí)服務(wù)端輸出會(huì)顯示:
$ python server.py Socket created Socket bind complete Socket now listening Connected with 127.0.0.1:59954
我們觀察到客戶端已經(jīng)連接上服務(wù)器了。在建立連接之后,我們可以用來(lái)與客戶端進(jìn)行通信。下面例子演示的是,服務(wù)器建立連接之后,接收客戶端發(fā)送來(lái)的數(shù)據(jù),并立即將數(shù)據(jù)發(fā)送回去,下面是完整的服務(wù)端程序:
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()
在一個(gè)終端中運(yùn)行這個(gè)程序,打開另一個(gè)終端,使用 telnet 連接服務(wù)器,隨便輸入字符串,你會(huì)看到:
$ telnet localhost 8888 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. happy happy Connection closed by foreign host.
客戶端(telnet)接收了服務(wù)器的響應(yīng)。
我們?cè)谕瓿梢淮雾憫?yīng)之后服務(wù)器立即斷開了連接,而像www.google.com 這樣的服務(wù)器總是一直等待接收連接的。我們需要將上面的服務(wù)器程序改造成一直運(yùn)行,最簡(jiǎn)單的辦法是將accept 放到一個(gè)循環(huán)中,那么就可以一直接收連接了。
保持服務(wù)
我們可以將代碼改成這樣讓服務(wù)器一直工作:
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()
現(xiàn)在在一個(gè)終端下運(yùn)行上面的服務(wù)器程序,再開啟三個(gè)終端,分別用 telnet 去連接,如果一個(gè)終端連接之后不輸入數(shù)據(jù)其他終端是沒(méi)辦法進(jìn)行連接的,而且每個(gè)終端只能服務(wù)一次就斷開連接。這從代碼上也是可以看出來(lái)的。
這顯然也不是我們想要的,我們希望多個(gè)客戶端可以隨時(shí)建立連接,而且每個(gè)客戶端可以跟服務(wù)器進(jìn)行多次通信,這該怎么修改呢?
處理連接
為了處理每個(gè)連接,我們需要將處理的程序與主程序的接收連接分開。一種方法可以使用線程來(lái)實(shí)現(xiàn),主服務(wù)程序接收連接,創(chuàng)建一個(gè)線程來(lái)處理該連接的通信,然后服務(wù)器回到接收其他連接的邏輯上來(lái)。
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()
再次運(yùn)行上面的程序,打開三個(gè)終端來(lái)與主服務(wù)器建立 telnet 連接,這時(shí)候三個(gè)客戶端可以隨時(shí)接入,而且每個(gè)客戶端可以與主服務(wù)器進(jìn)行多次通信。
telnet 終端下可能輸出如下:
$ telnet localhost 8888 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
要結(jié)束 telnet 的連接,按下 Ctrl-] 鍵,再輸入 close 命令。
服務(wù)器終端的輸出可能是這樣的:
$ python server.py Socket created Socket bind complete Socket now listening Connected with 127.0.0.1:60730 Connected with 127.0.0.1:60731
到目前為止,我們學(xué)習(xí)了Python 下基本的socket 編程,之后還有相關(guān)文章向大家介紹,不要走開。
相關(guān)文章
python如何獲取tensor()數(shù)據(jù)類型中的值
這篇文章主要介紹了python如何獲取tensor()數(shù)據(jù)類型中的值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07pyinstaller將python程序打包為可執(zhí)行文件
這篇文章主要介紹了pyinstaller將python程序打包為可執(zhí)行文件,pyinstaller是一個(gè)python打包工具,它將python程序及所需依賴都打包成一個(gè)可執(zhí)行文件2022-08-08Python網(wǎng)絡(luò)請(qǐng)求模塊urllib與requests使用介紹
網(wǎng)絡(luò)爬蟲的第一步就是根據(jù)URL,獲取網(wǎng)頁(yè)的HTML信息。在Python3中,可以使用urllib和requests進(jìn)行網(wǎng)頁(yè)數(shù)據(jù)獲取,這篇文章主要介紹了Python網(wǎng)絡(luò)請(qǐng)求模塊urllib與requests使用2022-10-10python datatable庫(kù)大型數(shù)據(jù)集和多核數(shù)據(jù)處理使用探索
這篇文章主要介紹了python datatable庫(kù)大型數(shù)據(jù)集和多核數(shù)據(jù)處理使用探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01如何通過(guò)python實(shí)現(xiàn)IOU計(jì)算代碼實(shí)例
這篇文章主要介紹了如何通過(guò)python實(shí)現(xiàn)IOU計(jì)算代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11Docker部署Python爬蟲項(xiàng)目的方法步驟
這篇文章主要介紹了Docker部署Python爬蟲項(xiàng)目的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01