深入解析進程間通信與Socket原理:從理論到TypeScript實戰

文章目錄

  • 一、進程中如何通信
    • 1.1 管道
      • 1.1.1 核心特性
      • 1.1.2 缺點
      • 1.1.3 匿名管道與命名管道的對比
    • 1.2 信號
      • 1.2.1 核心特性
      • 1.2.2 缺點
      • 1.2.3 信號分類對比
    • 1.3 消息隊列
      • 1.3.1 核心特性
      • 1.3.2 缺點
    • 1.4 共享內存
      • 1.4.1 核心特性
      • 1.4.2 缺點
    • 1.5 信號量
      • 1.5.1 核心特性
      • 1.5.2 缺點
  • 二、Socket
    • 2.1 Socket原理
      • 2.1.1 什么是Socket
      • 2.1.2 網絡進程如何通信
      • 2.1.3 Sokcet如何通信
    • 2.2 TCP/IP協議
      • 2.2.1 概念
      • 2.2.2 TCP數據報結構
    • 2.3 連接建立(三次握手)
      • 2.3.1 建立過程
      • 2.3.2 關鍵問題
        • 為什么是三次握手,而不是兩次四次?
    • 2.4 斷開連接(四次揮手)
      • 2.4.1 斷連過程
      • 2.4.2 關鍵問題
        • 為什么是四次揮手,不能是三次揮手
        • 為什么不能是兩次揮手
  • 三、TS通過Socket實現聊天室基礎功能
    • 3.1 服務端實現原理
    • 3.2 客戶端實現原理

一、進程中如何通信

重要的進程間通信(不同進程之間傳播或交換信息)方式分為六種

管道、信號、消息隊列、共享內存、信號量、socket。其中前五種主要用于一臺主機之中的各個進程之間的通信,socket套接字通信主要用于網絡之中不同主機之間的通信

1.1 管道

管道(Pipe)其本質是由內核維護的一段內存緩存區。一個進程向該緩存寫入數據,另一個進程從中讀取數據,形成單向數據流。管道傳輸的數據是無格式的字節流,且受內核緩沖區大小的限制。

1.1.1 核心特性

  1. 單向通信
    匿名管道僅支持單向數據傳輸(一端寫入,另一端讀取),若需雙向通信,必須建立兩條獨立的管道。這種單向性體現了其半雙工通信的特性。
  2. 親緣關系依賴
    • 匿名管道:通常用于父子進程或兄弟進程等有親緣關系的進程間通信。子進程通過繼承父進程的文件描述符訪問管道。
    • 命名管道(FIFO):通過文件系統中的路徑標識,允許無親緣關系的進程通過打開同一路徑進行通信,突破了匿名管道的親緣限制。
  3. 阻塞與非阻塞模式
    • 默認情況下,讀進程在管道無數據時會阻塞等待;寫進程在緩沖區滿時也會阻塞,直到有空間釋放。
    • 可通過fcntl函數設為非阻塞模式:讀空管道時直接返回EAGAIN錯誤,寫滿時丟棄數據或部分寫入。
  4. 生命周期管理
    • 匿名管道隨進程終止自動銷毀。
    • 命名管道需手動刪除其文件路徑(如unlink),否則會持久存在于文件系統中。
  5. 容量限制
    內核緩沖區大小固定(通常為4KB~64KB)。若寫入速度遠超讀取速度,寫進程可能被長時間阻塞,需設計合理的讀寫協同邏輯。

1.1.2 缺點

  1. 半雙工通信的天然限制
    匿名管道僅支持單向數據傳輸,雙向通信需額外建立一條管道,增加了資源管理和協調的復雜度。
  2. 讀寫阻塞的強依賴性
    若管道內的數據未被讀進程及時消費,寫進程會因緩沖區滿而阻塞,直到讀進程取走數據。這種強同步機制可能導致進程間死鎖(如雙方同時等待對方讀寫)。

1.1.3 匿名管道與命名管道的對比

特性匿名管道命名管道(FIFO)
創建方式pipe()系統調用mkfifo()命令或函數
通信方向半雙工(單向,需雙向則建兩條)半雙工,但支持多進程讀寫
進程關系僅限親緣進程任意進程(通過文件路徑訪問)
持久性隨進程結束銷毀需手動刪除文件路徑

注意事項

  • 數據原子性:若單次寫入數據量小于PIPE_BUF(通常512B~4KB),內核保證寫入的原子性;反之可能被拆分。
  • 同步問題:共享內存需配合信號量,而管道自身通過阻塞機制隱式同步,但仍需注意讀寫端協調。
  • 性能瓶頸:高頻大數據傳輸時,管道可能因拷貝開銷和容量限制成為瓶頸,此時可改用共享內存。

1.2 信號

信號(Signal)是輕量級異步通知機制,由內核或進程向目標進程發送特定事件的通知。其本質是預定義的事件編號(如SIGINT對應終端中斷),用于觸發進程的默認行為或自定義處理邏輯。

