結(jié)合Python的SimpleHTTPServer源碼來(lái)解析socket通信
何謂socket
計(jì)算機(jī),顧名思義即是用來(lái)做計(jì)算。因而也需要輸入和輸出,輸入需要計(jì)算的條件,輸出計(jì)算結(jié)果。這些輸入輸出可以抽象為I/O(input output)。
Unix的計(jì)算機(jī)處理IO是通過(guò)文件的抽象。計(jì)算機(jī)不同的進(jìn)程之間也有輸入輸出,也就是通信。因此這這個(gè)通信也是通過(guò)文件的抽象文件描述符來(lái)進(jìn)行。
在同一臺(tái)計(jì)算機(jī),進(jìn)程之間可以這樣通信,如果是不同的計(jì)算機(jī)呢?網(wǎng)絡(luò)上不同的計(jì)算機(jī),也可以通信,那么就得使用網(wǎng)絡(luò)套接字(socket)。socket就是在不同計(jì)算機(jī)之間進(jìn)行通信的一個(gè)抽象。他工作于TCP/IP協(xié)議中應(yīng)用層和傳輸層之間的一個(gè)抽象。如下圖:
服務(wù)器通信
socket保證了不同計(jì)算機(jī)之間的通信,也就是網(wǎng)絡(luò)通信。對(duì)于網(wǎng)站,通信模型是客戶(hù)端服務(wù)器之間的通信。兩個(gè)端都建立一個(gè)socket對(duì)象,然后通過(guò)socket對(duì)象對(duì)數(shù)據(jù)進(jìn)行傳輸。通常服務(wù)器處于一個(gè)無(wú)線(xiàn)循環(huán),等待客戶(hù)端連接:
socket 通信實(shí)例
socket接口是操作系統(tǒng)提供的,調(diào)用操作系統(tǒng)的接口。當(dāng)然高級(jí)語(yǔ)言一般也封裝了好用的函數(shù)接口,下面用python代碼寫(xiě)一個(gè)簡(jiǎn)單的socket服務(wù)端例子:
server.py
import socket HOST = 'localhost' # 服務(wù)器主機(jī)地址 PORT = 5000 # 服務(wù)器監(jiān)聽(tīng)端口 BUFFER_SIZE = 2048 # 讀取數(shù)據(jù)大小 # 創(chuàng)建一個(gè)套接字 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 綁定主機(jī)和端口 sock.bind((HOST, PORT)) # 開(kāi)啟socket監(jiān)聽(tīng) sock.listen(5) print 'Server start, listening {}'.format(PORT) while True: # 建立連接,連接為建立的時(shí)候阻塞 conn, addr = sock.accept() while True: # 讀取數(shù)據(jù),數(shù)據(jù)還沒(méi)到來(lái)阻塞 data = conn.recv(BUFFER_SIZE) if len(data): print 'Server Recv Data: {}'.format(data) conn.send(data) print 'Server Send Data: {}'.format(data) else: print 'Server Recv Over' break conn.close() sock.close()
client.py
import socket HOST = 'localhost' PORT = 5000 BUFFER_SIZE = 1024 # 創(chuàng)建客戶(hù)端套接字 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 連接到服務(wù)器 sock.connect((HOST, PORT)) try: message = "Hello" # 發(fā)起數(shù)據(jù)給服務(wù)器 sock.sendall(message) amount_received = 0 amount_expected = len(message) while amount_received < amount_expected: # 接收服務(wù)器返回的數(shù)據(jù) data = sock.recv(10) amount_received += len(data) print 'Client Received: {}'.format(data) except socket.errno, e: print 'Socket error: {}'.format(e) except Exception, e: print 'Other exception: %s'.format(e) finally: print 'Closing connection to the server' sock.close()
TCP 三次握手
python代碼寫(xiě)套接字很簡(jiǎn)單。傳說(shuō)的TCP三次握手又是如何體現(xiàn)的呢?什么是三次握手呢?
第一握:首先客戶(hù)端發(fā)送一個(gè)syn,請(qǐng)求連接,
第二握:服務(wù)器收到之后確認(rèn),并發(fā)送一個(gè) syn ack應(yīng)答
第三握:客戶(hù)端接收到服務(wù)器發(fā)來(lái)的應(yīng)答之后再給服務(wù)器發(fā)送建立連接的確定。
用下面的比喻就是
C:約么?
S:約
C:好的
約會(huì)
這樣就建立了一個(gè)TCP連接會(huì)話(huà)。如果是要斷開(kāi)連接,大致過(guò)程是:
上圖也很清晰的表明了三次握手的socket具體過(guò)程。
- 客戶(hù)端socket對(duì)象connect調(diào)用之后進(jìn)行阻塞,此過(guò)程發(fā)送了一個(gè)syn。
- 服務(wù)器socket對(duì)象調(diào)用accept函數(shù)之后阻塞,直到客戶(hù)端發(fā)送來(lái)的syn,然后發(fā)送syn和ack應(yīng)答
- 客戶(hù)端socket對(duì)象收到服務(wù)端發(fā)送的應(yīng)答之后,再發(fā)送一個(gè)ack給服務(wù)器,并返回connect調(diào)用,建立連接。
- 服務(wù)器socket對(duì)象接受客戶(hù)端最后一次握手確定ack返回accept函數(shù),建立連接。
至此,客戶(hù)端和服務(wù)器的socket通信連接建立完成,剩下的就是兩個(gè)端的連接對(duì)象收發(fā)數(shù)據(jù),從而完成網(wǎng)絡(luò)通信。
SimpleHTTPServer
構(gòu)建一個(gè)簡(jiǎn)單的HTTP服務(wù),需要繼承HTTPServer,同時(shí)requesthandler也需要繼承BaseHTTPRequestHandler。python已經(jīng)實(shí)現(xiàn)了一個(gè)例子,那就是SimpleHTTPServer。因此分析SimpleHTTPServer來(lái)查看如何使用前面的一些類(lèi)構(gòu)建http服務(wù)。
曾經(jīng)為了表示python的簡(jiǎn)潔優(yōu)雅,經(jīng)常會(huì)舉這樣的例子,python可以一行代碼開(kāi)啟一個(gè)服務(wù)器。
$ python -m SimpleHTTPServer
這里的SimpleHTTPServer就是實(shí)現(xiàn)了HTTPServer的模塊。
SimpleHTTPServer通過(guò)調(diào)用BaseHTTPServer模塊的test方法做為入口。
def test(HandlerClass = SimpleHTTPRequestHandler, ServerClass = BaseHTTPServer.HTTPServer): BaseHTTPServer.test(HandlerClass, ServerClass)
test方法做了兩件事,第一件就是使用HTTPServer接受一個(gè)監(jiān)聽(tīng)地址和requestClass參數(shù),創(chuàng)建了一個(gè)實(shí)例對(duì)象,調(diào)用server_forever方法開(kāi)啟服務(wù)。
1.SimpleHTTPRequestHandler
根據(jù)之前的分析,使用httpserver的服務(wù),我們只需要繼續(xù)BaseHTTPRequestHandler,并提供自省的method方法即可。
class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): server_version = "SimpleHTTP/" + __version__ def do_GET(self): f = self.send_head() if f: self.copyfile(f, self.wfile) f.close() def do_HEAD(self): f = self.send_head() if f: f.close()
do_GET 和 do_HEAD 分別實(shí)現(xiàn)了http的get請(qǐng)求和head請(qǐng)求的處理。他們調(diào)用send_head方法:
def send_head(self): path = self.translate_path(self.path) f = None if os.path.isdir(path): if not self.path.endswith('/'): self.send_response(301) self.send_header("Location", self.path + "/") self.end_headers() return None for index in "index.html", "index.htm": index = os.path.join(path, index) if os.path.exists(index): path = index break else: return self.list_directory(path) ctype = self.guess_type(path) try: f = open(path, 'rb') except IOError: self.send_error(404, "File not found") return None self.send_response(200) self.send_header("Content-type", ctype) fs = os.fstat(f.fileno()) self.send_header("Content-Length", str(fs[6])) self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) self.end_headers() return f
send_head 方法通過(guò)uri的path分析得到客戶(hù)請(qǐng)求的網(wǎng)路路徑。構(gòu)造head的mime元信息并發(fā)送到客戶(hù)端,然后返回一個(gè)打開(kāi)path的文件句柄。
2.copyfile
do_GET的下一步就是通過(guò) copyfile方法,將客戶(hù)請(qǐng)求的path的文件數(shù)據(jù)寫(xiě)入到緩沖可寫(xiě)文件中,發(fā)送給客戶(hù)端。
3.list_directory
SimpleHTTPServer模塊還提供了list_directory方法,用于響應(yīng)path是一個(gè)目錄,而不是文件的情況。
def list_directory(self, path): try: list = os.listdir(path) except os.error: self.send_error(404, "No permission to list directory") return None list.sort(key=lambda a: a.lower()) f = StringIO() displaypath = cgi.escape(urllib.unquote(self.path)) f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">') f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath) f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath) f.write("<hr>\n<ul>\n") for name in list: fullname = os.path.join(path, name) displayname = linkname = name # Append / for directories or @ for symbolic links if os.path.isdir(fullname): displayname = name + "/" linkname = name + "/" if os.path.islink(fullname): displayname = name + "@" # Note: a link to a directory displays with @ and links with / f.write('<li><a href="%s">%s</a>\n' % (urllib.quote(linkname), cgi.escape(displayname))) f.write("</ul>\n<hr>\n</body>\n</html>\n") length = f.tell() f.seek(0) self.send_response(200) encoding = sys.getfilesystemencoding() self.send_header("Content-type", "text/html; charset=%s" % encoding) self.send_header("Content-Length", str(length)) self.end_headers() return f
由此可見(jiàn),處理客戶(hù)端的請(qǐng)求,只需要使用 send_reponse, send_header 和 end_headers ,就能向客戶(hù)端發(fā)送reponse。
4.自定義http服務(wù)
定義一個(gè)CustomHTTPRequestHadnler繼承自BaseHTTPRequestHandler。在其內(nèi)實(shí)現(xiàn)do_GET 方法來(lái)處理get請(qǐng)求。
然后再定義一個(gè)CustomHTTPServer繼承自HTTPServer,它接受CustomHTTPRequestHadnler作為自己的handler。簡(jiǎn)單的代碼如下:
# -*- coding: utf-8 -*- from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer class CustomHTTPRequestHandler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write("hello world\r\n") class CustomHTTPServer(HTTPServer): def __init__(self, host, port): HTTPServer.__init__(self, (host, port), CustomHTTPRequestHandler) def main(): server = CustomHTTPServer('127.0.0.1', 8000) server.serve_forever() if __name__ == '__main__': main()
使用curl訪(fǎng)問(wèn)可以得到
➜ ~ curl http://127.0.0.1:8000 hello world ➜ ~
控制臺(tái)會(huì)打出訪(fǎng)問(wèn)的log。
127.0.0.1 - - [01/Jun/2015 11:42:33] "GET / HTTP/1.1" 200 -
從socket的建立,select的IO模式,再到Server和Handler的組合構(gòu)建服務(wù)。我們已經(jīng)熟悉了python的基本網(wǎng)絡(luò)編程。python的web開(kāi)發(fā)中,更多是使用WSGI協(xié)議。實(shí)現(xiàn)該協(xié)議的還有 uWSGI和gunicorn等庫(kù)。相比那些庫(kù),python內(nèi)部提供了一個(gè)wsgiref模塊,實(shí)現(xiàn)了一個(gè)簡(jiǎn)單wsgi服務(wù)--simple_server。
接下來(lái)將會(huì)通過(guò)分析simple_server,更好的掌握WSGI協(xié)議。
- Python socket實(shí)現(xiàn)的簡(jiǎn)單通信功能示例
- Python基于socket模塊實(shí)現(xiàn)UDP通信功能示例
- Python網(wǎng)絡(luò)編程使用select實(shí)現(xiàn)socket全雙工異步通信功能示例
- php實(shí)現(xiàn)與python進(jìn)行socket通信的方法示例
- Python socket網(wǎng)絡(luò)編程TCP/IP服務(wù)器與客戶(hù)端通信
- 利用Python中SocketServer 實(shí)現(xiàn)客戶(hù)端與服務(wù)器間非阻塞通信
- python實(shí)現(xiàn)簡(jiǎn)單socket通信的方法
- Python通過(guò)websocket與js客戶(hù)端通信示例分析
- python socket通信編程實(shí)現(xiàn)文件上傳代碼實(shí)例
相關(guān)文章
python實(shí)現(xiàn)楊輝三角的3種方法(迭代、生成器和遞歸)
這篇文章主要給大家介紹了關(guān)于python實(shí)現(xiàn)楊輝三角的3種方法,分別是迭代、生成器和遞歸的相關(guān)資料,楊輝三角形的規(guī)則就是每行的第一個(gè)數(shù)字和最后一個(gè)數(shù)字為1之外,其余每個(gè)數(shù)字等于上一行對(duì)應(yīng)兩個(gè)數(shù)字的和,需要的朋友可以參考下2023-11-11python解決方案:WindowsError: [Error 2]
使用Python的rename()函數(shù)重命名文件時(shí)出現(xiàn)問(wèn)題,提示 WindowsError: [Error 2] 錯(cuò)誤,需要的朋友可以參考下2016-08-08如何將python中的List轉(zhuǎn)化成dictionary
這篇文章主要介紹在python中如何將list轉(zhuǎn)化成dictionary,通過(guò)提出兩個(gè)問(wèn)題來(lái)告訴大家如何解決,有需要的可以參考借鑒。2016-08-08講解如何利用 Python完成 Saga 分布式事務(wù)
這篇文章主要介紹了如何利用 Python 完成一個(gè) Saga 的分布式事務(wù),需要的朋友可以參考下面文章具體的內(nèi)容2021-09-09使用python創(chuàng)建生成動(dòng)態(tài)鏈接庫(kù)dll的方法
這篇文章主要介紹了使用python創(chuàng)建生成動(dòng)態(tài)鏈接庫(kù)dll的方法,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05Python實(shí)現(xiàn)的簡(jiǎn)單計(jì)算器功能詳解
這篇文章主要介紹了Python實(shí)現(xiàn)的簡(jiǎn)單計(jì)算器功能,結(jié)合實(shí)例形式詳細(xì)分析了Python實(shí)現(xiàn)計(jì)算器功能的具體步驟、相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-08-08詳解python實(shí)現(xiàn)小波變換的一個(gè)簡(jiǎn)單例子
這篇文章主要介紹了詳解python實(shí)現(xiàn)小波變換的一個(gè)簡(jiǎn)單例子,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07Python分支結(jié)構(gòu)(switch)操作簡(jiǎn)介
這篇文章主要介紹了Python分支結(jié)構(gòu)(switch)操作簡(jiǎn)介,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01