客户端使用Netty逻辑
客户端根据用户输入创建指令对象(发送消息,群聊)
TCP通信需要的数据格式为二进制,客户端通过自定义二进制协议将指令对象封装成二进制格式(协议的编码)
服务端收到数据后,截取出完整的二进制数据包(拆包粘包)
服务端通过解析二进制数据包获取指令对象,并转到对应的逻辑处理器进行处理
服务端使用Netty逻辑
服务端创建指令对象
转成二进制
客户端收到数据后,解析指令,进行处理
Netty和传统Socket比较
IO编程
线程资源受限
线程切换效率低下
数据读写以字节流为单位(一次性只能从流中读取一个或者多个字节,并且读完之后流无法再读取,你需要自己缓存数据)
NIO编程
线程绑定轮询器(selector),例如:selector1负责轮询是否有新的连接,selector2负责轮询连接是否有数据可读。
服务端监测到新的连接之后,不再创建一个新的线程,而是直接将新连接绑定到selector1上,这样就不用 IO 模型中1w个连接,创建1w个while循环在死等。
selector1被一个 while 死循环包裹着,如果在某一时刻有多条连接有数据可读,那么通过 selector1.select(1)方法可以轮询出来,进而批量处理
数据的读写面向 Buffer(可以随意读取里面任何一个字节数据,不需要自己缓存数据,只需要移动读写指针)
Netty编程
封装了JDK的NIO,解决了很多NIO编程的bug(空轮询)
可随意切换IO和NIO模型
自带拆包解包,异常检测机制,各种协议栈
底层对线程,selector做了很多细小的优化,精心设计的reactor线程模型能做到非常高效的并发处理
ByteBuf
常见API
容量API
capacity():容量
maxCapacity():允许扩容的最大字节数
readableBytes() 与 isReadable():获取可读写的字节数
writableBytes()、 isWritable() 与 maxWritableBytes():获取当前可写的字节数
读写指针相关的API
readerIndex() 与 readerIndex(int):返回当前读指针,后者设置当前读指针
writeIndex() 与 writeIndex(int):返回当前写指针,后者设置当前写指针
markReaderIndex() 与 resetReaderIndex():保存读指针,以及恢复到之前保存的指针
markWriterIndex() 与 resetWriterIndex():保存写指针,以及恢复到之前保存的指针
读写API
writeByte(byte b) 与 buffer.readByte(),writeBytes(byte[] src) 与 buffer.readBytes(byte[] dst)系列(writeBoolean()、writeChar()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble() 与 readBoolean()、readChar()、readShort()、readInt()、readLong()、readFloat()、readDouble())
getBytes、getByte() 与 setBytes()、setByte() 系列
write,read和get,set区别:get/set 不会改变读写指针,而 read/write 会改变读写指针。release() 与 retain():Netty 使用了堆外内存,需要手动释放,Netty 的 ByteBuf 是通过引用计数的方式管理的,如果一个 ByteBuf 没有地方被引用到,需要回收底层内存。每次调用 retain() 方法, 它的引用就加一, release() 方法原理是将引用计数减一,减完之后如果发现引用计数为0,则直接回收 ByteBuf 底层的内存。
在一个函数体里面,只要增加了引用计数(包括 ByteBuf 的创建和手动调用 retain() 方法),就必须调用 release() 方法。slice()、duplicate()、copy():
slice和duplicate不会改变ByteBuf的引用计数,需要手动调用一次retain()(也可以调用组合方法:retainedSlice() 与 retainedDuplicate())
三个方法各自维护着自己的读写指针,与原始ByteBuf读写指针无关,slice() 方法与 duplicate() 方法不会拷贝数据,slice()只截取从 readerIndex 到 writerIndex 之间的数据,而duplicate()是整个数据共享。
自定义通信协议
魔数,通常情况下为固定的几个字节,服务端收到的是一个标准的 HTTP 协议数据包,会按照事先约定好的协议来处理 HTTP 协议,显然,这是会解析出错的。而有了这个魔数之后,服务端首先取出前面四个字节进行比对,能够在第一时间识别出这个数据包并非是遵循自定义协议的,也就是无效数据包,为了安全考虑可以直接关闭连接以节省资源。
序列化算法表示如何把 Java