一些網絡編程方面的總結,以及redis、memcache、nginx組件的一些介紹
網絡編程主要關注的一些問題
主要關注3個方面的問題
- 連接的建立
- 連接的斷開
- 消息的發送和到達
連接的建立
主要分為兩種情況:服務器處理接受客戶端的連接;服務端作為客戶端的連接第三方服務;
//這是服務端接受客戶端連接的時候;(三次握手完畢)
int clientfd=accept(listenfd,addr,sz);
//服務端作為客戶端連接第三方服務
//這里又分為阻塞IO和非阻塞IO
int connectfd=soket(AF_INET,SOCK_STREAM,0);
int ret=connect(connectfd,(struct sockaddr*)&addr,sizeof(addr))
//阻塞情況:
//直接return 0;
//非阻塞情況:
//ret==-1 && errno=EINPROGRESS正在建立連接
//ret==-1 && errno=EISCONN 連接建立成功
連接的斷開
分為兩種:一種主動斷開和被動斷開
//主動關閉
close(fd);
//主動關閉本地讀寫端
shutdown(fd,SHUT_RDWR);
//主動關閉本地讀端,對端寫端關閉
shutdown(fd,SHUT_RD);
//主動關閉本地寫端,對端讀端關閉
shutdown(fd,SHUT_WR);
// 被動:讀端關閉
int n =read(fd,buf,sz);
if(n==0){
close_read(fd);
}
//被動:寫端關閉
int n = write(fd,buf,sz);
if(n==-1&&errno==EPIPE){
close_write(fd);
}
消息的到達
從緩沖區中讀取數據:
int n= read(fd,buf,sz);
if(n<0){
if(errno==EINTR || errno == EWOULDBLOCK)
{
break;
}
close(fd);
}else if(n ==0 ){
close(fd);
}else{
//處理buf
}
消息的發送完畢
往寫緩沖區中寫數據:
int n =write(fd,buf,dz);
if (n == -1) {
if (errno == EINTR || errno == EWOULDBLOCK) {
return;
}
close(fd);
}
網絡IO的職責
檢測IO
io 函數本身可以檢測 io 的狀態;但是只能檢測一個 fd 對應的狀態;io 多路復用可以同時檢測多個io的狀態;區別是:io函數可以檢測具體狀態;io 多路復用只能檢測出可讀、可寫、錯誤、斷開等籠統的事件
操作IO
只能使用 io 函數來進行操作;分為兩種操作方式:阻塞 io 和非阻塞 io;
阻塞 IO 和 非阻塞 IO
阻塞在網絡線程;
連接的 fd 阻塞屬性決定了 io 函數是否阻塞;
具體差異在:io 函數在數據未到達時是否立刻返回;
// 默認情況下,fd 是阻塞的,設置非阻塞的方法如下;
int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);

IO多路復用
io 多路復用只負責檢測io,不負責操作io;
int n = epoll_wait(epfd, evs, sz, timeout);
timeout = -1 一直阻塞直到網絡事件到達;
imeout = 0 不管是否有事件就緒立刻返回;
timeout = 1000 最多等待 1 s,如果1 s內沒有事件觸發則返回

EPoll
結構以及接口
struct eventpoll {
// ...
struct rb_root rbr; // 管理 epoll 監聽的事件
struct list_head rdllist; // 保存著 epoll_wait 返回滿?條件的事件
// ...
};
struct epitem {
// ...
struct rb_node rbn; // 紅?樹節點
struct list_head rdllist; // 雙向鏈表節點
struct epoll_filefd ffd; // 事件句柄信息
struct eventpoll *ep; // 指向所屬的eventpoll對象
struct epoll_event event; // 注冊的事件類型
// ...
};
struct epoll_event {
__uint32_t events; // epollin epollout epollel(邊緣觸發)
epoll_data_t data; // 保存 關聯數據
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
int epoll_create(int size);
/**
op:
EPOLL_CTL_ADD
EPOLL_CTL_MOD
EPOLL_CTL_DEL
event.events:
EPOLLIN 注冊讀事件
EPOLLOUT 注冊寫事件
EPOLLET 注冊邊緣觸發模式,默認是水平觸發
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
/**
events[i].events:
EPOLLIN 觸發讀事件
EPOLLOUT 觸發寫事件
EPOLLERR 連接發生錯誤
EPOLLRDHUP 連接讀端關閉
EPOLLHUP 連接雙端關閉
*/
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int
timeout);

