計算機網路自頂向下方法套接字程式設計Python實現
歡迎關注天善智慧,我們是專注於商業智慧BI,人工智慧AI,大資料分析與挖掘領域的垂直社群,學習,問答、求職一站式搞定!
對商業智慧BI、大資料分析挖掘、機器學習,python,R等資料領域感興趣的同學加微信:tstoutiao,邀請你進入資料愛好者交流群,資料愛好者們都在這兒。
作者: 肖濤 Python愛好者社群專欄作者
個人公眾號:inspurer
個人網站: https://inspurer.github.io/
簡書: https://www.jianshu.com/u/1b872cf08f32
本部落格是針對,《計算機網路自頂向下方法》一書第二章後面套接字程式設計作業,所有程式碼均已上傳至我的github:https://github.com/inspurer/ComputerNetwork
所有程式碼均本人親自編寫,有問題歡迎評論交流。
作業1: Web伺服器
問題描述
使用Python開發一個簡單的Web伺服器,它僅能處理一個請求,具體而言,你的伺服器將
當一個客戶(瀏覽器)聯絡時建立一個連線套接字;
這個連線接受http請求;
解釋該請求以確定所請求的特定檔案;
從伺服器的檔案系統獲得請求的檔案;
建立一個由請求的檔案組成的HTTP響應報文,報文前有首部行;
經TCP連線向請求的瀏覽器傳送響應;
如果檔案不存在,返回 404 Not Found
問題解決
主要程式碼
服務端程式碼
from socket import * serverSocket = socket(AF_INET, SOCK_STREAM) serverSocket.bind(("127.0.0.1",9999)) serverSocket.listen(1) # 沒有客戶端連結時一直在此阻塞 connectionSocket, addr = serverSocket.accept() while True: print('waiting for connection...') try: #接收1k資料 data = connectionSocket.recv(1024) print(data) if not data: continue #data是一個get的http請求報文 filename = data.split()[1] #filename = /HelloWorld.html # #print(filename[1:]) f = open(filename[1:],encoding="utf-8") #f = HelloWorld.html outputdata = f.read() header = 'HTTP/1.1 200 OK\r\n\r\n' #回覆報文 connectionSocket.send(header.encode()) for i in range(0, len(outputdata)): connectionSocket.send(outputdata[i].encode()) #connectionSocket.close() except IOError: header = 'HTTP/1.1 404 NOT FOUND\r\n\r\n' connectionSocket.send(header.encode()) connectionSocket.close() # 瀏覽器鍵入 localhost:***/index.html會有兩個請求 # index.html && favicon.ico(網站的圖示)
客戶端程式碼
from socket import * ClientSocket = socket(AF_INET, SOCK_STREAM) ClientSocket.connect(('localhost',9999)) while True: #這裡的Connetction: close不同於瀏覽器常見的keep-alive, #close表示要求伺服器在傳送完被請求的物件後就關閉這條連結 Head = '''GET /index.html HTTP/1.1\r\nHost: localhost:9999\r\nConnection: close\r\nUser-agent: Mozilla/5.0\r\n\r\n''' ClientSocket.send(Head.encode('utf-8')) data = ClientSocket.recv(1024) print(data) with open("response.html","wb") as f: f.write(data)
How to run
首先執行服務端程式碼 WebServer.py
可以直接執行 WebClient.py
此時會在工程下得到一個響應檔案 response.html

