Linux c/c++ 高性能后端
redis/memcached/nginx网络组件
网络编程关注的问题
连接的建立、连接的断开、消息的到达、消息发送完毕
连接的建立
分为两种:服务端处理接收客户端的连接;服务端作为客户端连接第三方服务;
int clientfd = accept(listenfd, addr, sz);
// 举例为非阻塞io,阻塞io成功直接返回0;
int connectfd = socket(AF_INET, SOCK_STREAM, 0);
int ret = connect(connectfd, (struct sockaddr*)&addr, sizeof(addr));
// 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);
// write()
// close(fd);
}
// 被动:写端关闭
int n = write(fd, buf, sz);
if (n == -1 && errno == EPIPE) {
close_write(fd);
// close(fd);
}
消息的到达
从缓冲区中读取数据
int n = read(fd, buf, sz);
if (n < 0) { // n == -1
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 一直阻塞直到网络事件到达;
timeout = 0 不管是否有事件就绪立刻返回;
timeout = 1000 最多等待1s,如果1s内没有事件触发则返回;

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中的就绪事件拷贝到用户态中
reactor编程
连接建立
// 一、处理客户端的连接
// 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);
}
// 业务逻辑了
}
}
数据发送完毕
int n = write(fd, buf, dz);
if (n == -1) {
if (errno == EINTR)
continue;
if (errno == EWOULDBLOCK) {
struct epoll_event ev;
ev.events = EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
return;
}
close(fd);
}
// ...
if (e->events & EPOLLOUT) {
int n = write(fd, buf, sz);
//...
if (n == sz) {
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
}
}
redis、memcached、nginx网络组件
上边主要在说网络、io、reactor;要知道想要快速看懂redis、memcached、nginx中的网络组件上边是必备的知识;以至于使用中哪里出了问题也能较容易的去定位和解决。

redis、memcached、nginx中的网络组件都是基于reactor去实现的,区别就在于redis是单reactor、memcached是多线程的reactor、nginx是多进程reactor模型
redis、memcached、nginx都为开源应用,网络组件为对reactor(epoll)的封装,按照网络、io、epoll相关的接口去理清网络模块的代码就会容易很多,相关的接口在上文中都有提及,只要对linux网络足够熟悉,读它们的开源代码难度并不大
浙公网安备 33010602011771号