信號是進程間通信中最簡單、最直接的異步通知機制,適用于事件驅動、進程控制等場景。但其設計初衷是“通知”而非“數據傳輸”,因此復雜交互需結合其他IPC機制(如管道、共享內存)。

1.2.1 核心特性

  1. 異步通知
    信號在任意時刻可中斷進程當前操作,直接跳轉到信號處理函數執行,與進程的執行流無關。
  2. 預定義類型
    系統定義了約30種標準信號,編號范圍通常為1~31。
  3. 處理方式靈活性
    • 默認行為:終止進程、忽略信號、暫停進程。
    • 自定義處理:通過signal()sigaction()注冊用戶函數(如處理SIGINT實現優雅退出)。
  4. 生命周期
    • 生成:由內核、其他進程或終端觸發。
    • 傳遞:內核將信號加入目標進程的信號隊列,等待進程調度處理。
    • 處理:進程從內核態返回用戶態時,檢查并執行信號處理函數。

1.2.2 缺點

  1. 信息傳遞能力弱
    信號僅能傳遞事件編號,無法攜帶額外數據(實時信號如SIGRTMIN可攜帶少量信息,但需復雜處理)。
  2. 信號丟失與覆蓋
    • 同類非實時信號多次到達時,可能被合并為一次。
    • 處理函數執行期間,新到達的同類型信號可能被阻塞。
  3. 處理函數的安全限制
    信號處理函數需為可重入函數,避免使用非線程安全操作。
  4. 實時性受限
    非實時信號無優先級,內核可能延遲傳遞,無法保證嚴格時序。

1.2.3 信號分類對比

類型非實時信號(標準信號)實時信號(SIGRTMIN~SIGRTMAX
編號范圍1~3134~64(依系統不同)
隊列機制不排隊,多次發送可能合并支持排隊,按順序處理
數據攜帶不支持可通過sigqueue()附加數據
優先級支持信號優先級

注意事項

  1. 避免處理函數阻塞
    信號處理函數應快速完成,復雜邏輯可通過標記位在主線程序處理。
  2. 信號屏蔽與競態條件
    • 使用sigprocmask屏蔽關鍵代碼段的信號,防止處理函數中斷敏感操作。
    • 處理共享資源時需考慮信號引發的競態問題。
  3. 系統調用中斷
    信號可能中斷阻塞的系統調用,需檢查錯誤碼EINTR并重試。
  4. 信號與多線程
    多線程程序中,信號可能由任意線程處理,建議統一由主線程接管。

1.3 消息隊列

消息隊列(Message Queue)其本質是由內核維護的鏈表結構,允許進程以消息塊(結構化數據)的形式異步通信。相比管道,消息隊列支持更靈活的格式和隨機讀取,適用于頻繁或結構化的數據交換場景。

消息隊列彌補了管道在結構化數據和異步通信上的不足,適用于中等頻率、結構化消息交換的場景。但其性能瓶頸(上下文切換與拷貝開銷)使其難以應對超高頻需求,此類場景可優先考慮共享內存或Unix域套接字。

1.3.1 核心特性

  1. 異步非阻塞通信
    • 發送進程將消息寫入隊列后立即返回,無需等待接收進程響應。
    • 接收進程可主動拉取消息,若隊列為空可選擇阻塞或非阻塞模式。
  2. 結構化消息
    • 消息包含類型標識數據體,雙方需約定格式。
    • 支持按消息類型讀取,而非嚴格FIFO順序。
  3. 內核持久性
    • 消息隊列獨立于進程存在,進程終止后消息仍保留在內核中。
    • 可通過權限控制限制其他進程訪問。
  4. 原子性保證
    • 單次寫入的消息若小于MSGMAX,內核保證原子性)。
  5. 多進程共享
    任意進程(需權限)均可通過隊列標識符訪問同一隊列,支持多對多通信。

1.3.2 缺點

  1. 消息大小限制
    單條消息長度受內核參數MSGMAX限制(默認約8KB),超出需分片處理。
  2. 性能開銷
    • CPU上下文切換:每次讀寫需通過系統調用進入內核態,頻繁操作時開銷顯著。
    • 數據拷貝:消息從用戶空間拷貝到內核隊列,再拷貝到接收方用戶空間,高頻場景效率低。
  3. 隊列容量限制
    隊列總大小受內核參數MSGMNB限制(默認約16KB~64KB),寫滿后發送進程默認阻塞。
  4. 復雜性
    需自行處理消息類型匹配、分片重組、隊列滿/空等問題,開發復雜度較高。

注意事項

  1. 消息類型設計
    • 類型值應明確區分用途(如正數用于請求,負數用于響應)。
    • 避免類型沖突,建議使用枚舉或宏定義。
  2. 隊列泄露防護
    • 確保進程退出前釋放隊列。
    • 通過ipcs -qipcrm命令管理殘留隊列。
  3. 超長消息處理
    • 若消息長度超過MSGMAX,需在應用層分片發送,接收端重組。
  4. 信號量同步(可選)
    • 多進程競爭讀寫時,可結合信號量實現互斥鎖,避免消息覆蓋。