瀏覽器方式時,需要取消對服務端程式碼第25行 #connectionSocket.close()
的註釋再執行,至於為什麼是這樣,這是個歷史遺留問題,有興趣還是私戳我郵箱交流吧。[email protected]
作業2: UDP ping程式
問題描述
使用python採用UDP協議編寫一個ping程式,傳送一個簡單的ping報文給伺服器,並確定從客戶傳送ping報文伺服器到接受到pong報文為止的時延,稱為往返時延(RTT) 。因為UDP是一個不可靠的協議,客戶傳送的分組可能會丟失,為此,客戶不能無限期地等待伺服器的響應,等待時間至多為1s,否則,列印一條錯誤資訊。
問題解決
主要程式碼
服務端程式碼
import random from socket import * #AF_INET指定使用IPv4協議,如果要用更先進的IPv6,就指定為AF_INET6 #SOCK_DGRAM指定了這個Socket的型別是UDP serverSocket = socket(AF_INET, SOCK_DGRAM) #用0.0.0.0繫結到所有的網路地址,還可以用127.0.0.1繫結到本機地址 serverSocket.bind(('127.0.0.1',12000)) while True: #產生一個0到10之間的隨機數 rand = random.randint(0, 10) #從套介面上讀取資料,引數為緩衝區大小 message, address = serverSocket.recvfrom(1024) #通過列印我們可以看到UDP客戶端socket的埠是不確定,系統隨機分配的 print("收到來自 %s 的報文: (%s)" % (address,message)) # 把接收到的資訊全部轉為大寫 print("隨機數是: %d" % rand) message = message.upper() #如果隨機數小於4,服務端無應答,客戶端就會超時 if rand < 4: continue serverSocket.sendto(message, address)
客戶端程式碼
import time from socket import * serverName = '127.0.0.1' # 主機 serverPort = 12000 # 建立Socket時,AF_INET指定使用IPv4協議,如果要用更先進的IPv6,就指定為AF_INET6 # SOCK_DGRAM指定了這個Socket的型別是UDP # SOCK_STREAM指定使用面向流的TCP協議 clientSocket = socket(AF_INET, SOCK_DGRAM) clientSocket.settimeout(1) # 設定超時時間為1s for i in range(0, 10): oldTime = time.time() sendTime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(oldTime)) # encode()把str轉成bytes,傳輸格式要求 message = ('package %d,client_local_time:%s' % (i + 1, sendTime)).encode() try: # 傳送資料 clientSocket.sendto(message, (serverName, serverPort)) # 1024指定要接收的最大資料量為1kb = 1024 bytes # recvfrom是一個系統呼叫,由使用者態轉向系統態,從套介面上接收資料,並捕獲資料傳送源的地址。 # 如果資料報大於緩衝區,那麼緩衝區中只有資料報的前面部分,其他的資料都丟失了,並且recvfrom()函式返回WSAEMSGSIZE錯誤 # 如果沒有資料待讀,那麼除非是非阻塞模式,不然的話套介面將一直等待資料的到來,果沒有在Timeout = 1s內接收到資料,此時將返回SOCKET_ERROR錯誤,錯誤程式碼是WSAEWOULDBLOCK。用select()或WSAAsynSelect()可以獲知何時資料到達 # UDP的 recvfrom() 和 TCP 的recv()不一樣,具體可以看 TCP Ping專案 modifiedMessage, serverAddress = clientSocket.recvfrom(1024) # 計算往返時間 rtt = time.time() - oldTime # decode 把bytes轉成str modifiedMessage = modifiedMessage.decode("utf-8") print('報文 %d 收到來自 %s 的應答: %s,往返時延(RTT) = %fs' % (i+1, serverName,modifiedMessage, rtt)) except Exception as e: print('報文 %d: 的請求超時' % (i+1)) # 處理異常
How to Run
先執行服務端程式碼,再執行客戶端程式碼,注意不要佔用埠。

郵件客戶
問題描述
使用STMP協議從一個郵箱向另一個郵箱傳送郵件
問題解決
可以先了解一下:Windows下操作POP3
https://blog.csdn.net/ygdxt/article/details/83928802
主要程式碼
#作業3:郵件客戶 from smtplib import SMTP from email.mime.text import MIMEText from email.header import Header mail_server = 'smtp.163.com' #根據傳送方郵箱確定郵箱伺服器 #qq郵箱的伺服器為smtp.qq.com;163郵箱為smtp.163.com def get_mail_server(sender): key = sender[sender.index('@')+1:] return "smtp."+key port = '25' ## SMTP協議預設埠是25 sender = '[email protected]' mail_server = get_mail_server(sender) sender_pass = 'put your mail_code here' #注意是授權碼,而不是登入密碼,需要在郵箱端先獲取 receiver = '[email protected]' mail_msg = 'this is a demo' #第一個引數就是郵件正文, # 第二個引數是MIME的subtype,傳入'plain'表示純文字,最終的MIME就是'text/plain', # 最後一定要用utf-8編碼保證多語言相容性。 msg = MIMEText(mail_msg, 'plain', 'utf-8') msg['From'] = sender msg['To'] = receiver #Header物件編碼文字,包含utf-8編碼資訊和Base64編碼。 msg['Subject'] = Header('來自inspurer的個人計算機', 'utf-8') try: server = SMTP(mail_server, port) #用set_debuglevel(1),可以打印出和SMTP伺服器互動的所有資訊 #server.set_debuglevel(1) server.login(sender, sender_pass) #由於可以一次發給多個人,所以傳入一個list,郵件正文是一個str,as_string()把MIMEText物件變成str server.sendmail(sender, (receiver), msg.as_string() ) server.quit() print("郵件傳送成功!") except: server.quit() print("郵件傳送失敗!")
How to Run
直接執行 stmpDemo.py
注意程式碼裡的郵箱授權碼要填成你自己的,它和郵箱登入密碼不一樣,至於怎麼獲取百度吧,我不做搬運工。


作業4: 多執行緒Web代理伺服器
寫到這發現深夜了,以後更新
Python的愛好者社群歷史文章大合集:
Python的愛好者社群歷史文章列表

關注後在公眾號內回覆 “ 課程 ” 即可獲取:
小編的轉行入職資料科學(資料分析挖掘/機器學習方向)【最新免費】
小編的Python的入門免費視訊課程 !
小編的Python的快速上手matplotlib視覺化庫!
崔老師爬蟲實戰案例免費學習視訊。
陳老師資料分析報告擴充套件製作免費學習視訊。
玩轉大資料分析!Spark2.X + Python精華實戰課程免費學習視訊。