調用 epoll_create 會創建一個 epoll 對象;調用 epoll_ctl 添加到 epoll 中的事件都會與網卡驅動程序建立回調關系,相應事件觸發時會調用回調函數(ep_poll_callback ),將觸發的事件拷貝到 rdlist 雙向鏈表中;調用 epoll_wait 將會把 rdlist 中就緒事件拷貝到用戶態中;
epoll 編程
連接建立
// 一、處理客戶端的連接
// 1. 注冊監聽 listenfd 的讀事件
struct epoll_event ev;
ev.events |= EPOLLIN;
epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &ev);
// 2. 當觸發 listenfd 的讀事件,調用 accept 接收新的連接
int clientfd = accept(listenfd, addr, sz);
struct epoll_event ev;
ev.events |= EPOLLIN;
epoll_ctl(efd, EPOLL_CTL_ADD, clientfd, &ev);
// 二、處理連接第三方服務
// 1. 創建 socket 建立連接
int connectfd = socket(AF_INET, SOCK_STREAM, 0);
connect(connectfd, (struct sockaddr *)&addr, sizeof(addr));
// 2. 注冊監聽 connectfd 的寫事件
struct epoll_event ev;
ev.events |= EPOLLOUT;
epoll_ctl(efd, EPOLL_CTL_ADD, connectfd, &ev);
// 3. 當 connectfd 寫事件被觸發,連接建立成功
if (status == e_connecting && e->events & EPOLLOUT) {
status == e_connected;
// 這里需要把寫事件關閉
epoll_ctl(epfd, EPOLL_CTL_DEL, connectfd, NULL);
}
連接斷開
if (e->events & EPOLLRDHUP) {
// 讀端關閉
close_read(fd);
close(fd);
}
if (e->events & EPOLLHUP) {
// 讀寫端都關閉
close(fd);
}
數據到達
// reactor 要用非阻塞io
// select
if (e->events & EPOLLIN) {
while (1) {
int n = read(fd, buf, sz);
if (n < 0) {
if (errno == EINTR)
continue;
if (errno == EWOULDBLOCK)
break;
close(fd);
} else if (n == 0) {
close_read(fd);
// close(fd);
}
// 業務邏輯了
}
}
reactor應用
下面就要開始介紹一下redis,memcached,nginx網絡組件了。
這是我們自己的理解。
The reactor design pattern is an event handling pattern (事件處理模式)for handling service requests delivered concurrently to a service handler by one or more inputs(處理一個或多個并發傳遞到服務端的服務請求). The service handler then demultiplexes the incoming requests and dispatches them synchronously (同步)to the associated request handlers.
Redis :
Redis支持數據的持久化,可以將內存中的數據保存在磁盤中,重啟的時候可以再次加載進行使用。
Redis不僅僅支持簡單的key-value類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。
Redis支持數據的備份,即master-slave模式的數據備份。
redis主要使用的是單reactor的模式
- 單線程業務邏輯
- 命令處理是單線程的
具體的模型如下:

memcached:
Memcached,簡單來說就是一個免費開源并且高性能的分布式內存對象緩存系統,主要用于加速動態 Web 程序,減輕數據庫負載。
他也是key和value的內存數據庫
命令處理是多線程的。
memcached為什么使用多reactor?
- KV數據操作簡單
- 更高程度并發處理業務
具體的模型如下:

nginx:
nginx采用多進程模型,含一個master進程和多個worker進程,worker進程數目可配置,一般與機器CPU核心數目一致,master進程主要職責是:接收外界信號,如star,stop,restart,監控worker進程狀態。worker進程主要職責:負責處理客戶端請求。
使用反向代理,多進程處理業務的模式
nginx為什么要使用多進程?
- 業務類型復雜
- 通過進程隔離避免加鎖
但是多進程模式,很容易就會在接受到請求的時候,由于多進程的響應,導致驚群問題。
因此通過鎖在用戶態解決驚群問題,目的是為了在用戶層處理連接的負載均衡。
進程到達7/8 *connections 的時候,不在處理連接,讓其他進程處理連接。
當所有進程到達7/8 * connections的時候,連接處理將變得緩慢。
推薦一個零聲學院免費教程,個人覺得老師講得不錯,
分享給大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,
TCP/IP,協程,DPDK等技術內容,點擊立即學習:
服務器
音視頻
dpdk
Linux內核

浙公網安備 33010602011771號