計算機網路 自頂向下 第二章labs
來自專欄 Computer Science學習筆記2 人贊了文章
《計算機網路 自頂向下》第二章課後有四個實驗,用來熟悉應用層的幾個協議,以及socket編程:
- web 伺服器
- UDP ping
- 郵件客戶
- 多線程web代理伺服器
1. web 伺服器
第一個作業,要求用python完成一個簡單的web伺服器。相比TCP server,web伺服器就是多了對HTTP請求的處理,以及HTTP響應報文的生成。
首先是完成大致的框架,socket() -> bind -> listen -> accpet -> process -> responce
。然後,就是完成一下process
部分,因為並不要求完成一個完整的伺服器,所以就只是簡單的解析一下請求頭,根據request line來查找對應的資源是否存在,如果不存在就返回404頁面,否則就生成一個響應報文,把請求的資源載入responce header
後面,再send
就搞定了。
xvvx:CSAPP Lab -- Proxy Lab
之前寫過CSAPP的 proxy lab,所以這個lab寫起來很容易,然後再實現了一個最簡單的多線程,沒有用線程池,所以很容易爆內存 。代碼:
import sysimport threadfrom socket import *from time import *not_found_html = "<h1>404 NOT FOUND!</h1>"def request_parser(request): lines = request.split(
) request_line = lines[0].split() header_lines = dict() for each in lines[1:]: if each.strip() != "": key = each[:each.find(:)] value = each[each.find(:)+1:].strip() header_lines[key] = value return [request_line, header_lines]def generate_responce(request_header): method, url, http_version = request_header[0] url = ./static{}.format(url) status = 200 phase = { 200 : "OK", 404 : "NOT FOUND" } data = try: with open(url, rt) as fr: data = fr.read() except IOError: status = 404 data = not_found_html return """HTTP/1.1 {} {}
Connection: close
Content-type: {}
{}""".format( status, phase[status], text/html, data)def process_connection(conn_sock, addr): request = conn_sock.recv(1024).decode() request_header = request_parser(request) responce = generate_responce(request_header) conn_sock.send(responce.encode(utf-8)) print({} process finish! closing....format(addr)) conn_sock.close()if __name__ == __main__: server_port = int(sys.argv[1]) server_socket = socket(AF_INET, SOCK_STREAM) server_socket.bind((, server_port)) server_socket.listen(1000) while True: conn_sock, addr = server_socket.accept() thread.start_new_thread(process_connection, (conn_sock, addr))
用webbench
測試了一下:
2. UDP Ping 程序
本來標準的ping
命令使用ICMP協議來實現的,這裡是為了熟悉一下UDP協議啥的。實驗要求,用UDP來向目標伺服器發出10個ping報文,等到回答後,列印RRT
等信息。根據題目給的server,它會隨機讓某些請求丟失返回,所以,需要有超時處理,用python裡面socket的settimeout()
函數。
from socket import *from time import *if __name__ == __main__: port = 12000 server_addr = localhost client_sock = socket(AF_INET, SOCK_DGRAM) client_sock.settimeout(1) for i in range(1, 11): begin_time = time() try: send_data = a * 64 # 發送64bytes的數據 client_sock.sendto(send_data.encode(), (server_addr, port)) message, address = client_sock.recvfrom(2048) rrt = %.3f % (1000 * (time() - begin_time)) print({} bytes from {}: udp_seq={} time={}ms.format(len(message), address, i, rrt) ) except timeout: print(time out: udp_seq={}.format(i))
運行結果:
3. 郵件客戶
這個實驗要求自己跟mail server來創建TCP連接,然後根據SMTP協議來進行交互,最後來完成發送郵件。這個很容易,可以根據書上的SMTP協議一步一步來就好了。但是,有一些坑,就是AUTH LOGIN
來登陸的時候,需要把username和password都用BASE64來編碼才行。
import sysfrom socket import *if __name__ == __main__: # 用base64編碼的username和password username = eGllYmVpMTEwOEAxNjMuY29t
password = ************************
sender = your email receiver = target email server_addr = smtp.163.com server_port = 25 subject = Gettysburg Address text = Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that nation might live. It is altogether fitting and proper that we should do this.But, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth. client_sock = socket(AF_INET, SOCK_STREAM) client_sock.connect((server_addr, server_port)) client_sock.send(HELO 163.com
) print(client_sock.recv(1024)) client_sock.send(AUTH LOGIN
) print(client_sock.recv(1024)) client_sock.send(username) print(client_sock.recv(1024)) client_sock.send(password) print(client_sock.recv(1024)) client_sock.send(MAIL FROM: <{}>
.format(sender)) print(client_sock.recv(1024)) client_sock.send(RCPT TO: <{}>
.format(receiver)) print(client_sock.recv(1024).decode()) client_sock.send(DATA
) print(client_sock.recv(1024).decode()) message = """From: {}
To: {}
Subject: {}
{}
.
""".format( sender, receiver, subject, text ) client_sock.send(message) print(client_sock.recv(1024).decode()) client_sock.send(quit
) print(client_sock.recv(1024).decode())
運行結果:
4. 多線程Web代理伺服器
之前的CSAPP proxy lab就是做一個web代理伺服器,這個在那個lab的write up裡面有詳細的講解。這個其實就是將web server和web client融合起來,然後做一些中間的對接。
大致的過程是,從客戶端接收到HTTP報文,解析一下,從 request line中的URL解析得到host和URI,然後生成一個request報文,發送給要去的伺服器。從伺服器接收到responce後,直接傳發給客戶端就好了。
import threadimport sysfrom socket import *def request_parser(request): lines = request.split(
) request_line = lines[0].split() header_lines = dict() for each in lines[1:]: if each.strip() != "": key = each[:each.find(:)] value = each[each.find(:)+1:].strip() header_lines[key] = value return [request_line, header_lines]def generate_request(request_header): url = request_header[0][1] (host, uri) = parse_url(url) request_header[0][1] = uri request_header[1][Host] = host request_header[1][User-Agent] = Mozilla/5.0 request_header[1][Accept-Encoding] = gzip, deflate, br request_header[1][Accept-Language] = zh-CN,zh;q=0.9,en;q=0.8 request = .join(request_header[0]) for x in request_header[1]: request +=
{}: {}.format(x, request_header[1][x]) request +=
return requestdef parse_url(url): tokens = [x for x in url.split(/) if x != ] return (tokens[1], / + /.join(tokens[2:]))def getaddress(url): (host, uri) = parse_url(url) port = 80 if len(host.split(:)) == 2: (host, port) = host.split(:) port = int(port) print(url, host, port, uri) return (host, port, uri)def process_connection(conn_sock, addr): request = conn_sock.recv(2048*8).decode() request_header = request_parser(request) (host, port, _) = getaddress(request_header[0][1]) new_request = generate_request(request_header) client_sock = socket(AF_INET, SOCK_STREAM) client_sock.connect((host, port)) client_sock.send(new_request.encode()) responce = client_sock.recv(2048*8) print({}:{} process finished, sending to source.format(addr, port)) conn_sock.send(responce) client_sock.close() conn_sock.close()if __name__ == __main__: proxy_port = int(sys.argv[1]) proxy_socket = socket(AF_INET, SOCK_STREAM) proxy_socket.bind((, proxy_port)) proxy_socket.listen(1000) while True: conn_sock, addr = proxy_socket.accept() thread.start_new_thread(process_connection, (conn_sock, addr))
用telnet
來測試一下:
把之前寫得server放在1818埠跑,然後代理放在1919運行,可以看到向代理髮送GET http://localhost:1818/hello.html HTTP/1.1
後,得到的響應:
HTTP/1.1 200 OKConnection: closeContent-type: text/html<html> <body> <h1> HELLO WORLD! </h1> </body></html>
推薦閱讀:
※在 KVM 中測試 IPv6 網路:第 2 部分
※計算機網路:數據鏈路層
※解決方案——網路傳媒
※python3 爬取半次元cosplay圖片
※網路基本功(二十四):Wireshark抓包實例分析TCP重傳
TAG:計算機網路 | 計算機網路庫羅斯,羅斯著書籍 | 計算機 |