I/O模型
阿新 • • 發佈:2022-03-28
I/O到底是什麼?
I/O(input/output)是指記憶體與外部裝置之間的互動(資料拷貝)。
- 磁碟 I/O
- 指的是硬碟和記憶體之間的輸入輸出。讀取本地檔案的時候,要將磁碟的資料拷貝到記憶體中,修改本地檔案的時候,需要把修改後的資料拷貝到磁碟中
- 網路 I/O
- 指的是網絡卡與記憶體之間的輸入輸出。當網路上的資料到來時,網絡卡需要將資料拷貝到記憶體中。當要傳送資料給網路上的其他人時,需要將資料從記憶體拷貝到網卡里
socket(套接字)通訊
網路通訊分為 TCP 與 UDP 兩種,可以認為它們都是基於 socket 的,下面以 TCP 通訊為例來闡述 socket 的通訊流程。
1、建立 socket
首先服務端需要先建立一個 socket。在 Linux 中一切都是檔案,那麼建立的 socket 也是檔案,每個檔案都有一個整型的檔案描述符(fd)來指代這個檔案。
int socket(int domain, int type, int protocol);
- domain:用於選擇通訊的協議族,比如選擇 IPv4 通訊,還是 IPv6 通訊等
- type:選擇套接字型別,可選位元組流套接字、資料報套接字等
- protocol:指定使用的協議
方法的返回值為 int ,其實就是建立的 socket 的 fd。
2、bind
伺服器應用需要指明 IP 和埠,這樣客戶端才能請求相應服務。
建立的 socket 繫結地址和埠:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd : 建立的 socket 的 fd
3、listen
執行了 socket、bind 之後,此時的 socket 還處於 closed 的狀態,也就是不對外監聽的,然後我們需要呼叫 listen 方法,讓 socket 進入被動監聽狀態,這樣的 socket 才能夠監聽到客戶端的連線請求。
int listen(int sockfd, int backlog);
- backlog:Linux 使用兩個佇列分別儲存已完成連線和半連線,且 backlog 僅為已完成連線的佇列大小
4、accept
三次握手完成後的連線會被加入到已完成連線佇列中:
通過 accpet 從已完成連線佇列中拿到連線進行處理:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
方法返回的 int 值就是拿到的已完成連線的 socket 的檔案描述符,之後操作這個 socket 就可以進行通訊了。
如果已完成連線佇列沒有連線可以取,那麼呼叫 accept 的執行緒會阻塞等待。
5、connect
- 客戶端呼叫 socket()建立一個 socket,並呼叫 connect 之後,連線處於 SYN_SEND 狀態
- 收到服務端的 SYN+ACK 之後,連線就變為 ESTABLISHED 狀態(代表三次握手完畢)
客戶端這邊不需要呼叫 bind 操作,預設會選擇源 IP 和隨機埠。
這裡有兩個阻塞點:
- connect:需要阻塞等待三次握手的完成
- accept:需要等待可用的已完成的連線,如果已完成連線佇列為空,則被阻塞
6、read、write
- read 為讀資料
- 從服務端來看就是等待客戶端的請求,如果客戶端不發請求,那麼呼叫 read 會處於阻塞等待狀態
- write 為寫資料
- 傳送不是直接發出去,會把資料先拷貝到 TCP 的傳送緩衝區,由 TCP 自行控制傳送的時間和邏輯。如果傳送快取區滿了, write 就會發生阻塞
read 和 write 都會發生阻塞。
五種 I/O 模型
網路 I/O 會有很多阻塞點,阻塞 I/O 隨著使用者數的增長只能利用增加執行緒的方式來處理更多的請求,而執行緒不僅會佔用記憶體資源且太多的執行緒競爭會導致頻繁地上下文切換產生巨大的開銷,因此提出了多種 I/O 模型。
在 UNIX 系統下,一共有五種 I/O 模型。