1.4 共享內存

共享內存(Shared Memory)是進程間通信(IPC)中速度最快的機制,其本質是由內核分配的一段物理內存區域,被多個進程映射到各自的虛擬地址空間中。進程通過直接讀寫該內存區域實現數據交互,無需內核中轉或數據拷貝,從而極大提升通信效率。

共享內存是進程間通信的性能天花板,尤其適合對吞吐量和延遲敏感的場景。但其“直接訪問”的特性如同一把雙刃劍,在提供極致速度的同時,也要求開發者嚴格管理同步與數據一致性。結合信號量、互斥鎖等機制,可構建高效且穩定的多進程協作系統。

1.4.1 核心特性

  1. 零拷貝高效性
    數據直接在共享內存區域讀寫,避免了管道、消息隊列等機制中用戶態與內核態間的數據拷貝開銷。
  2. 虛擬地址映射
    • 每個進程通過頁表將共享內存映射到自身虛擬地址空間的不同位置。
    • 進程通過虛擬地址訪問共享內存,由MMU(內存管理單元)完成虛實地址轉換。
  3. 多進程并發訪問
    多個進程可同時映射同一共享內存區域,實現高速數據共享,但需配合同步機制(如信號量、互斥鎖)避免競爭。
  4. 內核持久性
    • 共享內存獨立于進程存在,進程退出后仍保留(除非顯式刪除)。
    • 通過shmctl(IPC_RMID)銷毀或系統重啟后清除。

1.4.2 缺點

  1. 同步復雜度高

    需額外機制(如信號量)協調讀寫

  2. 安全隱患

    惡意進程可能改寫數據

  3. 生命周期管理

    需顯示刪除避免內存泄漏

注意事項

  1. 內存對齊與訪問
    • 確保數據結構對齊,避免不同進程因編譯差異導致的內存解釋錯誤。
  2. 緩存一致性
    • 多核CPU中,共享內存可能引發緩存一致性問題,需通過內存屏障或原子操作保證可見性。
  3. 安全與權限控制
    • 設置嚴格的IPC權限(如0666僅允許同組用戶訪問),防止未授權進程篡改數據。
  4. 資源泄漏防護
    • 確保進程退出前調用shmdt()shmctl(),避免內存段永久占用。

1.5 信號量

信號量(Semaphore)是進程間或線程間同步與互斥的核心工具,其本質是由內核維護的整型計數器,用于協調多個執行單元對共享資源的訪問。信號量的核心思想是通過P(等待)和V(釋放)操作,實現資源的原子性分配與釋放,避免競態條件(Race Condition)。

信號量是解決并發編程中同步與資源分配問題的基石,其靈活性使其適用于從簡單互斥到復雜資源管理的廣泛場景。然而,信號量的低級特性也要求開發者對并發邏輯有深刻理解,避免死鎖、饑餓等典型問題。在實際開發中,可優先使用高層抽象(如線程池、無鎖隊列),但在需要精細控制時,信號量仍是不可替代的工具。

1.5.1 核心特性

  1. 計數器抽象
    • 信號量值表示當前可用資源數量:
      • 正值:剩余可用資源數。
      • 零值:資源已被完全占用,請求者需等待。
      • 負值:絕對值表示等待該資源的進程/線程數。
  2. 原子操作
    • P操作(Proberen,嘗試獲取):
      若信號量值 > 0,則減1并繼續;否則阻塞等待。
    • V操作(Verhogen,釋放資源):
      信號量值加1,并喚醒一個等待進程。
  3. 分類
    • 二進制信號量:值范圍為0或1,等同于互斥鎖(Mutex)。
    • 計數信號量:值范圍≥0,表示資源池容量(如連接池限制)。
  4. 內核與用戶態實現
    • System V信號量:內核維護,支持跨進程同步(如semget())。
    • POSIX信號量:可位于共享內存中,支持進程或線程級同步(如sem_init())。

1.5.2 缺點

  1. 死鎖風險

    錯誤使用可能導致進程永久阻塞

  2. 優先級反轉

    低優先級進程占用資源,高優先級進程饑餓

  3. 復雜性

    需手動管理信號量創建、初始化和銷毀

注意事項

  1. 死鎖預防
    • 順序一致性:所有進程以相同順序獲取信號量。
    • 超時機制:使用sem_timedwait()避免無限阻塞。
  2. 信號量泄漏
    • System V信號量需顯式調用semctl(IPC_RMID)刪除。
    • POSIX命名信號量需sem_unlink()防止殘留。
  3. 原子性與錯誤處理
    • 確保P/V操作的原子性(如SEM_UNDO標志應對進程崩潰)。
    • 檢查sem_wait()返回值,處理EINTR(信號中斷)等錯誤。
  4. 性能優化
    • 避免過度使用信號量,高頻場景可結合自旋鎖或無鎖數據結構。

二、Socket

2.1 Socket原理

2.1.1 什么是Socket

