【nginx】画龙点睛之服务器安全与完善

过往总结、心跳包代码实战

一、前面学习的总结

1、核心架构浓缩总结实现的功能

(1)服务器按照包头包体格式正确的接收客户端发送过来的数据包;

(2)根据手动的包的不同来执行不同的业务处理逻辑;

(3)把业务处理产生的结果数据包返回客户端。

2、用到的主要技术

(1)epoll高并发通讯技术

(2)线程池技术来处理业务逻辑

(3)线程之间的同步技术包括互斥量、信号量

其他技术:信号,日志打印,fork()子进程,守护进程

3、借鉴了哪些官方nginx的精华代码

(1)master进程,多个worker子进程——进程框架;

nginx:热更新,worker子进程挂了master能够重新启动worker,重载配置文件等

(2)借鉴了epoll的一些实现代码;官方nginx用的ET【边缘触发模式】,本项目中用的是水平触发模式LT;

(3)借鉴了接收数据包,以及发送数据包的核心代码;

4、哪些内容我们没有借鉴官方nginx呢?

(1)比如epoll技术中我们采用LT模式来书写网络数据的接收和发送;

(2)自己写一套线程池来处理业务逻辑,调用适当的业务逻辑处理函数,直至处理完毕把数据发送回客户端;

(3)连接池中连接的延迟回收,以及专门处理数据发送的发送线程。

二、心跳包概念【面试】

c/s程序:都有责任把心跳包机制在程序代码中实现好,以确保程序能良好的工作以及应付意外的情形发生。

1、什么叫心跳包以及如何使用

心跳包其实就是一个普通的数据包,一般每隔几十秒,最长一般也就是1分钟(10秒-60秒之间),由客户端主动发送给服务器,服务器收到之后,一般会给客户端返回一个心跳包。

三次握手,tcp连接建立之后,才存在发送心跳包的问题—— 如果c不给s发心跳包,服务器(约定30秒发送 一次)可能会在90秒或者100秒内,主动关闭该客户端的socket连接。

作为一个好的客户端程序,如果你发送了心跳包给服务器,但是在90或者100秒之内,你[客户端]没有收到服务器回应的心跳包,那么你就应该主动关闭与服务器端的链接,并且如果业务需要重连,客户端程序在关闭这个连接后还要重新主动再次尝试连接服务器端;客户端程序也有必要提示使用者与服务器的连接已经断开。

2、为什么引入心跳包

常规客户端关闭,服务器端能感知到;有一种特殊情况,连接断开c/s都感知不到。

为了判断连接后c/s长时间不发数据是否已经断线,tcp本身提供keepalive机制,但因为检测时间不好控制,不采用。

比如,c/s程序运行在不同的两个物理电脑上,tcp已经建立,拔掉c/s程序的网线,拔掉网线导致服务器感知不到客户端断开。

为了应对拔网线,导致不知道对方是否断开了tcp连接这种事,引入心跳包机制!

3、心跳包作用

超时没有发送来心跳包,那么就会将对端的socket连接close掉,回收资源;其他作用为检测网络延迟等等。

这里引入的主要目的就是检测双方的链接是否断开。

三、心跳包代码实战

1、接收心跳包与返回结果

心跳包(ping包):规定消息代码为0;一般心跳包也不需要包体,只有包头就够了。

2、处理不发送心跳包的客户端

超过30*3 +10 =100秒,仍旧没收到心跳包,那么服务器端就把tcp断开。

 

控制连入数,黑客攻击防范及畸形包应对

一、控制并发连入数量

epoll支持高并发:数万,数十万,百万(一台计算机);

并发数量取决于很多因素:

(1)采用的开发技术:epoll,支持数十万并发

(2)程序收发数据的频繁程度,以及具体要处理的业务复杂程度

(3)实际的物理内存:可用的物理内存,会直接决定你能支持的并发连接

(4)一些其他的tcp/ip配置项

一般,我们日常所写的服务器程序,支持几千甚至1-2万的并发,基本上就差不多了;一个服务器程序,要根据具体的物理内存,以及我们具体要实现的业务等等因素,控制能够同时连入的客户端数量;如果允许客户端无限连入,那么服务器肯定会在未来某时崩溃。

控制连入用户数量的解决思路:如果同时连入的用户数量超过了允许的最大连入数量时,就把这个连入的用户直接踢出去。

二、黑客攻击的防范

【攻击效果】

a)轻则:服务器工作延迟,效率明显降低

b)重则整个服务器完全停摆,没有办法提供正常服务,比如拒绝服务攻击就会导致这种状况(服务器失去响应);

c)最甚者,如果服务器程序写的有一些漏洞的话,恶意黑客很可能利用给一些远程溢出攻击手段直接攻破你的服务器程序所在的计算机;能拿到一定的权限,甚至可能是root权限,一旦拿到了root权限,那么你的整个计算机都在黑客控制中了。

