目录
目标
- 明白网络模块要处理那些事情
- reactor 是怎么处理这些事情的
- reactor 如何封装的
- 网络模块与业务逻辑的关系
- 如何优化 reactor
网络编程关注的问题
连接的建立
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 函数和系统调用中都有用到 检测 io。主要功能就是检测 io 是否就绪,如果对应到 socket 网络通信来说每个函数检测的部分如下:
acccept();//检测全连接队列是否有数据:
//第 1 次握手:将数据放到半连接队列
//第 3 次握手:将数据放入全连接队列
connect();//检测是否收到 ACK,收到 ACK 就代表 IO 就绪,连接成功
//第 2 次握手成功,就表示 client 连接成功
read = 0; //检测 buf 是否含有 EOF 标记
//关闭连接时,会往对应的缓冲区写入 EOF,读到 EOF 就会返回 0
write //就是把数据写到 send_buf 缓冲区中,至于数据什么时候写,以什么形式写,何时到达对端,都是根绝协议栈来决定的
操作 IO
阻塞IO 和 非阻塞IO
// 默认情况下,fd 是阻塞的,设置非阻塞的方法如下;
int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);
详细分析可以看I/O详解与五种网络I/O模型
IO 多路复用
详细分析可以看I/O详解与五种网络I/O模型
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);
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);
}
}