在計算機通信領域,socket被翻譯為套接字,他是計算機之間進行通信的一種約定或一種方式。通過socket這種約定,一臺計算機可以接收其他計算機的數據,也可以向其他計算機發送數據。

socket起源于Unix,而Unix/Linux基本哲學之一就是”一切皆文件“,都可以用”打開open -→讀寫write/read–> 關閉close”模式來操作。

我的理解就是Socket就是該模式的一個實現:即socket是一種特殊的文件,一些sokcet函數就是對其他進行的操作(讀寫IO、打開、關閉)。

Socket()函數返回一個整型的Socket描述符,隨后的連接建立、數據傳輸等操作都是通過該Socket實現的。

2.1.2 網絡進程如何通信

我們要理解網絡中進程如何通信,得解決兩個問題:
  a、我們要如何標識一臺主機,即怎樣確定我們將要通信的進程是在那一臺主機上運行。
  b、我們要如何標識唯一進程,本地通過pid標識,網絡中應該怎樣標識?
解決辦法:
  a、TCP/IP協議族已經幫我們解決了這個問題,網絡層的“ip地址”可以唯一標識網絡中的主機
  b、傳輸層的“協議+端口”可以唯一標識主機中的應用程序(進程),因此,我們利用三元組(ip地址,協議,端口)就可以標識網絡的進程了,網絡中的進程通信就可以利用這個標志與其它進程進行交互

2.1.3 Sokcet如何通信

現在,我們知道了網絡中進程如何進行通信,即利用三元組d[ip地址,協議,端口]可以進行網絡間通信了,那我們應該怎么實現?因此,我們sokcet應運而生,他就是利用三元組解決網絡通信的一個中間件工具,就目前而言,幾乎所有應用程序都是采用socket。

socket通信的數據傳輸方式常用的有兩種:

  • SOCK_STREAM:表示面向連接的數據傳輸方式。數據可以準確無誤的達到另一臺計算機,如果損壞或丟失,可以重新發送,但效率相對較慢。常見的http協議就使用了SOCK_STREAM數據傳輸,因為要確保數據的正確性,否則網頁不能正常解析。
  • OCK_DGRAM:表示無連接的數據傳輸方式。計算機只管傳輸數據,不作數據校驗,如果數據在傳輸中損壞,或者沒有到達另一臺計算機,是沒有辦法補救的。也就是說,數據錯了就錯了,無法重傳。因為 SOCK_DGRAM 所做的校驗工作少,所以效率比 SOCK_STREAM 高。

2.2 TCP/IP協議

2.2.1 概念

TCP/IP提供點對點的連接機制,將數據應該如何封裝、定址、傳輸、路由以及在目的地如何接收,都加以標準化。它將軟件通信過程抽象化為四個抽象層,采取協議堆棧的方式分別實現出不同通信協議。協議族下的各種協議,依其功能不同,被分別歸屬到四個層次結構中,常被視為是簡化的七層OSI模型。

  • 四層結構(由下至上)

    1. 網絡接口層(鏈路層):負責物理介質的數據幀傳輸(如以太網協議)。
    2. 網絡層(IP層):通過IP地址實現主機間的邏輯尋址和路由(如IP協議)。
    3. 傳輸層:提供端到端的數據傳輸服務(如TCP、UDP協議)。
    4. 應用層:面向用戶提供具體服務(如HTTP、FTP協議)。
  • TCP(傳輸控制協議)是面向連接的、可靠的、基于字節流的傳輸層協議。其核心特性包括:

    • 三次握手建立連接:確保雙方通信能力及初始序列號同步。

    • 四次揮手釋放連接:保證數據完整性并優雅關閉雙工通道。

    • 超時重傳、流量控制、擁塞控制:保障數據傳輸的可靠性。

2.2.2 TCP數據報結構

TCP協議及數據結構_tcp數據包結構-CSDN博客

TCP報文頭部固定20字節(不含選項字段),關鍵字段如下:

  1. 源端口與目的端口(各16位):標識發送方和接收方的應用進程。
  2. 序號(Seq,32位):本報文段發送數據的第一個字節的編號。
  3. 確認號(Ack,32位):期望接收的下一個字節的編號,Ack = 收到的Seq + 數據長度 + 1(若數據長度為0,如SYN/FIN標志位,視為占1個序號)。
  4. 數據偏移(4位):TCP首部長度(以4字節為單位)。
  5. 標志位(6位)
    • URG:緊急指針有效(需配合緊急指針字段使用)。
    • ACK:確認號有效(建立連接后所有報文必須置1)。
    • PSH:接收方應立即將數據提交應用層。
    • RST:強制斷開連接(異常終止)。
    • SYN:發起連接請求(同步序列號)。
    • FIN:請求終止連接。
  6. 窗口大小(16位):接收方當前可接受的數據量(流量控制)。
  7. 校驗和(16位):確保數據完整性。

2.3 連接建立(三次握手)

2.3.1 建立過程

TCP 的三次握手和四次揮手-CSDN博客

