原始實(shí)現(xiàn)VS框架實(shí)現(xiàn)
“掌握一個(gè)類似于框架的高級(jí)工具是有用的界睁,但是基礎(chǔ)的東西可以讓你永遠(yuǎn)不被淘汰。不要被工具限制了自己的發(fā)展彻亲。
”在當(dāng)今Python服務(wù)器框架 (framework, 比如Django, Twisted, web.py等等) 橫行的時(shí)代孕锄,從底層的socket開始寫服務(wù)器似乎是一個(gè)出力不討好的笨方法。
“框架的意義在于掩蓋底層的細(xì)節(jié)苞尝,提供一套對(duì)于開發(fā)人員更加友好的API畸肆,并處理諸如MVC的布局問(wèn)題≈嬷罚框架允許我們快速的構(gòu)建一個(gè)成型而且成熟的Python服務(wù)器轴脐。然而,框架本身也是依賴于底層(比如socket)。對(duì)于底層socket的了解大咱,不僅可以幫助我們更好的使用框架恬涧,更可以讓我們明白框架是如何設(shè)計(jì)的。
更進(jìn)一步碴巾,
”如果擁有良好的底層socket編程知識(shí)和其他系統(tǒng)編程知識(shí)溯捆,你完全可以設(shè)計(jì)并開發(fā)一款自己的框架。如果你可以從底層socket開始厦瓢,實(shí)現(xiàn)一個(gè)完整的Python服務(wù)器提揍,支持用戶層的協(xié)議,并處理好諸如MVC(Model-View-Control)煮仇、多線程(threading)等問(wèn)題碳锈,并整理出一套清晰的函數(shù)或者類,作為接口(API)呈現(xiàn)給用戶欺抗,你就相當(dāng)于設(shè)計(jì)了一個(gè)框架售碳。“
我們已經(jīng)看到:
許多成功的網(wǎng)站都是利用動(dòng)態(tài)語(yǔ)言(比如Python, Ruby或者PHP绞呈,比如twitter和facebook)快速開發(fā)贸人,在網(wǎng)站成功之后,將代碼轉(zhuǎn)換成諸如C和JAVA這樣一些效率比較高的語(yǔ)言佃声,從而讓服務(wù)器能更有效率的面對(duì)每天億萬(wàn)次的請(qǐng)求艺智。在這樣一些時(shí)間,底層的重要性圾亏,就遠(yuǎn)遠(yuǎn)超過(guò)了框架十拣。
預(yù)備知識(shí):TCP/IP和socket
我們需要對(duì)網(wǎng)絡(luò)傳輸,特別是TCP/IP協(xié)議和socket有一定的了解志鹃。socket是進(jìn)程間通信的一種方法 (參考Linux進(jìn)程間通信)夭问,它是基于網(wǎng)絡(luò)傳輸協(xié)議的上層接口。socket有許多種類型曹铃,比如基于TCP協(xié)議或者UDP協(xié)議(兩種網(wǎng)絡(luò)傳輸協(xié)議)缰趋。其中又以TCP socket最為常用。
socket接口是實(shí)際上是操作系統(tǒng)提供的系統(tǒng)調(diào)用陕见。
TCP socket與雙向管道(duplex PIPE)有些類似秘血,一個(gè)進(jìn)程向socket的一端寫入或讀取文本流,而另一個(gè)進(jìn)程可以從socket的另一端讀取或?qū)懭肫捞穑容^特別是灰粮,這兩個(gè)建立socket通信的進(jìn)程可以分別屬于兩臺(tái)不同的計(jì)算機(jī)。
所謂的TCP協(xié)議忍坷,就是規(guī)定了一些通信的守則粘舟,以便在網(wǎng)絡(luò)環(huán)境下能夠有效實(shí)現(xiàn)上述進(jìn)程間通信過(guò)程红柱。雙向管道(duplex PIPE)存活于同一臺(tái)電腦中,所以不必區(qū)分兩個(gè)進(jìn)程的所在計(jì)算機(jī)的地址蓖乘,而socket必須包含有地址信息,以便實(shí)現(xiàn)網(wǎng)絡(luò)通信韧骗。
一個(gè)socket包含四個(gè)地址信息: 兩臺(tái)計(jì)算機(jī)的IP地址和兩個(gè)進(jìn)程所使用的端口(port)嘉抒。IP地址用于定位計(jì)算機(jī),而port用于定位進(jìn)程 (一臺(tái)計(jì)算機(jī)上可以有多個(gè)進(jìn)程分別使用不同的端口)袍暴。
TCP socket
在互聯(lián)網(wǎng)上些侍,我們可以讓某臺(tái)計(jì)算機(jī)作為服務(wù)器。服務(wù)器開放自己的端口政模,被動(dòng)等待其他計(jì)算機(jī)連接岗宣。當(dāng)其他計(jì)算機(jī)作為客戶,主動(dòng)使用socket連接到服務(wù)器的時(shí)候淋样,服務(wù)器就開始為客戶提供服務(wù)耗式。
在Python中,我們使用標(biāo)準(zhǔn)庫(kù)中的socket包來(lái)進(jìn)行底層的socket編程趁猴。
socket類型和函數(shù)詳情請(qǐng)參見: Socket 類型 函數(shù)
首先是服務(wù)器端:
我們使用bind()方法來(lái)賦予socket以固定的地址和端口刊咳,并使用listen()方法來(lái)被動(dòng)的監(jiān)聽該端口。當(dāng)有客戶嘗試用connect()方法連接的時(shí)候儡司,服務(wù)器使用accept()接受連接娱挨,從而建立一個(gè)連接的socket:
import socket
# Address
HOST = ''
PORT = 8000
reply = 'Yes'
# 創(chuàng)建套接字,綁定套接字到本地IP與端口
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
# passively wait, 3: maximum number of connections in the queue
s.listen(3)
# accept and establish connectionconn,
addr = s.accept()
# receive message
request = conn.recv(1024)
print 'request is: ',request
print 'Connected by', addr
# send message
conn.sendall(reply)
# close connection
conn.close()
將以上保存為文件org_ser.py
其次捕犬,客戶端:我們主動(dòng)使用connect()方法來(lái)搜索服務(wù)器端的IP地址和端口跷坝,以便客戶可以找到服務(wù)器,并建立連接碉碉。
import socket
# Address
HOST = '172.20.202.155'
PORT = 8000
request = 'can you hear me?'
# configure socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
# send message
s.sendall(request)
# receive message
reply = s.recv(1024)
print 'reply is: ',reply
# close connection
s.close()
客戶端保存為org_client.py
調(diào)試運(yùn)行:
左上角是用瀏覽器對(duì)服務(wù)器進(jìn)行訪問(wèn)的結(jié)果柴钻,服務(wù)器回傳了“Yes”,對(duì)應(yīng)服務(wù)器中的代碼:reply = 'Yes'
垢粮。
中間圖是服務(wù)器運(yùn)行圖顿颅,最下面是客戶端運(yùn)行圖,可以看出客戶端向服務(wù)器請(qǐng)求的內(nèi)容為:request = 'can you hear me?'
足丢,而客戶端得到的反饋是“Yes”橡娄。
程序運(yùn)行正常。