昨天學習了一些C++網絡編程的一些相關知識,今天回憶復習一下
1. 相關知識
1.1 局域網和廣域網
局域網:局域網將一定區域內的各種計算機、外部設備和數據庫連接起來形成計算機通信的私有網絡。
廣域網:又稱廣域網、外網、公網。是連接不同地區局域網或城域網計算機通信的遠程公共網絡。
IP(Internet Protocol):本質是一個整形數,用於表示計算機在網絡中的地址。
IP 協議版本有兩個:IPv4 和 IPv6
-
IPv4(Internet Protocol version4):
-使用一個 32 位的整形數描述一個 IP 地址,4 個字節,int 型
也可以使用一個點分十進制字符串描述這個 IP 地址: 192.168.247.135
分成了 4 份,每份 1 字節,8bit(char),最大值為 255
0.0.0.0 是最小的 IP 地址
255.255.255.255 是最大的 IP 地址
按照 IPv4 協議計算,可以使用的 IP 地址共有 232 個 -
IPv6(Internet Protocol version6):
使用一個 128 位的整形數描述一個 IP 地址,16 個字節
也可以使用一個字符串描述這個 IP 地址:2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b
分成了 8 份,每份 2 字節,每一部分以 16 進制的方式表示
按照 IPv6 協議計算,可以使用的 IP 地址共有 2128 個 -
端口
端口的作用是定位到主機上的某一個進程,通過這個端口進程就可以接受到對應的網絡數據了。
端口也是一個整形數 unsigned short ,一個 16 位整形數,有效端口的取值范圍是:0 ~ 65535(0 ~ 216-1) -
OSI/ISO 網絡分層模型
1.2 Socket編程
Socket 套接字由遠景研究規划局(Advanced Research Projects Agency, ARPA)資助加里福尼亞大學伯克利分校的一個研究組研發。其目的是將 TCP/IP 協議相關軟件移植到 UNIX 類系統中。設計者開發了一個接口,以便應用程序能簡單地調用該接口通信。這個接口不斷完善,最終形成了 Socket 套接字。Linux 系統采用了 Socket 套接字,因此,Socket 接口就被廣泛使用,到現在已經成為事實上的標准。與套接字相關的函數被包含在頭文件 sys/socket.h 中。
通過上面的描述可以得知,套接字對應程序猿來說就是一套網絡通信的接口,使用這套接口就可以完成網絡通信。網絡通信的主體主要分為兩部分:客戶端和服務器端。在客戶端和服務器通信的時候需要頻繁提到三個概念:IP、端口、通信數據。
2. TCP通信流程
TCP 是一個面向連接的,安全的,流式傳輸協議,這個協議是一個傳輸層協議。
- 面向連接:是一個雙向連接,通過三次握手完成,斷開連接需要通過四次揮手完成。
- 安全:tcp 通信過程中,會對發送的每一數據包都會進行校驗,如果發現數據丟失,會自動重傳
- 流式傳輸:發送端和接收端處理數據的速度,數據的量都可以不一致
2.1 相關說明
在 tcp 的服務器端,有兩類文件描述符
-
監聽的文件描述符
只需要有一個
不負責和客戶端通信,負責檢測客戶端的連接請求,檢測到之后調用 accept 就可以建立新的連接 -
通信的文件描述符
負責和建立連接的客戶端通信
如果有 N 個客戶端和服務器建立了新的連接,通信的文件描述符就有 N 個,每個客戶端和服務器都對應一個通信的文件描述符
- 文件描述符
說明一下,一個文件文件描述符對應兩塊內存, 一塊內存是讀緩沖區, 一塊內存是寫緩沖區
對於監聽的文件描述符而言,它會一直監聽文件描述符的讀緩沖區,檢測不到數據,該函數阻塞,如果有數據,解除阻塞,新的連接建立。
對於通信的文件描述符而言,send()/write()函數,並不是直接將數據發送到網絡當中,而且將數據寫入到對應的寫緩沖區中,當系統內核檢測到通信的文件描述符寫緩沖區中有數據,內核會將數據發送到網絡中,同理,對read()/recv()函數而言也是如此
3. 服務器並發
進程場景下,服務器是無法處理多連接的,解決方案也有很多,常用的有三種:
- 使用多線程實現
- 使用多進程實現
- 使用 IO 多路轉接(復用)實現
- 使用 IO 多路轉接 + 多線程實現
當我們使用方法四時,服務器的性能會很棒,使用多線程和多進程的區別就是,多線程的性能會更好一些。
然后在底層,不同的線程在棧區做了划分,而在全局數據區和堆區,是可以共同訪問的,因此當多個線程訪問共享數據的時候,我們就要考慮同步的問題了
然后寫這篇文章的時候,我其實是對底層的棧堆不是很懂,特地去學習了一波
3.1 棧,堆
棧是為執行線程留出的內存空間。當函數被調用的時候,棧頂為局部變量和一些 bookkeeping 數據預留塊。當函數執行完畢,塊就沒有用了,可能在下次的函數調用的時候再被使用。棧通常用后進先出(LIFO)的方式預留空間;因此最近的保留塊(reserved block)通常最先被釋放。這么做可以使跟蹤堆棧變的簡單;從棧中釋放塊(free block)只不過是指針的偏移而已。
堆(heap)是為動態分配預留的內存空間。和棧不一樣,從堆上分配和重新分配塊沒有固定模式;你可以在任何時候分配和釋放它。這樣使得跟蹤哪部分堆已經被分配和被釋放變的異常復雜;有許多定制的堆分配策略用來為不同的使用模式下調整堆的性能。
每一個線程都有一個棧,但是每一個應用程序通常都只有一個堆(盡管為不同類型分配內存使用多個堆的情況也是有的)。
- 當線程創建的時候,操作系統(OS)為每一個系統級(system-level)的線程分配棧。通常情況下,操作系統通過調用語言的運行時(runtime)去為應用程序分配堆。
- 棧附屬於線程,因此當線程結束時棧被回收。堆通常通過運行時在應用程序啟動時被分配,當應用程序(進程)退出時被回收。
- 當線程被創建的時候,設置棧的大小。在應用程序啟動的時候,設置堆的大小,但是可以在需要的時候擴展(分配器向操作系統申請更多的內存)。
- 棧比堆要快,因為它存取模式使它可以輕松的分配和重新分配內存(指針/整型只是進行簡單的遞增或者遞減運算),然而堆在分配和釋放的時候有更多的復雜的 bookkeeping 參與。另外,在棧上的每個字節頻繁的被復用也就意味着它可能映射到處理器緩存中,所以很快。