网络编程主要关注的一些问题
主要关注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内核
原文地址:http://www.cnblogs.com/wenlong-4613615/p/16880157.html