一.网络编程关注的问题
连接建立
这个过程主要是TCP 三次握手过程:
第一次握手
客户主动(active open)去connect服务器,并且发送SYN 假设序列号为J, 服务器是被动打开(passive open),此时客户端状态出于SYN_SENT状态
1.半连接队列未满
服务器将该链接的状态变为SYN_RCVD,服务器把连接信息放到半连接队列里面
2.半连接队列未满
服务器不会将该连接的状态变为syn_RCVD,且该链接将会丢弃。SYN FLOOD攻击就是利用了这个原理,对于解决这个问题有以下几种方法
(1)cookie源认证:
原理是syn报文首先由DDOS防护系统来响应syn_ack。带上特定的sequence number (记为cookie)。真实的客户端会返回一个ack 并且Acknowledgment number 为cookie+1。 而伪造的客户端,将不会作出响应。这样我们就可以知道那些IP对应的客户端是真实的,将真实客户端IP加入白名单。下次访问直接通过,而其他伪造的syn报文就被拦截。下面为防护示意图:
(2)reset认证:
Reset认证利用的是TCP协议的可靠性,也是首先由DDOS防护系统来响应syn。防护设备收到syn后响应syn_ack,将Acknowledgement number (确认号)设为特定值(记为cookie)。当真实客户端收到这个报文时,发现确认号不正确,将发送reset报文,并且sequence number 为cookie + 1。 而伪造的源,将不会有任何回应。这样我们就可以将真实的客户端IP加入白名单。
第二次握手
服务器在收到SYN后,它会发送一个SYN以及一个ACK(应答)给客户, ACK的序列号是 J+1表示是给SYN J的应答,新发送的SYN K 序列号是K,此时服务器出于SYC_ACK状态
第三次握手
客户在收到新SYN K, ACK J+1 后,把状态置为ESTABLISHED,也回应ACK K+1 以表示收到了,然后两边就可以开始数据发送数据了,这时服务端也处于ESTABLISHED.全连接队列(accept queue)的最大值 /proc/sys/net/core/somaxconn (默认128)
全连接队列值 = min(backlog, somaxconn),这里的backlog是listen(int sockfd, int backlog)函数里面的那个参数backlog.
连接断开
TCP四次挥手:
第一次挥手
主动关闭方(active close)close会发送fin,seq报文序列号给对端,这个时候主动关闭方出于状态fin-wait-1状态。
第二次挥手
被动方收到fin包号,会返回一个ack包,这个时候被动关闭方状态会变为close_wait状态。主动方收到ack包后,状态将会变成fin-wait-2状态。此时整个连接处于半关闭状态。
第三次挥手
被动关闭方主动调用close,发送fin给主动关闭方,被动关闭方状态将变成last-ack状态。
第四次挥手
主动关闭方收到fin包后,然后将发送ack给被动关闭方,在等待2MSL后将变为close状态,被动关闭方在收到ack后,也会变为close状态
四次挥手中常见问题:
1.tcp连接大量CLOSE_WAIT和TIME_WAIT状态的出现和解决方法:这个请参考链接https://blog.csdn.net/lqglqglqg/article/details/54616380
消息到达
1.阻塞io模型和非io模型
(1)阻塞在哪里:阻塞在网络线程里
(2)什么来决定阻塞还是非阻塞:通过fcntl函数来设置
(3)具体的差异:io函数在数据未到达的时是否立刻返回
2.网络处理模型
(1)阻塞IO模型 + 多线程
优点:每个线程处理一个fd连接bio, 处理及时
缺点:线程利用率较低,线程的数量时有限的
(2)io多路复用
用一个线程来检测多个IO事件
水平触发的时候,io函数既可以时阻塞的也可以时非阻塞的;边缘触发的时候,io函数只能是非阻塞的。
epoll基础
重要数据结构:
struct eventpoll {
//...
struct rb_root rbr; //管理epoll监听事件
struct list_head rdlist; //保存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;
epoll_data_t data; // 保存 关联数据
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
epoll_create系统调⽤
size参数告诉内核这个epoll对象会处理的事件⼤致数量,⽽不是能够处理的事件的最⼤数。在现在linux版本中,这个size参数已经没有意义了;返回:epoll对象句柄;之后针对该epoll的操作需要通过该句柄来标识该epoll对象;
epoll_ctl系统调用
epoll_ctl向epoll对象添加,修改或删除事件; 返回:0表示成功,-1表示错误,根据errno错误码判断错误类型
**op类型:**
EPOLL_CTL_ADD 添加新的事件到epoll中
EPOLL_CTL_MOD 修改epoll中的事件
EPOLL_CTL_DEL 删除epoll中的事件
**event.events 取值**:
EPOLLIN 表示该连接上有数据可读(tcp连接远端主动关闭连接,也是可读事件,因为需要处理发送来的FIN包;FIN包就是read 返回 0)
EPOLLOUT 表示该连接上可写发送(主动向上游服务器发起⾮阻塞tcp连接,连接建⽴成功事件相当于可写事件)
EPOLLRDHUP 表示tcp连接的远端关闭或半关闭连接
EPOLLPRI 表示连接上有紧急数据需要读
EPOLLERR 表示连接发⽣错误
EPOLLHUP 表示连接被挂起
EPOLLET 将触发⽅式设置为边缘触发,系统默认为⽔平触发
EPOLLONESHOT 表示该事件只处理⼀次,下次需要处理时需重新加⼊epoll
**epoll_wait系统调⽤**
收集 epoll 监控的事件中已经发⽣的事件,如果 epoll 中没有任何⼀个事件发⽣,则最多等待timeout 毫秒后返回。
返回:表示当前发⽣的事件个数
返回:0表示本次没有事件发⽣;
返回:-1表示出现错误,需要检查errno错误码判断错误类型。
注意:
events 这个数组必须在⽤户态分配内存,内核负责把就绪事件复制到该数组中;
maxevents 表示本次可以返回的最⼤事件数⽬,⼀般设置为 events 数组的⻓度;
timeout表示在没有检测到事件发⽣时最多等待的时间;如果设置为0,检测到rdllist为空⽴刻返回;如果设置为-1,⼀直等待;
所有添加到epoll的事件都会与网卡驱动程序建立回调关系,相应的事件发生时会调用这里的回调方法,他会把这样的事件放在rdlist双向链表中。
reactor模型
组成: 非阻塞的io + io多路复用
特征: 基于事件循环,以事件驱动或者事件回调的方式来实现业务逻辑
表述:将连接的io处理转化为事件处理
**单reactor模型**
代表: redis
单reactor模型 + 任务队列 + 线程池
代表: skynet
多reactor
代表: memcache