网络编程关注的问题
连接的建立
分为两种:服务端处理接收客户端的连接;服务端作为客户端 连接第三方服务;
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 == -1if (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 和非 阻塞 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事件触发可以立即返回)
IO 多路复用
io 多路复用只负责检测 io,不负责操作 io;
int n = epoll_wait(epfd, evs, sz, timeout);
timeout = -1 一直阻塞直到网络事件到达;
imeout = 0 不管是否有事件就绪立刻返回;
timeout = 1000 最多等待 1 s,如果1 s内没有事件触发则返回
(用一个事件来检查多个IO的状态)
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_ADDEPOLL_CTL_MODEPOLL_CTL_DELevent.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),将触发的事件拷贝到 链表中;调用 epoll_wait 将会把 rdlist 双向 rdlist 中就绪事件拷贝到 用户态中;
(底层维护一个红黑树与等待队列链表)
reactor应用
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.
多reactor与多线程版
具体应用场景
redis -单reactor
kv,数据结构,内存数据库
redis为什么使用单reactor
单线程业务逻辑
操作具体命令时间复杂度比较低
redis针对reactor做了哪些优化
read,decode 记录日志
encode,write 获取排行榜记录
memcached
使用多reactor kv数据操作简单,更高程度并发处理业务。
nginx 多线程多reactor