top of page
實習期間完成進度
學習與成長

一、Socket

Socket是一個源自於BSD(柏克萊軟體發行版)的技術,又被稱為Berkeley Socket或「行程的門戶」。它用於TCP/IP網路通訊的應用程式介面(API),讓使用者能夠透過這個介面開發網路應用程式,這正是Socket最顯著的特點。

 

字面上來看,Socket的中譯為「插座」,這個名稱來自於其功能類似電話插座。就像設定電話號碼一樣,只需設定好Socket,任何一台電腦都能透過這個Socket進行通訊。因此,Socket可以被定義為「Socket是網路上的通訊端點,使用者或應用程式只需連接到Socket,就能夠和網路上的任何通訊端點建立連線,Socket之間的通訊方式類似作業系統內的不同程序間的通訊。」。

 

(一)Socket連線方式

網路由客戶端和伺服器端所組成,客戶端依賴伺服器端提供資源。當程式執行時,同時在這兩端建立進程,這兩個進程之間通過Socket進行資料的讀取(read())和寫入(write())。在這之前,必須建立連接(connect()),而Socket就像是位於協定和進程之間的閘道。開發者可以根據需要對Socket進行一些自訂的設定,例如設定傳送緩衝區(send buffer)和最大區段大小(Maximum Segment Size,MSS),通常情況下,MSS的值為1460位元組(bytes)。

二、Implementation

(一)Socket programming over TCP in Python​

Python為進程間的通訊提供Socket,C、Java也同樣支持,不過此次實作使用較熟悉的程式語言Python與平台VS code來完成。

  • Berkeley Socket API庫提供的一些方法概要

socket()-創建一個新的socket,由一整數來標識,並為其分配系統資源。其中需定義要在哪個領域溝通以及傳輸方式,例如下列兩點:

  1. AF_INET、AF_INET6 :兩台主機透過「網路」進行資料傳輸,AF_INET使用IPv4,AF_INET6使用IPv6。

  2. SOCK_STREAM:SOCK_STREAM提供一個序列化的連接導向位元流,可以做位元流傳輸。對應的protocol為TCP。

    1. setsockopt()-子常式,可用來啟用通訊協定層次的除錯、配置緩衝空間、控制逾時等。

    2. bind()-通常在伺服器端使用,可將兩端的socket連接起來,用來告訴對方本地位址。

    3. listen()-通常在伺服器端使用,用來監聽是否有用戶拜訪,規定有多少用戶可連入伺服器端。

    4. connect()-在客戶端使用,為socket分配一個空閒的端口(port),並嘗試建立一TCP連接。

    5. accept()-在伺服器端使用,接受來自客戶端嘗試創建的TCP連接。

    6. sendall()-用來發送資料,可定義發送多大的資料量。

    7. recv()-用來接收發送端的資料,可定義最大緩衝區大小,即最大接收量。

    8. close()-使系統Release分配給socket的資源,並終止TCP連接。

  • 程式編寫說明

TCP SERVER SIDE:

  1. 由使用者輸入SERVER_IP、SERVER_PORT、RUNNING TIME

  2. 創建Socket,定義由IPv4進行溝通以及以TCP方式傳輸資料。

  3. 添加子常式,先設定選項所在的協定層次為SOL_SOCKET,再以SO_SNDBUF來配置緩衝區大小為1000*1024*1024

  4. 以bind()綁定本地host&port

  5. 進入循環,不斷accept()接收客戶端嘗試建立的連接。

  6. 首先recv()接收客戶端準備好了,可以開始傳送檔案的信號。

  7. 使用sendall()發送開始傳輸信號

  8. 打開本地文件並讀取,將文件內容encode()成為bytes,使用sendall()發送內容。

  9. 使用sendall()發送結束傳輸信號

 