客戶端調用 socket() 函數創建套接字后,因為沒有建立連接,所以套接字處于CLOSED狀態;服務器端調用 listen() 函數后,套接字進入LISTEN狀態,開始監聽客戶端請求
這時客戶端發起請求:

  1. 當客戶端調用 connect() 函數后,TCP協議會組建一個數據包,并設置 SYN 標志位,表示該數據包是用來建立同步連接的。同時生成一個隨機數字 1000,填充“序號(Seq)”字段,表示該數據包的序號。完成這些工作,開始向服務器端發送數據包,客戶端就進入了SYN-SEND狀態。
  2. 服務器端收到數據包,檢測到已經設置了 SYN 標志位,就知道這是客戶端發來的建立連接的“請求包”。服務器端也會組建一個數據包,并設置 SYN 和 ACK 標志位,SYN 表示該數據包用來建立連接,ACK 用來確認收到了剛才客戶端發送的數據包
    服務器生成一個隨機數 2000,填充“序號(Seq)”字段。2000 和客戶端數據包沒有關系。
    服務器將客戶端數據包序號(1000)加1,得到1001,并用這個數字填充“確認號(Ack)”字段。
    服務器將數據包發出,進入SYN-RECV狀態
  3. 客戶端收到數據包,檢測到已經設置了 SYN 和 ACK 標志位,就知道這是服務器發來的“確認包”。客戶端會檢測“確認號(Ack)”字段,看它的值是否為 1000+1,如果是就說明連接建立成功。
    接下來,客戶端會繼續組建數據包,并設置 ACK 標志位,表示客戶端正確接收了服務器發來的“確認包”。同時,將剛才服務器發來的數據包序號(2000)加1,得到 2001,并用這個數字來填充“確認號(Ack)”字段。
    客戶端將數據包發出,進入ESTABLISED狀態,表示連接已經成功建立。
  4. 服務器端收到數據包,檢測到已經設置了 ACK 標志位,就知道這是客戶端發來的“確認包”。服務器會檢測“確認號(Ack)”字段,看它的值是否為 2000+1,如果是就說明連接建立成功,服務器進入ESTABLISED狀態。
    至此,客戶端和服務器都進入了ESTABLISED狀態,連接建立成功,接下來就可以收發數據了。

2.3.2 關鍵問題

為什么是三次握手,而不是兩次四次?
  • 三次握手才可以阻止重復歷史連接的初始化(主要原因)
  • 三次握手才可以雙方同步的初始序列號
  • 三次握手才可以避免浪費資源
  1. 阻止重復歷史連接的初始化(主要原因)

    • 在兩次握手的情況下,服務端沒有中間狀態給客戶端來阻止歷史連接,導致服務端可能建立一個歷史連接,造成資源浪費。

    • 三次握手已滿足上述所有需求,額外增加握手次數(如四次)會引入不必要的延遲,且無法進一步解決核心問題。三次是理論上的最小安全交互次數。

  2. 雙方同步的初始序列號

    當客戶端發送攜帶「初始序列號」的 SYN 報文的時候,需要服務端回一個 ACK 應答報文,表示客戶端的 SYN 報文已被服務端成功接收,那當服務端發送「初始序列號」給客戶端的時候,依然也要得到客戶端的應答回應,這樣一來一回,才能確保雙方的初始序列號能被可靠的同步。

  3. 避免資源浪費

    如果只有「兩次握手」,當客戶端的 SYN 請求連接在網絡中阻塞,客戶端沒有接收到 ACK 報文,就會重新發送 SYN ,由于沒有第三次握手,服務器不清楚客戶端是否收到了自己發送的建立連接的 ACK 確認信號,所以每收到一個 SYN 就只能先主動建立一個連接,這會造成什么情況呢?

    如果客戶端的 SYN 阻塞了,重復發送多次 SYN 報文,那么服務器在收到請求后就會建立多個冗余的無效鏈接,造成不必要的資源浪費。

2.4 斷開連接(四次揮手)

2.4.1 斷連過程

TCP連接的釋放需要四次揮手,其本質是雙向通信的全雙工特性決定的:每個方向必須獨立關閉。以下為詳細過程(以客戶端主動關閉為例):

  1. 第一次揮手(FIN)
    客戶端調用close()后,發送FIN報文(FIN=1),進入FIN_WAIT_1狀態,表示客戶端不再發送數據,但仍可接收數據。
  2. 第二次揮手(ACK)
    服務端收到FIN后,立即回復ACK報文,進入CLOSE_WAIT狀態。此時服務端可能仍有未發送完的數據,客戶端收到ACK后進入FIN_WAIT_2狀態。
  3. 第三次揮手(FIN)
    當服務端數據發送完畢,準備好關閉連接時,發送FIN報文,進入LAST_ACK狀態,表示服務端不再發送數據。
  4. 第四次揮手(ACK)
    客戶端收到FIN后,回復ACK報文,進入TIME_WAIT狀態,等待2MSL(Maximum Segment Lifetime,報文最大生存時間)后關閉連接。服務端收到ACK后立即進入CLOSED狀態。

