TCP三次握手过程
为什么需要三次握手
TCP是可靠的传输协议,三次握手能保证数据可靠传输又能提高传输效率。 两次握手不能保证连接已建立 四次握手没有必要
TCP四次挥手
为什么要有TIME_WAIT状态
- 可靠终止TCP连接,如果最后一个ACK报文因网络原因被丢弃,此时server因为没有收到ACK而超时重试FIN报文,处于TIME_WAIT状态的client可以继续对FIN报文回复,向server发送ACK报文
- 保障迟来的TCP报文段有足够的时间被识别和丢弃,连接结束了网络中的延迟报文也应该被丢弃掉,以免影响立刻建立的新连接。
SYN洪水攻击
属于DOS攻击的一种,发送大量的半连接请求,耗费CPU和内存。 客户端在短时间内模拟大量不存在的IP地址,向服务器不断发送SYN报文,服务器回复ACK,并等待客户端回复,由于源地址不存在,服务器需要不断重试直至超时,这些伪造的SYN 报文被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。
如果设计QQ,网络协议该如何设计
登录采用TCP和Http,发送消息采用UDP协议,内网传文件采用P2P。
- 登录过程,client采用tcp发送消息,http协议下载信息,登录之后使用tcp状态保持在线状态
- 发送消息,采用udp,但是需要通过服务器端转发,在上层应用确保消息的可靠传输,如果消息失败则会提示发送失败
- 内网传输文件不需要经过服务器
Netty的特点
- 高性能、异步事件驱动的NIO框架
- 使用高效的socket底层,对epoll空轮询引起的cpu占用飙升在内部做了处理,避免了直接使用NIO
- 采用多种encoder和decoder的处理,对TCP的粘包和分包做了处理
- 可配置IO线程数、TCP参数,TCP接收和发送缓冲区使用直接内存代替堆内存,通过内存池的方式循环利用ByteBuf
- 通过引用计数器即时申请释放不再引用的对象,降低GC
Netty的线程模型
Netty通过Reactor模型基于多路复用器接收并处理用户请求,内部实现了两个线程池:boss线程池和worker线程池,其中boss线程池负责处理请求的accept事件,并交给worker线程池,worker线程池负责处理请求的read和write事件,由对应的handler处理
- 单线程模型:所有I/O 操作都由一个线程完成,即多路复用、事件分发和处理都是在一个Reactor 线程上完成的。既要接收客户端的连接请求,向服务端发起连接,又要发送/读取请求或应答/响应消息。
- 多线程模型:有一个NIO 线程(Acceptor) 只负责监听服务端,接收客户端的TCP 连接请求;NIO 线程池负责网络IO 的操作,即消息的读取、解码、编码和发送;1 个NIO 线程可以同时处理N 条链路,但是1 个链路只对应1 个NIO 线程,这是为了防止发生并发操作问题。但在并发百万客户端连接或需要安全认证时,一个Acceptor 线程可能会存在性能不足问题。
- 主从多线程模型:Acceptor 线程用于绑定监听端口,接收客户端连接,将 SocketChannel 从主线程池的Reactor 线程的多路复用器上移除,重新注册到Sub线程池的线程上,用于处理I/O 的读写等操作,从而保证mainReactor 只负责接入认证、握手等操作;
TCP粘包和拆包的原因及解决方法
TCP是以流的方式处理数据,一个完整的包可能会被TCP拆分成多个包发送,也可能将很多小的包封装成一个大的数据库发送。 拆包:应用发送写入的字节大小大于socket发送缓冲区的大小 粘包:应用发送写入的字节大小小于发送缓冲区的大小,需要将应用多次写入的数据合起来发送 解决办法:
- 消息定长:FixedLengthFrameDecoder
- 包尾增加特殊字符分割:如行分隔符,LineBasedFrameDecoder
- 将消息分为消息头和消息体,LengthFieldBasedFrameDecoder
零拷贝实现
Netty 的接收和发送ByteBuffer 采用DIRECT BUFFERS,使用堆外直接内存进行Socket 读写,不需要进行字节缓冲区的二次拷贝。 Netty 提供了组合Buffer 对象,可以聚合多个ByteBuffer 对象,用户可以像操作一个Buffer 那样方便的对组合Buffer 进行操作,避免了传统通过内存拷贝的方式将几个小Buffer 合并成一个大的Buffer。 Netty 的文件传输采用了transferTo 方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write 方式导致的内存拷贝问题。
如何解决JDK的selector BUG
Selector BUG:若Selector 的轮询结果为空,也没有wakeup 或新消息处理,则发生空轮询,CPU 使用率100%。 Netty:对Selector 的select 操作周期进行统计,每完成一次空的select 操作进行一次计数,若在某个周期内连续发生N 次空轮询,则触发了epoll 死循环bug。重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel 从旧的Selector 上去除注册,重新注册到新的Selector 上,并将原来的Selector 关闭。
如何单机支持百万长连接
- 操作系统
在系统层面,最高的并发数量取决于用户单一线程同时可打开的文件数量的限制,ulimit - Netty调优
2.1 合理的线程数:主要设置Accceptor线程池(Boss group)以及worker线程池,服务端可同时监听多个端口,一般是CPU核数×2 2.2 心跳优化: 2.2.1 要能够及时检测失效的连接,并将其剔除,防止无效的连接句柄积压,导致OOM 等问题 2.2.2 设置合理的心跳周期,防止心跳定时任务积压,造成频繁的老年代GC(新生代和老年代都有导致STW 的GC,不过耗时差异较大) 2.2.3 使用Nety 提供的链路空闲检测机制 Nety 提供了三种链路空闲检测机制,利用该机制可以轻松地实现心跳检测 (1)读空闲,链路持续时间T 没有读取到任何消息。 (2)写空闲,链路持续时间T 没有发送任何消息 (3)读写空闲,链路持续时间T 没有接收或者发送任何消息 对于百万级的服务器,一般不建议很长的心跳周期和超时时长 2.3 接收和发送缓冲区优化 2.4 IO线程和业务线程分离:如果服务器不做复杂的业务逻辑处理,则可以调大NIOEventLoop线程池方式,直接在IO线程中执行业务,减少一次线程上下文切换;如果业务逻辑复杂,那么将业务线程池分离,线程池可以和IO线程绑定,减少锁竞争。
|