TCP CLIENT SIDE:

  1. 由使用者輸入SERVER_IP、SERVER_PORT、RUNNING TIME

  2. 創建Socket,定義由IPv4進行溝通以及以TCP方式傳輸資料。

  3. 添加子常式,先設定選項所在的協定層次為SOL_SOCKET,再以SO_SNDBUF來配置緩衝區大小為1000*1024*1024

  4. 以connect()與欲連線的伺服器端host&port嘗試建立連接

  5. 使用sendall()向伺服器端發送已準備好接收的信號。

  6. 接收recv()來自伺服器端開始傳輸之信號。

  7. 開始循環接收recv()資料,decode()後並逐筆加入暫存列表,直到收到特定結束信號。

  8. 將結束接收的時間減去開始接收的時間,並計算暫存列表中所收到之檔案大小,經下列計算方式可得到Downloads Throughput(吞吐量)

  • 計算方式

[說明]

  1. TOTAL_SIZE * 8 原先收到的檔案大小單位為Bytes,先將其轉換成bit。

  2. ((TOTAL_SIZE // 1460) * 54) * 8 取商數,因小數點後值極小,暫且先忽略不計,將商數乘以封包數目可得到最大檔案大小,最後轉換成bit。

  3. 54 * 8 為上一步暫且忽略的小數點,將其由Bytse轉換為bit。

  4. ((END_TIME – START_TIME) * 0.977) 求得基礎時間並校正時間。

  5. 最後將bits轉換成Mbs

   

  • Multi-threading

TCP連線提供了全雙工服務,這表示當客戶端向服務端發送資料時,服務端也同時可以給客戶端發送資料。Thread,也稱為線程,是Process的一部分,同一個Process中可以包含多個Thread,每個Thread負責不同的功能。在TCP資料傳輸過程中,伺服器端可以進行資料的Downloads,向客戶端發送資料,而客戶端可以進行資料的Uploads,向伺服器端發送資料。這兩種功能可以透過Multi-threading來實現,以充分利用CPU資源,更迅速地完成工作。

 

在Multi-threading中,多個線程同時訪問或修改Global Variable(全域變數)可能導致Process Synchronization(進程同步)的問題,導致共享資料在不同操作之間出現不一致。在Python中,可以使用Mutual Exclusion(互斥鎖)來解決這個問題。當一個線程需要修改共享數據時,它可以使用acquire()函數來鎖定資源,這樣其他線程就無法同時修改它,直到釋放資源的時候(使用release()),其他線程才能再次鎖定該資源。因此,Mutual Exclusion(互斥鎖)確保了多線程環境下數據的正確性。

 

(二)Socket programming over UDP in Python

  • UDP & TCP 之差異

UDP是一種無連接且不可靠的傳輸協議,其主要區別於TCP在於不提供三種實現「可靠傳輸」的機制。當使用UDP協議傳輸數據時,發送端不會主動確認接收端是否已成功接收數據,而是持續不斷地發送數據封包。由於UDP不維護連接狀態和數據序列,可能會導致數據包的「遺失」或「亂序」問題。若我們試圖使用確認/重傳機制或封包排序等方法來解決這些問題,則會違反UDP協議的本質,同時失去了其高效性的優勢。因此,我採用了一種基於時間控制的方法,來實現數據傳輸工作,這種方法通過計算傳輸所花費的時間以及實際接收到的檔案大小,來計算吞吐量(Throughput)。

 

  • Berkeley Socket API庫提供的一些方法概要

socket()-創建一個新的socket,由一整數來標識,並為其分配系統資源。其中需定義要在哪個領域溝通以及傳輸方式,例如下列兩點:

  1. AF_INET、AF_INET6 :兩台主機透過「網路」進行資料傳輸,AF_INET使用IPv4,AF_INET6使用IPv6。

  2. SOCK_DGRAM: SOCK_DGRAM提供的是一個一個的資料包(datagram),對應的protocol為UDP。

  1. bind()-通常在伺服器端使用,可將兩端的socket連接起來,用來告訴對方本地位址。

  2. sendto()-用來發送資料,可定義發送多大的資料量。

  3. recvfrom()-用來接收發送端的資料,可定義最大緩衝區大小,即最大接收量。

  4. close()-使系統Release分配給socket的資源,並終止TCP連接。

  • 程式說明

使用UDP協定進行資料傳輸時,無需事先建立連線,只需在兩端啟動Socket後,即可透過sendto()和recvfrom()函數來交換資料。在傳送資料時,必須明確指定對方Socket的位址信息。

UDP SERVER SIDE:

  1. 由使用者輸入SERVER_IP、SERVER_PORT、RUNNING TIME

  2. 創建Socket,定義由IPv4進行溝通以及以TCP方式傳輸資料。

  3. 以bind()綁定本地host&port

  4. 首先以recvfrom()接收客戶端所要求之接收時間,並記錄發送此消息之客戶地址。

  5. 設定封包大小為1472,進入循環分塊讀取文檔。

  6. 進入倒數循環,使用sendto()發送encode()後的資料,直到秒數歸零。

UDP CLIENT SIDE:

  1. 由使用者輸入SERVER_IP、SERVER_PORT、RUNNING TIME

  2. 創建Socket,定義由IPv4進行溝通以及以TCP方式傳輸資料。

  3. 以sendto()傳送使用者指定之接收時間(RUNNING TIME)

  4. 進入倒數循環,使用recvfrom()循環接收資料,並逐筆存入暫存列表中,直到秒數歸零。

  5. 計算暫存列表中所收到之檔案大小,經計算可得到Downloads Throughput(吞吐量)

(三)Socket programming over HTTP in Python

  • HTTP請求訊息

 

 

 

 

 

 

 

 

 

 

[說明]

  1. 第一行為請求行(request line),三個欄位分別為:方法/要請求的物件URL/HTTP版本欄位,方法欄位可以是GET、POST、HEAD、PUT、DELETE等等。

  2. 第二行指定開啟的host&port

  3. 第三行Content-Type指網頁中的內容類型,決定瀏覽器要以什麼形式、編碼讀取此文件,application/x-www-form-urlencoded是表單默認提交數據的格式,表單數據將被編碼為key/value格式發送到伺服器。

  4. 第四行Transfer-Encoding:chunk,指定接下來的資料將分塊傳輸。

  5. 第五行為請求訊息中的固定格式,2-4狀態行(header line)與第六行資料主體(entity body)中間必須要空一行(CRLF),空行後皆為實體資料。

  6. 第六行為資料主體(entity body)

  7. 補充:每行皆以CRLF做區隔

  • Chunked transfer encoding分塊傳輸編碼

HTTP中存在一種稱為「分塊傳輸」的機制,通常在需要傳輸大量或長度未知的資料時使用,例如文件、動態生成的內容、視訊等等,這種機制允許將資料分成多個不同大小的塊,而當傳輸完最後一個大小為0的塊時,表示整個資料傳輸完成。分塊傳輸方法具備邊讀取資料邊發送的能力,因此有助於提高傳輸效率。此外,它也支持永久性連接,這意味著可以透過單一的TCP連線來傳送所有資料。每個塊的格式通常包括請求訊息、資料本身,以及一個標記為長度0的結束塊(“0\r\n\r\n”)。

  • 程式編寫

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

HTTP SERVER SIDE:

  1. 由使用者輸入SERVER_IP、SERVER_PORT、RUNNING TIME

  2. 創建Socket,定義由IPv4進行溝通以及以TCP方式傳輸資料。

  3. 添加子常式,先設定選項所在的協定層次為SOL_SOCKET,再以SO_SNDBUF來配置緩衝區大小為1000*1024*1024

  4. 以bind()綁定本地host&port,listen()設定最大連接數。

  5. 進入循環,不斷accept()接收客戶端嘗試建立的連接。

  6. 創建HTTP請求訊息,內容如上(1)

  7. 讀取將要傳輸的文檔。

  8. 設定分塊讀取大小為1460,進入循環分段讀取。

  9. 在讀取循環中,發送的資料格式為HEADER + DATA + b’0\r\n\r\n’,使用sendall()將資料發送。

 

HTTP CLIENT SIDE:

  1. 由使用者輸入SERVER_IP、SERVER_PORT、RUNNING TIME

  2. 創建Socket,定義由IPv4進行溝通以及以TCP方式傳輸資料。

  3. 添加子常式,先設定選項所在的協定層次為SOL_SOCKET,再以SO_SNDBUF來配置緩衝區大小為1000*1024*1024。

  4. 以connect()與欲連線的伺服器端host&port嘗試建立連接。

  5. 開始循環接收recv()資料,decode()後並逐筆加入暫存列表。

  6. 將結束接收的時間減去開始接收的時間,計算暫存列表中所收到之檔案大小,經計算可得到Downloads Throughput(吞吐量)

 

三、Analysis

由上一小節中所計算出的吞吐量,可以藉由網路封包剖析器Wireshark來驗證。

(一)TCP

在這次的實作中,我使用TCP協定來傳送一個原始大小約為62 KB的檔案。在用戶端進行計算後,得到了一個Throughput(傳輸速率)為778.38 Mbps,並且整個傳輸過程大約花了1.06秒的時間。透過Wireshark抓包分析,獲得了Average Throughput(平均傳輸速率)的圖表。

 

 

 

 

 

 

 

 

 

在這張圖5、6中,我們可以看到藍點代表每個封包接收到的長度,而幾乎每個封包都幾乎填滿了1460 bytes。黃線表示吞吐量,根據資料傳輸結束時的數值估算為8.09(bit/s),轉換成Mbps單位約為771.52 Mbps。這個數值與我實際測得的值非常接近。

 

根據圖表,我們還可以觀察到整個傳輸過程的總時長約為1.09秒。需要注意的是,Wireshark的時間包括了設定的開始傳輸信號({S})和結束信號({E}),以及回應握手等等,因此時間稍長,而相對的Throughput也略低一些。

(二)Wi-Fi功能測試

一開始,我認為Wi-Fi測試是一個相當複雜且充滿難度的任務。然而,在實際進行測試的過程中,我逐漸體會到所需的不僅僅是技術,更需要豐富的耐心。特別是因為相同的測試步驟需要在三個不同的頻寬帶寬下進行,這三種頻寬分別是2.4GHz、5GHz、和6GHz,或是不同的mod。

 

在每一步驟完成後,都必須仔細核對結果。根據每個測試項目的描述,我首先須模擬相應的測試環境,例如,在DFS(動態頻譜共享)環境下,Wi-Fi是否能正常運作。這意味著我需要創建一個類似DFS環境的情景,然而,實際測試時很難確保能夠恰巧遇到這種特殊環境。

 

在這種情況下,我需要倚賴一些特定的命令。這些命令通常是在研發階段開發的功能,能夠幫助我們模擬出所需的測試環境。藉由執行這些命令,我們可以模擬各種特殊環境,並進行測試以確認Wi-Fi在不同情境下的性能表現,並比較測試前後的結果差異。

 

(三)測試報告撰寫

最後,我需要向客戶和研發團隊提交一份測試報告。這份報告詳細描述了每個測試項目的流程、確認結果的方法以及是否通過了測試。起初,我在第一次測試和報告的撰寫方面並不細心,經常會遺漏測試項目,或者忽略確認步驟,結果差不多全部被退回來。不過,幸運的是,我的同事非常耐心地指導我,他們在每個有問題的測試流程旁邊都寫下了問題所在,這讓我可以根據這些建議來修改我的測試流程和報告撰寫方式。通過這些建議,我才發現我之前漏測了很多細節。因為在某些情況下,同一環境可能需要確認多個事項,但我只注意到其中一個。這個專案讓我對Wi-Fi有了更深入的了解,同時也讓我明白了測試需要極大的耐心。

​圖1、Process communicating through TCP sockets

​圖2、TCP資料流程示意圖

​公式1

​圖3、UDP建立流程示意圖

​表1

​圖4、HTTP建立流程示意圖

​圖5、Average Throughtput

​圖6、Average Throughtput(放大)

​圖7、Wireshark抓包(擷取訊號1)

​圖8、Wireshark抓包(擷取訊號2)

​圖9、測試項目及步驟

Socket
WiFi
bottom of page