Epoll
C10k問題
非阻塞IO
1.是否使用了select poll epoll等就是非阻塞了?
非阻塞IO指的是套接字類型,而不是網絡事件模型(select,poll,epoll)
阻塞非阻塞對應數據準備過程,同步異步對應數據從內核到應用程序緩衝區過程
阻塞IO+多進程模型
1.多進程採用fork實現,程序調用 fork 一次,在父、子進程裏各返回一次。在調用該函數的進程(即爲父進程)中返回的是新派生的進程 ID 號,在子進程中返回的值爲 0。想要知道當前執行的進程到底是父進程,還是子進程,只能通過返回值來進行判斷。
2.當一個子進程退出時,系統內核還保留了該進程的若干信息,比如退出狀態。這樣的進程如果不回收,就會變成殭屍進程。在 Linux 下,這樣的“殭屍”進程會被掛到進程號爲 1 的 init 進程上。所以,由父進程派生出來的子進程,也必須由父進程負責回收,否則子進程就會變成殭屍進程。殭屍進程會佔用不必要的內存空間,如果量多到了一定數量級,就會耗盡我們的系統資源。
demo
#include<iostream>
#include <poll.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <unistd.h>
#include "message_objecte.h"
#include <signal.h>
#include <wait.h>
#define SERV_PORT 43211
#define MAXLINE 4096
#define UNIXSTR_PATH "/var/lib/unixstream1.sock"
#define LISTENQ 1024
#define BUFFER_SIZE 4096
#define MAXLINE 4096
int tcp_server_listen(u_int32_t port)
{
//1.create
int fd=socket(AF_INET,SOCK_STREAM,0);
//2.init
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_port=htons(port);
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=INADDR_ANY;
//3.set reuse(SOL_SOCKET 套接字級別)
int on=1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
//4.bind
socklen_t len= sizeof(server_addr);
int rt1=bind(fd,(struct sockaddr*)&server_addr,len);
if(rt1<0){
error(1,errno,"bind failed");
}
//5.listen
int rt2=listen(fd,1024);
if(rt2<0){
error(1,errno,"listen failed");
}
return fd;
}
void child_run(int fd){
char outbuf[MAXLINE+1];
size_t outbuf_used=0;
ssize_t result;
while(1){
char ch;
//這裏的recv是阻塞模式的
result = recv(fd,&ch,1,0);//一次接收一個字符
//如果recv函數在等待協議接收數據時網絡中斷了,那麼它返回0。
if(result == 0){
printf("client closed\n");
break;
}else if(result==-1){
perror("read");
break;
}
if(outbuf_used< sizeof(outbuf)){
outbuf[outbuf_used++]=ch;
}
if(ch=='\n'){
send(fd,outbuf,outbuf_used,0);
outbuf_used=0;
continue;
}
}
}
//處理子進程退出的方式一般是註冊一個信號處理函數,
// 捕捉信號 SIGCHILD 信號,然後再在信號處理函數裏
// 調用 waitpid 函數來完成子進程資源的回收。
void sigchld_handler(int sig)
{
//這裏選項 WNOHANG 用來告訴內核,即使還有未終止的子進程也不要阻塞
// 在 waitpid 上。注意這裏不可以使用 wait,
// 因爲 wait 函數在有未終止子進程的情況下,沒有辦法不阻塞。
printf("quit child process\n");
while(waitpid(-1,0,WNOHANG)>0){
return;
}
}
int main(int c,char **v){
int listen_fd=tcp_server_listen(SERV_PORT);
signal(SIGCHLD,sigchld_handler);
while(1){
struct sockaddr_storage ss;
socklen_t slen= sizeof(ss);
int conn_fd=accept(listen_fd,(struct sockaddr*)&ss,&slen);
if(conn_fd<0){
error(1,errno,"accept failed");
exit(1);
}
/*
* 從父進程派生出的子進程,同時也會複製一份描述字,也就是說,連接套接字和
* 監聽套接字的引用計數都會被加 1,而調用 close 函數則會對引用計數進行減 1 操作,
* 這樣在套接字引用計數到 0 時,纔可以將套接字資源回收。所以,這裏的 close
* 函數非常重要,缺少了它們,就會引起服務器端資源的泄露。*/
//fork先返回非零值,再返回零值
if(fork()==0){//子進程,只負責連接套接字
//子進程不需要關心監聽套接字,故而在這裏關閉掉監聽套接字 listen_fd
close(listen_fd);
printf("child process\n");
child_run(conn_fd);
exit(0);
}else{ //父進程,只負責監聽套接字
printf("this is main process\n");
//父進程不需要關心連接套接字,所以在這裏關閉連接套接字
close(conn_fd);
}
}
return 0;
}
輕量級方式:阻塞IO+線程程模型
事件驅動模型
事件驅動模型,也被叫做反應堆模型(reactor),或者是 Event loop 模型。這個模型的核心有兩點。
第一,它存在一個無限循環的事件分發線程,或者叫做 reactor 線程、Event loop 線程。這個事件分發線程的背後,就是 poll、epoll 等 I/O 分發技術的使用。
第二,所有的 I/O 操作都可以抽象成事件,每個事件必須有回調函數來處理。acceptor 上有連接建立成功、已連接套接字上發送緩衝區空出可以寫、通信管道 pipe 上有數據可以讀,這些都是一個個事件,通過事件分發,這些事件都可以一一被檢測,並調用對應的回調函數加以處理。
同步/異步IO、阻塞/非阻塞IO 30 | 真正的大殺器:異步I/O探索
同步異步和阻塞非阻塞完全是不同的概念啊。
同步異步指的是,做完一件事再做另一件事還是兩件事互不干擾的做。比如同步IO指的是從發起讀寫操作到完成讀寫操作是同步完成的,即發起-執行-完成。異步IO:發起讀寫然後就OK了,內核完成後通知完成的狀態。
阻塞:就是停在這裏不往下進行了。非阻塞:就是不停,繼續執行。
**阻塞非阻塞是一個動作。同步異步是一套過程。**可以這麼理解嗎?
**另外:**平時所說的多線程下保持數據同步,意思應該是保持數據在多線程下的一致。
PS :怎麼感覺脫離了這一章節理解的還是不到位,上面寫的還是不夠清楚。30 | 真正的大殺器:異步I/O探索
可以這麼理解: 阻塞非阻塞對應數據準備過程,同步異步對應數據從內核到應用程序緩衝區過程