一些網絡編程方面的總結,以及redis、memcache、nginx組件的一些介紹

網絡編程主要關注的一些問題

主要關注3個方面的問題

  1. 連接的建立
  2. 連接的斷開
  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的時候,連接處理將變得緩慢。

這樣我們就完成簡單的websocket服務器。
推薦一個零聲學院免費教程,個人覺得老師講得不錯,
分享給大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,
TCP/IP,協程,DPDK等技術內容,點擊立即學習:
服務器
音視頻
dpdk
Linux內核

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章