簡述TCP的三次握手和四次揮手_三次握手四次揮手簡述-CSDN博客

2.4.2 關鍵問題

為什么是四次揮手,不能是三次揮手
  • 全雙工通信的特性
    TCP連接是全雙工的,雙方需獨立關閉自己的數據通道。客戶端發送FIN僅表示其不再發送數據(但可接收),服務端的ACK僅確認收到FIN。服務端的FIN需等待其數據發送完畢后再發送,因此ACK和FIN不能合并為一次。
  • 數據完整性保障
    若服務端收到FIN后立即合并ACK與FIN(變為三次揮手),可能丟失未傳輸完的數據。分開發送確保服務端有足夠時間處理剩余數據。
  • 可靠性設計
    客戶端最后的TIME_WAIT狀態(等待2MSL)有兩個作用:
    • 確保服務端收到最后的ACK。若ACK丟失,服務端會重傳FIN,客戶端可再次響應。
    • 防止舊連接的延遲報文干擾新連接。
為什么不能是兩次揮手
  • 服務端未確認自身數據是否已發送完畢。
  • 客戶端無法確認服務端是否收到最終ACK,可能造成服務端持續等待。

三、TS通過Socket實現聊天室基礎功能

3.1 服務端實現原理

  1. 核心結構
  • 使用Node.js的net模塊創建TCP服務器

  • 定義了Room類型管理聊天室信息:

    type Room = {roomName: string;   // 房間名稱port: number;       // 監聽端口users: [string, net.Socket][]; // 用戶列表([客戶端地址, Socket對象])
    };
    
  1. 啟動流程

