結(jié)合Python的SimpleHTTPServer源碼來解析socket通信
何謂socket
計算機,顧名思義即是用來做計算。因而也需要輸入和輸出,輸入需要計算的條件,輸出計算結(jié)果。這些輸入輸出可以抽象為I/O(input output)。
Unix的計算機處理IO是通過文件的抽象。計算機不同的進程之間也有輸入輸出,也就是通信。因此這這個通信也是通過文件的抽象文件描述符來進行。
在同一臺計算機,進程之間可以這樣通信,如果是不同的計算機呢?網(wǎng)絡(luò)上不同的計算機,也可以通信,那么就得使用網(wǎng)絡(luò)套接字(socket)。socket就是在不同計算機之間進行通信的一個抽象。他工作于TCP/IP協(xié)議中應(yīng)用層和傳輸層之間的一個抽象。如下圖:

服務(wù)器通信
socket保證了不同計算機之間的通信,也就是網(wǎng)絡(luò)通信。對于網(wǎng)站,通信模型是客戶端服務(wù)器之間的通信。兩個端都建立一個socket對象,然后通過socket對象對數(shù)據(jù)進行傳輸。通常服務(wù)器處于一個無線循環(huán),等待客戶端連接:

socket 通信實例
socket接口是操作系統(tǒng)提供的,調(diào)用操作系統(tǒng)的接口。當(dāng)然高級語言一般也封裝了好用的函數(shù)接口,下面用python代碼寫一個簡單的socket服務(wù)端例子:
server.py
import socket
HOST = 'localhost' # 服務(wù)器主機地址
PORT = 5000 # 服務(wù)器監(jiān)聽端口
BUFFER_SIZE = 2048 # 讀取數(shù)據(jù)大小
# 創(chuàng)建一個套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定主機和端口
sock.bind((HOST, PORT))
# 開啟socket監(jiān)聽
sock.listen(5)
print 'Server start, listening {}'.format(PORT)
while True:
# 建立連接,連接為建立的時候阻塞
conn, addr = sock.accept()
while True:
# 讀取數(shù)據(jù),數(shù)據(jù)還沒到來阻塞
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)建客戶端套接字
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代碼寫套接字很簡單。傳說的TCP三次握手又是如何體現(xiàn)的呢?什么是三次握手呢?
第一握:首先客戶端發(fā)送一個syn,請求連接,
第二握:服務(wù)器收到之后確認,并發(fā)送一個 syn ack應(yīng)答
第三握:客戶端接收到服務(wù)器發(fā)來的應(yīng)答之后再給服務(wù)器發(fā)送建立連接的確定。
用下面的比喻就是
C:約么?
S:約
C:好的
約會
這樣就建立了一個TCP連接會話。如果是要斷開連接,大致過程是:

上圖也很清晰的表明了三次握手的socket具體過程。
- 客戶端socket對象connect調(diào)用之后進行阻塞,此過程發(fā)送了一個syn。
- 服務(wù)器socket對象調(diào)用accept函數(shù)之后阻塞,直到客戶端發(fā)送來的syn,然后發(fā)送syn和ack應(yīng)答
- 客戶端socket對象收到服務(wù)端發(fā)送的應(yīng)答之后,再發(fā)送一個ack給服務(wù)器,并返回connect調(diào)用,建立連接。
- 服務(wù)器socket對象接受客戶端最后一次握手確定ack返回accept函數(shù),建立連接。
至此,客戶端和服務(wù)器的socket通信連接建立完成,剩下的就是兩個端的連接對象收發(fā)數(shù)據(jù),從而完成網(wǎng)絡(luò)通信。
SimpleHTTPServer
構(gòu)建一個簡單的HTTP服務(wù),需要繼承HTTPServer,同時requesthandler也需要繼承BaseHTTPRequestHandler。python已經(jīng)實現(xiàn)了一個例子,那就是SimpleHTTPServer。因此分析SimpleHTTPServer來查看如何使用前面的一些類構(gòu)建http服務(wù)。
曾經(jīng)為了表示python的簡潔優(yōu)雅,經(jīng)常會舉這樣的例子,python可以一行代碼開啟一個服務(wù)器。
$ python -m SimpleHTTPServer
這里的SimpleHTTPServer就是實現(xiàn)了HTTPServer的模塊。
SimpleHTTPServer通過調(diào)用BaseHTTPServer模塊的test方法做為入口。
def test(HandlerClass = SimpleHTTPRequestHandler,
ServerClass = BaseHTTPServer.HTTPServer):
BaseHTTPServer.test(HandlerClass, ServerClass)
test方法做了兩件事,第一件就是使用HTTPServer接受一個監(jiān)聽地址和requestClass參數(shù),創(chuàng)建了一個實例對象,調(diào)用server_forever方法開啟服務(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 分別實現(xiàn)了http的get請求和head請求的處理。他們調(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 方法通過uri的path分析得到客戶請求的網(wǎng)路路徑。構(gòu)造head的mime元信息并發(fā)送到客戶端,然后返回一個打開path的文件句柄。
2.copyfile
do_GET的下一步就是通過 copyfile方法,將客戶請求的path的文件數(shù)據(jù)寫入到緩沖可寫文件中,發(fā)送給客戶端。
3.list_directory
SimpleHTTPServer模塊還提供了list_directory方法,用于響應(yīng)path是一個目錄,而不是文件的情況。
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
由此可見,處理客戶端的請求,只需要使用 send_reponse, send_header 和 end_headers ,就能向客戶端發(fā)送reponse。
4.自定義http服務(wù)
定義一個CustomHTTPRequestHadnler繼承自BaseHTTPRequestHandler。在其內(nèi)實現(xiàn)do_GET 方法來處理get請求。
然后再定義一個CustomHTTPServer繼承自HTTPServer,它接受CustomHTTPRequestHadnler作為自己的handler。簡單的代碼如下:
# -*- 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訪問可以得到
➜ ~ curl http://127.0.0.1:8000 hello world ➜ ~
控制臺會打出訪問的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開發(fā)中,更多是使用WSGI協(xié)議。實現(xiàn)該協(xié)議的還有 uWSGI和gunicorn等庫。相比那些庫,python內(nèi)部提供了一個wsgiref模塊,實現(xiàn)了一個簡單wsgi服務(wù)--simple_server。
接下來將會通過分析simple_server,更好的掌握WSGI協(xié)議。
- Python socket實現(xiàn)的簡單通信功能示例
- Python基于socket模塊實現(xiàn)UDP通信功能示例
- Python網(wǎng)絡(luò)編程使用select實現(xiàn)socket全雙工異步通信功能示例
- php實現(xiàn)與python進行socket通信的方法示例
- Python socket網(wǎng)絡(luò)編程TCP/IP服務(wù)器與客戶端通信
- 利用Python中SocketServer 實現(xiàn)客戶端與服務(wù)器間非阻塞通信
- python實現(xiàn)簡單socket通信的方法
- Python通過websocket與js客戶端通信示例分析
- python socket通信編程實現(xiàn)文件上傳代碼實例
相關(guān)文章
Pyecharts 動態(tài)地圖 geo()和map()的安裝與用法詳解
這篇文章主要介紹了Pyecharts 動態(tài)地圖 geo()和map()的安裝與用法詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Django 批量插入數(shù)據(jù)的實現(xiàn)方法
這篇文章主要介紹了Django 批量插入數(shù)據(jù)的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Tensorflow:轉(zhuǎn)置函數(shù) transpose的使用詳解
今天小編就為大家分享一篇Tensorflow:轉(zhuǎn)置函數(shù) transpose的使用詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-02-02