有些攻击是利用tcp/ip协议先天的一些设计问题来攻击,比如syn flood攻击。

DDOS攻击(syn flood攻击也是DDOS攻击的一种)甚至防火墙等设备都可能防不住,甚至得从数据路由器想办法;

1、flood攻击防范

以游戏服务器为例,假设我们认为一个合理的客户端一秒钟发送数据包给服务器不超过10个(手点不了那么快),如果客户端不停的给服务器发数据包,1秒钟超过了10个数据包 ,那我服务器就认为这个玩家有恶意攻击服务器的倾向;我们服务器就应该果断的把这个TCP客户端连接关闭,这个也是服务器发现恶意玩家以及保护自身安全的手段。

代码上如何实现1秒钟超过10个数据包则把客户端踢出去:连续不到100ms都收到了数据包10次就干掉。

增加测试函数TestFlood()位置:ngx_read_request_handler()、ngx_wait_request_handler_proc_p1()、ngx_wait_request_handler_proc_plast()。

2、畸形数据包防范

【意识】客户端发送过来的数据并不可信,因为这些数据有可能是造假的,甚至可能是畸形的。

以游戏服务器为例:

(1)造假:服务器端书写的时候要细致判断,基本能够避免客户端造假;

(2)畸形数据包:远程溢出攻击,攻击成功的主要原因就是服务器程序书写不当,比如接收到的数据缺少边界检查。

以_HandleRegister()函数为例,我们有必要在字符数组末尾自己增加一个“\0”(字符串结束标记),以确保我们用这个字符数组的时候不会出问题:

p_RecvInfo->username[sizeof(p_RecvInfo->username) - 1] = 0;
p_RecvInfo->password[sizeof(p_RecvInfo->password) - 1] = 0;

三、超时直接踢出服务器的需求

账号服务器:账号服务器有一个需求,任何一个连接不超过20秒,超过20秒不主动断开与本服务器连接的,那么本服务器就要主动的把这个用户踢下线(断开TCP连接)。

 

超负荷安全处理、综合压力测试

一、输出一些观察信息

每隔10秒钟把一些关键信息显示在屏幕上:

(1)当前在线人数;

(2)和连接池有关:连接列表大小,空闲连接列表大小,将来释放的连接多少个;

(3)当前时间队列大小;

(4)收消息队列和发消息队列大小;

打印统计信息的函数:printTDInfo(),每10秒打印一次重要信息。

二、遗漏的安全问题思考

1、收到太多数据包处理不过来

限速:epoll技术,一个限速的思路;在epoll红黑树节点中,把这个EPOLLIN(可读)通知干掉。

2、积压太多数据包发送不出去:壮士断腕,丢弃待发送的数据包。

3、连入安全的进一步完善

如果某些恶意用户连上来发了1条数据就断,并不断地产生这种连接,会导致频繁调用ngx_get_connection()使用我们短时间内产生大量连接,而且,这种连接还因为延迟调用的原因在垃圾队列里等待,容易让内存不断增大,以至崩溃。

三、压力测试:长时间运行程序,最好用多个物理电脑测试。

【测试什么】

a)程序崩溃,这明显不行,肯定要解决;

b)程序运行异常,比如过几个小时,服务器连接不上了,没有回应了,发过来的包服务器处理不了了;

c)服务器程序占用的内存才能不断增加,增加到一定程度,可能导致整个服务器崩溃;

top -p 3645    //显示进程占用的内存和cpu百分比,用q可以退出
cat /proc/3645/status    //VmRSS: 7700 kB

【发现错误】最大连接只在1000多个,且日志中一直报:CSocekt::ngx_event_accept()中accept4()失败

这个跟用户进程可打开的文件数限制有关:因为系统为每个tcp连接都要创建一个socekt句柄(accept返回),每个socket句柄同时也是一个文件句柄,比如日志、监听套接字等也是要占用文件句柄的。

ulimit -n    //1024

必须修改linux对当前用户的进程同时打开的文件数量的限制!

 

惊群、性能优化大局观

一、cpu占比与惊群

top -p pid,推荐文章:https://www.cnblogs.com/dragonsuc/p/5512797.html

【惊群现象】1个master进程4个worker进程,一个连接进入,惊动了4个worker进程,但是只有一个worker进程accept(),其他三个worker进程被惊动,这就叫惊群;但是,这三个被惊动的worker进程都做了无用功(操作系统本身的缺陷)。

官方nginx解决惊群的办法:锁,进程之间的锁;谁获得这个锁,谁就往监听端口增加EPOLLIN标记,有了这个标记,客户端连入就能够被服务器感知到。

3.9以上内核版本的linux,在内核中解决了惊群问题;而且性能比官方nginx解决办法效率高很多。