image-20250429183123624

  1. 關鍵功能實現

    • 客戶端連接管理:使用serverConnectEvent處理新連接

      • 記錄客戶端地址(client.remoteAddress:client.remotePort)

      • 存儲Socket對象到用戶列表

    • 消息廣播機制

      private broadcast(content: string) {for (const [_, userClient] of this.room.users) {if (userClient.writable) {userClient.write(content); // 向所有客戶端發送消息}}
      }
      
  2. 服務端實現示例

    // src/server/server.ts
    import * as net from 'net';
    import * as readline from 'readline';// 定義房間類型
    type Room = {roomName: string;port: number;users: [string, net.Socket][];
    };// 定義服務器類
    class MyTCPServer {private server: net.Server;private room: Room;constructor(port: number = 8080, roomName: string = '大廳') {this.room = {roomName,port,users: []};this.server = net.createServer(this.serverConnectEvent.bind(this));this.initServer();this.listenForShutdown()}private initServer() {this.server.listen(this.room.port, () => {console.log(`服務器已啟動,監聽端口 ${this.room.port}`);});this.server.on('close', () => {console.log('服務器已關閉');});}private serverConnectEvent(client: net.Socket) {console.log(`客戶端已連接: ${client.remoteAddress}:${client.remotePort}`);//設計用戶ID為標識const clientId = `${client.remoteAddress}:${client.remotePort}`;// 添加客戶端到用戶列表this.room.users.push([`${client.remoteAddress}:${client.remotePort}`, client]);client.on('data', (chunk) => {const content = chunk.toString();if( content === 'kick') {this.disconnectClient(client)} else {this.broadcast( `${clientId}: ${content}`, client)}});client.on('end', () => {console.log(`客戶端已斷開連接: ${client.remoteAddress}:${client.remotePort}`);this.removeClient(client);});client.on('error', (err) => {console.error(`客戶端發生錯誤: ${err.message}`);this.removeClient(client);});}private broadcast(content: string, sender: net.Socket) {for (const [_, userClient] of this.room.users) {if (userClient.writable && userClient !== sender) {userClient.write(content);}}}private removeClient(client: net.Socket) {const index = this.room.users.findIndex(([_, userClient]) => userClient === client);if (index !== -1) {this.room.users.splice(index, 1);}}//斷開客戶端連接private disconnectClient(client: net.Socket) {const clientInfo = `${client.remoteAddress}:${client.remotePort}`;console.log(`正在斷開客戶端連接: ${clientInfo}`);client.end();this.removeClient(client);}//關閉服務器private listenForShutdown() {const rl = readline.createInterface({input: process.stdin,output: process.stdout});rl.question('輸入 "shutdown" 關閉服務器: ', (input: string) => {if (input === 'shutdown') {// 關閉所有客戶端連接for (const [_, userClient] of this.room.users) {userClient.destroy(); // 強制斷開客戶端}//關閉服務器this.server.close(() => {console.log('服務器已關閉');});rl.close();} else {rl.close();this.listenForShutdown();}});}
    }// 啟動服務器
    new MyTCPServer();
    

3.2 客戶端實現原理

  1. 核心結構

    • 使用net.Socket連接服務器
    • 通過readline模塊實現控制臺的輸入
    • 事件驅動架構
  2. 工作流程

  3. 關鍵功能實現

    • 輸入處理

      private readInput() {this.rl.question('請輸入消息: ', (input) => {this.client.write(input);this.readInput(); // 遞歸調用實現持續輸入});
      }
      
    • 消息接收:

      this.client.on('data', (chunk) => {const content = chunk.toString();console.log(content); // 直接打印原始消息
      });
      
  4. 客戶端實現示例

    // src/client/client.ts
    import * as net from 'net';
    import * as readline from 'readline';// 定義客戶端類
    class MyTCPClient {private client: net.Socket;private rl: readline.Interface;constructor() {this.client = new net.Socket();this.rl = readline.createInterface({input: process.stdin,output: process.stdout});this.connectToServer();}private connectToServer() {this.client.connect(8080, () => {console.log('已連接到服務器');this.readInput();});this.client.on('data', (chunk) => {const content = chunk.toString();console.log('你接收到了一條消息\n' + content);});this.client.on('end', () => {console.log('與服務器的連接已斷開');this.rl.close();});this.client.on('error', (err) => {console.error(`與服務器的連接發生錯誤: ${err.message}`);this.rl.close();});}private readInput() {this.rl.question('請輸入消息(輸入"exit"斷開連接):\n ', (input) => {if( input === 'exit') {this.client.end();this.rl.close();} else {this.client.write(input, (err) => {if (err) {console.log('發送消息失敗');} else {console.log('你發出了一條消息\n' + input);}this.readInput();});}});}
    }// 啟動客戶端
    new MyTCPClient();
    

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/79777.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/79777.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/79777.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

力扣-hot100(旋轉圖像)

48. 旋轉圖像 中等 給定一個 n n 的二維矩陣 matrix 表示一個圖像。請你將圖像順時針旋轉 90 度。 你必須在 原地 旋轉圖像,這意味著你需要直接修改輸入的二維矩陣。請不要 使用另一個矩陣來旋轉圖像。 示例 1: 輸入:matrix [[1,2,3],[4…

Docker編排工具---Compose的概述及使用

目錄 一、Compose工具的概述 二、Compose的常用命令 1、列出容器 2、查看訪問日志 3、輸出綁定的公共端口 4、重新構建服務 5、啟動服務 6、停止服務 7、刪除已停止服務的容器 8、創建和啟動容器 9、在運行的容器中執行命令 10、指定一個服務啟動容器的個數 11、其…

C25-數組應用及練習

第一題 題目: 代碼 #include <stdio.h> int main() {//數組及相關數據定義int arr[10];int i;//基于循環的數組數據輸入for(i0;i<10;i){arr[i]i;}//基于循環的數組數據輸出for(i9;i>0;i--){printf("%d ",arr[i]);}return 0; }結果 第二題 題目 代碼 …

網絡安全怎么入門?快速了解

網絡安全是一個快速發展的領域&#xff0c;入門需要系統化的學習和實踐。以下是適合零基礎或轉行者的分階段學習路徑&#xff0c;涵蓋必備知識、學習資源、實戰方法和職業方向&#xff1a; 一、基礎階段&#xff08;1-3個月&#xff09; 1. 掌握核心基礎知識 計算機網絡&#…

express 怎么搭建 WebSocket 服務器

一&#xff1a;使用 express-ws var express require(express); var app express(); var expressWs require(express-ws)(app);app.use(function (req, res, next) {console.log(middleware);req.testing testing;return next(); });app.get(/, function(req, res, next){…

【AI論文】SuperEdit:修正并促進基于指令的圖像編輯的監督信號

摘要&#xff1a;由于手動收集準確的編輯數據存在挑戰&#xff0c;現有的數據集通常使用各種自動化方法構建&#xff0c;導致編輯指令和原始編輯圖像對之間不匹配導致監督信號出現噪聲。 最近的研究試圖通過生成更高質量的編輯圖像、在識別任務上進行預訓練或引入視覺語言模型&…

關于大疆紅外圖片提取溫度方法 python 方法

思路 紅外圖片需要是黑白圖片 提取紅外圖片最高和最低溫度 溫度圖例 根據最高溫度31.2攝氏度 最低溫度19.9攝氏度 那中間的值在 0到255 之間 那有這個值之后。就可以獲取到圖片里面 每個點或者面的值 實現方式 def find_Gray(self, t_max, t_min, c_temp):"""…

金融小知識

&#x1f4c9; 一、“做空”是啥&#xff1f; 通俗說法&#xff1a;押“它會跌”&#xff0c;賺錢&#xff01; ? 舉個例子&#xff1a; 有一天老王的包子漲價到 10 塊一個&#xff0c;張三覺得這價格肯定撐不住&#xff0c;未來會跌到 5 塊。于是他&#xff1a; 向朋友借了…

JavaScript 數據存儲全攻略:從 Cookie 到 IndexedDB

1. Cookie&#xff1a;傳統的輕量級存儲 Cookie 是最早的客戶端存儲解決方案之一&#xff0c;最初設計用于服務器和客戶端之間的狀態保持。 基本用法 javascript 復制 下載 // 設置cookie document.cookie "usernameJohnDoe; expiresThu, 18 Dec 2025 12:00:00 UTC…

Leetcode 刷題記錄 09 —— 鏈表第三彈

本系列為筆者的 Leetcode 刷題記錄&#xff0c;順序為 Hot 100 題官方順序&#xff0c;根據標簽命名&#xff0c;記錄筆者總結的做題思路&#xff0c;附部分代碼解釋和疑問解答&#xff0c;01~07為C語言&#xff0c;08及以后為Java語言。 01 合并 K 個升序鏈表 /*** Definitio…

如何利用 Elastic Load Balancing 提升應用性能與可用性?

當今云計算的快速發展中&#xff0c;隨著應用需求的增加&#xff0c;如何確保系統能夠高效、穩定地處理不斷增長的流量成為了每個技術團隊關注的焦點。Elastic Load Balancing&#xff08;ELB&#xff09;作為一種強大的工具&#xff0c;能夠幫助開發者和運維人員輕松應對流量波…

Word如何制作三線表格

1.需求 將像這樣的表格整理成論文中需要的三線表格。 2.直觀流程 選中表格 --> 表格屬性中的邊框與底紋B --> 在設置中選擇無&#xff08;重置表格&#xff09;–> 確定 --> 選擇第一行&#xff08;其實是將第一行看成獨立表格了&#xff0c;為了設置中線&…

JVM的雙親委派模型

引言 Java類加載機制中的雙親委派模型通過層層委托保證了核心類加載器與應用類加載器之間的職責分離和加載安全性&#xff0c;但其單向的委托關系也帶來了一些局限性。尤其是在核心類庫需要訪問或實例化由應用類加載器加載的類時&#xff0c;雙親委派模型無法滿足需求&#xf…

6.4.高并發設計

目錄 一、高并發系統設計基礎理論 CAP定理與高可用性權衡 ? 一致性&#xff08;C&#xff09; vs 可用性&#xff08;A&#xff09;在電商、社交場景的取舍 ? 分區容錯性&#xff08;P&#xff09;的實踐意義&#xff1a;異地多活與腦裂處理 性能指標與評估模型 ? QPS、TP…

工程師轉型算法工程師 深入淺出理解transformer-手搓板

編碼器 以下部分引用臺灣大學李宏毅教授的ppt 自己理解解釋一遍(在youtobe 上可以搜索李宏毅即可) 首先先來看transformer的架構圖 Embedding 我們先從Imput Embedding 跟 OutPutEmbedding 開始&#xff0c;讓我們用 bert 模型來做一個解釋 從huggingface上下載的bert-base…

軟件工程學概述

一、軟件危機 &#xff08;一&#xff09;軟件危機的介紹 1. 基本思想與定義 軟件危機&#xff08;Software Crisis&#xff09;是指在計算機軟件的開發和維護過程中所遇到的一系列嚴重問題&#xff0c;這些問題既包括技術層面的挑戰&#xff0c;也涉及管理層面的困境。其核心…

【ArcGIS Pro微課1000例】0068:Pro原來可以制作演示文稿(PPT)

文章目錄 一、新建演示文稿二、插入頁面1. 插入地圖2. 插入空白文檔3. 插入圖像4. 插入視頻三、播放與保存一、新建演示文稿 打開軟件,新建一個地圖文檔,再點擊【新建演示文稿】: 創建的演示文檔會默認保存在目錄中的演示文稿文件夾下。 然后可以對文檔進行簡單的設計,例如…

[吾愛出品][Windows] 產品銷售管理系統2.0

[Windows] 產品銷售管理系統 鏈接&#xff1a;https://pan.xunlei.com/s/VOPej1bHMRCHy2np9w3TBOyKA1?pwdgjy7# 使用方法&#xff1a;1、先設置一下圖片保存路徑 2、維護產品。客戶等基礎信息。例如&#xff1a;銷售類型&#xff1a;一次性 銷售編碼&#xff1a;RCX。 3、銷…

MySQL數據庫高可用(MHA)詳細方案與部署教程

一&#xff1a;MHA簡介 核心功能 二&#xff1a;MHA工作原理 三&#xff1a;MHA組件 四&#xff1a;MHA 架構與工具 MHA架構 Manager關鍵工具 Node工具 五&#xff1a;工作原理與流程 1: 故障檢測 2: 故障切換&#xff08;Failover&#xff09; 3 : 切換模式 六&a…

華為設備鏈路聚合實驗:網絡工程實戰指南

鏈路聚合就像為網絡搭建 “并行高速路”&#xff0c;既能擴容帶寬&#xff0c;又能保障鏈路冗余&#xff0c;超實用&#xff01; 一、實驗拓撲速覽 圖中兩臺交換機 LSW1 和 LSW2&#xff0c;PC1、PC2 歸屬 VLAN 10&#xff0c;PC3 歸屬 VLAN 30。LSW1 與 LSW2 通過 GE0/0/1、…