内核采用reuseport(复用端口),是一种套接字的复用机制,允许将多个套接字bind到同一个ip地址/端口上,这样一来,就可以建立多个服务器来接收到同一个端口的连接(多个worker进程能够监听同一个端口);

很多套接字配置项可以通过setsockopt()等等函数来配置,还有一些tcp/ip协议的一些配置项我们可以通过修改配置文件来生效。

惊群问题应该通过操作系统来解决!

二、性能优化大局观

从两个方面看下性能优化问题:

【软件层面】

a)充分利用cpu,比如刚才惊群问题;

b)深入了解tcp/ip协议,通过一些协议参数配置来进一步改善性能;

c)处理业务逻辑方面,算法方面有些内容,可以提前做好。

【硬件层面(花钱搞定)】

a)高速网卡,增加网络带宽;

b)专业服务器;数十个核心,马力极强;

c)内存:容量大,访问速度快;

d)主板,总线不断升级的。

三、性能优化的实施

1、绑定cpu、提升进程优先级

a)一个worker进程运行在一个核上,为什么能够提高性能呢?不存在进程切换问题。

cpu:缓存和cpu缓存命中率问题;把进程固定到cpu核上,可以大大增加cpu缓存命中率,从而提高程序运行效率。

b)提升进程优先级(setpriority()函数),使进程有机会被分配到更多的cpu时间(时间片),得到执行的机会就会增多。

c)一个服务器程序,一般只放在一个计算机上跑,专用机。

2、TCP / IP协议的配置选项

这些配置选项都有缺省值,通过修改,在某些场合下,对性能可能会有所提升;

若要修改这些配置项,要做到以下几点:

a)对这个配置项有明确的理解;

b)对相关的配置项,记录他的缺省值,做出修改;

c)要反复不断的亲自测试,亲自验证;仔细判断是否提升性能,是否有副作用再使用。

四、TCP / IP协议的配置选项

1、滑动窗口的概念

为了解决高速传输和流量控制问题(限速问题),滑动窗口的实现一般都会在操作系统内核里边干,但是老师仍旧希望大家能够了解相关的概念。

2、Nagle算法的概念

这个算法是避免发送很小的报文,大家都知道,报文再小他也要有包头,那么我们把几个小报文组合成一个大一点的报文再发送,那至少我们能够少发好几个包头,节省了流量和网络带宽。

3、Cork算法

比Nagle更硬气,完全禁止发送小报文,除非累积到一定数量的数据或者是超过某一个时间才会发送这种报文。

4、Keep - Alive机制

用于关闭已经断开的tcp连接,之前提到过,具体使用方法如下:

net.ipv4.tcp_keepalive_time:探测包发送周期单位是秒,默认是7200(2小时):如果2小时内没有任何报文在这个连接上发送,那么开始启动keep-alive机制,发送keepalive包。

net.ipv4.tcp_keepalive_intvl:缺省值75(单位秒)如果发送keepalive包没应答,则过75秒再次发送keepalive包;

net.ipv4.tcp_keepalive_probes:缺省值9,如果发送keepalive包对方一直不应答,发送9次后,如果仍然没有回应,则这个连接就被关闭掉。

5、SO_LINGER选项

延迟关闭选项, 一般调用setsockopt(SO_LINGER),这个选项设置在连接关闭的时候,与是否进行正常的四次挥手有关;因为缺省的tcp/ip协议在四次挥手时可能会导致某一通讯方给对方发送rst包以结束连接,从而导致对方收到rst包而丢弃收到的数据,那么这个延迟选项可以避免给对方发送rst包导致对方丢弃收到的数据

说的直白一点:这个选项用来设置延迟关闭的时间,等待套接字发送缓冲区中的数据发送完成。

五、配置最大允许打开的文件句柄数(遗留问题)

cat /proc/sys/fs/file-max    //查看操作系统可以使用的最大句柄数
cat /proc/sys/fs/file-nr     //查看当前已经分配的,分配了没使用的,文件句柄最大数目
ulimit -n                    //查看系统允许的当前用户进程打开的文件数限制
ulimit -HSn 5000             //临时设置,只对当前session有效;n:表示我们设置的是文件描述符

如果想要永久限制用户使用的最大句柄数:修改 /etc/security/limit.conf 文件

root soft nofile 60000 或在程序中 setrlimit(RLIMIT_NOFILE)

root hard nofile 60000(soft数字要小于hard,在程序中不可修改)

六、内存池补充说明

【为什么没有用内存池技术】内存池在多次分配小块内存时好,但是本项目中分配和释放内存都比较快,所以感觉必要性不大;TCMalloc(谷歌开发的内存分配库)用来取代malloc,虽然快但是稳定性不好说。

 

你可能感兴趣的:(nginx,学习)